Mercurial > printf
changeset 0:e3088565a6b8 default tip
*: initial commit
kinda dumb, but wifi was out and I was bored.
most of this code is shit.
| author | Paper <paper@tflc.us> |
|---|---|
| date | Wed, 03 Dec 2025 03:04:39 -0500 |
| parents | |
| children | |
| files | LICENSE Makefile printf.c printf.h test.c test.h test_asprintf.c test_fprintf.c test_snprintf.c |
| diffstat | 9 files changed, 1696 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2025 Paper +Copyright (c) 2005-2012 Rich Felker + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,5 @@ +printf: printf.o test_fprintf.o test_asprintf.o test_snprintf.o test.o + $(CC) -g -O2 -o $@ $^ -lm + +clean: + $(RM) *.o printf
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printf.c Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,1365 @@ +/** + * "Portable" printf implementation + * + * Copyright (c) 2025 Paper + * Copyright (c) 2005-2012 Rich Felker + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * NOTE: Floating point support is very scuffed, and doesn't + * work for many numbers. It also depends on math.h. If you don't + * care or don't want floating point support, toggle the #define + * to disable it. + * + * EXTRA NOTE: You don't need malloc() if you turn off wide character + * support :) +**/ + +#include "printf.h" + +/* Since C89, but optional because locale shit sucks */ +#define HAVE_WCTOMB 1 + +/* strlen has been here since the beginning... */ +#define HAVE_STRLEN 1 +/* ... but strnlen is a POSIX invention, added in C23. */ +/*#define HAVE_STRNLEN 1*/ + +#define HAVE_FLOATING_POINT 1 +#define HAVE_INTMAX_T 1 +#define HAVE_LONG_LONG 1 +#define HAVE_UINTPTR_T 1 +#define HAVE_SIZE_T 1 +#define HAVE_PTRDIFF_T 1 +#define HAVE_LONG_DOUBLE 1 + +/* #define to use sprintf for floats. */ +#define MY_PRINTF_USE_SPRINTF 1 + +#include <stdarg.h> +#include <stdio.h> /* stdout, FILE */ +#include <limits.h> +#if defined(HAVE_STRLEN) || defined(HAVE_STRNLEN) +# include <string.h> +#endif +#ifdef HAVE_WCTOMB +# include <stdlib.h> +#endif +#ifdef HAVE_INTMAX_T +# include <stdint.h> +typedef intmax_t my_printf_intmax; +typedef uintmax_t my_printf_uintmax; +#elif defined(HAVE_LONG_LONG) +typedef long long my_printf_intmax; +typedef unsigned long long my_printf_uintmax; +#else +typedef long my_printf_intmax; +typedef unsigned long my_printf_uintmax; +#endif +#ifdef HAVE_WCTOMB +# include <wchar.h> +#endif +#ifdef HAVE_FLOATING_POINT +# include <math.h> /* fmod */ +# include <float.h> +#endif +#ifdef HAVE_UINTPTR_T +# include <stdint.h> +typedef uintptr_t my_printf_uintptr; +#else +/* uintmax can probably hold a pointer?? hopefully :) */ +typedef my_printf_uintmax my_printf_uintptr; +#endif +#ifdef HAVE_SIZE_T +# include <stddef.h> +typedef size_t my_printf_size; +#else +typedef my_printf_uintptr my_printf_size; +#endif + +#ifdef HAVE_LONG_DOUBLE +/* Everything is long double */ +typedef long double floatmax; + +# define frexpm frexpl +#else +typedef double floatmax; + +# define frexpm frexp +#endif + +#ifdef HAVE_STRNLEN +# define my_strnlen strnlen +#else +static my_printf_size my_strnlen(const char *s, my_printf_size maxlen) +{ + my_printf_size len; + for (len = 0; len < maxlen && *s; len++, s++); + return len; +} +#endif + +#ifdef HAVE_STRLEN +# define my_strlen strlen +#else +static my_printf_size my_strlen(const char *s) +{ + my_printf_size len; + for (len = 0; *s; len++, s++); + return len; +} +#endif + +#ifdef TEST_MEMORY +static int my_mem_counter = 0; + +/* Make sure our memory allocation code still works, + * even in out of memory conditions */ +static void *my_realloc(void *x, my_printf_size sz) +{ + if (my_mem_counter++ > 4) + return NULL; + + return realloc(x, sz); +} + +static void *my_calloc(my_printf_size sz, my_printf_size c) +{ + if (my_mem_counter++ > 4) + return NULL; + + return calloc(sz, c); +} + +static void *my_malloc(my_printf_size sz) +{ + if (my_mem_counter++ > 4) + return NULL; + + return malloc(sz); +} +#else +# define my_realloc realloc +# define my_calloc calloc +# define my_malloc malloc +#endif + +void my_free(void *x) +{ + if (x) free(x); +} + +/* ------------------------------------------------------------------------ */ +/* ERRORS */ + +const char *my_strerror(int err) +{ + static const char *errs[] = { + "Out of memory", + "Invalid format string", + "Invalid or incomplete multibyte or wide character", + "Value too large to be stored in data type", + }; + + err = abs(err) - 1; + + if (err < 0 || err >= sizeof(errs)) + return NULL; + + return errs[err]; +} + +/* ------------------------------------------------------------------------ */ +/* FLAGS */ + +enum flags { + FLAG_JUSTIFY_LEFT = 0x01, + FLAG_PLUS = 0x02, + FLAG_SPACE = 0x04, + FLAG_HASH = 0x08, + FLAG_ZERO = 0x10 +}; + +/* ------------------------------------------------------------------------ */ +/* CONVERT SIZE_T TO SIGNED + * - this is required for handling %zd correctly on odd systems + * - basically any compiler ever (besides msvc) will do dead code removal + * because sizeof() is a compile time constant + * - we cannot do this in the preprocessor unless SIZE_MAX is defined... */ + +#ifdef HAVE_SIZE_T +static my_printf_intmax my_size_t_sign(size_t x) +{ +#define IF(type) \ + if (sizeof(size_t) == sizeof(type)) { \ + union { type s; size_t ss; } u; \ + u.ss = x; \ + return u.s; \ + } else \ + + IF(signed char) + IF(signed short) + IF(signed int) + IF(signed long) +#ifdef HAVE_LONG_LONG + IF(signed long long) +#endif +#ifdef HAVE_PTRDIFF_T + IF(ptrdiff_t) +#endif + /* hope this works */ + return (my_printf_intmax)x; +#undef IF +} +#endif + +/* ------------------------------------------------------------------------ */ +/* CHAR PRINTING */ + +static void print_chars(put_spec put, + void *opaque, my_printf_uintmax *pnum, unsigned char c, unsigned long width) +{ + *pnum += width; + while (width-- > 0) + put(opaque, c); +} + +#define print_spaces(a,b,c,d) print_chars(a,b,c,' ',d) + +/* ------------------------------------------------------------------------ */ +/* STRING PRINTING */ + +static void putstring(put_spec put, void *opaque, const char *s, + my_printf_size len, my_printf_uintmax *num, my_printf_uintmax width, int justify_left) +{ + my_printf_size i; + + /* Handle width */ + if (!justify_left && width > len) + print_spaces(put, opaque, num, width - len); + + for (i = 0; i < len; i++) + put(opaque, s[i]); + *num += len; + + if (justify_left && width > len) + print_spaces(put, opaque, num, width - len); +} + +/* ------------------------------------------------------------------------ */ +/* NUMBER PRINTING */ + +static my_printf_uintmax my_numput_width(my_printf_uintmax d, int radix) +{ + my_printf_uintmax x; + my_printf_uintmax width; + + /* ... */ + for (width = 0, x = d; x >= 1; width++, x /= radix); + width--; /* ;) */ + + return width; +} + +static void my_numput_ex(put_spec put, + void *opaque, my_printf_uintmax d, int radix, const char trans[36], + my_printf_uintmax *num) +{ + /* This is terrible but it doesn't need a intermediate buffer */ + my_printf_uintmax x; + my_printf_uintmax width, i; + + /* ... */ + width = my_numput_width(d, radix); + + for (i = 0, x = 1; i < width; x *= radix, i++); + + while (x >= 1) { + put(opaque, trans[d / x]); + ++*num; + d %= x; + x /= radix; + } +} + +/* note: for radix 2-10, the translation table doesn't actually matter. */ +static void my_numput_lower(put_spec put, + void *opaque, my_printf_uintmax d, int radix, my_printf_uintmax *num) +{ + static const char trans[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; + my_numput_ex(put, opaque, d, radix, trans, num); +} + +static void my_numput_upper(put_spec put, + void *opaque, my_printf_uintmax d, int radix, my_printf_uintmax *num) +{ + static const char trans[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + my_numput_ex(put, opaque, d, radix, trans, num); +} + +static void my_numput(put_spec put, + void *opaque, my_printf_uintmax d, int radix, my_printf_uintmax *num, + int hash, const char *hash_str, my_printf_size hash_strlen, + my_printf_uintmax width, int justify_left, int zero, + int upper) +{ + if (hash) { + putstring(put, opaque, hash_str, hash_strlen, num, 0, 0); + if (width >= 2) width -= 2; + } + + if (width > 0) { + my_printf_uintmax len; + + /* This is disgusting. */ + len = my_numput_width(d, 16); + + if ((!justify_left && !zero) && len + 1 < width) + print_chars(put, opaque, num, ' ', width - len - 1); + + if (zero && len + 1 < width) + print_chars(put, opaque, num, '0', width - len - 1); + + + (upper ? my_numput_upper : my_numput_lower)(put, opaque, d, radix, num); + + if ((justify_left) && len + 1 < width) + print_spaces(put, opaque, num, width - len - 1); + } else { + (upper ? my_numput_upper : my_numput_lower)(put, opaque, d, radix, num); + } +} + +/* ------------------------------------------------------------------------ */ +/* FLOATING POINT + * + * The following code was taken from musl libc. I won't even pretend to + * understand it. */ + +#ifdef HAVE_FLOATING_POINT +# ifndef isnan +static int isnan(floatmax d) +{ + return (d != d); +} +# endif + +# ifndef isfinite +static int isfinite(floatmax d) +{ + return !(d >= HUGE_VAL); +} +# endif + +# ifndef signbit +static int signbit(floatmax d) +{ + /* NOTE: this doesn't work for -NAN */ + return (d < 0.0 || d == -0.0); +} +# endif + +#define MARK_POS FLAG_PLUS +#define PAD_POS FLAG_SPACE +#define ALT_FORM FLAG_HASH +#define LEFT_ADJ FLAG_JUSTIFY_LEFT +#define ZERO_PAD FLAG_ZERO + +#define MIN(x,y) ((x)<(y)?(x):(y)) +#define MAX(x,y) ((x)>(y)?(x):(y)) +#define CONCAT2(x,y) x ## y +#define CONCAT(x,y) CONCAT2(x,y) + +static const char xdigits[16] = { + "0123456789ABCDEF" +}; + +static void out(put_spec put, void *opaque, const char *s, size_t l, my_printf_uintmax *num) +{ + putstring(put, opaque, s, l, num, 0, 0); +} + +static void pad(put_spec put, void *opaque, char c, int w, int l, enum flags fl, + my_printf_uintmax *num) +{ + char pad[256]; + if (fl & (LEFT_ADJ | ZERO_PAD) || l >= w) return; + l = w - l; + memset(pad, c, l>sizeof pad ? sizeof pad : l); + for (; l >= sizeof pad; l -= sizeof pad) + out(put, opaque, pad, sizeof pad, num); + out(put, opaque, pad, l, num); +} + +static char *fmt_u(uintmax_t x, char *s) +{ + unsigned long y; + for ( ; x>ULONG_MAX; x/=10) *--s = '0' + x%10; + for (y=x; y; y/=10) *--s = '0' + y%10; + return s; +} + +static int my_floatput(put_spec put, void *opaque, floatmax y, + int w, int p, enum flags fl, my_printf_uintmax *num, int t) +{ + /* Floating point implementation borrowed from musl libc */ + uint32_t big[(LDBL_MAX_EXP+LDBL_MANT_DIG)/9+1]; + uint32_t *a, *d, *r, *z; + int e2=0, e, i, j, l; + char buf[9+LDBL_MANT_DIG/4], *s; + const char *prefix="-0X+0X 0X-0x+0x 0x"; + int pl; + char ebuf0[3*sizeof(int)], *ebuf=&ebuf0[3*sizeof(int)], *estr; + + pl=1; + if (signbit(y)) { + y=-y; + } else if (fl & MARK_POS) { + prefix+=3; + } else if (fl & PAD_POS) { + prefix+=6; + } else prefix++, pl=0; + + if (!isfinite(y)) { + char *s; + if (isnan(y)) { + s = (t & 32) ? "nan" : "NAN"; + } else { + s = (t & 32) ? "inf" : "INF"; + } + pad(put, opaque, ' ', w, 3+pl, fl&~ZERO_PAD, num); + out(put, opaque, prefix, pl, num); + out(put, opaque, s, 3, num); + pad(put, opaque, ' ', w, 3+pl, fl^LEFT_ADJ, num); + return MAX(w, 3+pl); + } + + y = frexpm(y, &e2) * 2; + if (y) e2--; + + if ((t|32)=='a') { + long double round = 8.0; + int re; + + if (t&32) prefix += 9; + pl += 2; + + if (p<0 || p>=LDBL_MANT_DIG/4-1) re=0; + else re=LDBL_MANT_DIG/4-1-p; + + if (re) { + while (re--) round*=16; + if (*prefix=='-') { + y=-y; + y-=round; + y+=round; + y=-y; + } else { + y+=round; + y-=round; + } + } + + estr=fmt_u(e2<0 ? -e2 : e2, ebuf); + if (estr==ebuf) *--estr='0'; + *--estr = (e2<0 ? '-' : '+'); + *--estr = t+('p'-'a'); + + s=buf; + do { + int x=y; + *s++=xdigits[x]|(t&32); + y=16*(y-x); + if (s-buf==1 && (y||p>0||(fl&ALT_FORM))) *s++='.'; + } while (y); + + if (p && s-buf-2 < p) + l = (p+2) + (ebuf-estr); + else + l = (s-buf) + (ebuf-estr); + + pad(put, opaque, ' ', w, pl+l, fl, num); + out(put, opaque, prefix, pl, num); + pad(put, opaque, '0', w, pl+l, fl^ZERO_PAD, num); + out(put, opaque, buf, s-buf, num); + pad(put, opaque, '0', l-(ebuf-estr)-(s-buf), 0, 0, num); + out(put, opaque, estr, ebuf-estr, num); + pad(put, opaque, ' ', w, pl+l, fl^LEFT_ADJ, num); + return MAX(w, pl+l); + } + if (p<0) p=6; + + if (y) y *= 0x1p28, e2-=28; + + if (e2<0) a=r=z=big; + else a=r=z=big+sizeof(big)/sizeof(*big) - LDBL_MANT_DIG - 1; + + do { + *z = y; + y = 1000000000*(y-*z++); + } while (y); + + while (e2>0) { + uint32_t carry=0; + int sh=MIN(29,e2); + for (d=z-1; d>=a; d--) { + uint64_t x = ((uint64_t)*d<<sh)+carry; + *d = x % 1000000000; + carry = x / 1000000000; + } + if (!z[-1] && z>a) z--; + if (carry) *--a = carry; + e2-=sh; + } + while (e2<0) { + uint32_t carry=0, *b; + int sh=MIN(9,-e2); + for (d=a; d<z; d++) { + uint32_t rm = *d & (1<<sh)-1; + *d = (*d>>sh) + carry; + carry = (1000000000>>sh) * rm; + } + if (!*a) a++; + if (carry) *z++ = carry; + /* Avoid (slow!) computation past requested precision */ + b = (t|32)=='f' ? r : a; + if (z-b > 2+p/9) z = b+2+p/9; + e2+=sh; + } + + if (a<z) for (i=10, e=9*(r-a); *a>=i; i*=10, e++); + else e=0; + + /* Perform rounding: j is precision after the radix (possibly neg) */ + j = p - ((t|32)!='f')*e - ((t|32)=='g' && p); + if (j < 9*(z-r-1)) { + uint32_t x; + /* We avoid C's broken division of negative numbers */ + d = r + 1 + ((j+9*LDBL_MAX_EXP)/9 - LDBL_MAX_EXP); + j += 9*LDBL_MAX_EXP; + j %= 9; + for (i=10, j++; j<9; i*=10, j++); + x = *d % i; + /* Are there any significant digits past j? */ + if (x || d+1!=z) { + long double round = CONCAT(0x1p,LDBL_MANT_DIG); + long double small; + if (*d/i & 1) round += 2; + if (x<i/2) small=0x0.8p0; + else if (x==i/2 && d+1==z) small=0x1.0p0; + else small=0x1.8p0; + if (pl && *prefix=='-') round*=-1, small*=-1; + *d -= x; + /* Decide whether to round by probing round+small */ + if (round+small != round) { + *d = *d + i; + while (*d > 999999999) { + *d--=0; + (*d)++; + } + if (d<a) a=d; + for (i=10, e=9*(r-a); *a>=i; i*=10, e++); + } + } + if (z>d+1) z=d+1; + for (; !z[-1] && z>a; z--); + } + + if ((t|32)=='g') { + if (!p) p++; + if (p>e && e>=-4) { + t--; + p-=e+1; + } else { + t-=2; + p--; + } + if (!(fl&ALT_FORM)) { + /* Count trailing zeros in last place */ + if (z>a && z[-1]) for (i=10, j=0; z[-1]%i==0; i*=10, j++); + else j=9; + if ((t|32)=='f') + p = MIN(p,MAX(0,9*(z-r-1)-j)); + else + p = MIN(p,MAX(0,9*(z-r-1)+e-j)); + } + } + l = 1 + p + (p || (fl&ALT_FORM)); + if ((t|32)=='f') { + if (e>0) l+=e; + } else { + estr=fmt_u(e<0 ? -e : e, ebuf); + while(ebuf-estr<2) *--estr='0'; + *--estr = (e<0 ? '-' : '+'); + *--estr = t; + l += ebuf-estr; + } + + pad(put, opaque, ' ', w, pl+l, fl, num); + out(put, opaque, prefix, pl, num); + pad(put, opaque, '0', w, pl+l, fl^ZERO_PAD, num); + + if ((t|32)=='f') { + if (a>r) a=r; + for (d=a; d<=r; d++) { + char *s = fmt_u(*d, buf+9); + if (d!=a) while (s>buf) *--s='0'; + else if (s==buf+9) *--s='0'; + out(put, opaque, s, buf+9-s, num); + } + if (p || (fl&ALT_FORM)) out(put, opaque, ".", 1, num); + for (; d<z && p>0; d++, p-=9) { + char *s = fmt_u(*d, buf+9); + while (s>buf) *--s='0'; + out(put, opaque, s, MIN(9,p), num); + } + pad(put, opaque, '0', p+9, 9, 0, num); + } else { + if (z<=a) z=a+1; + for (d=a; d<z && p>=0; d++) { + char *s = fmt_u(*d, buf+9); + if (s==buf+9) *--s='0'; + if (d!=a) while (s>buf) *--s='0'; + else { + out(put, opaque, s++, 1, num); + if (p>0||(fl&ALT_FORM)) out(put, opaque, ".", 1, num); + } + out(put, opaque, s, MIN(buf+9-s, p), num); + p -= buf+9-s; + } + pad(put, opaque, '0', p+18, 18, 0, num); + out(put, opaque, estr, ebuf-estr, num); + } + + pad(put, opaque, ' ', w, pl+l, fl^LEFT_ADJ, num); + + return MAX(w, pl+l); +} +#endif + +/* ------------------------------------------------------------------------ */ +/* NUMBER PARSER */ + +static void parse_num(my_printf_uintmax *pnum, const char **pfmt) +{ + /* Width */ + (*pnum) = 0; + while (**pfmt >= '0' && **pfmt <= '9') { + /* Parse width */ + (*pnum) *= 10; + (*pnum) += **pfmt - '0'; + + (*pfmt)++; + } +} + +/* ------------------------------------------------------------------------ */ + +/* required for extrmeely scuffed %ls handling */ +int my_iprintf(put_spec put, void *opaque, const char *format, ...); + +int my_viprintf(put_spec put, void *opaque, const char *format, va_list ap) +{ + const char *fmt = format; + my_printf_uintmax num; + + num = 0; + + while (*fmt) { + if (fmt[0] == '%' && fmt[1] == '%') { + put(opaque, '%'); + num++; + fmt += 2; + } else if (*fmt == '%') { + my_printf_uintmax width, precision; + int have_precision; + enum flags flags = 0; + enum { + TYPE_NONE, + TYPE_hh, + TYPE_h, + TYPE_ll, + TYPE_l, + TYPE_j, + TYPE_z, + TYPE_t, + TYPE_L + } type = TYPE_NONE; + + fmt++; + + /* Flags */ + while (*fmt) { + int end = 0; + + switch (*fmt) { + case '-': + fmt++; + flags |= FLAG_JUSTIFY_LEFT; + /* Left justify (unimplemented) */ + break; + case '+': + fmt++; + flags |= FLAG_PLUS; + /* Forces plus/minus sign */ + break; + case ' ': + fmt++; + /* If no negative sign, add a space */ + flags |= FLAG_SPACE; + break; + case '#': + fmt++; + /* For hex and octal specifiers, this prepends with + * the C literal notation (such as 0x08 for "%#x", 8) */ + flags |= FLAG_HASH; + break; + case '0': + fmt++; + /* Left-pads the number with zeroes */ + flags |= FLAG_ZERO; + break; + default: + end = 1; + break; + } + + if (end) break; + } + + /* Width */ + parse_num(&width, &fmt); + + /* Precision */ + if (*fmt == '.') { + have_precision = 1; + fmt++; + if (*fmt == '*') { + /* Precision is in the va_list */ + precision = va_arg(ap, int); + fmt++; + } else { + /* NOTE this was not intentional, but we actually handle this right. + * + * "If the period is specified without an explicit value for precision, + * 0 is assumed." */ + parse_num(&precision, &fmt); + } + } else { + have_precision = 0; + } + + /* Length specifier (could be condensed to a switch) */ + switch (*fmt) { + case 'h': + fmt++; + if (*fmt == 'h') { + /* char */ + fmt++; + type = TYPE_hh; + } else { + /* short */ + type = TYPE_h; + } + break; + case 'l': + fmt++; + if (*fmt == 'l') { + /* long long */ + fmt++; + type = TYPE_ll; + } else { + /* long, wint_t (c), and wchar_t * (s) */ + type = TYPE_l; + } + break; + case 'j': + /* intmax_t */ + fmt++; + type = TYPE_j; + break; + case 'z': + /* size_t */ + fmt++; + type = TYPE_z; + break; + case 't': + /* ptrdiff_t */ + fmt++; + type = TYPE_t; + break; + case 'L': + /* long double */ + fmt++; + type = TYPE_L; + break; + } + +#define TYPE_CASE(TYPE, SIGN, T, X) \ + case TYPE: \ + d = (T)va_arg(ap, X); \ + break; + +#ifdef HAVE_LONG_LONG +# define LONG_LONG_CASE(sign) \ + TYPE_CASE(TYPE_ll, sign, sign long long, long long) +#else +# define LONG_LONG_CASE(sign) +#endif + +#ifdef HAVE_INTMAX_T +# define INTMAX_T_CASE(sign) \ + TYPE_CASE(TYPE_j, sign, intmax_t, intmax_t) +#else +# define INTMAX_T_CASE(sign) +#endif + +#ifdef HAVE_SIZE_T +# define SIZE_T_CASE(sign) \ + case TYPE_z: \ + d = my_size_t_sign(va_arg(ap, size_t)); \ + break; +#else +# define SIZE_T_CASE(sign) +#endif + +#ifdef HAVE_PTRDIFF_T +# define PTRDIFF_T_CASE(sign) \ + TYPE_CASE(TYPE_t, sign, ptrdiff_t, ptrdiff_t) +#else +# define PTRDIFF_T_CASE(sign) +#endif + +/* For numbers, these are basically all the same besides the sign. + * Note that we always(-ish) interpret numbers as signed, to prevent + * signed integer overflow (which is undefined behavior). The problem + * here is that if we, say, pass a size_t, there is no signed equivalent. + * If someone were to pass a size_t that's bigger than INTMAX_MAX, then + * we'll be in big trouble. But who's even going to use %zd anyway?? + * + * NOTE: ubsan doesn't scream at us when we do that. So I think it's + * probably fine. */ +#define TYPE_SWITCH(sign) \ + switch (type) { \ + TYPE_CASE(TYPE_hh, sign, sign char, int) \ + TYPE_CASE(TYPE_h, sign, sign short, int) \ + TYPE_CASE(TYPE_NONE, sign, sign int, int) \ + TYPE_CASE(TYPE_l, sign, sign long, long) \ + LONG_LONG_CASE(sign) \ + INTMAX_T_CASE(sign) \ + PTRDIFF_T_CASE(sign) \ + SIZE_T_CASE(sign) \ + default: \ + /* Bad type specifier */ \ + return MY_EINVAL; \ + } + + switch (*fmt) { + case 'd': { + /* Need special handling for some signed shit, so we can't + * do my_numput exactly. + * + * This is also the only place this needs to be... */ + my_printf_intmax d; + my_printf_uintmax ad; + my_printf_uintmax len; + + TYPE_SWITCH(signed) + + /* TODO handle width specifiers */ + + /* inline llabs with defined behavior for LLONG_MIN */ + ad = (d < 0) ? (~(my_printf_uintmax)d + 1) : d; + + if (width > 0) { + len = my_numput_width(ad, 10); + + if ((d < 0) || (flags & FLAG_PLUS) || (flags & FLAG_SPACE)) + len++; + + if (!(flags & (FLAG_JUSTIFY_LEFT|FLAG_ZERO)) && len + 1 < width) { + print_chars(put, opaque, &num, ' ', width - len - 1); + } + + if (d < 0) { + put(opaque, '-'); + num++; + } else if (flags & FLAG_PLUS) { + put(opaque, '+'); + num++; + } else if (flags & FLAG_SPACE) { + put(opaque, ' '); + num++; + } + + if ((flags & FLAG_ZERO) && len + 1 < width) + print_chars(put, opaque, &num, '0', width - len - 1); + + my_numput_lower(put, opaque, ad, 10, &num); + + if ((flags & FLAG_JUSTIFY_LEFT) && len + 1 < width) { + print_spaces(put, opaque, &num, width - len - 1); + } + } else { + /* Faster */ + if (d < 0) { + put(opaque, '-'); + num++; + } else if (flags & FLAG_PLUS) { + put(opaque, '+'); + num++; + } else if (flags & FLAG_SPACE) { + put(opaque, ' '); + num++; + } + + my_numput_lower(put, opaque, ad, 10, &num); + } + + fmt++; + + break; + } + case 'u': { + my_printf_uintmax d; + TYPE_SWITCH(unsigned) + my_numput(put, opaque, d, 10, &num, 0, NULL, 0, + width, flags & FLAG_JUSTIFY_LEFT, flags & FLAG_ZERO, 0); + fmt++; + break; + } + case 'x': { + my_printf_uintmax d; + TYPE_SWITCH(unsigned) + + /* stinky */ + my_numput(put, opaque, d, 16, &num, flags & FLAG_HASH, "0x", 2, + width, flags & FLAG_JUSTIFY_LEFT, flags & FLAG_ZERO, 0); + + fmt++; + break; + } + case 'X': { + my_printf_uintmax d; + TYPE_SWITCH(unsigned) + + /* stinky */ + my_numput(put, opaque, d, 16, &num, flags & FLAG_HASH, "0X", 2, + width, flags & FLAG_JUSTIFY_LEFT, flags & FLAG_ZERO, 1); + + fmt++; + break; + } + case 'o': { + my_printf_uintmax d; + TYPE_SWITCH(unsigned) + /* stinky */ + my_numput(put, opaque, d, 8, &num, flags & FLAG_HASH, "0", 1, + width, flags & FLAG_JUSTIFY_LEFT, flags & FLAG_ZERO, 0); + fmt++; + break; + } + +#undef TYPE_CASE +#undef LONG_LONG_CASE +#undef INTMAX_T_CASE +#undef SIZE_T_CASE +#undef PTRDIFF_T_CASE +#undef TYPE_SWITCH + +#ifdef HAVE_FLOATING_POINT + case 'a': case 'A': case 'e': case 'E': + case 'G': case 'g': case 'F': case 'f': { + /* balls */ + floatmax d; + switch (type) { +#ifdef HAVE_LONG_DOUBLE + case TYPE_L: + d = va_arg(ap, long double); + break; +#endif + case TYPE_NONE: + d = va_arg(ap, double); + break; + default: + /* Invalid */ + return MY_EINVAL; + } + if (!have_precision) precision = 6; + my_floatput(put, opaque, d, width, precision, flags, &num, *fmt); + fmt++; + break; + } +#endif + case 's': { + switch (type) { + case TYPE_l: { + wchar_t *s; + s = va_arg(ap, wchar_t *); + + /* This stinks and isn't right for width nor precision */ + while (*s && (!have_precision || precision-- > 0)) { + my_iprintf(put, opaque, "%lc", *(s++)); + num++; + } + + break; + } + case TYPE_NONE: { + char *s; + my_printf_size len; + + s = va_arg(ap, char *); + + if (width > 0) { + if (have_precision) { + /* Use strnlen to avoid overflow */ + len = my_strnlen(s, precision); + } else { + len = my_strlen(s); + } + + putstring(put, opaque, s, len, &num, width, !!(flags & FLAG_JUSTIFY_LEFT)); + } else { + /* Simpler implementation (only one pass of the string) */ + if (have_precision) { + while (*s && precision-- > 0) { + put(opaque, *(s++)); + num++; + } + } else { + while (*s) { + put(opaque, *(s++)); + num++; + } + } + } + break; + } + default: + /* Invalid */ + return MY_EINVAL; + } + + fmt++; + break; + } + case 'c': { + switch (type) { +#ifdef HAVE_WCTOMB + case TYPE_l: { + /* XXX this doesn't work on Windows. */ + char *c; + + /* TODO would make more sense to move the full buffer + * handling stuff out of here, instead of having it all + * intermixed like this */ + c = my_calloc(MB_CUR_MAX + 1, 1); + if (!c) + return MY_ENOMEM; + + /* handle wide char -> multi byte */ + if (wctomb(c, va_arg(ap, wint_t)) == -1) { + my_free(c); + return MY_EENCOD; + } + putstring(put, opaque, c, my_strlen(c), &num, width, !!(flags & FLAG_JUSTIFY_LEFT)); + my_free(c); + break; + } +#endif + case TYPE_NONE: { + /* Balls simple */ + char c = (char)va_arg(ap, int); + putstring(put, opaque, &c, 1, &num, width, !!(flags & FLAG_JUSTIFY_LEFT)); + break; + } + default: + /* Invalid type */ + return MY_EINVAL; + } + + fmt++; + break; + } + case 'n': { +#define STORE(TYPE, type) case TYPE: *va_arg(ap, type *) = num; break; + + switch (type) { + STORE(TYPE_hh, signed char) + STORE(TYPE_h, short) + STORE(TYPE_NONE, int) + STORE(TYPE_l, long) +#ifdef HAVE_LONG_LONG + STORE(TYPE_ll, long long) +#endif +#ifdef HAVE_INTMAX_T + STORE(TYPE_j, intmax_t) +#endif +#ifdef HAVE_SIZE_T + STORE(TYPE_z, size_t) +#endif +#ifdef HAVE_PTRDIFF_T + STORE(TYPE_t, ptrdiff_t) +#endif + default: + return -1; + } +#undef STORE + + fmt++; + break; + } + case 'p': { + /* This can pretty much be whatever we want, but I'm going to replicate + * the glibc result. */ + void *x; + + x = va_arg(ap, void *); + + put(opaque, '0'); + put(opaque, 'x'); + num += 2; + + my_numput_lower(put, opaque, (my_printf_uintptr)x, 16, &num); + fmt++; + break; + } + default: + /* Invalid/unknown selector */ + return MY_EINVAL; + } + } else { + /* Just append the character. */ + put(opaque, *(fmt++)); + num++; + } + } + + if (num > (my_printf_uintmax)INT_MAX) + return MY_EOVERFLOW; + + return num; +} + +int my_iprintf(put_spec put, void *opaque, const char *format, ...) +{ + va_list ap; + int r; + + va_start(ap, format); + r = my_viprintf(put, opaque, format, ap); + va_end(ap); + + return r; +} + +/* ------------------------------------------------------------------------ */ +/* vsnprintf */ + +struct put_vsnprintf_data { + char *out; + my_printf_size n; + int c; +}; + +static void put_vsnprintf(void *opaque, unsigned char c) +{ + struct put_vsnprintf_data *d = opaque; + + if (d->out && d->c < d->n) + d->out[d->c] = c; + + d->c++; +} + +int my_vsnprintf(char *out, my_printf_size n, const char *fmt, va_list ap) +{ + int r; + struct put_vsnprintf_data d; + + d.out = out; + d.n = n; + d.c = 0; + + r = my_viprintf(put_vsnprintf, &d, fmt, ap); + + /* case: returned size is smaller than buffer. */ + put_vsnprintf(&d, 0); + + /* case: returned size is larger than buffer. + * we still have to NUL terminate. */ + if (out && n > 0) + out[n-1] = 0; /* NUL terminate */ + + if (r < 0) + return r; + + /* subtract one because of NUL termination above */ + return d.c - 1; +} + +int my_snprintf(char *out, my_printf_size n, const char *fmt, ...) +{ + int r; + va_list ap; + + va_start(ap, fmt); + r = my_vsnprintf(out, n, fmt, ap); + va_end(ap); + + return r; +} + +/* ------------------------------------------------------------------------ */ +/* vasprintf + * we do this in one pass, instead of two (like the usual vsnprintf impl) + * why? well, it's a bit easier to implement, and doesn't require va_copy. */ + +struct put_vasprintf_data { + char *x; + my_printf_size len; + my_printf_size alloc; + + int dead; +}; + +static void put_vasprintf(void *opaque, unsigned char c) +{ + struct put_vasprintf_data *d = opaque; + + if (d->dead) + return; + + if (!d->x || d->len >= d->alloc) { + char *old; + + /* make a guesstimate */ + if (!d->alloc) { +#ifdef TEST + /* Stress test reallocation */ + d->alloc = 2; +#else + d->alloc = 128; +#endif + } else { + d->alloc *= 2; + } + + old = d->x; + d->x = my_realloc(old, d->alloc); + if (!d->x) { + my_free(old); + d->dead = 1; + return; + } + } + + d->x[d->len++] = c; +} + +int my_vasprintf(char **out, const char *fmt, va_list ap) +{ + int r; + struct put_vasprintf_data d; + + d.x = NULL; + d.len = 0; + d.alloc = 0; + d.dead = 0; + + r = my_viprintf(put_vasprintf, &d, fmt, ap); + + if (d.dead) /* memory allocation failed, punt */ + return MY_ENOMEM; + + /* Add NUL terminator */ + put_vasprintf(&d, 0); + + if (d.x) { + /* Trim the fat + * Even if the realloc calls fails, this is still okay + * because we still have the original allocation */ + void *x = my_realloc(d.x, d.len); + if (x) + d.x = x; + } + + if (r >= 0) + *out = d.x; + + return r; +} + +int my_asprintf(char **out, const char *fmt, ...) +{ + int r; + va_list ap; + + va_start(ap, fmt); + r = my_vasprintf(out, fmt, ap); + va_end(ap); + + return r; +} + +/* ------------------------------------------------------------------------ */ +/* Finally, functions for outputting to stdio. */ + +static void put_fprintf(void *opaque, unsigned char c) +{ + putc(c, opaque); +} + +int my_vfprintf(FILE *f, const char *fmt, va_list ap) +{ + return my_viprintf(put_fprintf, f, fmt, ap); +} + +int my_fprintf(FILE *f, const char *fmt, ...) +{ + int r; + va_list ap; + + va_start(ap, fmt); + r = my_viprintf(put_fprintf, f, fmt, ap); + va_end(ap); + + return r; +} + +/* ------------------------------------------------------------------------ */ + +int my_vprintf(const char *fmt, va_list ap) +{ + return my_vfprintf(stdout, fmt, ap); +} + +int my_printf(const char *fmt, ...) +{ + int r; + va_list ap; + + va_start(ap, fmt); + r = my_vprintf(fmt, ap); + va_end(ap); + + return r; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/printf.h Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,73 @@ +/** + * "Portable" printf implementation, with some extras. + * + * Copyright (c) Paper 2025 + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * NOTE: Floating point support is very scuffed, and doesn't + * work for many numbers. It also depends on math.h. If you don't + * care or don't want floating point support, toggle the #define + * to disable it. + * + * EXTRA NOTE: You don't need malloc() if you turn off wide character + * support :) +**/ + +#ifndef MY_PRINTF_H_ +#define MY_PRINTF_H_ + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <limits.h> + +/* XXX maybe we shouldn't use int as a return type. */ + +enum { + MY_ENOMEM = -1, /* Out of memory */ + MY_EINVAL = -2, /* Invalid format string */ + MY_EENCOD = -3, /* Invalid wide character */ + MY_EOVERFLOW = -4, /* Return value overflows */ +}; + +/* Get string representation of error */ +const char *my_strerror(int err); + +/* Free dynamically allocated memory */ +void my_free(void *x); + +/* ------ Actual printf stuff */ + +/* put function spec */ +typedef void (*put_spec)(void *opaque, unsigned char c); + +/* iprintf: my invention. it's basically the function that actually + * implements everything :) */ +int my_viprintf(put_spec put, void *opaque, const char *format, va_list ap); +int my_iprintf(put_spec put, void *opaque, const char *format, ...); + +/* Equivalent to C99 [v]snprintf */ +int my_vsnprintf(char *s, size_t n, const char *format, va_list ap); +int my_snprintf(char *s, size_t n, const char *format, ...); + +/* Puts an allocated printf'd string into '*ps'. Use my_free to deallocate. */ +int my_vasprintf(char **ps, const char *format, va_list ap); +int my_asprintf(char **ps, const char *format, ...); + +int my_vfprintf(FILE *f, const char *fmt, va_list ap); +int my_fprintf(FILE *f, const char *fmt, ...); + +int my_vprintf(const char *fmt, va_list ap); +int my_printf(const char *fmt, ...); + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test.c Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,40 @@ +#include "printf.h" + +#include <locale.h> +#include <stdlib.h> /* malloc */ +#include <limits.h> +#include <stdint.h> +#include <float.h> + +int test_fprintf(void); +int test_asprintf(void); +int test_snprintf(void); + +int main(void) +{ + /* :p */ + setlocale(LC_ALL, "C.UTF-8"); + + if (test_asprintf() != 0) { + fprintf(stderr, "asprintf test failed!\n"); + return 1; + } + + if (test_snprintf() != 0) { + fprintf(stderr, "snprintf test failed!\n"); + return 1; + } + + if (test_fprintf() != 0) { + fprintf(stderr, "fprintf test failed!\n"); + return 1; + } + + /* print this too... */ + my_printf( +#include "test.h" + ); + + printf("All tests succeeded.\n"); + return 0; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test.h Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,5 @@ +/* For all tests, this is the printf format string and all parameters. */ + +/* This is a really odd printf string. Chances are you won't use most of these + * but they are useful for stress-testing some of the weirder parts of printf. */ +"%% %Lf %Lf %f %555f %-4e %+08d %8d %6.s %-7lc %ls %c %hhu %#08x %#o %zu %p %zd\n", 0.1l + 0.2l, LDBL_MAX, DBL_MAX, -NAN, INFINITY, 1, 1, "yay", L'รค', L"good morning America", 'a', 1111, 123u, 0777, sizeof(long long), (void *)malloc, SIZE_MAX \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test_asprintf.c Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,45 @@ +#include "printf.h" + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <float.h> + +int test_asprintf(void) +{ + int n, n2; + char *s1, *s2; + + n = my_asprintf(&s1, +#include "test.h" + ); + if (n < 0) { + fprintf(stderr, "my_asprintf: %s\n", my_strerror(n)); + return -1; + } + n2 = asprintf(&s2, +#include "test.h" + ); + if (n2 < 0) { + my_free(s1); + perror("asprintf"); + return -1; + } + + if (strcmp(s1, s2) || (n != n2)) { + fprintf(stderr, "Got different results!!\n"); + fprintf(stderr, " fprintf: %d; %s", n, s1); + fprintf(stderr, " my_fprintf: %d; %s", n2, s2); + + my_free(s1); + free(s2); + + return -1; + } + + my_free(s1); + free(s2); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test_fprintf.c Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,66 @@ +#include "printf.h" + +#include <locale.h> +#include <stdlib.h> /* malloc */ +#include <limits.h> +#include <stdint.h> +#include <limits.h> +#include <float.h> + +int test_fprintf(void) +{ + int n, n2; + FILE *f1, *f2; + + f1 = tmpfile(); + f2 = tmpfile(); + + n = my_fprintf(f1, +#include "test.h" + ); + if (n < 0) { + fprintf(stderr, "my_fprintf: %s\n", my_strerror(n)); + return -1; + } + n2 = fprintf(f2, +#include "test.h" + ); + if (n2 < 0) { + perror("fprintf"); + return -1; + } + + fseek(f1, 0, SEEK_SET); + fseek(f2, 0, SEEK_SET); + + if (n != n2) { + fclose(f1); + fclose(f2); + return -1; + } + + for (;;) { + int c1, c2; + + c1 = fgetc(f1); + c2 = fgetc(f2); + + if (c1 != c2) { + fprintf(stderr, "Got different results!!\n"); + fprintf(stderr, " fprintf @ %ld: %c", ftell(f1), c1); + fprintf(stderr, " my_fprintf @ %ld: %c", ftell(f2), c2); + + fclose(f1); + fclose(f2); + return -1; + } + + if (c1 == EOF) + break; + } + + fclose(f1); + fclose(f2); + + return 0; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test_snprintf.c Wed Dec 03 03:04:39 2025 -0500 @@ -0,0 +1,74 @@ +#include "printf.h" + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <float.h> + +/* This is a smaller buffer size than test.h needs, because we want to test + * NUL-terminating behavior. */ +#define BUFSZ 128 + +int test_snprintf(void) +{ + int n1, n2, b1, b2; + char s1[BUFSZ], s2[BUFSZ]; + + b1 = my_snprintf(NULL, 0, +#include "test.h" + ); + if (b1 < 0) { + fprintf(stderr, "my_snprintf(NULL, 0, ...): %s\n", my_strerror(b1)); + return -1; + } + + b2 = snprintf(NULL, 0, +#include "test.h" + ); + if (b2 < 0) { + perror("snprintf(NULL, 0, ...)"); + return -1; + } + + if (b1 != b2) { + fprintf(stderr, "snprintf and my_snprintf returned different buffer sizes!\n"); + return -1; + } + + n1 = my_snprintf(s1, BUFSZ, +#include "test.h" + ); + if (n1 < 0) { + fprintf(stderr, "my_snprintf: %s\n", my_strerror(n1)); + return -1; + } + n2 = snprintf(s2, BUFSZ, +#include "test.h" + ); + if (n2 < 0) { + my_free(s1); + perror("snprintf"); + return -1; + } + + if (n1 != b1) { + fprintf(stderr, "my_snprintf with and without a buffer returned different results\n"); + return -1; + } + + if (n1 != n2) { + printf("snprintf and my_snprintf returned different buffer sizes\n"); + return -1; + } + + if (strcmp(s1, s2)) { + fprintf(stderr, "Got different results!!\n"); + fprintf(stderr, " snprintf: %s", s1); + fprintf(stderr, " my_snprintf: %s", s2); + + return -1; + } + + return 0; +}
