fxlibc/src/libc/stdio/printf/format_usual.c

303 lines
7.9 KiB
C
Raw Normal View History

#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <fxlibc/printf.h>
//---
// Digit generation
//---
/* digits_10(): Generate digits in base 10
Fills up the provided digit string from least significant to most
significant digit, not adding zeros except if argument is zero. Returns the
number of digits. No NUL terminator is added. */
static int digits_10(char *str, uint64_t n)
{
int digits = 0;
while(n || !digits)
{
/* TODO: Use fast division in __printf's digits_10() */
str[digits++] = (n % 10) + '0';
n /= 10;
}
return digits;
}
/* digits_16(): Generate digits in base 16 */
static int digits_16(char *str, int uppercase, uint64_t n)
{
char *hex = uppercase ? "0123456789ABCDEF" : "0123456789abcdef";
int digits = 0;
while(n || !digits)
{
str[digits++] = hex[n & 0xf];
n >>= 4;
}
return digits;
}
/* digits_8(): Generate digits in base 8 */
static int digits_8(char *str, uint64_t n)
{
int digits = 0;
while(n || !digits)
{
str[digits++] = (n & 0x7) + '0';
n >>= 3;
}
return digits;
}
//---
// Loading helpers
//---
static int64_t load_i(int size, va_list *args)
{
/* All smaller types are promoted to int with sign extension, so we
don't need to care about them. */
if(size == 3) return va_arg(*args, long);
if(size == 4) return va_arg(*args, long long);
return va_arg(*args, int);
}
static uint64_t load_u(int size, va_list *args)
{
/* Again, no need to care about small types */
if(size == 3) return va_arg(*args, unsigned long);
if(size == 4) return va_arg(*args, unsigned long long);
return va_arg(*args, unsigned int);
}
//---
// Formatter functions
//---
/* Character formatter (%c)
(-) Move spaces to the right
{len} Specifies numbers of (whitespace-padded) characters to print */
void __printf_format_c(
struct __printf_output * restrict out,
struct __printf_format * restrict opt,
va_list * restrict args)
{
int c = va_arg(*args, int);
struct __printf_geometry g = {
.prefix = 0, .sign = 0, .content = 1,
};
__printf_compute_geometry(opt, &g);
__printf_outn(out, ' ', g.left_spaces);
__printf_out(out, c);
__printf_outn(out, ' ', g.right_spaces);
}
/* String formatter (%s)
(-) Move spaces to the right
{len} Minimal numbers of characters to output
{pre} Maximal numbers of bytes to read from argument */
void __printf_format_s(
struct __printf_output * restrict out,
struct __printf_format * restrict opt,
va_list * restrict args)
{
char const *s = va_arg(*args, char const *);
/* Compute content length, which is the smallest of two sizes: the
length set as precision and the actual length of the string */
size_t len = 0;
uint32_t precision = opt->precision ? opt->precision : (-1);
while(s[len] && len < precision) len++;
struct __printf_geometry g = {
.prefix = 0, .sign = 0, .content = len,
};
__printf_compute_geometry(opt, &g);
__printf_outn(out, ' ', g.left_spaces);
for(size_t i = 0; i < len; i++) __printf_out(out, s[i]);
__printf_outn(out, ' ', g.right_spaces);
}
/*Integer formatter (%d, %i)
(0) Pad with zeros, rather than spaces, on the left
(-) Move spaces to the right (overrides '0', extends {pre})
( ) Force a blank sign before nonnegative numbers
(+) Force a sign before every number (overrides ' ')
{len} Minimal number of characters to print
{pre} Forces a minimal number of digits, creating 0s (overrides '0') */
void __printf_format_di(
struct __printf_output * restrict out,
struct __printf_format * restrict opt,
va_list * restrict args)
{
int64_t n = load_i(opt->size, args);
/* Compute the sign and the absolute value */
struct __printf_geometry g = {
.sign = (n < 0) ? '-' : '+',
.prefix = 0,
.style = _PRINTF_INTEGER,
};
if(n < 0) n = -n;
/* Get the digit string */
char digits[32];
int pure, total;
pure = digits_10(digits, n);
if(opt->precision == 0 && !n) pure = 0;
total = (pure > opt->precision ? pure : opt->precision);
g.content = total;
__printf_compute_geometry(opt, &g);
/* Print the result */
__printf_outn(out, ' ', g.left_spaces);
if(g.sign) __printf_out(out, g.sign);
__printf_outn(out, '0', g.zeros);
__printf_outn(out, '0', total - pure);
for(int i = pure - 1; i >= 0; i--) __printf_out(out, digits[i]);
__printf_outn(out, ' ', g.right_spaces);
}
/* Unsigned integer formatter in various bases (%u, %o, %x)
(#) Enable base prefixes ("0" in octal, "0x" in hexadecimal)
(0) Pad with zeros, rather than spaces, on the left
(-) Move spaces to the right (overrides '0', extends {pre})
{len} Minimal number of characters to print
{pre} Forces a minimal number of digits, creating 0s (overrides '0') */
void __printf_format_ouxX(
struct __printf_output * restrict out,
struct __printf_format * restrict opt,
va_list * restrict args)
{
uint64_t n = load_u(opt->size, args);
char digits[48];
int pure = 0, total;
int specl = opt->spec | 0x20;
if(specl == 'u') pure = digits_10(digits, n);
if(specl == 'o') pure = digits_8(digits, n);
if(specl == 'x') pure = digits_16(digits, opt->spec == 'X', n);
if(opt->precision == 0 && !n) pure = 0;
total = (pure > opt->precision ? pure : opt->precision);
/* Prefix length */
size_t prefix = 0;
if(opt->alternative) prefix = (specl != 'u') + (specl == 'x');
struct __printf_geometry g = {
.sign = 0, .prefix = prefix, .content = total,
.style = _PRINTF_INTEGER,
};
__printf_compute_geometry(opt, &g);
/* Output */
__printf_outn(out, ' ', g.left_spaces);
if(opt->alternative)
{
if(specl != 'u') __printf_out(out, '0');
if(specl == 'x') __printf_out(out, opt->spec);
}
__printf_outn(out, '0', g.zeros);
__printf_outn(out, '0', total - pure);
for(int i = pure - 1; i >= 0; i--) __printf_out(out, digits[i]);
__printf_outn(out, ' ', g.right_spaces);
}
/* Pointer formatter (%p) */
void __printf_format_p(
struct __printf_output * restrict out,
struct __printf_format * restrict opt,
va_list * restrict args)
{
(void)opt;
void *p = va_arg(*args, void *);
char digits[] = "00000000";
digits_16(digits, 0, (uint32_t)p);
__printf_out(out, '0');
__printf_out(out, 'x');
for(int i = 7; i >= 0; i--) __printf_out(out, digits[i]);
}
/* errno message formatter (%m) */
void __printf_format_m(
struct __printf_output * restrict out,
struct __printf_format * restrict opt,
va_list * restrict args)
{
(void)opt;
(void)args;
char const *message = strerror(errno);
__printf_outstr(out, message, strlen(message));
}
/* Number of characters written so far (%n) */
void __printf_format_n(
struct __printf_output * restrict out,
struct __printf_format * restrict opt,
va_list * restrict args)
{
void *p = va_arg(*args, void *);
if(opt->size == 0) *(char *)p = out->count;
if(opt->size == 1) *(short *)p = out->count;
if(opt->size == 2) *(int *)p = out->count;
if(opt->size == 3) *(long *)p = out->count;
if(opt->size == 4) *(long long *)p = out->count;
}
/* Fixed-point decimal formatter (extension: %k)
(0) Pad with zeros, rather than spaces, on the left
(-) Move spaces to the right (overrides '0')
( ) Force a blank sign before nonnegative numbers
(+) Force a sign before every number (overrides ' ')
{len} Minimal number of characters to print
{pre} Number of digits after the decimal dot */
void __printf_format_k(
struct __printf_output * restrict out,
struct __printf_format * restrict opt,
va_list * restrict args)
{
int64_t n = load_i(opt->size, args);
/* Compute the sign and the absolute value */
struct __printf_geometry g = {
.sign = (n < 0) ? '-' : '+',
.prefix = 0,
.style = _PRINTF_NUMERIC,
};
if(n < 0) n = -n;
/* Get the digit string */
char digits[32];
g.content = digits_10(digits, n) + 1;
__printf_compute_geometry(opt, &g);
/* Print the result */
__printf_outn(out, ' ', g.left_spaces);
if(g.sign) __printf_out(out, g.sign);
__printf_outn(out, '0', g.zeros);
for(int i = g.content - 2; i >= 0; i--)
{
if(i == opt->precision - 1) __printf_out(out, '.');
__printf_out(out, digits[i]);
}
__printf_outn(out, ' ', g.right_spaces);
}