llm
Download
265 lines 7.8 KiBCModified 2026-08-31 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
// This file defines some of the helper functions used for printing values

#include <ctype.h>
#include <gc.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <unistd.h>

#include "fpconv.h"
#include "print.h"
#include "util.h"

public
int _print_int(FILE *f, int64_t n) {
    char buf[21] = {[20] = 0}; // Big enough for INT64_MIN + '\0'
    char *p = &buf[19];
    bool negative = n < 0;
    if (negative) n = -n;

    do {
        *(p--) = '0' + (n % 10);
        n /= 10;
    } while (n > 0);

    if (negative) *(p--) = '-';

    return fwrite(p + 1, sizeof(char), (size_t)(&buf[19] - p), f);
}

public
int _print_uint(FILE *f, uint64_t n) {
    char buf[21] = {[20] = 0}; // Big enough for UINT64_MAX + '\0'
    char *p = &buf[19];

    do {
        *(p--) = '0' + (n % 10);
        n /= 10;
    } while (n > 0);

    return fwrite(p + 1, sizeof(char), (size_t)(&buf[19] - p), f);
}

public
int _print_hex(FILE *f, hex_format_t hex) {
    int printed = 0;
    if (!hex.no_prefix) printed += fputs("0x", f);
    if (hex.digits > 0) {
        hex.digits -= (hex.n == 0); // Don't need a leading zero for a zero
        for (uint64_t n = hex.n; n > 0 && hex.digits > 0; n /= 16) {
            hex.digits -= 1;
        }
        for (; hex.digits > 0; hex.digits -= 1) {
            printed += fputc('0', f);
        }
    }
    char buf[9] = {[8] = '\0'}; // Enough space for FFFFFFFF + '\0'
    char *p = &buf[7];
    do {
        uint8_t digit = hex.n % 16;
        if (digit <= 9) *(p--) = '0' + digit;
        else if (hex.uppercase) *(p--) = 'A' + digit - 10;
        else *(p--) = 'a' + digit - 10;
        hex.n /= 16;
    } while (hex.n > 0);
    printed += (int)fwrite(p + 1, sizeof(char), (size_t)(&buf[7] - p), f);
    return printed;
}

public
int _print_oct(FILE *f, oct_format_t oct) {
    int printed = 0;
    if (!oct.no_prefix) printed += fputs("0o", f);
    if (oct.digits > 0) {
        oct.digits -= (oct.n == 0); // Don't need a leading zero for a zero
        for (uint64_t n = oct.n; n > 0 && oct.digits > 0; n /= 8)
            oct.digits -= 1;
        for (; oct.digits > 0; oct.digits -= 1)
            printed += fputc('0', f);
    }
    char buf[12] = {[11] = '\0'}; // Enough space for octal UINT64_MAX + '\0'
    char *p = &buf[10];
    do {
        *(p--) = '0' + (oct.n % 8);
        oct.n /= 8;
    } while (oct.n > 0);
    printed += (int)fwrite(p + 1, sizeof(char), (size_t)(&buf[10] - p), f);
    return printed;
}

public
int _print_double(FILE *f, double n) {
    static char buf[24];
    int len = fpconv_dtoa(n, buf);
    return (int)fwrite(buf, sizeof(char), (size_t)len, f);
}

public
int _print_hex_double(FILE *f, hex_double_t hex) {
    if (hex.d != hex.d) return fputs("NAN", f);
    else if (hex.d == 1.0 / 0.0) return fputs("INF", f);
    else if (hex.d == -1.0 / 0.0) return fputs("-INF", f);
    else if (hex.d == 0.0) return fputs("0.0", f);

    union {
        double d;
        uint64_t u;
    } bits = {.d = hex.d};

    int sign = (bits.u >> 63) & 1ull;
    int exp = (int)((bits.u >> 52) & 0x7FF) - 1023ull;
    uint64_t frac = bits.u & 0xFFFFFFFFFFFFFull;

    char buf[25];
    char *p = buf;

    if (sign) *p++ = '-';
    *p++ = '0';
    *p++ = 'x';

    uint64_t mantissa = (1ull << 52) | frac; // implicit 1
    int mantissa_shift = 52;

    while ((mantissa & 0xF) == 0 && mantissa_shift > 0) {
        mantissa >>= 4;
        mantissa_shift -= 4;
    }

    uint64_t int_part = mantissa >> mantissa_shift;
    *p++ = "0123456789abcdef"[int_part];

    *p++ = '.';

    while (mantissa_shift > 0) {
        mantissa_shift -= 4;
        uint64_t digit = (mantissa >> mantissa_shift) & 0xF;
        *p++ = "0123456789abcdef"[digit];
    }

    *p++ = 'p';

    if (exp >= 0) {
        *p++ = '+';
    } else {
        *p++ = '-';
        exp = -exp;
    }

    char expbuf[6];
    int ei = 5;
    expbuf[ei--] = '\0';
    do {
        expbuf[ei--] = '0' + (exp % 10);
        exp /= 10;
    } while (exp && ei >= 0);

    ei++;
    while (expbuf[ei])
        *p++ = expbuf[ei++];

    *p = '\0';
    return fwrite(buf, sizeof(char), (size_t)(p - buf), f);
}

