Skip to content

Instantly share code, notes, and snippets.

@jkent
Last active November 4, 2023 03:15
Show Gist options
  • Save jkent/a149e8b437744d5b4a5d2dca22fc706c to your computer and use it in GitHub Desktop.
Save jkent/a149e8b437744d5b4a5d2dca22fc706c to your computer and use it in GitHub Desktop.
Public domain printf implementation (fp incomplete)
#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