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