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;
}