aboutsummaryrefslogtreecommitdiff
path: root/src/stdlib
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-12-31 15:32:47 -0500
committerBruce Hill <bruce@bruce-hill.com>2025-12-31 15:38:51 -0500
commit8c8444a03d98eb2b9262b14986859a8f4a0a23f4 (patch)
treef37c7d1e90637840d145980cd4ac45863dada347 /src/stdlib
parent41f92837bebae1de783dc7f4a8f880011c72757c (diff)
Move print.c/h back into stdlib
Diffstat (limited to 'src/stdlib')
-rw-r--r--src/stdlib/bigint.c2
-rw-r--r--src/stdlib/cli.c2
-rw-r--r--src/stdlib/files.c2
-rw-r--r--src/stdlib/memory.c2
-rw-r--r--src/stdlib/paths.c2
-rw-r--r--src/stdlib/print.c236
-rw-r--r--src/stdlib/print.h172
-rw-r--r--src/stdlib/stacktrace.c2
-rw-r--r--src/stdlib/stdlib.c2
9 files changed, 415 insertions, 7 deletions
diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c
index 11270848..f36cbe6a 100644
--- a/src/stdlib/bigint.c
+++ b/src/stdlib/bigint.c
@@ -10,11 +10,11 @@
#include <stdio.h>
#include <stdlib.h>
-#include "../print.h"
#include "../util.h"
#include "datatypes.h"
#include "integers.h"
#include "optionals.h"
+#include "print.h"
#include "siphash.h"
#include "text.h"
#include "types.h"
diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c
index d424f316..e30f7ced 100644
--- a/src/stdlib/cli.c
+++ b/src/stdlib/cli.c
@@ -11,7 +11,6 @@
#include <time.h>
#include "../config.h"
-#include "../print.h"
#include "bools.h"
#include "bytes.h"
#include "c_strings.h"
@@ -21,6 +20,7 @@
#include "nums.h"
#include "optionals.h"
#include "paths.h"
+#include "print.h"
#include "stdlib.h"
#include "tables.h"
#include "text.h"
diff --git a/src/stdlib/files.c b/src/stdlib/files.c
index 78c1bc94..7d56fcfc 100644
--- a/src/stdlib/files.c
+++ b/src/stdlib/files.c
@@ -12,8 +12,8 @@
#include <string.h>
#include <sys/param.h>
-#include "../print.h"
#include "files.h"
+#include "print.h"
#include "util.h"
static const int tabstop = 4;
diff --git a/src/stdlib/memory.c b/src/stdlib/memory.c
index 5b23e1b5..fcb1d5a6 100644
--- a/src/stdlib/memory.c
+++ b/src/stdlib/memory.c
@@ -5,9 +5,9 @@
#include <stdint.h>
#include <sys/param.h>
-#include "../print.h"
#include "memory.h"
#include "metamethods.h"
+#include "print.h"
#include "text.h"
#include "types.h"
#include "util.h"
diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c
index 0ef452d2..26e43dfa 100644
--- a/src/stdlib/paths.c
+++ b/src/stdlib/paths.c
@@ -17,7 +17,6 @@
#include <sys/types.h>
#include <unistd.h>
-#include "../print.h"
#include "../unistr-fixed.h"
#include "../util.h"
#include "enums.h"
@@ -25,6 +24,7 @@
#include "lists.h"
#include "optionals.h"
#include "paths.h"
+#include "print.h"
#include "structs.h"
#include "text.h"
#include "types.h"
diff --git a/src/stdlib/print.c b/src/stdlib/print.c
new file mode 100644
index 00000000..bc5b01c6
--- /dev/null
+++ b/src/stdlib/print.c
@@ -0,0 +1,236 @@
+// 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 <unistd.h>
+
+#include "fpconv.h"
+#include "print.h"
+#include "util.h"
+
+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;
+}
+
+#if defined(__GLIBC__) && defined(_GNU_SOURCE)
+// GLIBC has fopencookie()
+static ssize_t _gc_stream_write(void *cookie, const char *buf, size_t 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 (ssize_t)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;
+ cookie_io_functions_t functions = {.write = _gc_stream_write};
+ return fopencookie(stream, "w", functions);
+}
+#elif 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);
+}
+#else
+#error "This platform doesn't support fopencookie() or funopen()!"
+#endif
diff --git a/src/stdlib/print.h b/src/stdlib/print.h
new file mode 100644
index 00000000..5d789ead
--- /dev/null
+++ b/src/stdlib/print.h
@@ -0,0 +1,172 @@
+// This file defines some functions to make it easy to do formatted text
+// without using printf style specifiers:
+//
+// print(...) - print text
+// fprint(file, ...) - print text to file
+// print_err(...) - print an error message and exit with EXIT_FAILURE
+// String(...) - return an allocated string
+
+#pragma once
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+#include "bigint.h" // IWYU pragma: export
+#include "datatypes.h" // IWYU pragma: export
+#include "integers.h" // IWYU pragma: export
+#include "mapmacro.h"
+#include "paths.h" // IWYU pragma: export
+#include "text.h" // IWYU pragma: export
+
+// GCC lets you define macro-like functions which are always inlined and never
+// compiled using this combination of flags. See: https://gcc.gnu.org/onlinedocs/gcc/Inline.html
+#ifndef PRINT_FN
+#ifdef __TINYC__
+#define PRINT_FN static inline __attribute__((gnu_inline, always_inline)) int
+#else
+#define PRINT_FN extern inline __attribute__((gnu_inline, always_inline)) int
+#endif
+#endif
+
+typedef struct {
+ uint64_t n;
+ bool no_prefix;
+ bool uppercase;
+ int digits;
+} hex_format_t;
+#define hex(x, ...) ((hex_format_t){.n = x, __VA_ARGS__})
+
+typedef struct {
+ double d;
+} hex_double_t;
+#define hex_double(x, ...) ((hex_double_t){.d = x, __VA_ARGS__})
+
+typedef struct {
+ uint64_t n;
+ bool no_prefix;
+ int digits;
+} oct_format_t;
+#define oct(x, ...) ((oct_format_t){.n = x, __VA_ARGS__})
+
+typedef struct {
+ const char *str;
+} quoted_t;
+#define quoted(s) ((quoted_t){s})
+
+typedef struct {
+ const char *str;
+ size_t length;
+} string_slice_t;
+#define string_slice(...) ((string_slice_t){__VA_ARGS__})
+
+typedef struct {
+ char c;
+ int length;
+} repeated_char_t;
+#define repeated_char(ch, len) ((repeated_char_t){.c = ch, .length = len})
+
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#define FMT64 "ll"
+#else
+#define FMT64 "l"
+#endif
+
+int _print_uint(FILE *f, uint64_t x);
+int _print_double(FILE *f, double x);
+int _print_hex(FILE *f, hex_format_t hex);
+int _print_hex_double(FILE *f, hex_double_t hex);
+int _print_oct(FILE *f, oct_format_t oct);
+PRINT_FN _print_float(FILE *f, float x) {
+ return _print_double(f, (double)x);
+}
+PRINT_FN _print_pointer(FILE *f, void *p) {
+ return _print_hex(f, hex((uint64_t)p));
+}
+PRINT_FN _print_bool(FILE *f, bool b) {
+ return fputs(b ? "yes" : "no", f);
+}
+PRINT_FN _print_str(FILE *f, const char *s) {
+ return fputs(s ? s : "(null)", f);
+}
+int _print_char(FILE *f, char c);
+int _print_quoted(FILE *f, quoted_t quoted);
+PRINT_FN _print_string_slice(FILE *f, string_slice_t slice) {
+ return slice.str ? fwrite(slice.str, 1, slice.length, f) : (size_t)fputs("(null)", f);
+}
+PRINT_FN _print_repeated_char(FILE *f, repeated_char_t repeated) {
+ int len = 0;
+ for (int n = 0; n < repeated.length; n++)
+ len += fputc(repeated.c, f);
+ return len;
+}
+
+#ifndef _fprint1
+#define _fprint1(f, x) \
+ _Generic((x), \
+ char *: _print_str, \
+ const char *: _print_str, \
+ char: _print_char, \
+ bool: _print_bool, \
+ int64_t: Int64$print, \
+ int32_t: Int64$print, \
+ int16_t: Int64$print, \
+ int8_t: Int64$print, \
+ uint64_t: _print_uint, \
+ uint32_t: _print_uint, \
+ uint16_t: _print_uint, \
+ uint8_t: _print_uint, \
+ float: _print_float, \
+ double: _print_double, \
+ hex_format_t: _print_hex, \
+ hex_double_t: _print_hex_double, \
+ oct_format_t: _print_oct, \
+ quoted_t: _print_quoted, \
+ string_slice_t: _print_string_slice, \
+ repeated_char_t: _print_repeated_char, \
+ Text_t: Text$print, \
+ Path_t: Path$print, \
+ Int_t: Int$print, \
+ void *: _print_pointer)(f, x)
+#endif
+
+typedef struct {
+ char **buffer;
+ size_t *size;
+ size_t position;
+} gc_stream_t;
+
+FILE *gc_memory_stream(char **buf, size_t *size);
+
+#define _print(x) _n += _fprint1(_printing, x)
+#define _fprint(f, ...) \
+ ({ \
+ FILE *_printing = f; \
+ int _n = 0; \
+ MAP_LIST(_print, __VA_ARGS__); \
+ _n; \
+ })
+#define fprint(f, ...) _fprint(f, __VA_ARGS__, "\n")
+#define print(...) fprint(stdout, __VA_ARGS__)
+#define fprint_inline(f, ...) _fprint(f, __VA_ARGS__)
+#define print_inline(...) fprint_inline(stdout, __VA_ARGS__)
+#define String(...) \
+ ({ \
+ char *_buf = NULL; \
+ size_t _size = 0; \
+ FILE *_stream = gc_memory_stream(&_buf, &_size); \
+ assert(_stream); \
+ _fprint(_stream, __VA_ARGS__); \
+ fflush(_stream); \
+ _buf; \
+ })
+#define print_err(...) \
+ ({ \
+ fprint(stderr, "\033[31;1m", __VA_ARGS__, "\033[m"); \
+ exit(EXIT_FAILURE); \
+ })
diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c
index 7bc42755..1eba8188 100644
--- a/src/stdlib/stacktrace.c
+++ b/src/stdlib/stacktrace.c
@@ -15,7 +15,7 @@
#include <unistd.h>
#include "../config.h"
-#include "../print.h"
+#include "print.h"
#include "simpleparse.h"
#include "util.h"
diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c
index b156692c..aabeb6b0 100644
--- a/src/stdlib/stdlib.c
+++ b/src/stdlib/stdlib.c
@@ -14,12 +14,12 @@
#include <time.h>
#include "../config.h"
-#include "../print.h"
#include "../util.h"
#include "files.h"
#include "metamethods.h"
#include "optionals.h"
#include "paths.h"
+#include "print.h"
#include "siphash.h"
#include "stacktrace.h"
#include "stdlib.h"