#define _GNU_SOURCE
#ifdef CODE_WASM
#include "wasm_shim.h"
#else
#include <dlfcn.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif
#include "code_abi.h"
_Static_assert(sizeof(CodeValue) <= CODE_VALUE_SLOT_SIZE,
"CodeValue outgrew codegen.rs's VALUE_SIZE stride");
static CodeValue *slot_at(void *base, long long index) {
return (CodeValue *)((char *)base + index * CODE_VALUE_SLOT_SIZE);
}
_Noreturn void code_runtime_error(const char *message) {
#ifdef CODE_WASM
code_host_error(message, (unsigned int)strlen(message));
__builtin_trap();
#else
fprintf(stderr, "error: %s\n", message);
exit(1);
#endif
}
int code_failed = 0;
static char failure_message[256];
const char *code_location = NULL;
static void fail(const char *message) {
if (!code_failed) {
snprintf(failure_message, sizeof failure_message, "%s", message);
code_failed = 1;
}
}
_Noreturn void code_abort_failure(void) {
const char *message = code_failed ? failure_message : "unknown runtime error";
if (code_location) {
size_t n = strlen(message) + 1 + strlen(code_location) + 1;
char *located = malloc(n);
if (located) {
snprintf(located, n, "%s\n%s", message, code_location);
code_runtime_error(located);
}
}
code_runtime_error(message);
}
void code_take_failure(CodeValue *out) {
code_make_exception(out, "core",
code_failed ? failure_message : "unknown runtime error", NULL);
code_failed = 0;
}
typedef struct {
long long rc;
long long padding;
} CodeHeader;
static long long live_blocks = 0;
#define code_blocks_add(n) (void)__atomic_add_fetch(&live_blocks, (n), __ATOMIC_RELAXED)
#define code_blocks_read() __atomic_load_n(&live_blocks, __ATOMIC_RELAXED)
static void *heap_alloc(size_t bytes) {
CodeHeader *h = malloc(sizeof(CodeHeader) + bytes);
if (!h) {
code_runtime_error("out of memory");
}
h->rc = 1;
code_blocks_add(1);
return (char *)h + sizeof(CodeHeader);
}
static CodeHeader *header_of(const void *payload) {
return (CodeHeader *)((char *)payload - sizeof(CodeHeader));
}
static void *heap_block(const CodeValue *v) {
switch (v->tag) {
case CODE_STR:
return (void *)v->str;
case CODE_ARRAY:
return v->items;
case CODE_OBJECT:
return (void *)v->keys;
default:
return NULL;
}
}
void code_retain(const CodeValue *v) {
if (v->heap) {
header_of(heap_block(v))->rc++;
}
}
#ifdef CODE_WASM
#define CODE_THREAD_LOCAL
#else
#define CODE_THREAD_LOCAL __thread
#endif
static void *grow(void *buf, size_t *cap, size_t needed, size_t item_size) {
if (*cap >= needed) {
return buf;
}
size_t next = *cap ? *cap * 2 : 64;
while (next < needed) {
next *= 2;
}
void *bigger = realloc(buf, next * item_size);
if (!bigger) {
code_runtime_error("out of memory");
}
*cap = next;
return bigger;
}
static CODE_THREAD_LOCAL CodeValue *dead = NULL;
static CODE_THREAD_LOCAL size_t dead_cap = 0;
void code_release(CodeValue *v) {
if (!v->heap) {
return;
}
if (--header_of(heap_block(v))->rc != 0) {
return;
}
size_t len = 0;
dead = grow(dead, &dead_cap, len + 1, sizeof(CodeValue));
dead[len++] = *v;
while (len > 0) {
CodeValue current = dead[--len];
if (current.tag == CODE_ARRAY || current.tag == CODE_OBJECT) {
for (long long i = 0; i < current.len; i++) {
const CodeValue *child = slot_at(current.items, i);
if (child->heap && --header_of(heap_block(child))->rc == 0) {
dead = grow(dead, &dead_cap, len + 1, sizeof(CodeValue));
dead[len++] = *child;
}
}
}
free(header_of(heap_block(¤t)));
code_blocks_add(-1);
}
}
void code_clear(CodeValue *v) {
code_release(v);
memset(v, 0, sizeof *v);
}
void code_check_leaks(void) {
if (!getenv("CODE_CHECK_LEAKS")) {
return;
}
long long leaked = code_blocks_read();
if (leaked != 0) {
char msg[96];
snprintf(msg, sizeof msg, "%lld heap block(s) leaked", leaked);
code_runtime_error(msg);
}
}
void code_number(CodeValue *out, double n) {
code_release(out);
out->tag = CODE_NUMBER;
out->heap = 0;
out->number = n;
}
void code_str(CodeValue *out, const char *s) {
code_release(out);
out->tag = CODE_STR;
out->heap = 0;
out->str = s;
}
void code_bool(CodeValue *out, int b) {
code_release(out);
out->tag = CODE_BOOL;
out->heap = 0;
out->boolean = b;
}
void code_null(CodeValue *out) {
code_release(out);
out->tag = CODE_NULL;
out->heap = 0;
}
void code_array(CodeValue *out, void *items, long long len) {
void *buf = NULL;
if (len > 0) {
buf = heap_alloc((size_t)len * CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < len; i++) {
const CodeValue *src = slot_at(items, i);
code_retain(src);
*slot_at(buf, i) = *src;
}
}
code_release(out);
out->tag = CODE_ARRAY;
out->heap = len > 0;
out->items = buf;
out->len = len;
}
static const char *copy_key(char **chars, const char *key) {
size_t n = (key ? strlen(key) : 0) + 1;
if (key) {
memcpy(*chars, key, n);
} else {
(*chars)[0] = '\0';
}
const char *placed = *chars;
*chars += n;
return placed;
}
void code_object(CodeValue *out, const char **keys, void *values, long long len) {
const char **key_buf = NULL;
void *value_buf = NULL;
if (len > 0) {
size_t keys_bytes = (size_t)len * sizeof(const char *);
size_t slots_bytes = (size_t)len * CODE_VALUE_SLOT_SIZE;
size_t chars_bytes = 0;
for (long long i = 0; i < len; i++) {
chars_bytes += (keys[i] ? strlen(keys[i]) : 0) + 1;
}
key_buf = heap_alloc(keys_bytes + slots_bytes + chars_bytes);
value_buf = (char *)key_buf + keys_bytes;
char *chars = (char *)value_buf + slots_bytes;
for (long long i = 0; i < len; i++) {
key_buf[i] = copy_key(&chars, keys[i]);
const CodeValue *src = slot_at(values, i);
code_retain(src);
*slot_at(value_buf, i) = *src;
}
}
code_release(out);
out->tag = CODE_OBJECT;
out->heap = len > 0;
out->keys = key_buf;
out->items = value_buf;
out->len = len;
}
const char *code_str_text(const CodeValue *v) {
return v->tag == CODE_STR && v->str ? v->str : "";
}
void code_copy(CodeValue *out, const CodeValue *src) {
code_retain(src);
code_release(out);
*out = *src;
}
static const char *article_for(const CodeValue *v) {
return (v->tag == CODE_ARRAY || v->tag == CODE_OBJECT) ? "an" : "a";
}
static const char *type_name(const CodeValue *v) {
switch (v->tag) {
case CODE_NUMBER: return "number";
case CODE_STR: return "string";
case CODE_BOOL: return "boolean";
case CODE_NULL: return "null";
case CODE_ARRAY: return "array";
case CODE_OBJECT: return "object";
}
return "value";
}
static void operand_message(char *buf, size_t n, const char *requirement, const CodeValue *v) {
snprintf(buf, n, "%s, found %s %s", requirement, article_for(v), type_name(v));
}
static void fail_operand(const char *requirement, const CodeValue *v) {
char msg[192];
operand_message(msg, sizeof msg, requirement, v);
fail(msg);
}
static void fail_binary(const char *op, const CodeValue *a, const CodeValue *b) {
char msg[192];
snprintf(msg, sizeof msg, "cannot apply '%s' to %s %s and %s %s", op, article_for(a),
type_name(a), article_for(b), type_name(b));
fail(msg);
}
void code_field(CodeValue *out, const CodeValue *obj, const char *field) {
if (obj->tag != CODE_OBJECT) {
char msg[128];
snprintf(msg, sizeof msg,
"cannot read field '%s' of %s %s — '.' requires an object", field,
article_for(obj), type_name(obj));
fail(msg);
return;
}
for (long long i = 0; i < obj->len; i++) {
if (strcmp(obj->keys[i], field) == 0) {
code_copy(out, slot_at(obj->items, i));
return;
}
}
code_null(out);
}
void code_index(CodeValue *out, const CodeValue *arr, const CodeValue *index) {
if (arr->tag == CODE_ARRAY) {
if (index->tag == CODE_NUMBER) {
double n = index->number;
long long i = (long long)n;
if ((double)i == n && i >= 0 && i < arr->len) {
code_copy(out, slot_at(arr->items, i));
return;
}
}
code_null(out);
return;
}
if (arr->tag == CODE_OBJECT) {
if (index->tag == CODE_STR) {
for (long long i = 0; i < arr->len; i++) {
if (strcmp(arr->keys[i], index->str) == 0) {
code_copy(out, slot_at(arr->items, i));
return;
}
}
}
code_null(out);
return;
}
char msg[96];
snprintf(msg, sizeof msg, "cannot index %s %s — '[]' requires an array or object",
article_for(arr), type_name(arr));
fail(msg);
}
static const CodeValue *find_field(const CodeValue *obj, const char *key) {
for (long long i = 0; i < obj->len; i++) {
if (strcmp(obj->keys[i], key) == 0) {
return slot_at(obj->items, i);
}
}
return NULL;
}
static void code_make_result(CodeValue *out, const char *class_name, const CodeValue *value) {
const char *keys[2] = {"_class", "value"};
_Alignas(8) char slots[2 * CODE_VALUE_SLOT_SIZE] = {0};
code_str(slot_at(slots, 0), class_name);
code_copy(slot_at(slots, 1), value);
code_object(out, keys, slots, 2);
code_release(slot_at(slots, 0));
code_release(slot_at(slots, 1));
}
void code_make_exception(CodeValue *out, const char *source, const char *message,
const CodeValue *inner) {
const char *keys[4] = {"_class", "source", "message", "innerException"};
_Alignas(8) char slots[4 * CODE_VALUE_SLOT_SIZE] = {0};
code_str(slot_at(slots, 0), "Exception");
code_str_owned(slot_at(slots, 1), source);
code_str_owned(slot_at(slots, 2), message);
if (inner) {
code_copy(slot_at(slots, 3), inner);
} else {
code_null(slot_at(slots, 3));
}
code_object(out, keys, slots, 4);
for (int i = 0; i < 4; i++) {
code_release(slot_at(slots, i));
}
}
void code_core_dispatch(CodeValue *out, const CodeValue *particle) {
if (particle->tag != CODE_OBJECT) {
code_null(out);
return;
}
const CodeValue *class_val = find_field(particle, "_class");
if (!class_val || class_val->tag != CODE_STR) {
code_null(out);
return;
}
if (strcmp(class_val->str, "Timestamp") == 0) {
CodeValue ts = {0};
#ifdef CODE_WASM
code_number(&ts, code_host_now());
#else
code_number(&ts, (double)time(NULL));
#endif
code_make_result(out, "TimestampResult", &ts);
return;
}
if (strcmp(class_val->str, "Length") == 0) {
static const CodeValue absent = {.tag = CODE_NULL};
const CodeValue *value = find_field(particle, "value");
if (!value) {
value = &absent;
}
CodeValue count = {0};
if (value->tag == CODE_ARRAY) {
code_number(&count, (double)value->len);
code_make_result(out, "LengthResult", &count);
return;
}
if (value->tag == CODE_STR) {
long long chars = 0;
for (const char *p = value->str; *p; p++) {
if (((unsigned char)*p & 0xC0) != 0x80) {
chars++;
}
}
code_number(&count, (double)chars);
code_make_result(out, "LengthResult", &count);
return;
}
char msg[192];
operand_message(msg, sizeof msg, "Length requires an array or string 'value'", value);
code_make_exception(out, "core", msg, NULL);
return;
}
code_null(out);
}
#ifdef CODE_WASM
typedef int CodeMutex;
#define code_mutex_init(m) ((void)(m))
#define code_mutex_lock(m) ((void)(m))
#define code_mutex_unlock(m) ((void)(m))
#else
typedef pthread_mutex_t CodeMutex;
#define code_mutex_init(m) pthread_mutex_init((m), NULL)
#define code_mutex_lock(m) pthread_mutex_lock(m)
#define code_mutex_unlock(m) pthread_mutex_unlock(m)
#endif
typedef struct {
void (*dispatch)(CodeValue *out, const CodeValue *particle);
void (*release)(CodeValue *v);
CodeInboundReplyFn reply;
const CodeVarList *(*vars)(void);
CodeValue inbound[CODE_INBOUND_CAPACITY];
int inbound_head;
int inbound_count;
CodeMutex lock;
int has_inbound;
int closed;
} NativeHandle;
void code_static_module_check(uint32_t version, const char *what) {
if (version != CODE_ABI_VERSION) {
char msg[256];
snprintf(msg, sizeof msg, "native module '%s' has ABI version %u (expected %u)", what,
(unsigned)version, (unsigned)CODE_ABI_VERSION);
code_runtime_error(msg);
}
}
void code_emit_inbound(void *queue, const CodeValue *value);
void *code_native_open(const char *path) {
#ifdef CODE_WASM
(void)path;
code_runtime_error("native modules are not available in a wasm build");
return NULL;
#else
void *handle = dlopen(path, RTLD_NOW);
if (!handle) {
char msg[256];
snprintf(msg, sizeof msg, "cannot load native module '%s': %s", path, dlerror());
code_runtime_error(msg);
}
uint32_t (*version_fn)(void) = (uint32_t (*)(void))dlsym(handle, "code_module_abi_version");
if (!version_fn) {
char msg[256];
snprintf(msg, sizeof msg, "native module '%s' missing 'code_module_abi_version'", path);
code_runtime_error(msg);
}
code_static_module_check(version_fn(), path);
NativeHandle *nh = malloc(sizeof(NativeHandle));
if (!nh) {
code_runtime_error("out of memory");
}
nh->dispatch = (void (*)(CodeValue *, const CodeValue *))dlsym(handle, "code_module_dispatch");
nh->release = (void (*)(CodeValue *))dlsym(handle, "code_release");
if (!nh->dispatch || !nh->release) {
char msg[256];
snprintf(msg, sizeof msg,
"native module '%s' missing 'code_module_dispatch' or 'code_release'", path);
code_runtime_error(msg);
}
nh->vars = (const CodeVarList *(*)(void))dlsym(handle, "code_module_vars");
nh->reply = (CodeInboundReplyFn)dlsym(handle, "code_module_inbound_reply");
memset(nh->inbound, 0, sizeof nh->inbound);
nh->inbound_head = 0;
nh->inbound_count = 0;
nh->closed = 0;
code_mutex_init(&nh->lock);
void (*set_inbound)(void *, CodeEmitFn) =
(void (*)(void *, CodeEmitFn))dlsym(handle, "code_module_set_inbound");
nh->has_inbound = set_inbound != NULL;
if (set_inbound) {
set_inbound(nh, code_emit_inbound);
}
return nh;
#endif
}
void *code_static_open(void) {
NativeHandle *nh = malloc(sizeof(NativeHandle));
if (!nh) {
code_runtime_error("out of memory");
}
nh->dispatch = NULL;
nh->release = NULL;
nh->vars = NULL;
nh->reply = NULL;
memset(nh->inbound, 0, sizeof nh->inbound);
nh->inbound_head = 0;
nh->inbound_count = 0;
nh->closed = 0;
nh->has_inbound = 1;
code_mutex_init(&nh->lock);
return nh;
}
void code_str_owned(CodeValue *out, const char *s) {
size_t n = strlen(s);
char *buf = heap_alloc(n + 1);
memcpy(buf, s, n + 1);
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = buf;
}
static void code_native_copy_in(CodeValue *out, const CodeValue *from) {
switch (from->tag) {
case CODE_NUMBER:
code_number(out, from->number);
return;
case CODE_STR:
code_str_owned(out, from->str);
return;
case CODE_BOOL:
code_bool(out, from->boolean);
return;
case CODE_NULL:
code_null(out);
return;
case CODE_ARRAY: {
void *slots = from->len > 0 ? calloc((size_t)from->len, CODE_VALUE_SLOT_SIZE) : NULL;
for (long long i = 0; i < from->len; i++) {
code_native_copy_in(slot_at(slots, i), slot_at(from->items, i));
}
code_array(out, slots, from->len);
for (long long i = 0; i < from->len; i++) {
code_release(slot_at(slots, i));
}
free(slots);
return;
}
case CODE_OBJECT: {
const char **keys = from->len > 0 ? malloc((size_t)from->len * sizeof(const char *)) : NULL;
void *slots = from->len > 0 ? calloc((size_t)from->len, CODE_VALUE_SLOT_SIZE) : NULL;
for (long long i = 0; i < from->len; i++) {
keys[i] = from->keys[i];
code_native_copy_in(slot_at(slots, i), slot_at(from->items, i));
}
code_object(out, keys, slots, from->len);
for (long long i = 0; i < from->len; i++) {
code_release(slot_at(slots, i));
}
free(keys);
free(slots);
return;
}
}
}
void code_emit_inbound(void *queue, const CodeValue *value) {
if (!queue || !value) {
return;
}
NativeHandle *nh = (NativeHandle *)queue;
code_mutex_lock(&nh->lock);
if (nh->closed) {
code_mutex_unlock(&nh->lock);
return;
}
int slot;
if (nh->inbound_count == CODE_INBOUND_CAPACITY) {
slot = nh->inbound_head;
code_release(&nh->inbound[slot]);
memset(&nh->inbound[slot], 0, sizeof(CodeValue));
nh->inbound_head = (nh->inbound_head + 1) % CODE_INBOUND_CAPACITY;
} else {
slot = (nh->inbound_head + nh->inbound_count) % CODE_INBOUND_CAPACITY;
nh->inbound_count++;
}
code_native_copy_in(&nh->inbound[slot], value);
code_mutex_unlock(&nh->lock);
}
int code_poll_inbound(void *queue, CodeValue *out) {
if (!queue) {
return 0;
}
NativeHandle *nh = (NativeHandle *)queue;
code_mutex_lock(&nh->lock);
if (nh->inbound_count == 0) {
code_mutex_unlock(&nh->lock);
return 0;
}
int slot = nh->inbound_head;
code_copy(out, &nh->inbound[slot]);
code_release(&nh->inbound[slot]);
memset(&nh->inbound[slot], 0, sizeof(CodeValue));
nh->inbound_head = (nh->inbound_head + 1) % CODE_INBOUND_CAPACITY;
nh->inbound_count--;
code_mutex_unlock(&nh->lock);
return 1;
}
void code_native_reply(void *handle, const CodeValue *particle, const CodeValue *result) {
if (!handle) {
return;
}
NativeHandle *nh = (NativeHandle *)handle;
if (nh->reply) {
nh->reply(particle, result);
}
}
void code_native_close(void *handle) {
if (!handle) {
return;
}
NativeHandle *nh = (NativeHandle *)handle;
code_mutex_lock(&nh->lock);
for (int i = 0; i < nh->inbound_count; i++) {
code_release(&nh->inbound[(nh->inbound_head + i) % CODE_INBOUND_CAPACITY]);
}
nh->inbound_count = 0;
nh->closed = 1;
int has_inbound = nh->has_inbound;
code_mutex_unlock(&nh->lock);
if (has_inbound) {
return;
}
free(nh);
}
void code_native_dispatch(void *handle, CodeValue *out, const CodeValue *particle) {
NativeHandle *nh = (NativeHandle *)handle;
CodeValue result = {0};
nh->dispatch(&result, particle);
code_native_copy_in(out, &result);
nh->release(&result);
}
void code_native_vars_object(void *handle, CodeValue *out) {
NativeHandle *nh = (NativeHandle *)handle;
const CodeVarList *list = nh->vars ? nh->vars() : NULL;
long long count = list ? list->count : 0;
if (count < 0) {
code_runtime_error("native module reports a negative variable count");
}
const char **keys = NULL;
void *values = NULL;
if (count > 0) {
keys = (const char **)malloc((size_t)count * sizeof(const char *));
values = calloc((size_t)count, CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < count; i++) {
keys[i] = list->names[i];
code_native_copy_in(slot_at(values, i), slot_at(list->values, i));
}
}
code_object(out, keys, values, count);
if (count > 0) {
for (long long i = 0; i < count; i++) {
code_release(slot_at(values, i));
}
free(values);
}
free(keys);
}
void code_static_vars_object(const CodeVarList *list, CodeValue *out) {
long long count = list ? list->count : 0;
if (count < 0) {
code_runtime_error("native module reports a negative variable count");
}
const char **keys = NULL;
void *values = NULL;
if (count > 0) {
keys = (const char **)malloc((size_t)count * sizeof(const char *));
values = malloc((size_t)count * CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < count; i++) {
keys[i] = list->names[i];
CodeValue *slot = slot_at(values, i);
*slot = *slot_at(list->values, i);
code_retain(slot);
}
}
code_object(out, keys, values, count);
if (count > 0) {
for (long long i = 0; i < count; i++) {
code_release(slot_at(values, i));
}
free(values);
}
free(keys);
}
long long code_iter_len(const CodeValue *v) {
if (v->tag != CODE_ARRAY && v->tag != CODE_OBJECT) {
fail_operand("loop requires an array or object", v);
return 0;
}
return v->len;
}
void code_iter_at(CodeValue *out, const CodeValue *arr, long long i) {
code_copy(out, slot_at(arr->items, i));
}
void code_iter_key(CodeValue *out, const CodeValue *v, long long i) {
if (v->tag == CODE_OBJECT) {
code_str_owned(out, v->keys[i]);
return;
}
code_number(out, (double)i);
}
void code_check_emittable(const CodeValue *v) {
if (v->tag == CODE_OBJECT) {
for (long long i = 0; i < v->len; i++) {
if (strcmp(v->keys[i], "_class") == 0) {
return;
}
}
}
char msg[160];
snprintf(msg, sizeof msg,
"emit requires a particle — an object with a '_class' field — found %s %s",
article_for(v), type_name(v));
fail(msg);
}
void code_check_particle(const CodeValue *v) {
if (v->tag == CODE_OBJECT) {
for (long long i = 0; i < v->len; i++) {
if (strcmp(v->keys[i], "_class") == 0) {
return;
}
}
}
char msg[128];
snprintf(msg, sizeof msg,
"a handler must return a particle — an object with a '_class' field — found %s %s",
article_for(v), type_name(v));
fail(msg);
}
typedef struct {
char *buf;
size_t len;
size_t cap;
} TextBuf;
static void text_push(TextBuf *t, const char *s, size_t n) {
if (t->len + n + 1 > t->cap) {
size_t next = t->cap ? t->cap : 64;
while (next < t->len + n + 1) {
next *= 2;
}
char *bigger = realloc(t->buf, next);
if (!bigger) {
code_runtime_error("out of memory");
}
t->buf = bigger;
t->cap = next;
}
memcpy(t->buf + t->len, s, n);
t->len += n;
}
static void text_push_str(TextBuf *t, const char *s) { text_push(t, s, strlen(s)); }
static void number_exact(char *out, size_t cap, double d) {
#ifdef CODE_WASM
int written = code_host_number_exact(d, out, (unsigned int)cap);
if (written < 0 || (size_t)written >= cap) {
code_runtime_error("the host could not render a number as text");
}
out[written] = '\0';
#else
snprintf(out, cap, "%.40e", d);
#endif
}
static double number_parse(const char *text, size_t len) {
#ifdef CODE_WASM
return code_host_number_parse(text, (unsigned int)len);
#else
(void)len;
return strtod(text, NULL);
#endif
}
static void text_push_number(TextBuf *t, double d) {
char tmp[512];
if (d == (double)(long long)d && d >= -9007199254740992.0 && d <= 9007199254740992.0) {
if (d == 0.0 && 1.0 / d < 0.0) {
text_push_str(t, "-0");
return;
}
snprintf(tmp, sizeof tmp, "%lld", (long long)d);
text_push_str(t, tmp);
return;
}
char exact[80];
number_exact(exact, sizeof exact, d);
const char *p = exact;
int negative = (*p == '-');
if (negative) {
p++;
}
char full[48];
size_t nfull = 0;
for (; *p && *p != 'e'; p++) {
if (*p != '.') {
full[nfull++] = *p;
}
}
int fullexp = (int)strtol(p + 1, NULL, 10);
char m[48];
size_t n = 1;
int exp10 = fullexp;
for (int len = 1; len <= 17; len++) {
n = (size_t)len;
exp10 = fullexp;
memcpy(m, full, n);
if (nfull > n && full[n] >= '5') {
size_t i = n;
while (i > 0) {
if (m[i - 1] == '9') {
m[i - 1] = '0';
i--;
} else {
m[i - 1]++;
break;
}
}
if (i == 0) {
memmove(m + 1, m, n);
m[0] = '1';
exp10++;
}
}
char sci[64];
size_t o = 0;
if (negative) {
sci[o++] = '-';
}
sci[o++] = m[0];
if (n > 1) {
sci[o++] = '.';
memcpy(sci + o, m + 1, n - 1);
o += n - 1;
}
o += (size_t)snprintf(sci + o, sizeof sci - o, "e%d", exp10);
sci[o] = '\0';
if (number_parse(sci, o) == d) {
break;
}
}
while (n > 1 && m[n - 1] == '0') {
n--;
}
size_t out = 0;
if (negative) {
tmp[out++] = '-';
}
if (exp10 >= (int)n - 1) {
memcpy(tmp + out, m, n);
out += n;
for (int i = 0; i < exp10 - (int)n + 1; i++) {
tmp[out++] = '0';
}
} else if (exp10 >= 0) {
memcpy(tmp + out, m, (size_t)exp10 + 1);
out += (size_t)exp10 + 1;
tmp[out++] = '.';
memcpy(tmp + out, m + exp10 + 1, n - (size_t)exp10 - 1);
out += n - (size_t)exp10 - 1;
} else {
tmp[out++] = '0';
tmp[out++] = '.';
for (int i = 0; i < -exp10 - 1; i++) {
tmp[out++] = '0';
}
memcpy(tmp + out, m, n);
out += n;
}
text_push(t, tmp, out);
}
static void text_push_json_string(TextBuf *t, const char *s) {
text_push(t, "\"", 1);
for (const char *p = s; *p; p++) {
switch (*p) {
case '"': text_push(t, "\\\"", 2); break;
case '\\': text_push(t, "\\\\", 2); break;
case '\n': text_push(t, "\\n", 2); break;
case '\t': text_push(t, "\\t", 2); break;
default: text_push(t, p, 1); break;
}
}
text_push(t, "\"", 1);
}
typedef struct {
const CodeValue *value;
const char *punct;
int is_key;
} TextStep;
static TextStep *steps = NULL;
static size_t steps_cap = 0;
void code_to_text(CodeValue *out, const CodeValue *v) {
TextBuf t = {NULL, 0, 0};
size_t len = 0;
steps = grow(steps, &steps_cap, len + 1, sizeof(TextStep));
steps[len++] = (TextStep){v, NULL, 0};
int top_level = 1;
while (len > 0) {
TextStep step = steps[--len];
if (!step.value) {
if (step.is_key) {
text_push_json_string(&t, step.punct);
text_push(&t, ":", 1);
} else {
text_push_str(&t, step.punct);
}
continue;
}
const CodeValue *current = step.value;
switch (current->tag) {
case CODE_NUMBER:
text_push_number(&t, current->number);
break;
case CODE_STR:
if (top_level) {
text_push_str(&t, current->str);
} else {
text_push_json_string(&t, current->str);
}
break;
case CODE_BOOL:
text_push_str(&t, current->boolean ? "true" : "false");
break;
case CODE_NULL:
text_push_str(&t, "null");
break;
case CODE_ARRAY:
text_push(&t, "[", 1);
steps = grow(steps, &steps_cap, len + 1, sizeof(TextStep));
steps[len++] = (TextStep){NULL, "]", 0};
for (long long i = current->len - 1; i >= 0; i--) {
steps = grow(steps, &steps_cap, len + 2, sizeof(TextStep));
steps[len++] = (TextStep){slot_at(current->items, i), NULL, 0};
if (i > 0) {
steps[len++] = (TextStep){NULL, ",", 0};
}
}
break;
case CODE_OBJECT:
text_push(&t, "{", 1);
steps = grow(steps, &steps_cap, len + 1, sizeof(TextStep));
steps[len++] = (TextStep){NULL, "}", 0};
for (long long i = current->len - 1; i >= 0; i--) {
steps = grow(steps, &steps_cap, len + 3, sizeof(TextStep));
steps[len++] = (TextStep){slot_at(current->items, i), NULL, 0};
steps[len++] = (TextStep){NULL, current->keys[i], 1};
if (i > 0) {
steps[len++] = (TextStep){NULL, ",", 0};
}
}
break;
}
top_level = 0;
}
text_push(&t, "", 0);
t.buf[t.len] = '\0';
char *owned = heap_alloc(t.len + 1);
memcpy(owned, t.buf, t.len + 1);
free(t.buf);
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = owned;
}
void code_add(CodeValue *out, const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
code_number(out, a->number + b->number);
return;
}
if (a->tag == CODE_STR && b->tag == CODE_STR) {
size_t la = strlen(a->str);
size_t lb = strlen(b->str);
char *buf = heap_alloc(la + lb + 1);
memcpy(buf, a->str, la);
memcpy(buf + la, b->str, lb);
buf[la + lb] = '\0';
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = buf;
return;
}
if (a->tag == CODE_ARRAY || b->tag == CODE_ARRAY) {
long long na = (a->tag == CODE_ARRAY) ? a->len : 1;
long long nb = (b->tag == CODE_ARRAY) ? b->len : 1;
long long total = na + nb;
void *buf = NULL;
if (total > 0) {
buf = heap_alloc((size_t)total * CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < na; i++) {
const CodeValue *src = (a->tag == CODE_ARRAY) ? slot_at(a->items, i) : a;
code_retain(src);
*slot_at(buf, i) = *src;
}
for (long long i = 0; i < nb; i++) {
const CodeValue *src = (b->tag == CODE_ARRAY) ? slot_at(b->items, i) : b;
code_retain(src);
*slot_at(buf, na + i) = *src;
}
}
code_release(out);
out->tag = CODE_ARRAY;
out->heap = total > 0;
out->items = buf;
out->len = total;
return;
}
if (a->tag == CODE_OBJECT && b->tag == CODE_OBJECT) {
long long total = a->len;
for (long long j = 0; j < b->len; j++) {
if (find_field(a, b->keys[j]) == NULL) {
total++;
}
}
const char **key_buf = NULL;
void *value_buf = NULL;
if (total > 0) {
size_t keys_bytes = (size_t)total * sizeof(const char *);
size_t slots_bytes = (size_t)total * CODE_VALUE_SLOT_SIZE;
size_t chars_bytes = 0;
for (long long i = 0; i < a->len; i++) {
chars_bytes += (a->keys[i] ? strlen(a->keys[i]) : 0) + 1;
}
for (long long j = 0; j < b->len; j++) {
if (find_field(a, b->keys[j]) == NULL) {
chars_bytes += (b->keys[j] ? strlen(b->keys[j]) : 0) + 1;
}
}
key_buf = heap_alloc(keys_bytes + slots_bytes + chars_bytes);
value_buf = (char *)key_buf + keys_bytes;
char *chars = (char *)value_buf + slots_bytes;
long long n = 0;
for (long long i = 0; i < a->len; i++) {
const CodeValue *override_val = find_field(b, a->keys[i]);
const CodeValue *src = override_val ? override_val : slot_at(a->items, i);
key_buf[n] = copy_key(&chars, a->keys[i]);
code_retain(src);
*slot_at(value_buf, n) = *src;
n++;
}
for (long long j = 0; j < b->len; j++) {
if (find_field(a, b->keys[j]) != NULL) {
continue;
}
key_buf[n] = copy_key(&chars, b->keys[j]);
const CodeValue *src = slot_at(b->items, j);
code_retain(src);
*slot_at(value_buf, n) = *src;
n++;
}
}
code_release(out);
out->tag = CODE_OBJECT;
out->heap = total > 0;
out->keys = key_buf;
out->items = value_buf;
out->len = total;
return;
}
fail_binary("+", a, b);
}
void code_sub(CodeValue *out, const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
code_number(out, a->number - b->number);
return;
}
fail_binary("-", a, b);
}
void code_mul(CodeValue *out, const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
code_number(out, a->number * b->number);
return;
}
fail_binary("*", a, b);
}
void code_div(CodeValue *out, const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
if (b->number == 0.0) {
fail("division by zero");
return;
}
code_number(out, a->number / b->number);
return;
}
fail_binary("/", a, b);
}
long long code_compare(const CodeValue *a, const CodeValue *b, const char *op) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
if (a->number < b->number) {
return -1;
}
return a->number > b->number ? 1 : 0;
}
fail_binary(op, a, b);
return 0;
}
void code_neg(CodeValue *out, const CodeValue *a) {
if (a->tag == CODE_NUMBER) {
code_number(out, -a->number);
return;
}
char msg[96];
snprintf(msg, sizeof msg, "cannot negate %s %s", article_for(a), type_name(a));
fail(msg);
}
void code_not(CodeValue *out, const CodeValue *a) {
if (a->tag == CODE_BOOL) {
code_bool(out, !a->boolean);
return;
}
fail_operand("'not' requires a boolean", a);
}
int code_is_kind(const CodeValue *a, int tag) {
return a->tag == (CodeTag)tag ? 1 : 0;
}
int code_is_particle(const CodeValue *a, const char *name) {
if (a->tag != CODE_OBJECT) {
return 0;
}
const CodeValue *class_val = find_field(a, "_class");
if (!class_val || class_val->tag != CODE_STR) {
return 0;
}
return strcmp(class_val->str, name) == 0 ? 1 : 0;
}
int code_bool_value(const CodeValue *v, const char *requirement) {
if (v->tag != CODE_BOOL) {
fail_operand(requirement, v);
return 0;
}
return v->boolean;
}
typedef struct {
const CodeValue *a;
const CodeValue *b;
} Pair;
static Pair *pending = NULL;
static size_t pending_cap = 0;
int code_values_equal(const CodeValue *a, const CodeValue *b) {
size_t len = 0;
pending = grow(pending, &pending_cap, len + 1, sizeof(Pair));
pending[len].a = a;
pending[len].b = b;
len++;
while (len > 0) {
Pair pair = pending[--len];
const CodeValue *x = pair.a;
const CodeValue *y = pair.b;
if (x->tag != y->tag) {
return 0;
}
switch (x->tag) {
case CODE_NUMBER:
if (x->number != y->number) {
return 0;
}
break;
case CODE_STR:
if (strcmp(x->str, y->str) != 0) {
return 0;
}
break;
case CODE_BOOL:
if (x->boolean != y->boolean) {
return 0;
}
break;
case CODE_NULL:
break;
case CODE_ARRAY:
case CODE_OBJECT:
if (x->len != y->len) {
return 0;
}
pending = grow(pending, &pending_cap, len + (size_t)x->len, sizeof(Pair));
for (long long i = 0; i < x->len; i++) {
if (x->tag == CODE_OBJECT && strcmp(x->keys[i], y->keys[i]) != 0) {
return 0;
}
pending[len].a = slot_at(x->items, i);
pending[len].b = slot_at(y->items, i);
len++;
}
break;
}
}
return 1;
}
void code_assert(const CodeValue *v) {
if (v->tag != CODE_BOOL) {
fail_operand("assert requires a boolean", v);
return;
}
if (!v->boolean) {
fail("assertion failed");
}
}