Last active
November 4, 2023 03:15
-
-
Save jkent/a149e8b437744d5b4a5d2dca22fc706c to your computer and use it in GitHub Desktop.
Public domain printf implementation (fp incomplete)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <ctype.h> | |
#include <stdarg.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <sys/types.h> | |
enum { | |
LENGTH_DEFAULT, | |
LENGTH_HH, | |
LENGTH_H, | |
LENGTH_J, | |
LENGTH_LL, | |
LENGTH_L, | |
LENGTH_T, | |
LENGTH_Z, | |
}; | |
#define EMIT(data, ch) ({ \ | |
if ((data).count < (data).size - 1 && emit(ctx, ch)) { \ | |
(data).count++; \ | |
} else { \ | |
goto done; \ | |
} \ | |
}) | |
typedef struct { | |
size_t size; | |
size_t count; | |
struct { | |
int alt_flag; | |
int left_flag; | |
int sign_flag; | |
int pad_char; | |
int width; | |
int precision; | |
int length; | |
int unsigned_flag; | |
int base; | |
int conv; | |
} arg; | |
} printf_data_t; | |
static int count_digits(uintmax_t n, int base) | |
{ | |
int ret = 1; | |
while (n >= (uintmax_t) base) { | |
n /= base; | |
ret++; | |
} | |
return ret; | |
} | |
static void emit_integer(printf_data_t *data, void *ctx, stdio_emit_t emit, | |
uintmax_t n) | |
{ | |
const char digits[] = "0123456789abcdef"; | |
char buf[66]; | |
if (data->arg.base == 0) { | |
data->arg.base = 10; | |
} | |
int negative = 0; | |
if (!data->arg.unsigned_flag && (intmax_t) n < 0LL && | |
data->arg.base == 10) { | |
negative = 1; | |
n = -n; | |
} | |
size_t len = count_digits(n, data->arg.base); | |
if (data->arg.alt_flag) { | |
if (data->arg.base == 8 && n != 0) { | |
len += 1; | |
} else if (data->arg.base == 16) { | |
len += 2; | |
} | |
} | |
if (data->arg.sign_flag || negative) { | |
len += 1; | |
} | |
char *p = buf + len; | |
*p = 0; | |
do { | |
*(--p) = digits[n % data->arg.base]; | |
} while ((n /= data->arg.base) > 0); | |
if (negative) { | |
*(--p) = '-'; | |
} | |
if ((size_t) data->arg.width > len) { | |
data->arg.width -= len; | |
} else { | |
data->arg.width = 0; | |
} | |
while (!data->arg.left_flag && data->arg.width) { | |
EMIT(*data, data->arg.pad_char); | |
data->arg.width--; | |
} | |
size_t count = 0; | |
if (data->arg.alt_flag) { | |
if (data->arg.base == 8 && buf[0] != '0') { | |
EMIT(*data, '0'); | |
count += 1; | |
} else if (data->arg.base == 16) { | |
EMIT(*data, '0'); | |
EMIT(*data, islower(data->arg.conv) ? 'x' : 'X'); | |
count += 2; | |
} | |
} | |
if (data->arg.sign_flag || negative) { | |
EMIT(*data, negative ? '-' : '+'); | |
count += 1; | |
} | |
for (; count < len; count++) { | |
char ch = *p++; | |
EMIT(*data, islower(data->arg.conv) ? ch : toupper(ch)); | |
} | |
while (data->arg.left_flag && data->arg.width) { | |
EMIT(*data, ' '); | |
data->arg.width--; | |
} | |
done: | |
} | |
static void emit_string(printf_data_t *data, void *ctx, stdio_emit_t emit, | |
const char *s) | |
{ | |
size_t len = strlen(s); | |
if (data->arg.precision >= 0) { | |
len = (size_t) data->arg.precision < len ? | |
(size_t) data->arg.precision : len; | |
} | |
if ((size_t) data->arg.width > len) { | |
data->arg.width -= len; | |
} else { | |
data->arg.width = 0; | |
} | |
while (!data->arg.left_flag && data->arg.width) { | |
EMIT(*data, data->arg.pad_char); | |
data->arg.width--; | |
} | |
for (size_t count = 0; count < len; count++) { | |
EMIT(*data, *s++); | |
} | |
while (data->arg.left_flag && data->arg.width) { | |
EMIT(*data, ' '); | |
data->arg.width--; | |
} | |
done: | |
} | |
static size_t __generic_vsnprintf(void *ctx, stdio_emit_t emit, size_t size, | |
const char *fmt, va_list *ap) | |
{ | |
printf_data_t data; | |
memset(&data, 0, sizeof(data)); | |
data.size = size; | |
while (*fmt) { | |
if (*fmt != '%') { | |
EMIT(data, *fmt++); | |
continue; | |
} | |
fmt++; | |
memset(&data.arg, 0, sizeof(data.arg)); | |
data.arg.precision = -1; | |
data.arg.pad_char = ' '; | |
fmt_loop: | |
switch (*fmt) { | |
case 0: | |
break; | |
case '#': | |
fmt++; | |
data.arg.alt_flag = 1; | |
goto fmt_loop; | |
case '0': | |
fmt++; | |
data.arg.pad_char = '0'; | |
goto fmt_loop; | |
case '-': | |
fmt++; | |
data.arg.left_flag = 1; | |
goto fmt_loop; | |
case ' ': | |
fmt++; | |
data.arg.pad_char = ' '; | |
goto fmt_loop; | |
case '+': | |
fmt++; | |
data.arg.sign_flag = 1; | |
goto fmt_loop; | |
// Width | |
case '*': | |
case '1': | |
case '2': | |
case '3': | |
case '4': | |
case '5': | |
case '6': | |
case '7': | |
case '8': | |
case '9': | |
if (*fmt == '*') { | |
fmt++; | |
data.arg.width = va_arg(*ap, int); | |
} else { | |
data.arg.width = strtoull(fmt, (char **) &fmt, 10); | |
} | |
goto fmt_loop; | |
// Precision | |
case '.': | |
fmt++; | |
if (*fmt == '*') { | |
fmt++; | |
data.arg.precision = va_arg(*ap, int); | |
} else { | |
data.arg.precision = strtoull(fmt, (char **) &fmt, 10); | |
} | |
goto fmt_loop; | |
// Length modifiers | |
case 'h': | |
fmt++; | |
if (*fmt == 'h') { | |
data.arg.length = LENGTH_HH; | |
fmt++; | |
} else { | |
data.arg.length = LENGTH_H; | |
} | |
goto fmt_loop; | |
case 'l': | |
fmt++; | |
if (*fmt == 'l') { | |
fmt++; | |
data.arg.length = LENGTH_LL; | |
} else { | |
data.arg.length = LENGTH_L; | |
} | |
goto fmt_loop; | |
case 'L': | |
fmt++; | |
data.arg.length = LENGTH_LL; | |
goto fmt_loop; | |
case 'j': | |
fmt++; | |
data.arg.length = LENGTH_J; | |
goto fmt_loop; | |
case 't': | |
fmt++; | |
data.arg.length = LENGTH_T; | |
goto fmt_loop; | |
case 'z': | |
fmt++; | |
data.arg.length = LENGTH_Z; | |
goto fmt_loop; | |
// Conversion specifiers | |
case 'd': | |
case 'i': | |
{ | |
intmax_t n; | |
data.arg.conv = *fmt++; | |
switch (data.arg.length) { | |
case LENGTH_HH: | |
n = (signed char) va_arg(*ap, int); | |
break; | |
case LENGTH_H: | |
n = (short) va_arg(*ap, int); | |
break; | |
case LENGTH_J: | |
n = (intmax_t) va_arg(*ap, intmax_t); | |
break; | |
case LENGTH_LL: | |
n = (long long) va_arg(*ap, long long); | |
break; | |
case LENGTH_L: | |
n = (long) va_arg(*ap, long); | |
break; | |
case LENGTH_T: | |
n = (ptrdiff_t) va_arg(*ap, ptrdiff_t); | |
break; | |
case LENGTH_Z: | |
n = (ssize_t) va_arg(*ap, ssize_t); | |
break; | |
default: | |
n = (int) va_arg(*ap, int); | |
break; | |
} | |
emit_integer(&data, ctx, emit, n); | |
if (data.count == data.size - 1) { | |
goto done; | |
} | |
break; | |
} | |
case 'x': | |
case 'X': | |
data.arg.base += 8; | |
/* fallthrough */ | |
case 'o': | |
data.arg.base += 8; | |
/* fallthrough */ | |
case 'u': | |
{ | |
uintmax_t n; | |
data.arg.conv = *fmt++; | |
data.arg.unsigned_flag = 1; | |
switch (data.arg.length) { | |
case LENGTH_HH: | |
n = (unsigned char) va_arg(*ap, int); | |
break; | |
case LENGTH_H: | |
n = (unsigned short) va_arg(*ap, int); | |
break; | |
case LENGTH_J: | |
n = (uintmax_t) va_arg(*ap, uintmax_t); | |
break; | |
case LENGTH_LL: | |
n = (unsigned long long) va_arg(*ap, unsigned long long); | |
break; | |
case LENGTH_L: | |
n = (unsigned long) va_arg(*ap, unsigned long); | |
break; | |
case LENGTH_T: | |
n = (ptrdiff_t) va_arg(*ap, ptrdiff_t); | |
break; | |
case LENGTH_Z: | |
n = (size_t) va_arg(*ap, size_t); | |
break; | |
default: | |
n = (unsigned int) va_arg(*ap, unsigned int); | |
break; | |
} | |
emit_integer(&data, ctx, emit, n); | |
break; | |
} | |
#ifdef PRINTF_FLOAT | |
case 'e': | |
case 'E': | |
break; | |
case 'f': | |
case 'F': | |
break; | |
case 'g': | |
case 'G': | |
break; | |
case 'a': | |
case 'A': | |
break; | |
#endif | |
case 'c': | |
fmt++; | |
EMIT(data, va_arg(*ap, int)); | |
break; | |
case 's': | |
fmt++; | |
emit_string(&data, ctx, emit, va_arg(*ap, const char *)); | |
break; | |
case 'p': | |
fmt++; | |
data.arg.alt_flag = 1; | |
data.arg.base = 16; | |
emit_integer(&data, ctx, emit, | |
(uintptr_t) va_arg(*ap, const void *)); | |
break; | |
case 'n': | |
fmt++; | |
*va_arg(*ap, int *) = data.count; | |
break; | |
case '%': | |
fmt++; | |
EMIT(data, '%'); | |
break; | |
} | |
if (data.count == size - 1) { | |
goto done; | |
} | |
} | |
done: | |
if (data.count < size) { | |
emit(ctx, 0); | |
} | |
return data.count; | |
} | |
int printf(const char *fmt, ...) | |
{ | |
va_list ap; | |
size_t ret; | |
va_start(ap, fmt); | |
ret = __generic_vsnprintf(NULL, _stdout_emit, SIZE_MAX, fmt, &ap); | |
va_end(ap); | |
return ret; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment