Mercurial > printf
view printf.c @ 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 |
line wrap: on
line source
/** * "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; }
