diff --git a/include/stdio.h b/include/stdio.h index 1d0a5cc..d85d7da 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -18,8 +18,8 @@ typedef struct { /* Just for rewinding the stream. */ unsigned has_unput : 1; unsigned char unput; - /* Used only for terminal output (on stdout) */ - unsigned short termx, termy; + /* Used only for terminal output (default for stdout) */ + int termx, termy; /* Whether to output to the serial (default for stderr and stdout) */ unsigned char out_serial : 1; /* Whether to output to the screen (default for stdout) */ diff --git a/libc/Makefile b/libc/Makefile index fb53143..9075f54 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -3,7 +3,7 @@ CC=$(TOOLCHAIN_PREFIX)gcc AS=$(TOOLCHAIN_PREFIX)as AR=$(TOOLCHAIN_PREFIX)gcc-ar CFLAGS=-c -ffunction-sections -fdata-sections -Os -flto -ffat-lto-objects \ - -m4a-nofpu -mhitachi -ffreestanding -Wall -Wsystem-headers -g -I../include + -m4a-nofpu -mhitachi -ffreestanding -Wall -Wsystem-headers -g -I../include -DPRINTF_MINIMAL LDFLAGS=-Wl,-static -lfxcg ARFLAGS=rsv OBJECTS=printf.o setjmp.o stdio.o stdlib.o ctype.o string.o unistd.o time.o math.o diff --git a/libc/printf.c b/libc/printf.c index 6cd31ef..47c53ab 100755 --- a/libc/printf.c +++ b/libc/printf.c @@ -1,324 +1,551 @@ +/* + * Standaloneified version of the FreeBSD kernel printf family. + */ + #include +#include #include +#include +#include #include #include +#include -typedef struct { - int width; - int precision; - unsigned alternate:1; - unsigned zeropad:1; - unsigned leftjustify:1; - unsigned spacepad:1; - unsigned sign:1; -} format_t; - -typedef void (*writer_t)(const void *, char); -typedef int (*formatter_t)(va_list *, writer_t, const void *, format_t); - -/* These are the workers for formatting. The main printf wrapper gets a writer - * and destination, and this does the formatting while feeding characters to - * the writer, passing dest through to the writer. */ -static int _printf_do_udecimal(int x, writer_t writer, const void *dest, format_t fmt) { - int log10 = 0; // Number of digits neeeded - int xt = x; - while (xt > 0) { - log10++; - xt /= 10; - } - int count = log10; +#define MAXNBUF (sizeof(intmax_t) * CHAR_BIT + 1) - do { // Defer comparison to ensure 0 gets a digit - int xpow = 1, i; - for (i = log10; i > 1; i--) { // 10^log10 - xpow *= 10; - } - char digit = (x / xpow) % 10; - digit += '0'; - writer(dest, digit); - } while (--log10 > 0); - return count; +char const hex2ascii[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +typedef void(kvprintf_fn_t)(int, void *); + +static char *ksprintn(char *buf, uintmax_t num, int base, int *len, int upper); +static int kvprintf(char const *fmt, kvprintf_fn_t *func, void *arg, int radix, + va_list ap); + +static void putchar_wrapper(int cc, void *arg) { putchar(cc); } + +int printf(const char *fmt, ...) { + va_list ap; + int retval; + + va_start(ap, fmt); + retval = kvprintf(fmt, putchar_wrapper, NULL, 10, ap); + va_end(ap); + return retval; } -static int _printf_udecimal(va_list *ap, writer_t writer, const void *dest, format_t fmt) { - return _printf_do_udecimal(va_arg(*ap, unsigned), writer, dest, fmt); +int vprintf(const char *fmt, va_list ap) { + return (kvprintf(fmt, putchar_wrapper, NULL, 10, ap)); } -static int _printf_decimal(va_list *ap, writer_t writer, const void *dest, format_t fmt) { - int x = va_arg(*ap, int); - - int count = 1; - if (x < 0) { - writer(dest, '-'); - } else if (fmt.sign) { - writer(dest, '+'); - } else if (fmt.spacepad) { - writer(dest, ' '); - } else { - count = 0; - } +int sprintf(char *buf, const char *cfmt, ...) { + int retval; + va_list ap; - x = abs(x); - return count + _printf_do_udecimal(x, writer, dest, fmt); + va_start(ap, cfmt); + retval = kvprintf(cfmt, NULL, (void *)buf, 10, ap); + buf[retval] = '\0'; + va_end(ap); + return retval; } -static int _printf_char(va_list *ap, writer_t writer, const void *dest, format_t fmt) { - char c = va_arg(*ap, int); - writer(dest, c); - return 1; +struct print_buf { + char *buf; + size_t size; +}; + +static void snprint_func(int ch, void *arg) { + struct print_buf *pbuf = arg; + + if (pbuf->size < 2) { + /* + * Reserve last buffer position for the terminating + * character: + */ + return; + } + *(pbuf->buf)++ = ch; + pbuf->size--; } -static int _printf_string(va_list *ap, writer_t writer, const void *dest, format_t fmt) { - const char *s = va_arg(*ap, const char *); - int count = 0; - char c; - while ((c = *s++) != 0) { - writer(dest, c); - count++; - } - return count; +int asprintf(char **buf, const char *cfmt, ...) { + int retval; + struct print_buf arg; + va_list ap; + + *buf = NULL; + va_start(ap, cfmt); + retval = kvprintf(cfmt, NULL, NULL, 10, ap); + va_end(ap); + if (retval <= 0) + return (-1); + + arg.size = retval + 1; + arg.buf = *buf = malloc(arg.size); + if (*buf == NULL) + return (-1); + + va_start(ap, cfmt); + retval = kvprintf(cfmt, &snprint_func, &arg, 10, ap); + va_end(ap); + + if (arg.size >= 1) + *(arg.buf)++ = 0; + return (retval); } -int _printf_ptr(va_list *ap, writer_t writer, const void *dest, format_t fmt) { - writer(dest, '0'); - writer(dest, 'x'); - unsigned x = (unsigned)va_arg(*ap, void *); +int snprintf(char *buf, size_t size, const char *cfmt, ...) { + int retval; + va_list ap; + struct print_buf arg; - // This will be wrong on non-32-bit platforms - int i; - char chars[] = "0123456789ABCDEF"; - for (i = 7; i >= 0; i--) { - writer(dest, chars[(x >> (4 * i)) & 0xF]); - } - return 10; + arg.buf = buf; + arg.size = size; + + va_start(ap, cfmt); + retval = kvprintf(cfmt, &snprint_func, &arg, 10, ap); + va_end(ap); + + if (arg.size >= 1) + *(arg.buf)++ = 0; + return retval; } -int _printf_weird(va_list *ap, writer_t writer, const void *dest, format_t fmt) { - // Format out a warning, not actually something - // Does NOT consume any arguments because we have no way of knowing what - // size was expected. - int i; - for (i = 0; i < 3; i++) - writer(dest, '!'); - return 3; +int vsnprintf(char *buf, size_t size, const char *cfmt, va_list ap) { + struct print_buf arg; + int retval; + + arg.buf = buf; + arg.size = size; + + retval = kvprintf(cfmt, &snprint_func, &arg, 10, ap); + + if (arg.size >= 1) + *(arg.buf)++ = 0; + + return (retval); } -/* Character writers. Pass a destination (void * interpreted as needed), eg - * a buffer or stream. */ -void _writer_stream(const void *wdest, char c) { - fputc(c, (FILE *)wdest); +int vsprintf(char *buf, const char *cfmt, va_list ap) { + int retval; + + retval = kvprintf(cfmt, NULL, (void *)buf, 10, ap); + buf[retval] = '\0'; + + return (retval); } -void _writer_buffer(const void *wdest, char c) { - // Needs to track buffer location, so double pointer - char **dest = (char **)wdest; - **dest = c; - (*dest)++; + +/* + * Put a NUL-terminated ASCII number (base <= 36) in a buffer in reverse + * order; return an optional length and a pointer to the last character + * written in the buffer (i.e., the first character of the string). + * The buffer pointed to by `nbuf' must have length >= MAXNBUF. + */ +static char *ksprintn(char *nbuf, uintmax_t num, int base, int *lenp, + int upper) { + char *p, c; + + p = nbuf; + *p = '\0'; + do { + c = hex2ascii[num % base]; + *++p = upper ? toupper(c) : c; + } while (num /= base); + if (lenp) + *lenp = p - nbuf; + return (p); } -/* Main worker and such. */ - -// Used for catching end-of-string while grabbing chars -// Used within switch blocks, so can't break. -#define _NEXT(fmt) if ((c = *fmt++) == 0) goto out; - -// TODO mostly incomplete. Python parser only depends on %d and %s, so this is -// mostly placeholder code. -static int _v_printf(const char *fmt, va_list ap_in, writer_t writer, const void - *warg) { - // The va_copy here is dumb, but necessary for things to work on x86_64. - // See GCC bug #14557, but the issue boils down to va_list being - // transparently passed by reference so &ap is wrong if ap is a function - // parameter. - va_list ap; - va_copy(ap, ap_in); - - // Bytes transmitted - int count = 0; - format_t f = { - .width = 0, - .precision = 0, - .alternate = 0, - .zeropad = 0, - .leftjustify = 0, - .spacepad = 0, - .sign = 0, - }; - - char c; - // _NEXT returns on end of string. - while (1) { - _NEXT(fmt); - if (c != '%') { // Literal character - writer(warg, c); - count++; - continue; - } - _NEXT(fmt); - if (c == '%') { // Literal % - writer(warg, c); - count++; - continue; - } +/* + * Scaled down version of printf(3). + * + * Two additional formats: + * + * The format %b is supported to decode error registers. + * Its usage is: + * + * printf("reg=%b\n", regval, "*"); + * + * where is the output base expressed as a control character, e.g. + * \10 gives octal; \20 gives hex. Each arg is a sequence of characters, + * the first of which gives the bit number to be inspected (origin 1), and + * the next characters (up to a control character, i.e. a character <= 32), + * give the name of the register. Thus: + * + * kvprintf("reg=%b\n", 3, "\10\2BITTWO\1BITONE"); + * + * would produce output: + * + * reg=3 + * + * XXX: %D -- Hexdump, takes pointer and separator string: + * ("%6D", ptr, ":") -> XX:XX:XX:XX:XX:XX + * ("%*D", len, ptr, " " -> XX XX XX XX ... + */ +static int kvprintf(char const *fmt, kvprintf_fn_t *func, void *arg, int radix, + va_list ap) { +#define PCHAR(c) \ + { \ + int cc = (c); \ + \ + if (func) { \ + (*func)(cc, arg); \ + } else if (d != NULL) { \ + *d++ = cc; \ + } \ + retval++; \ + } - // Flags - char getflags = 0; - do { - switch (c) { - case '#': // Alternate form - f.alternate = 1; - _NEXT(fmt); - break; - case '0': // Zero-pad - f.zeropad = 1; - _NEXT(fmt); - break; - case '-': // Left-justify - f.leftjustify = 1; - _NEXT(fmt); - break; - case ' ': // Pad positive sign with space - f.spacepad = 1; - _NEXT(fmt); - break; - case '+': // Force sign - f.sign = 1; - _NEXT(fmt); - break; - default: - getflags = 0; - } - } while (getflags); - - // Field width - if (isdigit(c) && c != '0') { // From string - f.width = (int)strtol(fmt - 1, (char **)(&fmt), 10); // Updates fmt - _NEXT(fmt); - } else if (c == '*') { // From arg - f.width = va_arg(ap, int); - _NEXT(fmt); - } + char nbuf[MAXNBUF]; + char *d; + const char *p, *percent; + int ch, n; + uintmax_t num; + int base, lflag, tmp, width, ladjust, sharpflag, neg, sign, dot; + int cflag, hflag; +#ifndef PRINTF_MINIMAL + int jflag, qflag, tflag, zflag; + uint16_t *S; + unsigned char *up; + const char *q; +#endif + int dwidth, upper; + char padc; + int stop = 0, retval = 0; - // Precision - if (c == '.') { - _NEXT(fmt); - if (c == '*') { // From arg - f.precision = va_arg(ap, int); - _NEXT(fmt); - } else { // From string - f.precision = (int)strtol(fmt, (char **)(&fmt), 10); // Updates fmt - } - // Negative precision should be ignored - if (f.precision < 0) - f.precision = 0; - } + num = 0; + if (!func) + d = (char *)arg; + else + d = NULL; - // Argument width - switch (c) { - case 'h': - _NEXT(fmt); - if (c == 'h') { // "hh" char - _NEXT(fmt); - } else { // short - } - break; - case 'l': - _NEXT(fmt); - if (c == 'l') { // "ll" long long - _NEXT(fmt); - } else { // long - } - break; - case 'j': // intmax_t - _NEXT(fmt); - break; - case 'z': // size_t - _NEXT(fmt); - break; - case 't': // ptrdiff_t - _NEXT(fmt); - break; - case 'L': // long double (unsupported) - default: // Not a length modifier - break; - } + if (fmt == NULL) + fmt = "(fmt null)\n"; - // Conversion - formatter_t formatter; - switch (c) { - case 'd': - case 'i': // decimal - formatter = _printf_decimal; - break; - case 'u': - formatter = _printf_udecimal; - break; - case 'c': - formatter = _printf_char; - break; - case 's': - formatter = _printf_string; - break; - case 'o': - formatter = _printf_weird; - break; - case 'x': - case 'X': - formatter = _printf_weird; - break; - case 'p': - formatter = _printf_ptr; - break; - case 'f': // All (currently) unsupported - case 'F': - case 'e': - case 'E': - case 'g': - case 'G': - case 'a': - case 'A': - case 'C': - case 'S': - case 'n': // Probably needs to be a special case - default: - formatter = _printf_weird; - break; - } - formatter(&ap, writer, warg, f); + if (radix < 2 || radix > 36) + radix = 10; + + for (;;) { + padc = ' '; + width = 0; + while ((ch = (unsigned char)*fmt++) != '%' || stop) { + if (ch == '\0') + return retval; + + PCHAR(ch); } -out: - va_end(ap); - writer(warg, 0); // Null terminator - return count; -} -int vfprintf(FILE *stream, const char *fmt, va_list ap) { - return _v_printf(fmt, ap, _writer_stream, stream); -} + percent = fmt - 1; + lflag = 0; + ladjust = 0; + sharpflag = 0; + neg = 0; + sign = 0; + dot = 0; + dwidth = 0; + upper = 0; + cflag = 0; + hflag = 0; -int fprintf(FILE *stream, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int ret = vfprintf(stream, fmt, ap); - va_end(ap); - return ret; -} +#ifndef PRINTF_MINIMAL + qflag = 0; + jflag = 0; + tflag = 0; + zflag = 0; +#endif -int printf(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int ret = vfprintf(stdout, fmt, ap); - va_end(ap); - return ret; -} + reswitch: + switch (ch = (unsigned char)*fmt++) { +#ifndef PRINTF_MINIMAL + case 'b': + num = (unsigned int)va_arg(ap, int); + p = va_arg(ap, char *); + for (q = ksprintn(nbuf, num, *p++, NULL, 0); *q;) + PCHAR(*q--); -int vsprintf(char *str, const char *fmt, va_list ap) { - return _v_printf(fmt, ap, _writer_buffer, &str); -} + if (num == 0) + break; + + for (tmp = 0; *p;) { + n = *p++; + if (num & (1 << (n - 1))) { + PCHAR(tmp ? ',' : '<'); + for (; (n = *p) > ' '; ++p) + PCHAR(n); + tmp = 1; + } else + for (; *p > ' '; ++p) + continue; + } + if (tmp) + PCHAR('>'); + break; + case 'D': + up = va_arg(ap, unsigned char *); + p = va_arg(ap, char *); + if (!width) + width = 16; + while (width--) { + PCHAR(hex2ascii[*up >> 4]); + PCHAR(hex2ascii[*up & 0x0f]); + up++; + if (width) + for (q = p; *q; q++) + PCHAR(*q); + } + break; + case 'j': + jflag = 1; + goto reswitch; + case 'n': + if (jflag) + *(va_arg(ap, intmax_t *)) = retval; + else if (qflag) + *(va_arg(ap, int64_t *)) = retval; + else if (lflag) + *(va_arg(ap, long *)) = retval; + else if (zflag) + *(va_arg(ap, size_t *)) = retval; + else if (hflag) + *(va_arg(ap, short *)) = retval; + else if (cflag) + *(va_arg(ap, char *)) = retval; + else + *(va_arg(ap, int *)) = retval; + break; + case 'q': + qflag = 1; + goto reswitch; + case 'r': + base = radix; + if (sign) + goto handle_sign; + goto handle_nosign; + case 'S': /* Assume console can cope with wide chars */ + for (S = va_arg(ap, uint16_t *); *S != 0; S++) + PCHAR(*S); + break; + case 't': + tflag = 1; + goto reswitch; + case 'y': + base = 16; + sign = 1; + goto handle_sign; + case 'z': + zflag = 1; + goto reswitch; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + for (n = 0;; ++fmt) { + n = n * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') + break; + } + if (dot) + dwidth = n; + else + width = n; + goto reswitch; +#endif + case '.': + dot = 1; + goto reswitch; + case '#': + sharpflag = 1; + goto reswitch; + case '+': + sign = 1; + goto reswitch; + case '-': + ladjust = 1; + goto reswitch; + case '%': + PCHAR(ch); + break; + case '*': + if (!dot) { + width = va_arg(ap, int); + if (width < 0) { + ladjust = !ladjust; + width = -width; + } + } else { + dwidth = va_arg(ap, int); + } + goto reswitch; + case '0': + if (!dot) { + padc = '0'; + goto reswitch; + } + case 'c': + PCHAR(va_arg(ap, int)); + break; + case 'd': + case 'i': + base = 10; + sign = 1; + goto handle_sign; + case 'h': + if (hflag) { + hflag = 0; + cflag = 1; + } else + hflag = 1; + goto reswitch; + case 'l': + if (lflag) { + lflag = 0; +#ifndef PRINTF_MINIMAL + qflag = 1; +#endif + } else + lflag = 1; + goto reswitch; + case 'o': + base = 8; + goto handle_nosign; + case 'p': + base = 16; + sharpflag = (width == 0); + sign = 0; + num = (uintptr_t)va_arg(ap, void *); + goto number; + case 's': + p = va_arg(ap, char *); + if (p == NULL) + p = "(null)"; + if (!dot) + n = strlen(p); + else + for (n = 0; n < dwidth && p[n]; n++) + continue; + + width -= n; + + if (!ladjust && width > 0) + while (width--) + PCHAR(padc); + while (n--) + PCHAR(*p++); + if (ladjust && width > 0) + while (width--) + PCHAR(padc); + break; + case 'u': + base = 10; + goto handle_nosign; + case 'X': + upper = 1; + case 'x': + base = 16; + goto handle_nosign; + handle_nosign: + sign = 0; + if (lflag) + num = va_arg(ap, unsigned long); + else if (hflag) + num = (unsigned short)va_arg(ap, int); + else if (cflag) + num = (unsigned char)va_arg(ap, int); +#ifndef PRINTF_MINIMAL + else if (jflag) + num = va_arg(ap, uintmax_t); + else if (qflag) + num = va_arg(ap, uint64_t); + else if (tflag) + num = va_arg(ap, ptrdiff_t); + else if (zflag) + num = va_arg(ap, size_t); +#endif + else + num = va_arg(ap, unsigned int); + goto number; + handle_sign: + if (lflag) + num = va_arg(ap, long); + else if (hflag) + num = (short)va_arg(ap, int); + else if (cflag) + num = (char)va_arg(ap, int); +#ifndef PRINTF_MINIMAL + else if (jflag) + num = va_arg(ap, intmax_t); + else if (qflag) + num = va_arg(ap, int64_t); + else if (tflag) + num = va_arg(ap, ptrdiff_t); + else if (zflag) + num = va_arg(ap, int); +#endif + else + num = va_arg(ap, int); + number: + if (sign && (intmax_t)num < 0) { + neg = 1; + num = -(intmax_t)num; + } + p = ksprintn(nbuf, num, base, &n, upper); + tmp = 0; + if (sharpflag && num != 0) { + if (base == 8) + tmp++; + else if (base == 16) + tmp += 2; + } + if (neg) + tmp++; -int sprintf(char *str, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int ret = vsprintf(str, fmt, ap); - va_end(ap); - return ret; + if (!ladjust && padc == '0') + dwidth = width - tmp; + + // Get max of dwidth and n + width -= tmp + dwidth > n ? dwidth : n; + dwidth -= n; + if (!ladjust) + while (width-- > 0) + PCHAR(' '); + if (neg) + PCHAR('-'); + if (sharpflag && num != 0) { + if (base == 8) { + PCHAR('0'); + } else if (base == 16) { + PCHAR('0'); + PCHAR('x'); + } + } + while (dwidth-- > 0) + PCHAR('0'); + + while (*p) + PCHAR(*p--); + + if (ladjust) + while (width-- > 0) + PCHAR(' '); + + break; + default: + while (percent < fmt) + PCHAR(*percent++); + /* + * Since we ignore a formatting argument it is no + * longer safe to obey the remaining formatting + * arguments as the arguments will no longer match + * the format specs. + */ + stop = 1; + break; + } + } +#undef PCHAR } diff --git a/libc/stdio.c b/libc/stdio.c index 013b25b..101b4eb 100644 --- a/libc/stdio.c +++ b/libc/stdio.c @@ -184,17 +184,13 @@ static size_t fwrite_term(const void *ptr, size_t size, size_t nitems, // Loop over all lines in buffer, terminate once we've printed all lines. do { eol = strchr(outp, '\n'); - if(eol) { + if (eol) *eol = '\0'; - } - // Cast to wider type for correct pointers - int termx = stream->termx, termy = stream->termy; - PrintMiniMini(&termx, &termy, outp, 0, TEXT_COLOR_BLACK, 0); + PrintMiniMini(&stream->termx, &stream->termy, outp, 0, TEXT_COLOR_BLACK, 0); // CR/LF if applicable - if(eol) - { + if (eol) { stream->termx = 0; stream->termy += 10; outp = eol + 1;