summaryrefslogtreecommitdiff
path: root/src/runtime.c
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2025-09-20 23:25:29 -0400
committerLLLL Colonq <llll@colonq>2025-09-20 23:25:29 -0400
commit85a67a25ac9757e694166b3c9e9e2c8cdeefc6da (patch)
treea381993c6f0d85aec3c3ead4ed7fa66583338ccb /src/runtime.c
Initial commit
Diffstat (limited to 'src/runtime.c')
-rw-r--r--src/runtime.c429
1 files changed, 429 insertions, 0 deletions
diff --git a/src/runtime.c b/src/runtime.c
new file mode 100644
index 0000000..ba5d131
--- /dev/null
+++ b/src/runtime.c
@@ -0,0 +1,429 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "utils.h"
+#include "runtime.h"
+
+pit_arena *pit_arena_new(i64 capacity, i64 elem_size) {
+ pit_arena *a = malloc(sizeof(pit_arena) + capacity * elem_size);
+ a->elem_size = elem_size;
+ a->capacity = capacity;
+ a->next = 0;
+ return a;
+}
+
+i32 pit_arena_alloc_idx(pit_arena *a) {
+ i32 byte_idx; pit_mul(&byte_idx, a->elem_size, a->next);
+ if (byte_idx >= a->capacity) { return -1; }
+ a->next += 1;
+ return byte_idx;
+}
+
+i32 pit_arena_alloc_bulk_idx(pit_arena *a, i64 num) {
+ i32 byte_idx; pit_mul(&byte_idx, a->elem_size, a->next);
+ i32 byte_len; pit_mul(&byte_len, a->elem_size, num);
+ if (byte_idx + byte_len > a->capacity) { return -1; }
+ a->next += num;
+ return byte_idx;
+}
+
+void *pit_arena_idx(pit_arena *a, i32 idx) {
+ if (idx < 0 || idx >= a->capacity) return NULL;
+ return &a->data[idx];
+}
+
+void *pit_arena_alloc(pit_arena *a) {
+ i32 byte_idx = pit_arena_alloc_idx(a);
+ return pit_arena_idx(a, byte_idx);
+}
+
+void *pit_arena_alloc_bulk(pit_arena *a, i64 num) {
+ i32 byte_idx = pit_arena_alloc_bulk_idx(a, num);
+ return pit_arena_idx(a, byte_idx);
+}
+
+enum pit_value_sort pit_value_sort(pit_value v) {
+ // if this isn't a NaN, or it's a quiet NaN, this is a real double
+ if (((v >> 52) & 0b011111111111) != 0b011111111111 || ((v >> 51) & 0b1) == 1) return PIT_VALUE_SORT_DOUBLE;
+ // otherwise, we've packed something else in the significand
+ // 0 for signaling NaN -+
+ // sign --+ +- 1 (NaN)| +- our sort tag + our data
+ // | | | | |
+ // s111111111110ttddddddddddddddddddddddddddddddddddddddddddddddddd
+ return (v & 0b0000000000000110000000000000000000000000000000000000000000000000) >> 49;
+}
+
+u64 pit_value_data(pit_value v) {
+ return v & 0b0000000000000001111111111111111111111111111111111111111111111111;
+}
+
+pit_runtime *pit_runtime_new() {
+ pit_runtime *ret = malloc(sizeof(*ret));
+ ret->values = pit_arena_new(64 * 1024, sizeof(pit_value_heavy));
+ ret->bytes = pit_arena_new(64 * 1024, sizeof(u8));
+ ret->symtab = pit_arena_new(1024, sizeof(pit_symtab_entry));
+ ret->symtab_len = 0;
+ ret->error = PIT_NIL;
+ pit_intern_cstr(ret, "nil");
+ return ret;
+}
+
+i64 pit_dump(pit_runtime *rt, char *buf, i64 len, pit_value v) {
+ pit_value_heavy *h = NULL;
+ if (len <= 0) return 0;
+ switch (pit_value_sort(v)) {
+ case PIT_VALUE_SORT_DOUBLE:
+ return snprintf(buf, len, "%lf", pit_as_double(rt, v));
+ case PIT_VALUE_SORT_INTEGER:
+ return snprintf(buf, len, "%ld", pit_as_integer(rt, v));
+ case PIT_VALUE_SORT_SYMBOL:
+ pit_symtab_entry *ent = pit_symtab_lookup(rt, v);
+ if (ent
+ && pit_value_sort(ent->name) == PIT_VALUE_SORT_REF
+ && (h = pit_deref(rt, pit_as_ref(rt, ent->name)))
+ ) {
+ i64 i = 0;
+ for (; i < h->bytes.len && i < len - 1; ++i) {
+ buf[i] = h->bytes.data[i];
+ }
+ return i;
+ } else {
+ return snprintf(buf, len, "<broken symbol %d>", pit_as_symbol(rt, v));
+ }
+ case PIT_VALUE_SORT_REF:
+ pit_ref r = pit_as_ref(rt, v);
+ h = pit_deref(rt, r);
+ if (!h) snprintf(buf, len, "<ref %d>", r);
+ else {
+ switch (h->hsort) {
+ case PIT_VALUE_HEAVY_SORT_CONS:
+ char *end = buf + len;
+ char *start = buf;
+ pit_value cur = v;
+ do {
+ if (pit_is_cons(rt, cur)) {
+ *(buf++) = ' '; if (buf >= end) return end - buf;
+ buf += pit_dump(rt, buf, end - buf, pit_car(rt, cur));
+ if (buf >= end) return end - buf;
+ } else {
+ buf += snprintf(buf, end - buf, " . ");
+ if (buf >= end) return end - buf;
+ buf += pit_dump(rt, buf, end - buf, cur);
+ if (buf >= end) return end - buf;
+ }
+ } while (!pit_eq((cur = pit_cdr(rt, cur)), PIT_NIL));
+ *start = '(';
+ *(buf++) = ')';
+ return buf - start;
+ case PIT_VALUE_HEAVY_SORT_BYTES:
+ buf[0] = '"';
+ i64 i = 1;
+ for (i64 j = 0; i < len - 1 && j < h->bytes.len;) {
+ if (buf[i - 1] != '\\' && (h->bytes.data[j] == '\\' || h->bytes.data[j] == '"'))
+ buf[i++] = '\\';
+ else buf[i++] = h->bytes.data[j++];
+ }
+ if (i < len - 1) buf[i++] = '"';
+ return i;
+ default:
+ return snprintf(buf, len, "<ref %d>", r);
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+void pit_trace_(pit_runtime *rt, const char *format, pit_value v) {
+ char buf[1024] = {0};
+ pit_dump(rt, buf, sizeof(buf), v);
+ fprintf(stderr, format, buf);
+}
+
+void pit_error(pit_runtime *rt, const char *format, ...) {
+ if (pit_eq(rt->error, PIT_NIL)) { // only record the first error encountered
+ char buf[1024] = {0};
+ va_list vargs;
+ va_start(vargs, format);
+ vsnprintf(buf, sizeof(buf), format, vargs);
+ va_end(vargs);
+ rt->error = pit_bytes_new_cstr(rt, buf);
+ }
+}
+
+void pit_check_error_maybe_panic(pit_runtime *rt) {
+ if (!pit_eq(rt->error, PIT_NIL)) {
+ char buf[1024] = {0};
+ pit_dump(rt, buf, sizeof(buf), rt->error);
+ pit_panic("runtime error: %s", buf);
+ }
+}
+
+pit_value pit_value_new(pit_runtime *rt, enum pit_value_sort s, u64 data) {
+ if (s == PIT_VALUE_SORT_DOUBLE) {
+ if (((data >> 52) & 0b011111111111) == 0b011111111111 && ((data >> 51) & 0b1) == 0) {
+ pit_error(rt, "attempted to create a signalling NaN double");
+ return PIT_NIL;
+ }
+ return data;
+ }
+ return
+ 0b1111111111110000000000000000000000000000000000000000000000000000
+ | (((u64) (s & 0b11)) << 49)
+ | (data & 0b1111111111111111111111111111111111111111111111111);
+}
+
+double pit_as_double(pit_runtime *rt, pit_value v) {
+ if (pit_value_sort(v) != PIT_VALUE_SORT_DOUBLE) {
+ pit_error(rt, "invalid use of value as double");
+ return NAN;
+ }
+ return (double) v;
+}
+pit_value pit_double_new(pit_runtime *rt, double d) {
+ return pit_value_new(rt, PIT_VALUE_SORT_DOUBLE, (u64) d);
+}
+
+i64 pit_as_integer(pit_runtime *rt, pit_value v) {
+ if (pit_value_sort(v) != PIT_VALUE_SORT_INTEGER) {
+ pit_error(rt, "invalid use of value as integer");
+ return -1;
+ }
+ u64 lo = pit_value_data(v);
+ return ((i64) (lo << 15)) >> 15; // sign-extend low 49 bits
+}
+pit_value pit_integer_new(pit_runtime *rt, i64 i) {
+ return pit_value_new(rt, PIT_VALUE_SORT_INTEGER, (u64) i);
+}
+
+pit_symbol pit_as_symbol(pit_runtime *rt, pit_value v) {
+ if (pit_value_sort(v) != PIT_VALUE_SORT_SYMBOL) {
+ pit_error(rt, "invalid use of value as symbol");
+ return -1;
+ }
+ return pit_value_data(v) & 0xffffffff;
+}
+pit_value pit_symbol_new(pit_runtime *rt, pit_symbol s) {
+ return pit_value_new(rt, PIT_VALUE_SORT_SYMBOL, (u64) s);
+}
+
+pit_ref pit_as_ref(pit_runtime *rt, pit_value v) {
+ if (pit_value_sort(v) != PIT_VALUE_SORT_REF) {
+ pit_error(rt, "invalid use of value as ref");
+ return -1;
+ }
+ return pit_value_data(v) & 0xffffffff;
+}
+pit_value pit_ref_new(pit_runtime *rt, pit_ref r) {
+ return pit_value_new(rt, PIT_VALUE_SORT_REF, (u64) r);
+}
+
+pit_value pit_heavy_new(pit_runtime *rt) {
+ i32 idx = pit_arena_alloc_idx(rt->values);
+ return pit_ref_new(rt, idx);
+}
+
+pit_value_heavy *pit_deref(pit_runtime *rt, pit_ref p) {
+ return pit_arena_idx(rt->values, p);
+}
+
+bool pit_is_cons(pit_runtime *rt, pit_value a) {
+ switch (pit_value_sort(a)) {
+ case PIT_VALUE_SORT_REF:
+ pit_value_heavy *ha = pit_deref(rt, a);
+ if (!ha) { pit_error(rt, "bad ref"); return false; }
+ switch (ha->hsort) {
+ case PIT_VALUE_HEAVY_SORT_CONS:
+ return true;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ return false;
+}
+bool pit_eq(pit_value a, pit_value b) {
+ return a == b;
+}
+bool pit_equal(pit_runtime *rt, pit_value a, pit_value b) {
+ if (pit_value_sort(a) != pit_value_sort(b)) return false;
+ switch (pit_value_sort(a)) {
+ case PIT_VALUE_SORT_DOUBLE:
+ case PIT_VALUE_SORT_INTEGER:
+ case PIT_VALUE_SORT_SYMBOL:
+ return pit_value_data(a) == pit_value_data(b);
+ case PIT_VALUE_SORT_REF:
+ pit_value_heavy *ha = pit_deref(rt, a);
+ if (!ha) { pit_error(rt, "bad ref"); return false; }
+ pit_value_heavy *hb = pit_deref(rt, b);
+ if (!hb) { pit_error(rt, "bad ref"); return false; }
+ if (ha->hsort != hb->hsort) return false;
+ switch (ha->hsort) {
+ case PIT_VALUE_HEAVY_SORT_CONS:
+ return pit_equal(rt, ha->cons.car, hb->cons.car) && pit_equal(rt, ha->cons.cdr, hb->cons.cdr);
+ case PIT_VALUE_HEAVY_SORT_ARRAY:
+ if (ha->array.len != hb->array.len) return false;
+ for (i64 i = 0; i < ha->array.len; ++i) {
+ if (!pit_equal(rt, ha->array.data[i], hb->array.data[i])) return false;
+ }
+ return true;
+ case PIT_VALUE_HEAVY_SORT_BYTES:
+ if (ha->bytes.len != hb->bytes.len) return false;
+ for (i64 i = 0; i < ha->bytes.len; ++i) {
+ if (ha->bytes.data[i] != hb->bytes.data[i]) return false;
+ }
+ return true;
+ case PIT_VALUE_HEAVY_SORT_NATIVEFUNC:
+ return ha->nativefunc == hb->nativefunc;
+ }
+ }
+ return false;
+}
+
+pit_value pit_bytes_new(pit_runtime *rt, u8 *buf, i64 len) {
+ u8 *dest = pit_arena_alloc_bulk(rt->bytes, len);
+ if (!dest) { pit_error(rt, "failed to allocate bytes"); return PIT_NIL; }
+ memcpy(dest, buf, len);
+ if (!dest) return PIT_NIL;
+ pit_value ret = pit_heavy_new(rt);
+ pit_value_heavy *h = pit_deref(rt, ret);
+ if (!h) { pit_error(rt, "failed to create new heavy value for bytes"); return PIT_NIL; }
+ h->hsort = PIT_VALUE_HEAVY_SORT_BYTES;
+ h->bytes.data = dest;
+ h->bytes.len = len;
+ return ret;
+}
+
+pit_value pit_bytes_new_cstr(pit_runtime *rt, char *s) {
+ return pit_bytes_new(rt, (u8 *) s, strlen(s));
+}
+
+// return true if v is a reference to bytes that are the same as those in buf
+bool pit_bytes_match(pit_runtime *rt, pit_value v, u8 *buf, i64 len) {
+ if (pit_value_sort(v) != PIT_VALUE_SORT_REF) return false;
+ pit_value_heavy *h = pit_deref(rt, pit_as_ref(rt, v));
+ if (!h) { pit_error(rt, "bad ref"); return false; }
+ if (h->hsort != PIT_VALUE_HEAVY_SORT_BYTES) return false;
+ if (h->bytes.len != len) return false;
+ for (i64 i = 0; i < len; ++i)
+ if (h->bytes.data[i] != buf[i]) {
+ return false;
+ }
+ return true;
+}
+
+pit_value pit_intern(pit_runtime *rt, u8 *nm, i64 len) {
+ for (i64 i = 0; i < rt->symtab_len; ++i) {
+ pit_symbol idx = i * sizeof(pit_symtab_entry);
+ pit_symtab_entry *ent = pit_arena_idx(rt->symtab, idx);
+ if (!ent) { pit_error(rt, "corrupted symbol table"); return PIT_NIL; }
+ if (pit_bytes_match(rt, ent->name, nm, len)) return pit_symbol_new(rt, idx);
+ }
+ i64 idx = pit_arena_alloc_idx(rt->symtab);
+ pit_symtab_entry *ent = pit_arena_idx(rt->symtab, idx);
+ if (!ent) { pit_error(rt, "failed to allocate symtab entry"); return PIT_NIL; }
+ ent->name = pit_bytes_new(rt, nm, len);
+ ent->value = PIT_NIL;
+ ent->function = PIT_NIL;
+ rt->symtab_len += 1;
+ return pit_symbol_new(rt, idx);
+}
+
+pit_value pit_intern_cstr(pit_runtime *rt, char *nm) {
+ return pit_intern(rt, (u8 *) nm, strlen(nm));
+}
+
+pit_symtab_entry *pit_symtab_lookup(pit_runtime *rt, pit_value sym) {
+ pit_symbol s = pit_as_symbol(rt, sym);
+ return pit_arena_idx(rt->symtab, s);
+}
+pit_value pit_get(pit_runtime *rt, pit_value sym) {
+ pit_symtab_entry *ent = pit_symtab_lookup(rt, sym);
+ if (!ent) { pit_error(rt, "bad symbol"); return PIT_NIL; }
+ return ent->value;
+}
+void pit_set(pit_runtime *rt, pit_value sym, pit_value v) {
+ pit_symtab_entry *ent = pit_symtab_lookup(rt, sym);
+ if (!ent) { pit_error(rt, "bad symbol"); return; }
+ ent->value = v;
+}
+pit_value pit_fget(pit_runtime *rt, pit_value sym) {
+ pit_symtab_entry *ent = pit_symtab_lookup(rt, sym);
+ if (!ent) { pit_error(rt, "bad symbol"); return PIT_NIL; }
+ return ent->function;
+}
+void pit_fset(pit_runtime *rt, pit_value sym, pit_value v) {
+ pit_symtab_entry *ent = pit_symtab_lookup(rt, sym);
+ if (!ent) { pit_error(rt, "bad symbol"); return; }
+ ent->function = v;
+}
+
+pit_value pit_cons(pit_runtime *rt, pit_value car, pit_value cdr) {
+ pit_value ret = pit_heavy_new(rt);
+ pit_value_heavy *h = pit_deref(rt, ret);
+ if (!h) { pit_error(rt, "failed to create new heavy value for cons"); return PIT_NIL; }
+ h->hsort = PIT_VALUE_HEAVY_SORT_CONS;
+ h->cons.car = car;
+ h->cons.cdr = cdr;
+ return ret;
+}
+
+pit_value pit_list(pit_runtime *rt, i64 num, ...) {
+ pit_value *temp = calloc(num, sizeof(pit_value));
+ va_list elems;
+ va_start(elems, num);
+ for (i64 i = 0; i < num; ++i) {
+ temp[i] = va_arg(elems, pit_value);
+ }
+ va_end(elems);
+ pit_value ret = PIT_NIL;
+ for (i64 i = 0; i < num; ++i) {
+ ret = pit_cons(rt, temp[num - i - 1], ret);
+ }
+ return ret;
+}
+
+pit_value pit_car(pit_runtime *rt, pit_value v) {
+ if (pit_value_sort(v) != PIT_VALUE_SORT_REF) return PIT_NIL;
+ pit_value_heavy *h = pit_deref(rt, pit_as_ref(rt, v));
+ if (!h) { pit_error(rt, "bad ref"); return PIT_NIL; }
+ if (h->hsort != PIT_VALUE_HEAVY_SORT_CONS) return PIT_NIL;
+ return h->cons.car;
+}
+pit_value pit_cdr(pit_runtime *rt, pit_value v) {
+ if (pit_value_sort(v) != PIT_VALUE_SORT_REF) return PIT_NIL;
+ pit_value_heavy *h = pit_deref(rt, pit_as_ref(rt, v));
+ if (!h) { pit_error(rt, "bad ref"); return PIT_NIL; }
+ if (h->hsort != PIT_VALUE_HEAVY_SORT_CONS) return PIT_NIL;
+ return h->cons.cdr;
+}
+
+pit_value pit_nativefunc_new(pit_runtime *rt, pit_nativefunc f) {
+ pit_value ret = pit_heavy_new(rt);
+ pit_value_heavy *h = pit_deref(rt, ret);
+ if (!h) { pit_error(rt, "failed to create new heavy value for nativefunc"); return PIT_NIL; }
+ h->hsort = PIT_VALUE_HEAVY_SORT_NATIVEFUNC;
+ h->nativefunc = f;
+ return ret;
+}
+
+pit_value pit_apply(pit_runtime *rt, pit_value f, pit_value args) {
+ switch (pit_value_sort(f)) {
+ case PIT_VALUE_SORT_REF:
+ pit_value_heavy *h = pit_deref(rt, pit_as_ref(rt, f));
+ if (!h) { pit_error(rt, "bad ref"); return PIT_NIL; }
+ if (h->hsort != PIT_VALUE_HEAVY_SORT_NATIVEFUNC) {
+ pit_error(rt, "attempt to apply non-nativefunc ref");
+ return PIT_NIL;
+ }
+ return h->nativefunc(rt, args);
+ default:
+ pit_error(rt, "attempted to apply non-function value");
+ return PIT_NIL;
+ }
+}