#include #include #include #include #include #include #include #include #include #include typedef int (pfx(cbfn))(void *ctx, ctype ch); #define FLAG_ZERO 0x0001 // "0" #define FLAG_LEFT 0x0002 // "-" #define FLAG_PLUS 0x0004 // "+" #define FLAG_SPACE 0x0008 // " " #define FLAG_HASH 0x0010 // "#" #define FLAG_PREC 0x0020 // ".precision" #define FLAG_LONG 0x0040 // "l" #define FLAG_LLONG 0x0080 // "ll" #define FLAG_SHORT 0x0100 // "h" #define FLAG_CHAR 0x0200 // "hh" #define FLAG_UPPER 0x0400 // for hex digits 0xa-0xf // Helper function declaration static inline int pfx(_isdigit)(ctype ch); static inline int pfx(_atoi)(ctype const **sp, int *err); static inline int pfx(_ntoa)( int w, void *ctx, pfx(cbfn) cb, int neg, unsigned long long value, int base, int prec, int width, int flags ); static inline int pfx(_dtoh)( int w, void *ctx, pfx(cbfn) cb, double value, int prec, int width, int flags ); static inline int pfx(_dtoa)( int w, void *ctx, pfx(cbfn) cb, double value, int prec, int width, int flags ); static inline int pfx(_etoa)( int w, void *ctx, pfx(cbfn) cb, double value, int prec, int width, int flags ); static inline int pfx(_infnantoa)( int w, void *ctx, pfx(cbfn) cb, double value, int prec, int width, int flags ); #define out(ch) do { if((w++, !cb(ctx, (ch)))) return -1; } while(0) // Generic print static int pfx(vprintfcb)( void *ctx, pfx(cbfn) *cb, const ctype *fmt, va_list va ) { int w = 0; while(*fmt) { // Write a character if it's not a '%' sign while(*fmt && *fmt != '%') { out(*fmt++); continue; } if(*fmt == '%') { ++fmt; } // Parse flags int flags = 0; int isflag = 0; do { isflag = 1; switch(*fmt) { case '0': flags |= FLAG_ZERO; break; case '-': flags |= FLAG_LEFT; break; case '+': flags |= FLAG_PLUS; break; case ' ': flags |= FLAG_SPACE; break; case '#': flags |= FLAG_HASH; break; default: isflag = 0; } if(isflag) ++fmt; } while(isflag); if(flags & FLAG_LEFT) flags &= ~FLAG_ZERO; // Parse width field int width = 0; if(pfx(_isdigit(*fmt))) { int err = 0; width = pfx(_atoi(&fmt, &err)); if(err) return -1; } else if(*fmt == '*') { ++fmt; width = va_arg(va, int); if(width < 0) { width = -width; flags |= FLAG_LEFT; } } // If present, parse the precision field int prec = 0; if(*fmt == '.') { flags |= FLAG_PREC; ++fmt; if(pfx(_isdigit(*fmt))) { int err = 0; prec = pfx(_atoi(&fmt, &err)); if(err) return -1; } else if(*fmt == '*') { ++fmt; prec = va_arg(va, int); prec = (prec > 0? prec : 0); } } // Parse length field switch(*fmt) { case 'l': ++fmt; flags |= FLAG_LONG; if(*fmt == 'l') { ++fmt; flags |= FLAG_LLONG; } break; case 'h': ++fmt; flags |= FLAG_SHORT; if(*fmt == 'h') { ++fmt; flags |= FLAG_CHAR; } break; case 'j': ++fmt; if(sizeof(intmax_t) == sizeof(long)) flags |= FLAG_LONG; else flags |= FLAG_LLONG; break; case 'z': ++fmt; if(sizeof(size_t) == sizeof(long)) flags |= FLAG_LONG; else flags |= FLAG_LLONG; break; case 't': ++fmt; if(sizeof(ptrdiff_t) == sizeof(long)) flags |= FLAG_LONG; else flags |= FLAG_LLONG; break; case 'L': ++fmt; break; default: break; } // Print according to specifier switch(*fmt) { case 'd': case 'i': case 'u': case 'x': case 'X': case 'o': case 'b': { // Figure out the signedness and base int base = 10; if(*fmt == 'x' || *fmt == 'X') base = 16; if(*fmt == 'o') base = 8; if(*fmt == 'b') base = 2; int signd = (*fmt == 'i' || *fmt == 'd'); // Figure out flags if(*fmt == 'X') flags |= FLAG_UPPER; if(base == 10) flags &= ~FLAG_HASH; if(!signd) flags &= ~(FLAG_PLUS|FLAG_SPACE); if(flags & FLAG_PREC) flags &= ~FLAG_ZERO; ++fmt; // Read-in the integer argument from var args int neg = 0; unsigned long long value = 0; if(signd) { long long num; if(flags & FLAG_LLONG) num = (long long)va_arg(va, long long); else if(flags & FLAG_LONG) num = (long long)va_arg(va, long); else num = (long long)va_arg(va, int); // Avoiding UB on unary negation like a bitch if(num == LLONG_MIN) { neg = 1; value = (unsigned long long)LLONG_MAX + 1; } else { neg = num >= 0? 0 : 1; num = num >= 0? num : -num; value = (unsigned long long)num; } } else { if(flags & FLAG_LLONG) value = (unsigned long long)va_arg(va, unsigned long long); else if(flags & FLAG_LONG) value = (unsigned long long)va_arg(va, unsigned long); else value = (unsigned long long)va_arg(va, unsigned int); } // Output integer w = pfx(_ntoa)(w, ctx, cb, neg, value, base, prec, width, flags); if(w < 0) return -1; } break; case 'a': case 'A': { if(*fmt == 'A') flags |= FLAG_UPPER; ++fmt; double value = va_arg(va, double); w = pfx(_dtoh)(w, ctx, cb, value, prec, width, flags); if(w < 0) return -1; } break; case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': { if(*fmt == 'E' || *fmt == 'F' || *fmt == 'G') flags |= FLAG_UPPER; char conv = tolower(*fmt); ++fmt; double value = va_arg(va, double); if(conv == 'f') { w = pfx(_dtoa)(w, ctx, cb, value, prec, width, flags); } else if(conv == 'e') { w = pfx(_etoa)(w, ctx, cb, value, prec, width, flags); } else { int P = prec; if(!(flags & FLAG_PREC)) P = 6; if(prec == 0) P = 1; union { uint64_t bits; double value; } _ = {.value = value}; uint64_t bits = _.bits; int E = (int)((bits >> 52) & 0x7ff) - 1023; int class = fpclassify(value); if(class == FP_SUBNORMAL || class == FP_ZERO) { E = 0; } if(P > E && E >= -4) { w = pfx(_dtoa)(w, ctx, cb, value, P-(E+1), width, flags); } else { w = pfx(_etoa)(w, ctx, cb, value, P-1, width, flags); } } if(w < 0) return -1; }break; case 'c': { ++fmt; int ch; if(flags & FLAG_LONG) { ch = va_arg(va, wint_t); } else { ch = va_arg(va, int); } int pad = width-1; // Print width left-pad if(!(flags & FLAG_LEFT) && !(flags & FLAG_ZERO)) while(pad-- > 0) { out(' '); } out(ch); // Print right-pad if(flags & FLAG_LEFT) while(pad-- > 0) { out(' '); } } break; case 's': { ++fmt; char *str = NULL; wchar_t *wstr = NULL; int len = 0; if(flags & FLAG_LONG) { wstr = va_arg(va, wchar_t *); wchar_t *s = wstr; while(*s++!=0) ++len; int pad = width - len; // Print width left-pad if(!(flags & FLAG_LEFT) && !(flags & FLAG_ZERO)) while(pad-- > 0) { out(' '); } // Print string mbstate_t ps = {0}; for(int i = 0; i < len; ++i) { if(sizeof(ctype) == 2) { out(wstr[i]); } else { char utf8[5]; char *s = utf8; size_t r = c16rtomb(s, wstr[i], &ps); if(r == (size_t)(-1)) return -1; if(r != 0) { while(*s != 0) { out(*s++); } } } } // Print right-pad if(flags & FLAG_LEFT) while(pad-- > 0) { out(' '); } } else { str = va_arg(va, char *); char *s = str; if(flags & FLAG_PREC) { while(*s++!=0 && len 0) { out(' '); } // Print string mbstate_t ps = {0}; for(int i = 0; i < len; ++i) { out(str[i]); } // Print right-pad if(flags & FLAG_LEFT) while(pad-- > 0) { out(' '); } } } break; case 'p': { ++fmt; void *ptr = va_arg(va, void *); unsigned long long iptr = (unsigned long long)(uintptr_t)ptr; w = pfx(_ntoa)(w, ctx, cb, 0, iptr, 16, 16, 0, FLAG_HASH); if(w < 0) return -1; } break; case 'n': { ++fmt; int *p = va_arg(va, int *); *p = w; } break; case '%': { ++fmt; out('%'); } break; } } return w; } static inline int pfx(_isdigit)(ctype ch) { return '0' <= ch && ch <= '9'; } static inline int pfx(_atoi)(ctype const **sp, int *err) { int i = 0; ctype const *s = *sp; while(pfx(_isdigit(*s))) { int d = *s-'0'; if(i > (INT_MAX - d)/10) { *err = 1; return 0; } i = 10*i + d; ++s; } *sp = s; return i; } static inline int pfx(_ntoa)( int w, void *ctx, pfx(cbfn) cb, int neg, unsigned long long value, int base, int prec, int width, int flags ) { // We'll Write digits into buf in reverse and then call _out_rbuf int ndigits = 0; static ctype digits[64]; // Remove hash flag for 0 values if(value == 0) flags &= ~FLAG_HASH; // Write digits to buffer in reverse (starting from least significant) do { int d = (int)(value%base); value /= base; if(d < 10) d += '0'; else if(flags & FLAG_UPPER) d += 'A' - 10; else d += 'a' - 10; digits[ndigits++] = d; } while(value); // Figure out the length of the prefix (part of number before digit stirng) int pref_len = 0; if(flags & FLAG_HASH) pref_len = base == 8? 1 : 2; else if(neg) pref_len = 1; else if(flags & FLAG_PLUS) pref_len = 1; else if(flags & FLAG_SPACE) pref_len = 1; if(ndigits > prec) prec = ndigits; int num_len = pref_len + prec; int pad_len = width - num_len; if(!(flags & FLAG_LEFT)) { ctype pad_ch = ' '; if(flags & FLAG_ZERO) pad_ch = '0'; while(pad_len-- > 0) { out(pad_ch); } } // Print width left-pad if it's made out of space if(!(flags & FLAG_LEFT) && !(flags & FLAG_ZERO)) while(pad_len-- > 0) { out(' '); } // Print prefix if(flags & FLAG_HASH) { out('0'); if(base == 16) { out('x'); } else if(base == 2) { out('b'); } } else if(neg) { out('-'); } else if(flags & FLAG_PLUS) { out('+'); } else if(flags & FLAG_SPACE) { out(' '); } // Print width left-pad if it's made out of zero if(!(flags & FLAG_LEFT) && (flags & FLAG_ZERO)) while(pad_len-- > 0) { out('0'); } // Print zero-pad due to precision for(int i = ndigits; i < prec; ++i) { out('0'); } // Output buffer in reverse if(ndigits) while(ndigits--) { out(digits[ndigits]); } // Print right-pad if(flags & FLAG_LEFT) while(pad_len-- > 0) { out(' '); } return w; } static inline int pfx(_infnantoa)( int w, void *ctx, pfx(cbfn) cb, double value, int prec, int width, int flags ) { char const *name = NULL; int nchars = 0; if(isinf(value)) name = "inf", nchars = 3; if(isnan(value)) name = "nan", nchars = 3; // Figure out prefix int pref_len = 0; union { uint64_t bits; double value; } _ = {value}; uint64_t bits = _.bits; uint64_t neg = bits >> 63; if(neg) pref_len = 1; else if(flags & FLAG_PLUS) pref_len = 1; else if(flags & FLAG_SPACE) pref_len = 1; // Figure out pad int str_len = pref_len + nchars; int pad_len = width - str_len; // Print left-pad if(!(flags & FLAG_LEFT)) { while(pad_len-- > 0) { out(' '); } } // Print the prefix if(neg) out('-'); else if(flags & FLAG_PLUS) out('+'); else if(flags & FLAG_SPACE) out(' '); // Print the string while(*name) { out(*name++); } // Print right-pad if(flags & FLAG_LEFT) { while(pad_len-- > 0) { out(' '); } } return w; } static inline int pfx(_dtoh)( int w, void *ctx, pfx(cbfn) cb, double value, int prec, int width, int flags ) { int class = fpclassify(value); if(class == FP_INFINITE || class == FP_NAN) { return pfx(_infnantoa)(w, ctx, cb, value, prec, width, flags); } // Disassemble the float into parts union { uint64_t bits; double value; } _ = {.value = value}; uint64_t bits = _.bits; uint64_t neg = bits >> 63; int64_t exp = (int64_t)((bits >> 52) & 0x7ff) - 1023; int64_t mant = bits & ((UINT64_C(1)<<51)-1); if(class == FP_SUBNORMAL || class == FP_ZERO) { exp = 0; } // Figure out how many hex digits does mantissa take up (mant_digits_n) // and the number of digits after decimal point (prec) static ctype mant_buf[64] = {0}; int mant_digits_n; int nonsig_digits_n = 0; if(mant != 0) { int64_t m = mant; while((m & 0xf) == 0) { ++nonsig_digits_n; m >>= 4; } } else { nonsig_digits_n = 52/4; } mant_digits_n = 52/4 - nonsig_digits_n; if((flags & FLAG_PREC) == 0) { prec = mant_digits_n; } // Figure out how many symbols decimal point takes up (0 or 1) int decimal_point_n = 1; if(prec == 0) { decimal_point_n = 0; if(flags & FLAG_HASH) { decimal_point_n = 1; } } // Figure out how many digits exponent take up and it's sign // also save exponent digits to a buffer starting from LSD static ctype exp_buf[64] = {0}; int exp_digits_n = 0; ctype exp_sign; if(exp < 0) { exp_sign = '-'; exp = -exp; } else { exp_sign = '+'; } { int64_t e = exp; do { exp_buf[exp_digits_n++] = e % 10 + '0'; e /= 10; } while(e != 0); } // Figure out sign symbol and number of chars it takes up (0 or 1) int sign_n = 0; ctype sign_ch; if(value < 0) { sign_n = 1; sign_ch = '-'; } else if(flags & FLAG_PLUS) { sign_n = 1; sign_ch = '+'; } else if(flags & FLAG_SPACE) { sign_n = 1; sign_ch = ' '; } // Figure out the width of the number int significand_width = 1 + decimal_point_n + prec; int exp_part_width = 2 /*p+-*/ + exp_digits_n; int n_width = sign_n + 2 /*0x*/ + significand_width + exp_part_width; // Figure out width-padding required int pad = 0; if(width > n_width) pad = width - n_width; // Print width left-pad if it's made out of space if(!(flags & FLAG_LEFT) && !(flags & FLAG_ZERO)) while(pad-- > 0) { out(' '); } // Print sign if there if(sign_n) out(sign_ch); // Print 0x, 0X out('0'); out((flags & FLAG_UPPER)? 'X' : 'x'); // Print width left-pad if it's made out of zero if(!(flags & FLAG_LEFT) && (flags & FLAG_ZERO)) while(pad-- > 0) { out('0'); } // Print whole part out((class == FP_SUBNORMAL || class == FP_ZERO)? '0' : '1'); // Print decimal point if(decimal_point_n) out('.'); // Print precision padding for(int i = mant_digits_n; i < prec; ++i) { out('0'); } if(prec < mant_digits_n) mant_digits_n = prec; for(int i = 0; i != mant_digits_n; ++i) { int bit = 52 - 4 - i; int digit = (int)((mant >> bit) & 0xf); ctype ch; if(digit < 10) ch = digit+'0'; else if(flags & FLAG_UPPER) ch = digit+'A'-10; else ch = digit+'a'-10; out(ch); } // Print the exponent part out((flags & FLAG_UPPER)? 'P' : 'p'); out(exp_sign); if(exp_digits_n) do out(exp_buf[exp_digits_n]); while(exp_digits_n--); // Print right-pad if(flags & FLAG_LEFT) while(pad-- > 0) { out(' '); } return w; } static inline int pfx(_dtoa)( int w, void *ctx, pfx(cbfn) cb, double value, int prec, int width, int flags ) { int class = fpclassify(value); if(class == FP_INFINITE || class == FP_NAN) { return pfx(_infnantoa)(w, ctx, cb, value, prec, width, flags); } // This guy does memory allocation which makes it pretty cringe if(!(flags & FLAG_PREC)) prec = 6; char *buf = d2fixed(value, prec); int len = (int)strlen(buf); int pad = width - len; // Left pad if(!(flags & FLAG_LEFT) && !(flags & FLAG_ZERO)) while(pad-- > 0) { out(' '); } { char *str = buf; while(*str) { out(*str); ++str; } } free(buf); // Right pad if(flags & FLAG_LEFT) while(pad-- > 0) { out(' '); } return w; } static inline int pfx(_etoa)( int w, void *ctx, pfx(cbfn) cb, double value, int prec, int width, int flags ) { int class = fpclassify(value); if(class == FP_INFINITE || class == FP_NAN) { return pfx(_infnantoa)(w, ctx, cb, value, prec, width, flags); } // This guy does memory allocation which makes it pretty cringe if(!(flags & FLAG_PREC)) prec = 6; char *buf = d2exp(value, prec); int len = (int)strlen(buf); int pad = width - len; // Left pad if(!(flags & FLAG_LEFT) && !(flags & FLAG_ZERO)) while(pad-- > 0) { out(' '); } { char *str = buf; while(*str) { out(*str); ++str; } } free(buf); // Right pad if(flags & FLAG_LEFT) while(pad-- > 0) { out(' '); } return w; } #undef out