public
int _print_char(FILE *f, char c) {
#define ESC(e) "'\\" e "'"
    const char *named[256] = {['\''] = ESC("'"), ['\\'] = ESC("\\"), ['\n'] = ESC("n"),
                              ['\t'] = ESC("t"), ['\r'] = ESC("r"),  ['\033'] = ESC("e"),
                              ['\v'] = ESC("v"), ['\a'] = ESC("a"),  ['\b'] = ESC("b")};
    const char *name = named[(uint8_t)c];
    if (name != NULL) return fputs(name, f);
    else if (isprint(c)) return fputc('\'', f) + fputc(c, f) + fputc('\'', f);
    else
        return (fputs("'\\x", f) + _print_hex(f, hex((uint64_t)c, .digits = 2, .no_prefix = true, .uppercase = true))
                + fputs("'", f));
#undef ESC
}

public
int _print_quoted(FILE *f, quoted_t quoted) {
#define ESC(e) "\\" e
    const char *named[256] = {['"'] = ESC("\""), ['\\'] = ESC("\\"), ['\n'] = ESC("n"),
                              ['\t'] = ESC("t"), ['\r'] = ESC("r"),  ['\033'] = ESC("e"),
                              ['\v'] = ESC("v"), ['\a'] = ESC("a"),  ['\b'] = ESC("b")};
    int printed = fputc('"', f);
    for (const char *p = quoted.str; *p; p++) {
        const char *name = named[(uint8_t)*p];
        if (name != NULL) {
            printed += fputs(name, f);
        } else if (isprint(*p) || (uint8_t)*p > 0x7F) {
            printed += fputc(*p, f);
        } else {
            printed +=
                fputs("\\x", f) + _print_hex(f, hex((uint64_t)*p, .digits = 2, .no_prefix = true, .uppercase = true));
        }
    }
    printed += fputc('"', f);
#undef ESC
    return printed;
}

// gc_memory_stream() returns a FILE* that accumulates written data into a
// growable buffer, and gc_stream_finalize() flushes it, closes the stream, and
// returns the accumulated bytes as a GC-managed, NUL-terminated string.
//
// macOS and the BSDs let us hook stdio directly (funopen()) so the buffer can
// be GC memory from the start. musl libc (all Linux builds) has no such hook,
// so there we fall back to POSIX open_memstream() (which uses a malloc'd
// buffer) and copy the result into GC memory when finalizing.

#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
// BSDs have funopen() and fwopen()
static int _gc_stream_write(void *cookie, const char *buf, int size) {
    gc_stream_t *stream = (gc_stream_t *)cookie;
    if (stream->position + size + 1 > *stream->size)
        *stream->buffer =
            GC_REALLOC(*stream->buffer, (*stream->size += MAX(MAX(16UL, *stream->size / 2UL), size + 1UL)));
    memcpy(&(*stream->buffer)[stream->position], buf, size);
    stream->position += size;
    (*stream->buffer)[stream->position] = '\0';
    return size;
}

public
FILE *gc_memory_stream(char **buf, size_t *size) {
    gc_stream_t *stream = GC_MALLOC(sizeof(gc_stream_t));
    stream->size = size;
    stream->buffer = buf;
    *stream->size = 16;
    *stream->buffer = GC_MALLOC_ATOMIC(*stream->size);
    (*stream->buffer)[0] = '\0';
    stream->position = 0;
    return fwopen(stream, _gc_stream_write);
}

public
char *gc_stream_finalize(FILE *stream, char **buf, size_t *size) {
    (void)size;
    fflush(stream);
    char *result = *buf; // Already GC memory
    fclose(stream);
    return result;
}
#else
// Fallback for libcs without a custom-stream hook (e.g. musl): use POSIX
// open_memstream() into a malloc'd buffer, then copy into GC memory on finalize.
public
FILE *gc_memory_stream(char **buf, size_t *size) {
    *buf = NULL;
    *size = 0;
    return open_memstream(buf, size);
}

public
char *gc_stream_finalize(FILE *stream, char **buf, size_t *size) {
    fflush(stream);
    size_t len = *size;
    char *result = GC_MALLOC_ATOMIC(len + 1);
    memcpy(result, *buf, len);
    result[len] = '\0';
    fclose(stream); // Frees the malloc'd buffer that open_memstream allocated
    return result;
}
#endif