aboutsummaryrefslogtreecommitdiff
path: root/builtins
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-03-10 00:03:21 -0500
committerBruce Hill <bruce@bruce-hill.com>2024-03-10 00:03:21 -0500
commita33f73061776d6814f67fecd230c0706bc1ff10c (patch)
tree7f47f83f783f5b08a02bdd940988d9637102e6e4 /builtins
parentb639f01294f8c528ad0597a179f7e426bccdfb80 (diff)
Rearranging some files
Diffstat (limited to 'builtins')
-rw-r--r--builtins/array.c4
-rw-r--r--builtins/array.h2
-rw-r--r--builtins/bool.c4
-rw-r--r--builtins/color.c2
-rw-r--r--builtins/files.c320
-rw-r--r--builtins/files.h42
-rw-r--r--builtins/functions.c12
-rw-r--r--builtins/halfsiphash.h22
-rw-r--r--builtins/integers.c2
-rw-r--r--builtins/macros.h31
-rw-r--r--builtins/memory.c4
-rw-r--r--builtins/nums.c4
-rw-r--r--builtins/pointer.c8
-rw-r--r--builtins/table.c6
-rw-r--r--builtins/text.c2
-rw-r--r--builtins/tomo.h50
-rw-r--r--builtins/types.c8
-rw-r--r--builtins/util.c84
-rw-r--r--builtins/util.h59
19 files changed, 637 insertions, 29 deletions
diff --git a/builtins/array.c b/builtins/array.c
index 07814f66..2fd66f58 100644
--- a/builtins/array.c
+++ b/builtins/array.c
@@ -11,8 +11,8 @@
#include "array.h"
#include "types.h"
#include "functions.h"
-#include "../SipHash/halfsiphash.h"
-#include "../util.h"
+#include "halfsiphash.h"
+#include "util.h"
static inline size_t get_item_size(const TypeInfo *info)
{
diff --git a/builtins/array.h b/builtins/array.h
index 52fd65e3..d36db573 100644
--- a/builtins/array.h
+++ b/builtins/array.h
@@ -2,7 +2,7 @@
#include <stdbool.h>
#include <gc/cord.h>
-#include "../util.h"
+#include "util.h"
#include "datatypes.h"
#include "functions.h"
#include "types.h"
diff --git a/builtins/bool.c b/builtins/bool.c
index 327a101d..35eaafec 100644
--- a/builtins/bool.c
+++ b/builtins/bool.c
@@ -8,9 +8,9 @@
#include <sys/param.h>
#include <err.h>
-#include "../SipHash/halfsiphash.h"
-#include "../util.h"
+#include "util.h"
#include "bool.h"
+#include "halfsiphash.h"
#include "types.h"
public CORD Bool__as_text(const bool *b, bool colorize, const TypeInfo *type)
diff --git a/builtins/color.c b/builtins/color.c
index b84d8a71..4732382a 100644
--- a/builtins/color.c
+++ b/builtins/color.c
@@ -4,7 +4,7 @@
#include <string.h>
#include <unistd.h>
-#include "../util.h"
+#include "util.h"
#include "color.h"
public bool USE_COLOR = true;
diff --git a/builtins/files.c b/builtins/files.c
new file mode 100644
index 00000000..9fe9b916
--- /dev/null
+++ b/builtins/files.c
@@ -0,0 +1,320 @@
+//
+// files.c - Implementation of some file loading functionality.
+//
+
+#include <err.h>
+#include <fcntl.h>
+#include <gc.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+
+#include "files.h"
+#include "util.h"
+
+static const int tabstop = 4;
+
+public char *resolve_path(const char *path, const char *relative_to, const char *system_path)
+{
+ if (!relative_to || streq(relative_to, "/dev/stdin")) relative_to = ".";
+ if (!path || strlen(path) == 0) return NULL;
+
+ // Resolve the path to an absolute path, assuming it's relative to the file
+ // it was found in:
+ char buf[PATH_MAX] = {0};
+ if (streq(path, "~") || strncmp(path, "~/", 2) == 0) {
+ char *resolved = realpath(heap_strf("%s%s", getenv("HOME"), path+1), buf);
+ if (resolved) return heap_str(resolved);
+ } else if (streq(path, ".") || strncmp(path, "./", 2) == 0) {
+ char *relative_dir = dirname(heap_str(relative_to));
+ char *resolved = realpath(heap_strf("%s/%s", relative_dir, path), buf);
+ if (resolved) return heap_str(resolved);
+ } else if (path[0] == '/') {
+ // Absolute path:
+ char *resolved = realpath(path, buf);
+ if (resolved) return heap_str(resolved);
+ } else {
+ // Relative path:
+ char *relative_dir = dirname(heap_str(relative_to));
+ if (!system_path) system_path = ".";
+ char *copy = strdup(system_path);
+ for (char *dir, *pos = copy; (dir = strsep(&pos, ":")); ) {
+ if (dir[0] == '/') {
+ char *resolved = realpath(heap_strf("%s/%s", dir, path), buf);
+ if (resolved) return heap_str(resolved);
+ } else if (dir[0] == '~' && (dir[1] == '\0' || dir[1] == '/')) {
+ char *resolved = realpath(heap_strf("%s%s/%s", getenv("HOME"), dir, path), buf);
+ if (resolved) return heap_str(resolved);
+ } else if (streq(dir, ".") || strncmp(dir, "./", 2) == 0) {
+ char *resolved = realpath(heap_strf("%s/%s", relative_dir, path), buf);
+ if (resolved) return heap_str(resolved);
+ } else if (streq(dir, ".") || streq(dir, "..") || strncmp(dir, "./", 2) == 0 || strncmp(dir, "../", 3) == 0) {
+ char *resolved = realpath(heap_strf("%s/%s/%s", relative_dir, dir, path), buf);
+ if (resolved) return heap_str(resolved);
+ } else {
+ char *resolved = realpath(heap_strf("%s/%s", dir, path), buf);
+ if (resolved) return heap_str(resolved);
+ }
+ }
+ free(copy);
+ }
+ return NULL;
+}
+
+static file_t *_load_file(const char* filename, FILE *file)
+{
+ if (!file) return NULL;
+
+ file_t *ret = new(file_t, .filename=filename);
+
+ size_t file_size = 0, line_cap = 0;
+ char *file_buf = NULL, *line_buf = NULL;
+ FILE *mem = open_memstream(&file_buf, &file_size);
+ int64_t line_len = 0;
+ while ((line_len = getline(&line_buf, &line_cap, file)) >= 0) {
+ file_line_t line_info = {.offset=file_size, .indent=0, .is_empty=false};
+ char *p;
+ for (p = line_buf; *p == '\t'; ++p)
+ line_info.indent += 1;
+ line_info.is_empty = *p != '\r' && *p != '\n';
+ if (ret->line_capacity <= ret->num_lines) {
+ ret->lines = GC_REALLOC(ret->lines, sizeof(file_line_t)*(ret->line_capacity += 32));
+ }
+ ret->lines[ret->num_lines++] = line_info;
+ fwrite(line_buf, sizeof(char), line_len, mem);
+ fflush(mem);
+ }
+ fclose(file);
+
+ char *copy = GC_MALLOC_ATOMIC(file_size+1);
+ memcpy(copy, file_buf, file_size);
+ copy[file_size] = '\0';
+ ret->text = copy;
+ ret->len = file_size;
+ fclose(mem);
+
+ free(file_buf);
+ ret->relative_filename = filename;
+ if (filename && filename[0] != '<' && !streq(filename, "/dev/stdin")) {
+ filename = resolve_path(filename, ".", ".");
+ // Convert to relative path (if applicable)
+ char buf[PATH_MAX];
+ char *cwd = getcwd(buf, sizeof(buf));
+ int64_t cwd_len = strlen(cwd);
+ if (strncmp(cwd, filename, cwd_len) == 0 && filename[cwd_len] == '/')
+ ret->relative_filename = &filename[cwd_len+1];
+ }
+ return ret;
+}
+
+//
+// Read an entire file into memory.
+//
+public file_t *load_file(const char* filename)
+{
+ FILE *file = filename[0] ? fopen(filename, "r") : stdin;
+ return _load_file(filename, file);
+}
+
+//
+// Create a virtual file from a string.
+//
+public file_t *spoof_file(const char* filename, const char *text)
+{
+ FILE *file = fmemopen((char*)text, strlen(text)+1, "r");
+ return _load_file(filename, file);
+}
+
+//
+// Given a pointer, determine which line number it points to (1-indexed)
+//
+public int64_t get_line_number(file_t *f, const char *p)
+{
+ // Binary search:
+ int64_t lo = 0, hi = (int64_t)f->num_lines-1;
+ if (p < f->text) return 0;
+ int64_t offset = (int64_t)(p - f->text);
+ while (lo <= hi) {
+ int64_t mid = (lo + hi) / 2;
+ file_line_t *line = &f->lines[mid];
+ if (line->offset == offset)
+ return mid + 1;
+ else if (line->offset < offset)
+ lo = mid + 1;
+ else if (line->offset > offset)
+ hi = mid - 1;
+ }
+ return lo; // Return the line number whose line starts closest before p
+}
+
+//
+// Given a pointer, determine which line column it points to.
+//
+public int64_t get_line_column(file_t *f, const char *p)
+{
+ int64_t line_no = get_line_number(f, p);
+ file_line_t *line = &f->lines[line_no-1];
+ return 1 + (int64_t)(p - (f->text + line->offset));
+}
+
+//
+// Given a pointer, get the indentation of the line it's on.
+//
+public int64_t get_indent(file_t *f, const char *p)
+{
+ int64_t line_no = get_line_number(f, p);
+ file_line_t *line = &f->lines[line_no-1];
+ return line->indent;
+}
+
+//
+// Return a pointer to the line with the specified line number (1-indexed)
+//
+public const char *get_line(file_t *f, int64_t line_number)
+{
+ if (line_number == 0 || line_number > (int64_t)f->num_lines) return NULL;
+ file_line_t *line = &f->lines[line_number-1];
+ return f->text + line->offset;
+}
+
+//
+// Return a value like /foo:line:col
+//
+public const char *get_file_pos(file_t *f, const char *p)
+{
+ return heap_strf("%s:%ld:%ld", f->filename, get_line_number(f, p), get_line_column(f, p));
+}
+
+static int fputc_column(FILE *out, char c, char print_char, int *column)
+{
+ int printed = 0;
+ if (print_char == '\t') print_char = ' ';
+ if (c == '\t') {
+ for (int to_fill = tabstop - (*column % tabstop); to_fill > 0; --to_fill) {
+ printed += fputc(print_char, out);
+ ++*column;
+ }
+ } else {
+ printed += fputc(print_char, out);
+ ++*column;
+ }
+ return printed;
+}
+
+//
+// Print a span from a file
+//
+public int fprint_span(FILE *out, file_t *file, const char *start, const char *end, const char *hl_color, int64_t context_lines, bool use_color)
+{
+ if (!file) return 0;
+
+ // Handle spans that come from multiple files:
+ if (start < file->text || start > file->text + file->len)
+ start = end;
+ if (end < file->text || end > file->text + file->len)
+ end = start;
+ // Just in case neither end of the span came from this file:
+ if (end < file->text || end > file->text + file->len)
+ start = end = file->text;
+
+ const char *lineno_fmt, *normal_color, *empty_marker;
+ bool print_carets = false;
+ int printed = 0;
+ if (use_color) {
+ lineno_fmt = "\x1b[0;2m%*lu\x1b(0\x78\x1b(B\x1b[m ";
+ normal_color = "\x1b[m";
+ empty_marker = "\x1b(0\x61\x1b(B";
+ printed += fprintf(out, "\x1b[33;4;1m%s\x1b[m\n", file->relative_filename);
+ } else {
+ lineno_fmt = "%*lu| ";
+ hl_color = "";
+ normal_color = "";
+ empty_marker = " ";
+ print_carets = true;
+ printed += fprintf(out, "%s\n", file->relative_filename);
+ }
+
+ if (context_lines == 0)
+ return fprintf(out, "%s%.*s%s", hl_color, (int)(end - start), start, normal_color);
+
+ int64_t start_line = get_line_number(file, start),
+ end_line = get_line_number(file, end);
+
+ int64_t first_line = start_line - (context_lines - 1),
+ last_line = end_line + (context_lines - 1);
+
+ if (first_line < 1) first_line = 1;
+ if (last_line > file->num_lines) last_line = file->num_lines;
+
+ int digits = 1;
+ for (int64_t i = last_line; i > 0; i /= 10) ++digits;
+
+ for (int64_t line_no = first_line; line_no <= last_line; ++line_no) {
+ if (line_no > first_line + 5 && line_no < last_line - 5) {
+ if (use_color)
+ printed += fprintf(out, "\x1b[0;2;3;4m ... %ld lines omitted ... \x1b[m\n", (last_line - first_line) - 11);
+ else
+ printed += fprintf(out, " ... %ld lines omitted ...\n", (last_line - first_line) - 11);
+ line_no = last_line - 6;
+ continue;
+ }
+
+ printed += fprintf(out, lineno_fmt, digits, line_no);
+ const char *line = get_line(file, line_no);
+ if (!line) break;
+
+ int column = 0;
+ const char *p = line;
+ // Before match
+ for (; *p && *p != '\r' && *p != '\n' && p < start; ++p)
+ printed += fputc_column(out, *p, *p, &column);
+
+ // Zero-width matches
+ if (p == start && start == end) {
+ printed += fprintf(out, "%s%s%s", hl_color, empty_marker, normal_color);
+ column += 1;
+ }
+
+ // Inside match
+ if (start <= p && p < end) {
+ printed += fputs(hl_color, out);
+ for (; *p && *p != '\r' && *p != '\n' && p < end; ++p)
+ printed += fputc_column(out, *p, *p, &column);
+ printed += fputs(normal_color, out);
+ }
+
+ // After match
+ for (; *p && *p != '\r' && *p != '\n'; ++p)
+ printed += fputc_column(out, *p, *p, &column);
+
+ printed += fprintf(out, "\n");
+
+ const char *eol = strchrnul(line, '\n');
+ if (print_carets && start >= line && start < eol && line <= start) {
+ for (int num = 0; num < digits; num++)
+ printed += fputc(' ', out);
+ printed += fputs(": ", out);
+ int column = 0;
+ for (const char *sp = line; *sp && *sp != '\n'; ++sp) {
+ char print_char;
+ if (sp < start)
+ print_char = ' ';
+ else if (sp == start && sp == end)
+ print_char = '^';
+ else if (sp >= start && sp < end)
+ print_char = '-';
+ else
+ print_char = ' ';
+ printed += fputc_column(out, *sp, print_char, &column);
+ }
+ printed += fputs("\n", out);
+ }
+ }
+ fflush(out);
+ return printed;
+}
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/builtins/files.h b/builtins/files.h
new file mode 100644
index 00000000..79dd48cd
--- /dev/null
+++ b/builtins/files.h
@@ -0,0 +1,42 @@
+//
+// files.h - Definitions of an API for loading files.
+//
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+typedef struct {
+ int64_t offset;
+ int64_t indent:63;
+ bool is_empty:1;
+} file_line_t;
+
+typedef struct {
+ const char *filename, *relative_filename;
+ const char *text;
+ int64_t len;
+ int64_t num_lines, line_capacity;
+ file_line_t *lines;
+} file_t;
+
+char *resolve_path(const char *path, const char *relative_to, const char *system_path);
+__attribute__((nonnull))
+file_t *load_file(const char *filename);
+__attribute__((nonnull, returns_nonnull))
+file_t *spoof_file(const char *filename, const char *text);
+__attribute__((pure, nonnull))
+int64_t get_line_number(file_t *f, const char *p);
+__attribute__((pure, nonnull))
+int64_t get_line_column(file_t *f, const char *p);
+__attribute__((pure, nonnull))
+int64_t get_indent(file_t *f, const char *p);
+__attribute__((pure, nonnull))
+const char *get_line(file_t *f, int64_t line_number);
+__attribute__((pure, nonnull))
+const char *get_file_pos(file_t *f, const char *p);
+int fprint_span(FILE *out, file_t *file, const char *start, const char *end, const char *hl_color, int64_t context_lines, bool use_color);
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/builtins/functions.c b/builtins/functions.c
index 448b7758..ae67fcb9 100644
--- a/builtins/functions.c
+++ b/builtins/functions.c
@@ -7,15 +7,15 @@
#include <sys/param.h>
#include <uninorm.h>
-#include "../SipHash/halfsiphash.h"
-#include "../files.h"
-#include "../util.h"
-#include "functions.h"
+#include "files.h"
+#include "util.h"
#include "array.h"
-#include "table.h"
-#include "text.h"
+#include "functions.h"
+#include "halfsiphash.h"
#include "pointer.h"
#include "string.h"
+#include "table.h"
+#include "text.h"
#include "types.h"
extern bool USE_COLOR;
diff --git a/builtins/halfsiphash.h b/builtins/halfsiphash.h
new file mode 100644
index 00000000..a1af8cd2
--- /dev/null
+++ b/builtins/halfsiphash.h
@@ -0,0 +1,22 @@
+/*
+ SipHash reference C implementation
+
+ Copyright (c) 2012-2021 Jean-Philippe Aumasson
+ <jeanphilippe.aumasson@gmail.com>
+ Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>
+
+ To the extent possible under law, the author(s) have dedicated all copyright
+ and related and neighboring rights to this software to the public domain
+ worldwide. This software is distributed without any warranty.
+
+ You should have received a copy of the CC0 Public Domain Dedication along
+ with
+ this software. If not, see
+ <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+
+int halfsiphash(const void *in, const size_t inlen, const void *k, uint8_t *out,
+ const size_t outlen);
diff --git a/builtins/integers.c b/builtins/integers.c
index 6de85c3e..4116879e 100644
--- a/builtins/integers.c
+++ b/builtins/integers.c
@@ -4,9 +4,9 @@
#include <stdint.h>
#include <stdlib.h>
-#include "../SipHash/halfsiphash.h"
#include "array.h"
#include "datatypes.h"
+#include "halfsiphash.h"
#include "integers.h"
#include "string.h"
#include "types.h"
diff --git a/builtins/macros.h b/builtins/macros.h
new file mode 100644
index 00000000..c6f474ec
--- /dev/null
+++ b/builtins/macros.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <gc.h>
+#include <gc/cord.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define $heap(x) (__typeof(x)*)memcpy(GC_MALLOC(sizeof(x)), (__typeof(x)[1]){x}, sizeof(x))
+#define $stack(x) (__typeof(x)*)((__typeof(x)[1]){x})
+#define $tagged(obj_expr, type_name, tag_name) ({ __typeof(obj_expr) $obj = obj_expr; \
+ $obj.$tag == $tag$##type_name##$##tag_name ? &$obj.tag_name : NULL; })
+
+
+#define not(x) _Generic(x, bool: (bool)!(x), int64_t: ~(x), int32_t: ~(x), int16_t: ~(x), int8_t: ~(x), \
+ array_t: ((x).length == 0), table_t: ((x).entries.length == 0), CORD: ((x) == CORD_EMPTY), \
+ default: _Static_assert(0, "Not supported"))
+#define Bool(x) _Generic(x, bool: (bool)(x), int64_t: (x != 0), int32_t: (x != 0), int16_t: (x != 0), int8_t: (x != 0), CORD: ((x) == CORD_EMPTY), \
+ array_t: ((x).length > 0), table_t: ((x).entries.length > 0), CORD: ((x) != CORD_EMPTY), \
+ default: _Static_assert(0, "Not supported"))
+#define and(x, y) _Generic(x, bool: (bool)((x) && (y)), default: ((x) & (y)))
+#define or(x, y) _Generic(x, bool: (bool)((x) || (y)), default: ((x) | (y)))
+#define xor(x, y) _Generic(x, bool: (bool)((x) ^ (y)), default: ((x) ^ (y)))
+#define mod(x, n) ((x) % (n))
+#define mod1(x, n) (((x) % (n)) + (__typeof(x))1)
+#define $cmp(x, y, info) (_Generic(x, int8_t: (x>0)-(y>0), int16_t: (x>0)-(y>0), int32_t: (x>0)-(y>0), int64_t: (x>0)-(y>0), bool: (x>0)-(y>0), \
+ CORD: CORD_cmp((CORD)x, (CORD)y), char*: strcmp((char*)x, (char*)y), default: generic_compare($stack(x), $stack(y), info)))
diff --git a/builtins/memory.c b/builtins/memory.c
index d4af4cc2..f33196be 100644
--- a/builtins/memory.c
+++ b/builtins/memory.c
@@ -8,8 +8,8 @@
#include <sys/param.h>
#include <err.h>
-#include "../util.h"
-#include "../SipHash/halfsiphash.h"
+#include "util.h"
+#include "halfsiphash.h"
#include "memory.h"
#include "types.h"
diff --git a/builtins/nums.c b/builtins/nums.c
index 32ec89fb..b05c6fab 100644
--- a/builtins/nums.c
+++ b/builtins/nums.c
@@ -1,13 +1,13 @@
+#include <float.h>
#include <gc.h>
#include <gc/cord.h>
-#include <float.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
-#include "../SipHash/halfsiphash.h"
#include "array.h"
+#include "halfsiphash.h"
#include "nums.h"
#include "string.h"
#include "types.h"
diff --git a/builtins/pointer.c b/builtins/pointer.c
index 54bab4b3..d490485f 100644
--- a/builtins/pointer.c
+++ b/builtins/pointer.c
@@ -1,16 +1,16 @@
+#include <ctype.h>
+#include <err.h>
#include <gc.h>
#include <gc/cord.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
-#include <ctype.h>
#include <sys/param.h>
-#include <err.h>
-#include "../util.h"
-#include "../SipHash/halfsiphash.h"
+#include "util.h"
#include "functions.h"
+#include "halfsiphash.h"
#include "types.h"
typedef struct recursion_s {
diff --git a/builtins/table.c b/builtins/table.c
index 5631a7fb..be4444ac 100644
--- a/builtins/table.c
+++ b/builtins/table.c
@@ -17,13 +17,13 @@
#include <string.h>
#include <sys/param.h>
-#include "../SipHash/halfsiphash.h"
-#include "../util.h"
+#include "util.h"
#include "array.h"
#include "datatypes.h"
+#include "halfsiphash.h"
#include "memory.h"
-#include "text.h"
#include "table.h"
+#include "text.h"
#include "types.h"
// #define DEBUG_TABLES
diff --git a/builtins/text.c b/builtins/text.c
index 4641bc1d..e443da33 100644
--- a/builtins/text.c
+++ b/builtins/text.c
@@ -14,9 +14,9 @@
#include <uninorm.h>
#include <unistr.h>
-#include "../SipHash/halfsiphash.h"
#include "array.h"
#include "functions.h"
+#include "halfsiphash.h"
#include "text.h"
#include "types.h"
diff --git a/builtins/tomo.h b/builtins/tomo.h
new file mode 100644
index 00000000..dd00c628
--- /dev/null
+++ b/builtins/tomo.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <err.h>
+#include <gc.h>
+#include <gc/cord.h>
+#include <math.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "array.h"
+#include "bool.h"
+#include "color.h"
+#include "datatypes.h"
+#include "functions.h"
+#include "halfsiphash.h"
+#include "integers.h"
+#include "macros.h"
+#include "memory.h"
+#include "nums.h"
+#include "pointer.h"
+#include "table.h"
+#include "text.h"
+#include "types.h"
+
+#define $heap(x) (__typeof(x)*)memcpy(GC_MALLOC(sizeof(x)), (__typeof(x)[1]){x}, sizeof(x))
+#define $stack(x) (__typeof(x)*)((__typeof(x)[1]){x})
+#define $tagged(obj_expr, type_name, tag_name) ({ __typeof(obj_expr) $obj = obj_expr; \
+ $obj.$tag == $tag$##type_name##$##tag_name ? &$obj.tag_name : NULL; })
+
+
+#define not(x) _Generic(x, bool: (bool)!(x), int64_t: ~(x), int32_t: ~(x), int16_t: ~(x), int8_t: ~(x), \
+ array_t: ((x).length == 0), table_t: ((x).entries.length == 0), CORD: ((x) == CORD_EMPTY), \
+ default: _Static_assert(0, "Not supported"))
+#define Bool(x) _Generic(x, bool: (bool)(x), int64_t: (x != 0), int32_t: (x != 0), int16_t: (x != 0), int8_t: (x != 0), CORD: ((x) == CORD_EMPTY), \
+ array_t: ((x).length > 0), table_t: ((x).entries.length > 0), CORD: ((x) != CORD_EMPTY), \
+ default: _Static_assert(0, "Not supported"))
+#define and(x, y) _Generic(x, bool: (bool)((x) && (y)), default: ((x) & (y)))
+#define or(x, y) _Generic(x, bool: (bool)((x) || (y)), default: ((x) | (y)))
+#define xor(x, y) _Generic(x, bool: (bool)((x) ^ (y)), default: ((x) ^ (y)))
+#define mod(x, n) ((x) % (n))
+#define mod1(x, n) (((x) % (n)) + (__typeof(x))1)
+#define $cmp(x, y, info) (_Generic(x, int8_t: (x>0)-(y>0), int16_t: (x>0)-(y>0), int32_t: (x>0)-(y>0), int64_t: (x>0)-(y>0), bool: (x>0)-(y>0), \
+ CORD: CORD_cmp((CORD)x, (CORD)y), char*: strcmp((char*)x, (char*)y), default: generic_compare($stack(x), $stack(y), info)))
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/builtins/types.c b/builtins/types.c
index 59bf5c47..72be5afa 100644
--- a/builtins/types.c
+++ b/builtins/types.c
@@ -1,16 +1,16 @@
// Generic type constructor
#include <err.h>
#include <gc.h>
-#include <string.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/param.h>
+#include "util.h"
#include "array.h"
-#include "table.h"
+#include "halfsiphash.h"
#include "pointer.h"
+#include "table.h"
#include "types.h"
-#include "../util.h"
-#include "../SipHash/halfsiphash.h"
public CORD Type__as_text(const void *typeinfo, bool colorize, const TypeInfo *type)
{
diff --git a/builtins/util.c b/builtins/util.c
new file mode 100644
index 00000000..16ef7aaf
--- /dev/null
+++ b/builtins/util.c
@@ -0,0 +1,84 @@
+#include <ctype.h>
+#include <gc.h>
+#include <gc/cord.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+public char *heap_strn(const char *str, size_t len)
+{
+ if (!str) return NULL;
+ if (len == 0) return "";
+ char *heaped = GC_MALLOC_ATOMIC(len + 1);
+ memcpy(heaped, str, len);
+ heaped[len] = '\0';
+ return heaped;
+}
+
+public char *heap_str(const char *str)
+{
+ if (!str) return NULL;
+ return heap_strn(str, strlen(str));
+}
+
+public char *heap_strf(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ char *tmp = NULL;
+ int len = vasprintf(&tmp, fmt, args);
+ if (len < 0) return NULL;
+ va_end(args);
+ char *ret = heap_strn(tmp, (size_t)len);
+ free(tmp);
+ return ret;
+}
+
+// Name mangling algorithm to produce valid identifiers:
+// Escape individual chars as "_x%02X"
+// Things being escaped:
+// - Leading digit
+// - Non alphanumeric/non-underscore characters
+// - "_" when followed by "x" and two uppercase hex digits
+public char *mangle(const char *name)
+{
+ size_t len = 0;
+ for (const char *p = name; *p; p++) {
+ if ((!isalnum(*p) && *p != '_') // Non-identifier character
+ || (p == name && isdigit(*p)) // Leading digit
+ || (p[0] == '_' && p[1] == 'x' && strspn(p+2, "ABCDEF0123456789") >= 2)) { // Looks like hex escape
+ len += strlen("_x00"); // Hex escape
+ } else {
+ len += 1;
+ }
+ }
+ char *mangled = GC_MALLOC_ATOMIC(len + 1);
+ char *dest = mangled;
+ for (const char *src = name; *src; src++) {
+ if ((!isalnum(*src) && *src != '_') // Non-identifier character
+ || (src == name && isdigit(*src)) // Leading digit
+ || (src[0] == '_' && src[1] == 'x' && strspn(src+2, "ABCDEF0123456789") >= 2)) { // Looks like hex escape
+ dest += sprintf(dest, "_x%02X", *src); // Hex escape
+ } else {
+ *(dest++) = *src;
+ }
+ }
+ mangled[len] = '\0';
+ return mangled;
+}
+
+CORD CORD_asprintf(CORD fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ CORD c = NULL;
+ CORD_vsprintf(&c, fmt, args);
+ va_end(args);
+ return c;
+}
+
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/builtins/util.h b/builtins/util.h
new file mode 100644
index 00000000..c858f6ce
--- /dev/null
+++ b/builtins/util.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <assert.h>
+#include <gc.h>
+#include <gc/cord.h>
+#include <stdio.h>
+#include <string.h>
+#include <err.h>
+
+#define streq(a, b) (((a) == NULL && (b) == NULL) || (((a) == NULL) == ((b) == NULL) && strcmp(a, b) == 0))
+#define new(t, ...) ((t*)memcpy(GC_MALLOC(sizeof(t)), &(t){__VA_ARGS__}, sizeof(t)))
+#define copy(obj_ptr) ((__typeof(obj_ptr))memcpy(GC_MALLOC(sizeof(*(obj_ptr))), obj_ptr, sizeof(*(obj_ptr))))
+#define grow(arr, new_size) ((typeof (arr))GC_REALLOC(arr, (sizeof(arr[0]))*(new_size)))
+#define Match(x, _tag) ((x)->tag == _tag ? &(x)->__data._tag : (errx(1, __FILE__ ":%d This was supposed to be a " # _tag "\n", __LINE__), &(x)->__data._tag))
+#define Tagged(t, _tag, ...) new(t, .tag=_tag, .__data._tag={__VA_ARGS__})
+
+#ifndef auto
+#define auto __auto_type
+#endif
+
+#ifndef public
+#define public __attribute__ ((visibility ("default")))
+#endif
+
+char *heap_strn(const char *str, size_t len);
+char *heap_str(const char *str);
+char *heap_strf(const char *fmt, ...);
+CORD CORD_asprintf(CORD fmt, ...);
+#define CORD_appendf(cord, fmt, ...) CORD_sprintf(cord, "%r" fmt, *(cord) __VA_OPT__(,) __VA_ARGS__)
+#define CORD_all(...) CORD_catn(sizeof((CORD[]){__VA_ARGS__})/sizeof(CORD), __VA_ARGS__)
+
+#define asprintfa(...) ({ char *_buf = alloca(snprintf(0, 0, __VA_ARGS__)); sprintf(_buf, __VA_ARGS__); _buf; })
+
+#define REVERSE_LIST(list) do { \
+ __typeof(list) _prev = NULL; \
+ __typeof(list) _next = NULL; \
+ auto _current = list; \
+ while (_current != NULL) { \
+ _next = _current->next; \
+ _current->next = _prev; \
+ _prev = _current; \
+ _current = _next; \
+ } \
+ list = _prev; \
+} while(0)
+
+#define LIST_MAP(src, var, ...) ({\
+ __typeof(src) __mapped = NULL; \
+ __typeof(src) *__next = &__mapped; \
+ for (__typeof(src) var = src; var; var = var->next) { \
+ *__next = GC_MALLOC(sizeof(__typeof(*(src)))); \
+ **__next = *var; \
+ **__next = (__typeof(*(src))){__VA_ARGS__}; \
+ __next = &((*__next)->next); \
+ } \
+ __mapped; })
+
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0