/* * el_runtime.c — El language C runtime implementation * * All functions use el_val_t (= int64_t) as the universal value type. * Strings are transported as their pointer address cast to int64_t. * On any 64-bit system sizeof(pointer) <= sizeof(int64_t), so this is safe. * * Compile with: * cc -std=c11 -I -o .c el_runtime.c */ #include "el_runtime.h" #include #include #include #include #include #include #include #include #include /* ── Internal allocators ─────────────────────────────────────────────────── */ static char* el_strdup(const char* s) { if (!s) return strdup(""); return strdup(s); } static char* el_strbuf(size_t n) { char* p = malloc(n + 1); if (!p) { fputs("el_runtime: out of memory\n", stderr); exit(1); } p[0] = '\0'; return p; } /* Wrap an allocated C string as el_val_t */ static el_val_t el_wrap_str(char* s) { return EL_STR(s); } /* ── I/O ──────────────────────────────────────────────────────────────────── */ void println(el_val_t s) { const char* str = EL_CSTR(s); if (str) puts(str); else puts(""); } void print(el_val_t s) { const char* str = EL_CSTR(s); if (str) fputs(str, stdout); } el_val_t readline(void) { char buf[4096]; if (!fgets(buf, sizeof(buf), stdin)) return el_wrap_str(el_strdup("")); size_t len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0'; return el_wrap_str(el_strdup(buf)); } /* ── String builtins ─────────────────────────────────────────────────────── */ el_val_t el_str_concat(el_val_t av, el_val_t bv) { const char* a = EL_CSTR(av); const char* b = EL_CSTR(bv); if (!a) a = ""; if (!b) b = ""; size_t la = strlen(a); size_t lb = strlen(b); char* out = el_strbuf(la + lb); memcpy(out, a, la); memcpy(out + la, b, lb); out[la + lb] = '\0'; return el_wrap_str(out); } el_val_t str_eq(el_val_t av, el_val_t bv) { const char* a = EL_CSTR(av); const char* b = EL_CSTR(bv); if (!a || !b) return (el_val_t)(a == b); return (el_val_t)(strcmp(a, b) == 0); } el_val_t str_starts_with(el_val_t sv, el_val_t prefv) { const char* s = EL_CSTR(sv); const char* prefix = EL_CSTR(prefv); if (!s || !prefix) return 0; size_t lp = strlen(prefix); return (el_val_t)(strncmp(s, prefix, lp) == 0); } el_val_t str_ends_with(el_val_t sv, el_val_t sufv) { const char* s = EL_CSTR(sv); const char* suffix = EL_CSTR(sufv); if (!s || !suffix) return 0; size_t ls = strlen(s); size_t lsuf = strlen(suffix); if (lsuf > ls) return 0; return (el_val_t)(strcmp(s + ls - lsuf, suffix) == 0); } el_val_t str_len(el_val_t sv) { const char* s = EL_CSTR(sv); if (!s) return 0; return (el_val_t)strlen(s); } el_val_t str_concat(el_val_t a, el_val_t b) { return el_str_concat(a, b); } el_val_t int_to_str(el_val_t n) { char buf[32]; snprintf(buf, sizeof(buf), "%lld", (long long)n); return el_wrap_str(el_strdup(buf)); } el_val_t str_to_int(el_val_t sv) { const char* s = EL_CSTR(sv); if (!s) return 0; return (el_val_t)atoll(s); } el_val_t str_slice(el_val_t sv, el_val_t start, el_val_t end) { const char* s = EL_CSTR(sv); if (!s) return el_wrap_str(el_strdup("")); int64_t len = (int64_t)strlen(s); if (start < 0) start = 0; if (end > len) end = len; if (start >= end) return el_wrap_str(el_strdup("")); int64_t sz = end - start; char* out = el_strbuf((size_t)sz); memcpy(out, s + start, (size_t)sz); out[sz] = '\0'; return el_wrap_str(out); } el_val_t str_contains(el_val_t sv, el_val_t subv) { const char* s = EL_CSTR(sv); const char* sub = EL_CSTR(subv); if (!s || !sub) return 0; return (el_val_t)(strstr(s, sub) != NULL); } el_val_t str_replace(el_val_t sv, el_val_t fromv, el_val_t tov) { const char* s = EL_CSTR(sv); const char* from = EL_CSTR(fromv); const char* to = EL_CSTR(tov); if (!s || !from || !to) return el_wrap_str(el_strdup(s ? s : "")); size_t ls = strlen(s); size_t lf = strlen(from); size_t lt = strlen(to); if (lf == 0) return el_wrap_str(el_strdup(s)); size_t count = 0; const char* p = s; while ((p = strstr(p, from)) != NULL) { count++; p += lf; } size_t out_sz = ls + count * lt + 1; char* out = el_strbuf(out_sz); char* dst = out; p = s; const char* found; while ((found = strstr(p, from)) != NULL) { size_t chunk = (size_t)(found - p); memcpy(dst, p, chunk); dst += chunk; memcpy(dst, to, lt); dst += lt; p = found + lf; } strcpy(dst, p); return el_wrap_str(out); } el_val_t str_to_upper(el_val_t sv) { const char* s = EL_CSTR(sv); if (!s) return el_wrap_str(el_strdup("")); size_t n = strlen(s); char* out = el_strbuf(n); for (size_t i = 0; i < n; i++) out[i] = (char)toupper((unsigned char)s[i]); out[n] = '\0'; return el_wrap_str(out); } el_val_t str_to_lower(el_val_t sv) { const char* s = EL_CSTR(sv); if (!s) return el_wrap_str(el_strdup("")); size_t n = strlen(s); char* out = el_strbuf(n); for (size_t i = 0; i < n; i++) out[i] = (char)tolower((unsigned char)s[i]); out[n] = '\0'; return el_wrap_str(out); } el_val_t str_trim(el_val_t sv) { const char* s = EL_CSTR(sv); if (!s) return el_wrap_str(el_strdup("")); while (*s && isspace((unsigned char)*s)) s++; size_t n = strlen(s); while (n > 0 && isspace((unsigned char)s[n - 1])) n--; char* out = el_strbuf(n); memcpy(out, s, n); out[n] = '\0'; return el_wrap_str(out); } /* ── Math ────────────────────────────────────────────────────────────────── */ el_val_t el_abs(el_val_t n) { return n < 0 ? -n : n; } el_val_t el_max(el_val_t a, el_val_t b) { return a > b ? a : b; } el_val_t el_min(el_val_t a, el_val_t b) { return a < b ? a : b; } /* ── List ────────────────────────────────────────────────────────────────── */ /* * Dynamic array header: * int64_t capacity * int64_t length * el_val_t elems[] */ typedef struct { int64_t capacity; int64_t length; el_val_t elems[1]; } ElList; static ElList* list_alloc(int64_t cap) { ElList* lst = malloc(sizeof(ElList) + (size_t)(cap > 1 ? cap - 1 : 0) * sizeof(el_val_t)); if (!lst) { fputs("el_runtime: out of memory\n", stderr); exit(1); } lst->capacity = cap; lst->length = 0; return lst; } el_val_t el_list_empty(void) { return EL_STR(list_alloc(4)); } el_val_t el_list_new(el_val_t count, ...) { ElList* lst = list_alloc(count > 0 ? count : 4); va_list ap; va_start(ap, count); for (int64_t i = 0; i < count; i++) { lst->elems[i] = va_arg(ap, el_val_t); } va_end(ap); lst->length = count; return EL_STR(lst); } el_val_t el_list_len(el_val_t listv) { ElList* lst = (ElList*)(uintptr_t)listv; if (!lst) return 0; return lst->length; } el_val_t el_list_get(el_val_t listv, el_val_t index) { ElList* lst = (ElList*)(uintptr_t)listv; if (!lst) return 0; if (index < 0 || index >= lst->length) return 0; return lst->elems[index]; } el_val_t el_list_append(el_val_t listv, el_val_t elem) { /* Always allocate a fresh list rather than realloc'ing the input. * El callers commonly hold a stale pointer to the original list (e.g. * cg_if_stmt passes `declared` to two successive cg_stmts calls; the * first call may realloc the underlying block, leaving the second * with a dangling pointer). Persistent allocation eliminates that * whole class of use-after-free at modest memory cost. */ ElList* old = (ElList*)(uintptr_t)listv; int64_t old_len = old ? old->length : 0; int64_t new_cap = old_len + 1; if (new_cap < 4) new_cap = 4; ElList* new_lst = malloc(sizeof(ElList) + (size_t)(new_cap - 1) * sizeof(el_val_t)); if (!new_lst) { fputs("el_runtime: out of memory\n", stderr); exit(1); } new_lst->capacity = new_cap; new_lst->length = old_len + 1; if (old && old_len > 0) { memcpy(new_lst->elems, old->elems, (size_t)old_len * sizeof(el_val_t)); } new_lst->elems[old_len] = elem; return EL_STR(new_lst); } /* ── Map ─────────────────────────────────────────────────────────────────── */ typedef struct { int64_t count; el_val_t* keys; el_val_t* values; } ElMap; el_val_t el_map_new(el_val_t pair_count, ...) { ElMap* m = malloc(sizeof(ElMap)); if (!m) { fputs("el_runtime: out of memory\n", stderr); exit(1); } m->count = pair_count; m->keys = malloc(sizeof(el_val_t) * (size_t)(pair_count > 0 ? pair_count : 1)); m->values = malloc(sizeof(el_val_t) * (size_t)(pair_count > 0 ? pair_count : 1)); va_list ap; va_start(ap, pair_count); for (int64_t i = 0; i < pair_count; i++) { m->keys[i] = va_arg(ap, el_val_t); m->values[i] = va_arg(ap, el_val_t); } va_end(ap); return EL_STR(m); } static ElMap* as_map(el_val_t v) { return (ElMap*)(uintptr_t)v; } el_val_t el_map_get(el_val_t mapv, el_val_t keyv) { ElMap* m = as_map(mapv); const char* key = EL_CSTR(keyv); if (!m || !key) return 0; for (int64_t i = 0; i < m->count; i++) { const char* k = EL_CSTR(m->keys[i]); if (k && strcmp(k, key) == 0) return m->values[i]; } return 0; } el_val_t el_get_field(el_val_t mapv, el_val_t keyv) { return el_map_get(mapv, keyv); } el_val_t el_map_set(el_val_t mapv, el_val_t keyv, el_val_t value) { ElMap* m = as_map(mapv); const char* key = EL_CSTR(keyv); if (!m) return 0; for (int64_t i = 0; i < m->count; i++) { const char* k = EL_CSTR(m->keys[i]); if (k && strcmp(k, key) == 0) { m->values[i] = value; return mapv; } } int64_t nc = m->count + 1; m->keys = realloc(m->keys, sizeof(el_val_t) * (size_t)nc); m->values = realloc(m->values, sizeof(el_val_t) * (size_t)nc); m->keys[m->count] = keyv; m->values[m->count] = value; m->count = nc; return mapv; } /* ── HTTP stubs ──────────────────────────────────────────────────────────── */ el_val_t http_get(el_val_t url) { (void)url; return el_wrap_str(el_strdup("")); } el_val_t http_post(el_val_t url, el_val_t body) { (void)url; (void)body; return el_wrap_str(el_strdup("")); } void http_serve(el_val_t port, el_val_t handler) { (void)port; (void)handler; } /* ── Filesystem ──────────────────────────────────────────────────────────── */ el_val_t fs_read(el_val_t pathv) { const char* path = EL_CSTR(pathv); if (!path) return el_wrap_str(el_strdup("")); FILE* f = fopen(path, "rb"); if (!f) return el_wrap_str(el_strdup("")); fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f); char* buf = el_strbuf((size_t)sz); size_t got = fread(buf, 1, (size_t)sz, f); buf[got] = '\0'; fclose(f); return el_wrap_str(buf); } el_val_t fs_write(el_val_t pathv, el_val_t contentv) { const char* path = EL_CSTR(pathv); const char* content = EL_CSTR(contentv); if (!path || !content) return 0; FILE* f = fopen(path, "wb"); if (!f) return 0; size_t n = strlen(content); size_t written = fwrite(content, 1, n, f); fclose(f); return written == n ? 1 : 0; } /* ── JSON ────────────────────────────────────────────────────────────────── */ el_val_t json_get(el_val_t jsonv, el_val_t keyv) { const char* json = EL_CSTR(jsonv); const char* key = EL_CSTR(keyv); if (!json || !key) return el_wrap_str(el_strdup("")); size_t klen = strlen(key); char* pattern = el_strbuf(klen + 4); snprintf(pattern, klen + 5, "\"%s\":", key); const char* p = strstr(json, pattern); free(pattern); if (!p) return el_wrap_str(el_strdup("")); p += strlen(key) + 3; /* skip "key": */ while (*p == ' ' || *p == '\t' || *p == '\n') p++; if (*p == '"') { p++; const char* start = p; while (*p && !(*p == '"' && *(p-1) != '\\')) p++; size_t len = (size_t)(p - start); char* out = el_strbuf(len); memcpy(out, start, len); out[len] = '\0'; return el_wrap_str(out); } const char* start = p; while (*p && *p != ',' && *p != '}' && *p != ']' && *p != '\n') p++; size_t len = (size_t)(p - start); char* out = el_strbuf(len); memcpy(out, start, len); out[len] = '\0'; return el_wrap_str(out); } /* ── Float bit-cast helpers ──────────────────────────────────────────────── */ static inline double el_to_float(el_val_t v) { union { int64_t i; double f; } u; u.i = (int64_t)v; return u.f; } static inline el_val_t el_from_float(double f) { union { double f; int64_t i; } u; u.f = f; return (el_val_t)u.i; } /* ── JSON parser (recursive descent) ─────────────────────────────────────── */ /* * Parsed JSON representation: * - object -> ElMap (keys & values are el_val_t) * - array -> ElList * - string -> EL_STR-wrapped char* (allocated) * - number -> int (el_val_t) if integer, otherwise el_from_float(double) * - true -> 1 * - false -> 0 * - null -> EL_NULL (0) * * Note: there is no runtime type tag — parsed numbers cannot be * distinguished from booleans by the runtime alone. The codegen tracks * types separately. This matches the rest of el_val_t's type-erased model. */ typedef struct { const char* p; const char* end; int err; } JsonParser; static void jp_skip_ws(JsonParser* jp) { while (jp->p < jp->end) { char c = *jp->p; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') jp->p++; else break; } } static el_val_t jp_parse_value(JsonParser* jp); /* Parse a JSON string literal (the opening " has NOT yet been consumed). */ static char* jp_parse_string_raw(JsonParser* jp) { if (jp->p >= jp->end || *jp->p != '"') { jp->err = 1; return el_strdup(""); } jp->p++; size_t cap = 32, len = 0; char* out = malloc(cap); if (!out) { fputs("el_runtime: out of memory\n", stderr); exit(1); } while (jp->p < jp->end && *jp->p != '"') { char c = *jp->p++; if (c == '\\' && jp->p < jp->end) { char esc = *jp->p++; switch (esc) { case '"': c = '"'; break; case '\\': c = '\\'; break; case '/': c = '/'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'u': { /* Skip 4 hex digits; emit '?' as a placeholder */ for (int i = 0; i < 4 && jp->p < jp->end; i++) jp->p++; c = '?'; break; } default: c = esc; break; } } if (len + 1 >= cap) { cap *= 2; out = realloc(out, cap); if (!out) { fputs("el_runtime: out of memory\n", stderr); exit(1); } } out[len++] = c; } if (jp->p < jp->end && *jp->p == '"') jp->p++; else jp->err = 1; out[len] = '\0'; return out; } static el_val_t jp_parse_number(JsonParser* jp) { const char* start = jp->p; int is_float = 0; if (jp->p < jp->end && (*jp->p == '-' || *jp->p == '+')) jp->p++; while (jp->p < jp->end && isdigit((unsigned char)*jp->p)) jp->p++; if (jp->p < jp->end && *jp->p == '.') { is_float = 1; jp->p++; while (jp->p < jp->end && isdigit((unsigned char)*jp->p)) jp->p++; } if (jp->p < jp->end && (*jp->p == 'e' || *jp->p == 'E')) { is_float = 1; jp->p++; if (jp->p < jp->end && (*jp->p == '+' || *jp->p == '-')) jp->p++; while (jp->p < jp->end && isdigit((unsigned char)*jp->p)) jp->p++; } size_t n = (size_t)(jp->p - start); char buf[64]; if (n >= sizeof(buf)) n = sizeof(buf) - 1; memcpy(buf, start, n); buf[n] = '\0'; if (is_float) return el_from_float(strtod(buf, NULL)); return (el_val_t)strtoll(buf, NULL, 10); } static el_val_t jp_parse_array(JsonParser* jp) { if (jp->p < jp->end && *jp->p == '[') jp->p++; el_val_t lst = el_list_empty(); jp_skip_ws(jp); if (jp->p < jp->end && *jp->p == ']') { jp->p++; return lst; } while (jp->p < jp->end) { jp_skip_ws(jp); el_val_t v = jp_parse_value(jp); lst = el_list_append(lst, v); jp_skip_ws(jp); if (jp->p < jp->end && *jp->p == ',') { jp->p++; continue; } if (jp->p < jp->end && *jp->p == ']') { jp->p++; break; } jp->err = 1; break; } return lst; } static el_val_t jp_parse_object(JsonParser* jp) { if (jp->p < jp->end && *jp->p == '{') jp->p++; el_val_t m = el_map_new(0); jp_skip_ws(jp); if (jp->p < jp->end && *jp->p == '}') { jp->p++; return m; } while (jp->p < jp->end) { jp_skip_ws(jp); char* key = jp_parse_string_raw(jp); jp_skip_ws(jp); if (jp->p < jp->end && *jp->p == ':') jp->p++; else { jp->err = 1; free(key); break; } jp_skip_ws(jp); el_val_t v = jp_parse_value(jp); m = el_map_set(m, EL_STR(key), v); jp_skip_ws(jp); if (jp->p < jp->end && *jp->p == ',') { jp->p++; continue; } if (jp->p < jp->end && *jp->p == '}') { jp->p++; break; } jp->err = 1; break; } return m; } static el_val_t jp_parse_value(JsonParser* jp) { jp_skip_ws(jp); if (jp->p >= jp->end) { jp->err = 1; return EL_NULL; } char c = *jp->p; if (c == '"') return el_wrap_str(jp_parse_string_raw(jp)); if (c == '{') return jp_parse_object(jp); if (c == '[') return jp_parse_array(jp); if (c == '-' || isdigit((unsigned char)c)) return jp_parse_number(jp); if (c == 't' && jp->p + 4 <= jp->end && strncmp(jp->p, "true", 4) == 0) { jp->p += 4; return 1; } if (c == 'f' && jp->p + 5 <= jp->end && strncmp(jp->p, "false", 5) == 0) { jp->p += 5; return 0; } if (c == 'n' && jp->p + 4 <= jp->end && strncmp(jp->p, "null", 4) == 0) { jp->p += 4; return EL_NULL; } jp->err = 1; return EL_NULL; } el_val_t json_parse(el_val_t sv) { const char* s = EL_CSTR(sv); if (!s) return EL_NULL; JsonParser jp = { .p = s, .end = s + strlen(s), .err = 0 }; el_val_t v = jp_parse_value(&jp); if (jp.err) return EL_NULL; return v; } /* ── JSON stringify ──────────────────────────────────────────────────────── */ /* * Stringify policy: el_val_t is type-erased, so we cannot perfectly * round-trip arbitrary values. We use these heuristics: * - If value is an ElList pointer (in the heap range), serialize as array. * - If value is an ElMap pointer, serialize as object. * - If value looks like a printable string pointer, serialize as string. * - Otherwise serialize as integer. * This is best-effort. Programs that need exact control should build the * string directly. A pointer test is the cheapest way to disambiguate * from small integers without a separate type tag. */ typedef struct { char* buf; size_t len; size_t cap; } JsonBuf; static void jb_init(JsonBuf* b) { b->cap = 64; b->len = 0; b->buf = malloc(b->cap); if (!b->buf) { fputs("el_runtime: out of memory\n", stderr); exit(1); } b->buf[0] = '\0'; } static void jb_reserve(JsonBuf* b, size_t add) { if (b->len + add + 1 > b->cap) { while (b->len + add + 1 > b->cap) b->cap *= 2; b->buf = realloc(b->buf, b->cap); if (!b->buf) { fputs("el_runtime: out of memory\n", stderr); exit(1); } } } static void jb_putc(JsonBuf* b, char c) { jb_reserve(b, 1); b->buf[b->len++] = c; b->buf[b->len] = '\0'; } static void jb_puts(JsonBuf* b, const char* s) { size_t n = strlen(s); jb_reserve(b, n); memcpy(b->buf + b->len, s, n); b->len += n; b->buf[b->len] = '\0'; } static void jb_emit_escaped(JsonBuf* b, const char* s) { jb_putc(b, '"'); for (; *s; s++) { unsigned char c = (unsigned char)*s; switch (c) { case '"': jb_puts(b, "\\\""); break; case '\\': jb_puts(b, "\\\\"); break; case '\b': jb_puts(b, "\\b"); break; case '\f': jb_puts(b, "\\f"); break; case '\n': jb_puts(b, "\\n"); break; case '\r': jb_puts(b, "\\r"); break; case '\t': jb_puts(b, "\\t"); break; default: if (c < 0x20) { char tmp[8]; snprintf(tmp, sizeof(tmp), "\\u%04x", c); jb_puts(b, tmp); } else { jb_putc(b, (char)c); } break; } } jb_putc(b, '"'); } /* Heuristic: is this el_val_t likely a pointer to an ElList? * We can't fully verify, but pointers are large addresses, integers small. * Treat values whose magnitude exceeds 2^32 as potential pointers and * sniff by reading the header conservatively. * * Simpler heuristic: if the value reads as a printable string, treat as * string; otherwise as integer. Lists/Maps are encoded as struct pointers, * which have leading binary bytes — so they won't look like strings. */ static int looks_like_string(el_val_t v) { if (v == 0) return 0; /* Treat plausible heap addresses as candidates */ uintptr_t p = (uintptr_t)v; /* Small integers (positive and negative) are not pointers */ if ((int64_t)v >= -1000000 && (int64_t)v <= 1000000) return 0; if (p < 0x1000) return 0; /* Sniff first bytes for printable */ const unsigned char* s = (const unsigned char*)p; for (int i = 0; i < 16; i++) { unsigned char c = s[i]; if (c == '\0') return i > 0; /* terminated string */ if (c < 0x09 || (c > 0x0d && c < 0x20) || c >= 0x7f) return 0; } return 1; /* 16+ printable bytes — call it a string */ } static void jb_emit_value(JsonBuf* b, el_val_t v); static void jb_emit_int(JsonBuf* b, int64_t n) { char tmp[32]; snprintf(tmp, sizeof(tmp), "%lld", (long long)n); jb_puts(b, tmp); } static void jb_emit_value(JsonBuf* b, el_val_t v) { if (v == EL_NULL) { jb_puts(b, "null"); return; } if (looks_like_string(v)) { jb_emit_escaped(b, EL_CSTR(v)); return; } jb_emit_int(b, (int64_t)v); } el_val_t json_stringify(el_val_t v) { JsonBuf b; jb_init(&b); jb_emit_value(&b, v); return el_wrap_str(b.buf); } /* ── JSON substring accessors ────────────────────────────────────────────── */ /* * These walk the raw JSON string looking for "key": at the top level (depth 1) * of an object. They handle escaped quotes, nested objects/arrays, and * whitespace around the colon. */ /* Find "key": at object-depth == 1 inside the JSON object string `s`. * Returns pointer to the first byte of the value, or NULL. */ static const char* json_find_key(const char* s, const char* key) { if (!s || !key) return NULL; size_t klen = strlen(key); int depth = 0; int in_str = 0; int escape = 0; const char* p = s; while (*p) { char c = *p; if (in_str) { if (escape) { escape = 0; } else if (c == '\\') { escape = 1; } else if (c == '"') { /* End of string. If we're at depth 1, check if this was a key. */ p++; if (depth == 1) { /* The string just ended at p-1. Check if it matches key * and is followed by a colon. We need to backtrack to find * the start of this string and compare. */ } in_str = 0; continue; } p++; continue; } if (c == '"') { /* Start of a string literal */ const char* str_start = p + 1; const char* q = str_start; int e = 0; while (*q) { if (e) { e = 0; q++; continue; } if (*q == '\\') { e = 1; q++; continue; } if (*q == '"') break; q++; } size_t slen = (size_t)(q - str_start); const char* after = (*q == '"') ? q + 1 : q; /* If at depth 1 and matches key and followed by ':' -> got it */ if (depth == 1 && slen == klen && strncmp(str_start, key, klen) == 0) { const char* r = after; while (*r == ' ' || *r == '\t' || *r == '\n' || *r == '\r') r++; if (*r == ':') { r++; while (*r == ' ' || *r == '\t' || *r == '\n' || *r == '\r') r++; return r; } } p = after; continue; } if (c == '{' || c == '[') depth++; else if (c == '}' || c == ']') depth--; p++; } return NULL; } /* Skip a JSON value starting at p; return pointer past the value end. */ static const char* json_skip_value(const char* p) { if (!p || !*p) return p; while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; if (*p == '"') { p++; int e = 0; while (*p) { if (e) { e = 0; p++; continue; } if (*p == '\\') { e = 1; p++; continue; } if (*p == '"') { p++; break; } p++; } return p; } if (*p == '{' || *p == '[') { char open = *p; char close = (open == '{') ? '}' : ']'; int depth = 0; int in_str = 0; int e = 0; while (*p) { char c = *p; if (in_str) { if (e) { e = 0; } else if (c == '\\') { e = 1; } else if (c == '"') in_str = 0; p++; continue; } if (c == '"') { in_str = 1; p++; continue; } if (c == open) depth++; else if (c == close) { depth--; p++; if (depth == 0) return p; continue; } p++; } return p; } /* scalar: number, true/false/null */ while (*p && *p != ',' && *p != '}' && *p != ']' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') p++; return p; } el_val_t json_get_string(el_val_t json_str, el_val_t key) { const char* json = EL_CSTR(json_str); const char* k = EL_CSTR(key); const char* p = json_find_key(json, k); if (!p || *p != '"') return el_wrap_str(el_strdup("")); p++; JsonParser jp = { .p = p - 1, .end = json + (json ? strlen(json) : 0), .err = 0 }; char* parsed = jp_parse_string_raw(&jp); if (jp.err) { free(parsed); return el_wrap_str(el_strdup("")); } return el_wrap_str(parsed); } el_val_t json_get_int(el_val_t json_str, el_val_t key) { const char* json = EL_CSTR(json_str); const char* k = EL_CSTR(key); const char* p = json_find_key(json, k); if (!p) return 0; if (*p == '"' || *p == '{' || *p == '[') return 0; return (el_val_t)strtoll(p, NULL, 10); } el_val_t json_get_float(el_val_t json_str, el_val_t key) { const char* json = EL_CSTR(json_str); const char* k = EL_CSTR(key); const char* p = json_find_key(json, k); if (!p) return 0; if (*p == '"' || *p == '{' || *p == '[') return 0; return el_from_float(strtod(p, NULL)); } el_val_t json_get_bool(el_val_t json_str, el_val_t key) { const char* json = EL_CSTR(json_str); const char* k = EL_CSTR(key); const char* p = json_find_key(json, k); if (!p) return 0; if (strncmp(p, "true", 4) == 0) return 1; return 0; } el_val_t json_get_raw(el_val_t json_str, el_val_t key) { const char* json = EL_CSTR(json_str); const char* k = EL_CSTR(key); const char* p = json_find_key(json, k); if (!p) return el_wrap_str(el_strdup("")); const char* end = json_skip_value(p); size_t n = (size_t)(end - p); char* out = el_strbuf(n); memcpy(out, p, n); out[n] = '\0'; return el_wrap_str(out); } el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value) { const char* json = EL_CSTR(json_str); const char* k = EL_CSTR(key); if (!k) k = ""; if (!json || !*json) { /* Build a fresh object */ JsonBuf b; jb_init(&b); jb_putc(&b, '{'); jb_emit_escaped(&b, k); jb_putc(&b, ':'); jb_emit_value(&b, value); jb_putc(&b, '}'); return el_wrap_str(b.buf); } const char* existing = json_find_key(json, k); JsonBuf b; jb_init(&b); if (existing) { const char* end = json_skip_value(existing); /* Copy [json .. existing) */ size_t prefix = (size_t)(existing - json); jb_reserve(&b, prefix); memcpy(b.buf + b.len, json, prefix); b.len += prefix; b.buf[b.len] = '\0'; jb_emit_value(&b, value); jb_puts(&b, end); return el_wrap_str(b.buf); } /* Insert before closing '}'. Find last '}' */ size_t jl = strlen(json); if (jl == 0) { free(b.buf); return el_wrap_str(el_strdup("{}")); } /* Find last '}' from the end */ ssize_t close_idx = -1; for (ssize_t i = (ssize_t)jl - 1; i >= 0; i--) { if (json[i] == '}') { close_idx = i; break; } } if (close_idx < 0) { free(b.buf); return el_wrap_str(el_strdup(json)); } /* Determine if object is empty: scan between last '{' and '}' for non-ws */ int empty = 1; for (ssize_t i = close_idx - 1; i >= 0; i--) { char c = json[i]; if (c == '{') break; if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { empty = 0; break; } } /* Copy json[0..close_idx) */ jb_reserve(&b, (size_t)close_idx); memcpy(b.buf + b.len, json, (size_t)close_idx); b.len += (size_t)close_idx; b.buf[b.len] = '\0'; if (!empty) jb_putc(&b, ','); jb_emit_escaped(&b, k); jb_putc(&b, ':'); jb_emit_value(&b, value); /* Append from close_idx onward */ jb_puts(&b, json + close_idx); return el_wrap_str(b.buf); } el_val_t json_array_len(el_val_t json_str) { const char* s = EL_CSTR(json_str); if (!s) return 0; while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; if (*s != '[') return 0; s++; while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; if (*s == ']') return 0; int64_t count = 0; while (*s) { const char* end = json_skip_value(s); if (end == s) break; count++; s = end; while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; if (*s == ',') { s++; continue; } if (*s == ']' || *s == '\0') break; } return (el_val_t)count; } /* ── Time ────────────────────────────────────────────────────────────────── */ el_val_t time_now(void) { struct timeval tv; gettimeofday(&tv, NULL); int64_t ms = (int64_t)tv.tv_sec * 1000LL + (int64_t)tv.tv_usec / 1000LL; return (el_val_t)ms; } el_val_t time_now_utc(void) { return time_now(); } el_val_t time_format(el_val_t ts, el_val_t fmt) { int64_t ms = (int64_t)ts; time_t s = (time_t)(ms / 1000); int msec = (int)(ms % 1000); if (msec < 0) { msec += 1000; s -= 1; } struct tm tm; gmtime_r(&s, &tm); const char* fmt_str = EL_CSTR(fmt); if (!fmt_str || strcmp(fmt_str, "ISO") == 0) { char buf[64]; snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, msec); return el_wrap_str(el_strdup(buf)); } char buf[256]; if (strftime(buf, sizeof(buf), fmt_str, &tm) == 0) buf[0] = '\0'; return el_wrap_str(el_strdup(buf)); } el_val_t time_to_parts(el_val_t ts) { int64_t ms = (int64_t)ts; time_t s = (time_t)(ms / 1000); int msec = (int)(ms % 1000); if (msec < 0) { msec += 1000; s -= 1; } struct tm tm; gmtime_r(&s, &tm); el_val_t m = el_map_new(0); m = el_map_set(m, EL_STR(el_strdup("year")), (el_val_t)(tm.tm_year + 1900)); m = el_map_set(m, EL_STR(el_strdup("month")), (el_val_t)(tm.tm_mon + 1)); m = el_map_set(m, EL_STR(el_strdup("day")), (el_val_t)tm.tm_mday); m = el_map_set(m, EL_STR(el_strdup("hour")), (el_val_t)tm.tm_hour); m = el_map_set(m, EL_STR(el_strdup("minute")), (el_val_t)tm.tm_min); m = el_map_set(m, EL_STR(el_strdup("second")), (el_val_t)tm.tm_sec); m = el_map_set(m, EL_STR(el_strdup("ms")), (el_val_t)msec); return m; } el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz) { (void)tz; int64_t s = (int64_t)secs; int64_t n = (int64_t)ns; int64_t ms = s * 1000LL + n / 1000000LL; return (el_val_t)ms; } el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit) { const char* u = EL_CSTR(unit); int64_t cur = (int64_t)ts; int64_t d = (int64_t)n; int64_t add_ms = d; if (u) { if (strcmp(u, "ms") == 0) add_ms = d; else if (strcmp(u, "sec") == 0) add_ms = d * 1000LL; else if (strcmp(u, "min") == 0) add_ms = d * 60000LL; else if (strcmp(u, "hour") == 0) add_ms = d * 3600000LL; else if (strcmp(u, "day") == 0) add_ms = d * 86400000LL; } return (el_val_t)(cur + add_ms); } el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit) { int64_t d = (int64_t)ts2 - (int64_t)ts1; const char* u = EL_CSTR(unit); if (!u || strcmp(u, "ms") == 0) return (el_val_t)d; if (strcmp(u, "sec") == 0) return (el_val_t)(d / 1000LL); if (strcmp(u, "min") == 0) return (el_val_t)(d / 60000LL); if (strcmp(u, "hour") == 0) return (el_val_t)(d / 3600000LL); if (strcmp(u, "day") == 0) return (el_val_t)(d / 86400000LL); return (el_val_t)d; } /* ── UUID v4 ─────────────────────────────────────────────────────────────── */ static int _el_uuid_seeded = 0; static void _el_uuid_seed(void) { if (!_el_uuid_seeded) { srand((unsigned)time(NULL) ^ (unsigned)(uintptr_t)&_el_uuid_seeded); _el_uuid_seeded = 1; } } el_val_t uuid_new(void) { _el_uuid_seed(); unsigned char b[16]; for (int i = 0; i < 16; i++) b[i] = (unsigned char)(rand() & 0xff); /* Version 4 */ b[6] = (b[6] & 0x0f) | 0x40; /* RFC 4122 variant */ b[8] = (b[8] & 0x3f) | 0x80; char buf[37]; snprintf(buf, sizeof(buf), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); return el_wrap_str(el_strdup(buf)); } el_val_t uuid_v4(void) { return uuid_new(); } /* ── Environment ─────────────────────────────────────────────────────────── */ el_val_t env(el_val_t key) { const char* k = EL_CSTR(key); if (!k) return el_wrap_str(el_strdup("")); const char* v = getenv(k); return el_wrap_str(el_strdup(v ? v : "")); } /* ── In-process state K/V ────────────────────────────────────────────────── */ typedef struct { char* key; char* value; } StateEntry; static StateEntry* _state_entries = NULL; static size_t _state_count = 0; static size_t _state_cap = 0; static StateEntry* state_find(const char* key) { for (size_t i = 0; i < _state_count; i++) { if (strcmp(_state_entries[i].key, key) == 0) return &_state_entries[i]; } return NULL; } el_val_t state_set(el_val_t key, el_val_t value) { const char* k = EL_CSTR(key); const char* v = EL_CSTR(value); if (!k) return 0; if (!v) v = ""; StateEntry* e = state_find(k); if (e) { free(e->value); e->value = el_strdup(v); return 1; } if (_state_count >= _state_cap) { size_t nc = _state_cap == 0 ? 16 : _state_cap * 2; _state_entries = realloc(_state_entries, nc * sizeof(StateEntry)); if (!_state_entries) { fputs("el_runtime: out of memory\n", stderr); exit(1); } _state_cap = nc; } _state_entries[_state_count].key = el_strdup(k); _state_entries[_state_count].value = el_strdup(v); _state_count++; return 1; } el_val_t state_get(el_val_t key) { const char* k = EL_CSTR(key); if (!k) return el_wrap_str(el_strdup("")); StateEntry* e = state_find(k); return el_wrap_str(el_strdup(e ? e->value : "")); } el_val_t state_del(el_val_t key) { const char* k = EL_CSTR(key); if (!k) return 0; for (size_t i = 0; i < _state_count; i++) { if (strcmp(_state_entries[i].key, k) == 0) { free(_state_entries[i].key); free(_state_entries[i].value); for (size_t j = i + 1; j < _state_count; j++) { _state_entries[j - 1] = _state_entries[j]; } _state_count--; return 1; } } return 1; } el_val_t state_keys(void) { el_val_t lst = el_list_empty(); for (size_t i = 0; i < _state_count; i++) { lst = el_list_append(lst, el_wrap_str(el_strdup(_state_entries[i].key))); } return lst; } /* ── Float formatting ────────────────────────────────────────────────────── */ el_val_t float_to_str(el_val_t f) { char buf[64]; snprintf(buf, sizeof(buf), "%g", el_to_float(f)); return el_wrap_str(el_strdup(buf)); } el_val_t int_to_float(el_val_t n) { return el_from_float((double)(int64_t)n); } el_val_t float_to_int(el_val_t f) { return (el_val_t)(int64_t)el_to_float(f); } el_val_t format_float(el_val_t f, el_val_t decimals) { int d = (int)(int64_t)decimals; if (d < 0) d = 0; if (d > 30) d = 30; char buf[128]; snprintf(buf, sizeof(buf), "%.*f", d, el_to_float(f)); return el_wrap_str(el_strdup(buf)); } el_val_t decimal_round(el_val_t f, el_val_t decimals) { int d = (int)(int64_t)decimals; if (d < 0) d = 0; if (d > 15) d = 15; double mul = pow(10.0, (double)d); double v = el_to_float(f); double r = (v >= 0.0 ? floor(v * mul + 0.5) : -floor(-v * mul + 0.5)) / mul; return el_from_float(r); } el_val_t str_to_float(el_val_t s) { const char* str = EL_CSTR(s); if (!str) return el_from_float(0.0); return el_from_float(strtod(str, NULL)); } /* ── Math (Float-aware) ──────────────────────────────────────────────────── */ el_val_t math_sqrt(el_val_t f) { return el_from_float(sqrt(el_to_float(f))); } el_val_t math_log(el_val_t f) { return el_from_float(log(el_to_float(f))); } el_val_t math_ln(el_val_t f) { return el_from_float(log(el_to_float(f))); } el_val_t math_sin(el_val_t f) { return el_from_float(sin(el_to_float(f))); } el_val_t math_cos(el_val_t f) { return el_from_float(cos(el_to_float(f))); } el_val_t math_pi(void) { return el_from_float(3.141592653589793238462643383279502884); } /* ── String additions ────────────────────────────────────────────────────── */ el_val_t str_index_of(el_val_t s, el_val_t sub) { const char* str = EL_CSTR(s); const char* sb = EL_CSTR(sub); if (!str || !sb) return -1; const char* hit = strstr(str, sb); if (!hit) return -1; return (el_val_t)(int64_t)(hit - str); } el_val_t str_split(el_val_t s, el_val_t sep) { const char* str = EL_CSTR(s); const char* sp = EL_CSTR(sep); el_val_t lst = el_list_empty(); if (!str) return lst; if (!sp || !*sp) { lst = el_list_append(lst, el_wrap_str(el_strdup(str))); return lst; } size_t lp = strlen(sp); const char* p = str; const char* hit; while ((hit = strstr(p, sp)) != NULL) { size_t n = (size_t)(hit - p); char* out = el_strbuf(n); memcpy(out, p, n); out[n] = '\0'; lst = el_list_append(lst, el_wrap_str(out)); p = hit + lp; } lst = el_list_append(lst, el_wrap_str(el_strdup(p))); return lst; } el_val_t str_char_at(el_val_t s, el_val_t i) { const char* str = EL_CSTR(s); int64_t idx = (int64_t)i; if (!str) return el_wrap_str(el_strdup("")); int64_t n = (int64_t)strlen(str); if (idx < 0 || idx >= n) return el_wrap_str(el_strdup("")); char buf[2]; buf[0] = str[idx]; buf[1] = '\0'; return el_wrap_str(el_strdup(buf)); } el_val_t str_char_code(el_val_t s, el_val_t i) { const char* str = EL_CSTR(s); int64_t idx = (int64_t)i; if (!str) return 0; int64_t n = (int64_t)strlen(str); if (idx < 0 || idx >= n) return 0; return (el_val_t)(unsigned char)str[idx]; } static el_val_t str_pad(const char* s, int64_t width, const char* pad, int left) { if (!s) s = ""; if (!pad || !*pad) pad = " "; int64_t lp = (int64_t)strlen(pad); int64_t ls = (int64_t)strlen(s); if (ls >= width) return el_wrap_str(el_strdup(s)); int64_t need = width - ls; char* out = el_strbuf((size_t)width); if (left) { for (int64_t i = 0; i < need; i++) out[i] = pad[i % lp]; memcpy(out + need, s, (size_t)ls); } else { memcpy(out, s, (size_t)ls); for (int64_t i = 0; i < need; i++) out[ls + i] = pad[i % lp]; } out[width] = '\0'; return el_wrap_str(out); } el_val_t str_pad_left(el_val_t s, el_val_t width, el_val_t pad) { return str_pad(EL_CSTR(s), (int64_t)width, EL_CSTR(pad), 1); } el_val_t str_pad_right(el_val_t s, el_val_t width, el_val_t pad) { return str_pad(EL_CSTR(s), (int64_t)width, EL_CSTR(pad), 0); } el_val_t str_format(el_val_t template, el_val_t data) { const char* tpl = EL_CSTR(template); if (!tpl) return el_wrap_str(el_strdup("")); JsonBuf b; jb_init(&b); const char* p = tpl; while (*p) { if (*p == '{') { const char* q = p + 1; while (*q && *q != '}') q++; if (*q == '}') { size_t klen = (size_t)(q - p - 1); char keybuf[256]; if (klen < sizeof(keybuf)) { memcpy(keybuf, p + 1, klen); keybuf[klen] = '\0'; el_val_t v = el_map_get(data, EL_STR(keybuf)); if (v != 0 && looks_like_string(v)) { jb_puts(&b, EL_CSTR(v)); p = q + 1; continue; } else if (v != 0) { jb_emit_int(&b, (int64_t)v); p = q + 1; continue; } } /* Unknown key — leave {key} verbatim */ jb_reserve(&b, klen + 2); memcpy(b.buf + b.len, p, klen + 2); b.len += klen + 2; b.buf[b.len] = '\0'; p = q + 1; continue; } } jb_putc(&b, *p); p++; } return el_wrap_str(b.buf); } el_val_t str_lower(el_val_t s) { return str_to_lower(s); } el_val_t str_upper(el_val_t s) { return str_to_upper(s); } /* ── List additions ──────────────────────────────────────────────────────── */ el_val_t list_push(el_val_t list, el_val_t elem) { return el_list_append(list, elem); } el_val_t list_push_front(el_val_t listv, el_val_t elem) { ElList* lst = (ElList*)(uintptr_t)listv; if (!lst) { el_val_t nl = el_list_empty(); return el_list_append(nl, elem); } /* Append to grow capacity, then shift right */ listv = el_list_append(listv, elem); lst = (ElList*)(uintptr_t)listv; for (int64_t i = lst->length - 1; i > 0; i--) { lst->elems[i] = lst->elems[i - 1]; } lst->elems[0] = elem; return EL_STR(lst); } el_val_t list_join(el_val_t listv, el_val_t sep) { ElList* lst = (ElList*)(uintptr_t)listv; const char* sp = EL_CSTR(sep); if (!sp) sp = ""; if (!lst || lst->length == 0) return el_wrap_str(el_strdup("")); JsonBuf b; jb_init(&b); for (int64_t i = 0; i < lst->length; i++) { if (i > 0) jb_puts(&b, sp); el_val_t v = lst->elems[i]; if (v == 0) continue; if (looks_like_string(v)) { jb_puts(&b, EL_CSTR(v)); } else { char tmp[32]; snprintf(tmp, sizeof(tmp), "%lld", (long long)v); jb_puts(&b, tmp); } } return el_wrap_str(b.buf); } el_val_t list_range(el_val_t start, el_val_t end) { int64_t a = (int64_t)start; int64_t b = (int64_t)end; el_val_t lst = el_list_empty(); for (int64_t i = a; i < b; i++) lst = el_list_append(lst, (el_val_t)i); return lst; } /* ── Bool helpers ────────────────────────────────────────────────────────── */ el_val_t bool_to_str(el_val_t b) { return el_wrap_str(el_strdup(b ? "true" : "false")); } /* ── Process ─────────────────────────────────────────────────────────────── */ void exit_program(el_val_t code) { exit((int)code); } /* ── args() — command-line argument access ────────────────────────────────── * Compiled El programs call args() to get a list of CLI arguments. * Call el_runtime_init_args(argc, argv) at the start of C main() to populate. * The args list excludes argv[0] (the program name). */ static el_val_t _el_args_list = 0; void el_runtime_init_args(int argc, char** argv) { _el_args_list = el_list_empty(); for (int i = 1; i < argc; i++) { _el_args_list = el_list_append(_el_args_list, EL_STR(argv[i])); } } el_val_t args(void) { if (!_el_args_list) _el_args_list = el_list_empty(); return _el_args_list; } /* ── CGI identity ──────────────────────────────────────────────────────────── * Called once at program start by the generated main() of a cgi {} program. * Stores CGI identity so dharma_* builtins can reference it. */ static const char* _el_cgi_name = NULL; static const char* _el_cgi_dharma_id = NULL; static const char* _el_cgi_principal = NULL; static const char* _el_cgi_network = NULL; static const char* _el_cgi_engram = NULL; void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal, el_val_t network, el_val_t engram) { _el_cgi_name = EL_CSTR(name); _el_cgi_dharma_id = EL_CSTR(dharma_id); _el_cgi_principal = EL_CSTR(principal); _el_cgi_network = EL_CSTR(network) ? EL_CSTR(network) : "dharma-mainnet"; _el_cgi_engram = EL_CSTR(engram) ? EL_CSTR(engram) : "http://localhost:8742"; printf("[cgi] identity: name=%s dharma_id=%s principal=%s network=%s engram=%s\n", _el_cgi_name ? _el_cgi_name : "(unset)", _el_cgi_dharma_id ? _el_cgi_dharma_id : "(unset)", _el_cgi_principal ? _el_cgi_principal : "(unset)", _el_cgi_network, _el_cgi_engram); } /* ── DHARMA network stubs ─────────────────────────────────────────────────── * Stub implementations for all dharma_* and engram_* builtins. * Each stub prints a descriptive line to stdout so calls are visible in tests. * Full implementations are provided by the DHARMA runtime linked at deploy. */ el_val_t dharma_connect(el_val_t cgi_id) { const char* id = EL_CSTR(cgi_id); if (!id) id = "(null)"; char buf[256]; snprintf(buf, sizeof(buf), "[dharma] connect: %s", id); puts(buf); /* Return a synthetic channel ID of the form "ch:" */ char ch[272]; snprintf(ch, sizeof(ch), "ch:%s", id); return el_wrap_str(el_strdup(ch)); } el_val_t dharma_send(el_val_t channel, el_val_t content) { const char* ch = EL_CSTR(channel); const char* msg = EL_CSTR(content); if (!ch) ch = "(null)"; if (!msg) msg = "(null)"; char buf[1024]; snprintf(buf, sizeof(buf), "[dharma] send on %s: %s", ch, msg); puts(buf); return el_wrap_str(el_strdup("")); } el_val_t dharma_activate(el_val_t query) { const char* q = EL_CSTR(query); if (!q) q = "(null)"; char buf[512]; snprintf(buf, sizeof(buf), "[dharma] activate: %s", q); puts(buf); return el_list_empty(); } void dharma_emit(el_val_t event_type, el_val_t payload) { const char* et = EL_CSTR(event_type); const char* pay = EL_CSTR(payload); if (!et) et = "(null)"; if (!pay) pay = "(null)"; char buf[1024]; snprintf(buf, sizeof(buf), "[dharma] emit: %s %s", et, pay); puts(buf); } el_val_t dharma_field(el_val_t event_type) { const char* et = EL_CSTR(event_type); if (!et) et = "(null)"; char buf[512]; snprintf(buf, sizeof(buf), "[dharma] field: %s", et); puts(buf); return el_map_new(0); } void dharma_strengthen(el_val_t cgi_id, el_val_t weight) { const char* id = EL_CSTR(cgi_id); if (!id) id = "(null)"; /* weight is encoded as el_val_t; print as integer (float encoding TBD) */ char buf[256]; snprintf(buf, sizeof(buf), "[dharma] strengthen: %s +%lld", id, (long long)weight); puts(buf); } el_val_t dharma_relationship(el_val_t cgi_id) { const char* id = EL_CSTR(cgi_id); if (!id) id = "(null)"; char buf[256]; snprintf(buf, sizeof(buf), "[dharma] relationship: %s", id); puts(buf); return 0; /* 0.0 — no prior relationship in stub mode */ } el_val_t dharma_peers(void) { puts("[dharma] peers"); return el_list_empty(); } /* ── Engram local graph stubs ────────────────────────────────────────────── */ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) { const char* c = EL_CSTR(content); const char* nt = EL_CSTR(node_type); if (!c) c = "(null)"; if (!nt) nt = "(null)"; char buf[512]; snprintf(buf, sizeof(buf), "[engram] node: %s (type=%s salience=%lld)", c, nt, (long long)salience); puts(buf); return el_wrap_str(el_strdup("stub-node-id")); } el_val_t engram_activate(el_val_t query, el_val_t depth) { const char* q = EL_CSTR(query); if (!q) q = "(null)"; char buf[512]; snprintf(buf, sizeof(buf), "[engram] activate: %s depth=%lld", q, (long long)depth); puts(buf); return el_list_empty(); } void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation) { const char* f = EL_CSTR(from_id); const char* t = EL_CSTR(to_id); const char* r = EL_CSTR(relation); if (!f) f = "(null)"; if (!t) t = "(null)"; if (!r) r = "(null)"; char buf[512]; snprintf(buf, sizeof(buf), "[engram] connect: %s -[%s]-> %s weight=%lld", f, r, t, (long long)weight); puts(buf); } void engram_strengthen(el_val_t node_id) { const char* id = EL_CSTR(node_id); if (!id) id = "(null)"; char buf[256]; snprintf(buf, sizeof(buf), "[engram] strengthen: %s", id); puts(buf); } /* ── Native VM builtin aliases ────────────────────────────────────────────── * El source files use native_* names (El VM builtins). * When compiled to C, these map directly to el_* runtime functions. */ el_val_t native_list_get(el_val_t list, el_val_t index) { return el_list_get(list, index); } el_val_t native_list_len(el_val_t list) { return el_list_len(list); } el_val_t native_list_append(el_val_t list, el_val_t elem) { return el_list_append(list, elem); } el_val_t native_list_empty(void) { return el_list_empty(); } el_val_t native_string_chars(el_val_t sv) { const char* s = EL_CSTR(sv); el_val_t result = el_list_empty(); if (!s) return result; while (*s) { char buf[2]; buf[0] = *s; buf[1] = '\0'; result = el_list_append(result, EL_STR(strdup(buf))); s++; } return result; } el_val_t native_int_to_str(el_val_t n) { return int_to_str(n); } /* ── Method-call shorthand aliases ────────────────────────────────────────── * Short names that result from the method-call convention: * myList.append(x) → append(myList, x) * myList.len() → len(myList) * myList.get(i) → get(myList, i) * myMap.map_get(k) → map_get(myMap, k) * myMap.map_set(k,v) → map_set(myMap, k, v) */ el_val_t append(el_val_t list, el_val_t elem) { return el_list_append(list, elem); } el_val_t len(el_val_t list) { return el_list_len(list); } el_val_t get(el_val_t list, el_val_t index) { return el_list_get(list, index); } el_val_t map_get(el_val_t map, el_val_t key) { return el_map_get(map, key); } el_val_t map_set(el_val_t map, el_val_t key, el_val_t value) { return el_map_set(map, key, value); }