diff --git a/el-compiler/runtime/el_seed.c b/el-compiler/runtime/el_seed.c new file mode 100644 index 0000000..b036b67 --- /dev/null +++ b/el-compiler/runtime/el_seed.c @@ -0,0 +1,1027 @@ +/* + * el_seed.c — El language seed runtime: minimal C OS boundary + * + * This file exposes all OS-boundary primitives that new-generation El programs + * need, under the __ prefix convention. Implementations are lifted directly + * from el_runtime.c and wrapped/renamed rather than reimplemented. + * + * Threading is the most important new piece here. __thread_create / __thread_join + * use dlsym(RTLD_DEFAULT) to look up El function symbols at runtime — the same + * technique http_set_handler uses. This is the foundation of El's parallelism. + * + * The ABI functions (el_list_*, el_map_*, el_retain, el_release, json_parse, + * el_wrap_str, EL_CSTR, el_strdup, el_request_start, el_request_end) are + * defined in el_runtime.c and used here by declaration only. This file does + * NOT redefine them. + * + * Link: cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \ + * -o .c el_seed.c el_runtime.c + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "el_seed.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ── Forward declarations from el_runtime.c (ABI + allocators) ──────────── */ +/* These are defined in el_runtime.c and linked in. */ + +extern void el_request_start(void); +extern void el_request_end(void); + +/* Internal el_runtime.c allocators — declared here so el_seed.c can use them. + * They are static in el_runtime.c; el_seed.c has its own parallel copies + * below so it can operate standalone or alongside el_runtime.c. */ + +/* ── Private allocator (parallel to el_runtime.c) ────────────────────────── */ +/* + * el_seed.c carries its own arena so it can be compiled with or without + * el_runtime.c. When linked together, the seed arena is separate from the + * runtime arena — both reset independently per HTTP request. + * + * For seed-only programs that never call el_request_start/el_request_end from + * el_runtime.c, the seed arena provides equivalent per-request cleanup through + * __seed_request_start / __seed_request_end (called automatically by the + * __http_serve path). + */ + +#define SEED_ARENA_INITIAL 512 + +typedef struct { + char** ptrs; + size_t count; + size_t cap; +} SeedArena; + +static _Thread_local SeedArena _seed_arena = {NULL, 0, 0}; +static _Thread_local int _seed_arena_on = 0; + +static void seed_arena_track(char* p) { + if (!_seed_arena_on || !p) return; + if (_seed_arena.count >= _seed_arena.cap) { + size_t nc = _seed_arena.cap == 0 ? SEED_ARENA_INITIAL : _seed_arena.cap * 2; + char** g = realloc(_seed_arena.ptrs, nc * sizeof(char*)); + if (!g) return; + _seed_arena.ptrs = g; + _seed_arena.cap = nc; + } + _seed_arena.ptrs[_seed_arena.count++] = p; +} + +static void seed_request_start(void) { + _seed_arena.count = 0; + _seed_arena_on = 1; +} + +static void seed_request_end(void) { + _seed_arena_on = 0; + for (size_t i = 0; i < _seed_arena.count; i++) free(_seed_arena.ptrs[i]); + _seed_arena.count = 0; +} + +/* Persistent alloc — bypasses arena (state, engram internals). */ +static char* seed_strdup_persist(const char* s) { + if (!s) return strdup(""); + return strdup(s); +} + +static char* seed_strdup(const char* s) { + char* p = strdup(s ? s : ""); + seed_arena_track(p); + return p; +} + +static char* seed_strbuf(size_t n) { + char* p = malloc(n + 1); + if (!p) { fputs("el_seed: out of memory\n", stderr); exit(1); } + p[0] = '\0'; + seed_arena_track(p); + return p; +} + +static el_val_t seed_wrap_str(char* s) { return EL_STR(s); } + +/* ── String primitives ───────────────────────────────────────────────────── */ + +el_val_t __str_len(el_val_t s) { + const char* p = EL_CSTR(s); + if (!p) return 0; + return (el_val_t)strlen(p); +} + +el_val_t __str_char_at(el_val_t s, el_val_t i) { + const char* p = EL_CSTR(s); + if (!p) return 0; + int64_t len = (int64_t)strlen(p); + int64_t idx = (int64_t)i; + if (idx < 0 || idx >= len) return 0; + return (el_val_t)(unsigned char)p[idx]; +} + +el_val_t __str_alloc(el_val_t n) { + int64_t sz = (int64_t)n; + if (sz < 0) sz = 0; + char* buf = seed_strbuf((size_t)sz); + memset(buf, 0, (size_t)sz + 1); + return seed_wrap_str(buf); +} + +el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c) { + char* p = (char*)(uintptr_t)s; + if (!p) return s; + int64_t len = (int64_t)strlen(p); + int64_t idx = (int64_t)i; + if (idx < 0 || idx >= len) return s; + p[idx] = (char)(unsigned char)(int64_t)c; + return s; +} + +el_val_t __str_cmp(el_val_t a, el_val_t b) { + const char* sa = EL_CSTR(a); + const char* sb = EL_CSTR(b); + if (!sa) sa = ""; + if (!sb) sb = ""; + return (el_val_t)strcmp(sa, sb); +} + +el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n) { + const char* sa = EL_CSTR(a); + const char* sb = EL_CSTR(b); + if (!sa) sa = ""; + if (!sb) sb = ""; + return (el_val_t)strncmp(sa, sb, (size_t)(int64_t)n); +} + +el_val_t __str_concat_raw(el_val_t a, el_val_t b) { + const char* sa = EL_CSTR(a); + const char* sb = EL_CSTR(b); + if (!sa) sa = ""; + if (!sb) sb = ""; + size_t la = strlen(sa), lb = strlen(sb); + char* out = seed_strbuf(la + lb); + memcpy(out, sa, la); + memcpy(out + la, sb, lb); + out[la + lb] = '\0'; + return seed_wrap_str(out); +} + +el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end) { + const char* p = EL_CSTR(s); + if (!p) return seed_wrap_str(seed_strdup("")); + int64_t len = (int64_t)strlen(p); + int64_t st = (int64_t)start; + int64_t en = (int64_t)end; + if (st < 0) st = 0; + if (en > len) en = len; + if (st >= en) return seed_wrap_str(seed_strdup("")); + int64_t sz = en - st; + char* out = seed_strbuf((size_t)sz); + memcpy(out, p + st, (size_t)sz); + out[sz] = '\0'; + return seed_wrap_str(out); +} + +el_val_t __int_to_str(el_val_t n) { + char buf[32]; + snprintf(buf, sizeof(buf), "%lld", (long long)(int64_t)n); + return seed_wrap_str(seed_strdup(buf)); +} + +el_val_t __str_to_int(el_val_t s) { + const char* p = EL_CSTR(s); + if (!p) return 0; + return (el_val_t)atoll(p); +} + +el_val_t __float_to_str(el_val_t f) { + char buf[64]; + snprintf(buf, sizeof(buf), "%g", el_to_float(f)); + return seed_wrap_str(seed_strdup(buf)); +} + +el_val_t __str_to_float(el_val_t s) { + const char* p = EL_CSTR(s); + if (!p) return el_from_float(0.0); + return el_from_float(strtod(p, NULL)); +} + +/* ── I/O ─────────────────────────────────────────────────────────────────── */ + +void __println(el_val_t s) { + const char* p = EL_CSTR(s); + puts(p ? p : ""); +} + +void __print(el_val_t s) { + const char* p = EL_CSTR(s); + if (p) fputs(p, stdout); +} + +el_val_t __readline(void) { + char buf[4096]; + if (!fgets(buf, sizeof(buf), stdin)) return seed_wrap_str(seed_strdup("")); + size_t len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0'; + return seed_wrap_str(seed_strdup(buf)); +} + +/* ── Filesystem ──────────────────────────────────────────────────────────── */ + +el_val_t __fs_read(el_val_t path) { + const char* p = EL_CSTR(path); + if (!p) return seed_wrap_str(seed_strdup("")); + FILE* f = fopen(p, "rb"); + if (!f) return seed_wrap_str(seed_strdup("")); + fseek(f, 0, SEEK_END); + long sz = ftell(f); + rewind(f); + if (sz < 0) { fclose(f); return seed_wrap_str(seed_strdup("")); } + char* buf = seed_strbuf((size_t)sz); + size_t got = fread(buf, 1, (size_t)sz, f); + buf[got] = '\0'; + fclose(f); + return seed_wrap_str(buf); +} + +el_val_t __fs_write(el_val_t path, el_val_t content) { + const char* p = EL_CSTR(path); + const char* c = EL_CSTR(content); + if (!p || !c) return 0; + FILE* f = fopen(p, "wb"); + if (!f) return 0; + size_t n = strlen(c); + size_t w = fwrite(c, 1, n, f); + fclose(f); + return w == n ? 1 : 0; +} + +el_val_t __fs_exists(el_val_t path) { + const char* p = EL_CSTR(path); + if (!p || !*p) return 0; + struct stat st; + return (el_val_t)(stat(p, &st) == 0 ? 1 : 0); +} + +el_val_t __fs_list_raw(el_val_t path) { + const char* p = EL_CSTR(path); + if (!p) return seed_wrap_str(seed_strdup("")); + DIR* d = opendir(p); + if (!d) return seed_wrap_str(seed_strdup("")); + /* Build newline-separated list of filenames. */ + size_t cap = 4096, len = 0; + char* buf = malloc(cap); + if (!buf) { closedir(d); return seed_wrap_str(seed_strdup("")); } + buf[0] = '\0'; + struct dirent* e; + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue; + size_t nlen = strlen(e->d_name); + while (len + nlen + 2 >= cap) { + cap *= 2; + char* g = realloc(buf, cap); + if (!g) { free(buf); closedir(d); return seed_wrap_str(seed_strdup("")); } + buf = g; + } + if (len > 0) buf[len++] = '\n'; + memcpy(buf + len, e->d_name, nlen); + len += nlen; + buf[len] = '\0'; + } + closedir(d); + seed_arena_track(buf); + return seed_wrap_str(buf); +} + +el_val_t __fs_mkdir(el_val_t path) { + const char* p = EL_CSTR(path); + if (!p || !*p) return 0; + size_t n = strlen(p); + char* buf = malloc(n + 1); + if (!buf) return 0; + memcpy(buf, p, n + 1); + for (size_t i = 1; i <= n; i++) { + if (buf[i] == '/' || buf[i] == '\0') { + char saved = buf[i]; + buf[i] = '\0'; + if (buf[0] != '\0') { + if (mkdir(buf, 0755) != 0 && errno != EEXIST) { + struct stat st; + if (stat(buf, &st) != 0 || !S_ISDIR(st.st_mode)) { + free(buf); return 0; + } + } + } + buf[i] = saved; + } + } + free(buf); + return 1; +} + +el_val_t __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n) { + const char* p = EL_CSTR(path); + const char* b = EL_CSTR(bytes); + int64_t sz = (int64_t)n; + if (!p || !b || sz < 0) return 0; + FILE* f = fopen(p, "wb"); + if (!f) return 0; + size_t written = (sz > 0) ? fwrite(b, 1, (size_t)sz, f) : 0; + int ok1 = (fflush(f) == 0); + int ok2 = (fclose(f) == 0); + if (!ok1 || !ok2 || written != (size_t)sz) { remove(p); return 0; } + return 1; +} + +/* ── HTTP client ─────────────────────────────────────────────────────────── */ + +typedef struct { + char* data; + size_t len; + size_t cap; +} SeedHttpBuf; + +static void seed_httpbuf_init(SeedHttpBuf* b) { + b->cap = 1024; b->len = 0; + b->data = malloc(b->cap); + if (!b->data) { fputs("el_seed: out of memory\n", stderr); exit(1); } + b->data[0] = '\0'; +} + +static void seed_httpbuf_append(SeedHttpBuf* b, const void* src, size_t n) { + while (b->len + n + 1 > b->cap) b->cap *= 2; + b->data = realloc(b->data, b->cap); + if (!b->data) { fputs("el_seed: out of memory\n", stderr); exit(1); } + memcpy(b->data + b->len, src, n); + b->len += n; + b->data[b->len] = '\0'; +} + +static size_t seed_http_write_cb(char* ptr, size_t size, size_t nmemb, void* ud) { + size_t n = size * nmemb; + seed_httpbuf_append((SeedHttpBuf*)ud, ptr, n); + return n; +} + +/* Build a curl_slist from a JSON object string of header name:value pairs. */ +static struct curl_slist* seed_headers_from_json(const char* hj) { + struct curl_slist* h = NULL; + if (!hj || !*hj) return NULL; + /* Walk key:value pairs at depth 1. Simple parser — same logic as json_find_key + * in el_runtime.c but adapted for building curl headers. */ + const char* p = hj; + while (*p && *p != '{') p++; + if (*p == '{') p++; + while (*p) { + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',') p++; + if (*p == '}' || *p == '\0') break; + if (*p != '"') break; + /* Parse key */ + p++; + const char* ks = p; + while (*p && *p != '"') { if (*p == '\\') p++; p++; } + size_t klen = (size_t)(p - ks); + if (*p == '"') p++; + /* Skip : */ + while (*p == ' ' || *p == ':') p++; + /* Parse value */ + if (*p != '"') break; + p++; + const char* vs = p; + while (*p && *p != '"') { if (*p == '\\') p++; p++; } + size_t vlen = (size_t)(p - vs); + if (*p == '"') p++; + /* Build "Key: Value" header line */ + size_t line_len = klen + 2 + vlen + 1; + char* line = malloc(line_len); + if (line) { + memcpy(line, ks, klen); + memcpy(line + klen, ": ", 2); + memcpy(line + klen + 2, vs, vlen); + line[klen + 2 + vlen] = '\0'; + h = curl_slist_append(h, line); + free(line); + } + } + return h; +} + +static el_val_t seed_http_error_json(const char* msg) { + if (!msg) msg = "unknown error"; + size_t n = strlen(msg) * 6 + 20; + char* buf = seed_strbuf(n); + /* Simple escape: replace " with \" */ + char* d = buf; + *d++ = '{'; *d++ = '"'; *d++ = 'e'; *d++ = 'r'; *d++ = 'r'; + *d++ = 'o'; *d++ = 'r'; *d++ = '"'; *d++ = ':'; *d++ = '"'; + for (const char* s = msg; *s; s++) { + if (*s == '"' || *s == '\\') *d++ = '\\'; + *d++ = *s; + } + *d++ = '"'; *d++ = '}'; *d = '\0'; + return seed_wrap_str(buf); +} + +el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body, + el_val_t headers_json, el_val_t timeout_ms) { + const char* m = EL_CSTR(method); + const char* u = EL_CSTR(url); + const char* b = EL_CSTR(body); + const char* hj = EL_CSTR(headers_json); + int64_t tms = (int64_t)timeout_ms; + if (tms <= 0) tms = 60000; + + if (!u || !*u) return seed_http_error_json("empty url"); + + CURL* c = curl_easy_init(); + if (!c) return seed_http_error_json("curl_easy_init failed"); + + SeedHttpBuf rb; seed_httpbuf_init(&rb); + char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0'; + + curl_easy_setopt(c, CURLOPT_URL, u); + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, seed_http_write_cb); + curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb); + curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, (long)tms); + curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(c, CURLOPT_USERAGENT, "el-seed/1.0"); + + struct curl_slist* hdrs = seed_headers_from_json(hj); + if (hdrs) curl_easy_setopt(c, CURLOPT_HTTPHEADER, hdrs); + + if (m && strcmp(m, "POST") == 0) { + curl_easy_setopt(c, CURLOPT_POST, 1L); + curl_easy_setopt(c, CURLOPT_POSTFIELDS, b ? b : ""); + curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)(b ? strlen(b) : 0)); + } else if (m && strcmp(m, "PUT") == 0) { + curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "PUT"); + if (b) { + curl_easy_setopt(c, CURLOPT_POSTFIELDS, b); + curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)strlen(b)); + } + } else if (m && strcmp(m, "DELETE") == 0) { + curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "DELETE"); + } else if (m && strcmp(m, "PATCH") == 0) { + curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "PATCH"); + if (b) { + curl_easy_setopt(c, CURLOPT_POSTFIELDS, b); + curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)strlen(b)); + } + } + /* GET is the default */ + + CURLcode rc = curl_easy_perform(c); + if (hdrs) curl_slist_free_all(hdrs); + curl_easy_cleanup(c); + + if (rc != CURLE_OK) { + free(rb.data); + const char* em = errbuf[0] ? errbuf : curl_easy_strerror(rc); + return seed_http_error_json(em); + } + + seed_arena_track(rb.data); + return seed_wrap_str(rb.data); +} + +static size_t seed_http_file_write_cb(char* ptr, size_t size, size_t nmemb, void* ud) { + return fwrite(ptr, size, nmemb, (FILE*)ud); +} + +el_val_t __http_do_to_file(el_val_t method, el_val_t url, el_val_t body, + el_val_t headers_json, el_val_t out_path) { + const char* m = EL_CSTR(method); + const char* u = EL_CSTR(url); + const char* b = EL_CSTR(body); + const char* hj = EL_CSTR(headers_json); + const char* op = EL_CSTR(out_path); + + if (!u || !*u || !op || !*op) return 0; + FILE* f = fopen(op, "wb"); + if (!f) return 0; + + CURL* c = curl_easy_init(); + if (!c) { fclose(f); remove(op); return 0; } + + char errbuf[CURL_ERROR_SIZE]; errbuf[0] = '\0'; + curl_easy_setopt(c, CURLOPT_URL, u); + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, seed_http_file_write_cb); + curl_easy_setopt(c, CURLOPT_WRITEDATA, f); + curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(c, CURLOPT_TIMEOUT_MS, 60000L); + curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(c, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(c, CURLOPT_USERAGENT, "el-seed/1.0"); + + struct curl_slist* hdrs = seed_headers_from_json(hj); + if (hdrs) curl_easy_setopt(c, CURLOPT_HTTPHEADER, hdrs); + + if (m && strcmp(m, "POST") == 0) { + curl_easy_setopt(c, CURLOPT_POST, 1L); + curl_easy_setopt(c, CURLOPT_POSTFIELDS, b ? b : ""); + curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, (long)(b ? strlen(b) : 0)); + } + + CURLcode rc = curl_easy_perform(c); + if (hdrs) curl_slist_free_all(hdrs); + curl_easy_cleanup(c); + + int ok1 = (fflush(f) == 0); + int ok2 = (fclose(f) == 0); + + if (rc != CURLE_OK || !ok1 || !ok2) { remove(op); return 0; } + return 1; +} + +/* ── HTTP server ─────────────────────────────────────────────────────────── */ +/* Delegate to el_runtime.c's http_serve / http_serve_v2 via the existing + * http_set_handler mechanism. */ + +void __http_serve(el_val_t port, el_val_t handler_name) { + http_serve(port, handler_name); +} + +void __http_serve_v2(el_val_t port, el_val_t handler_name) { + http_serve_v2(port, handler_name); +} + +el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body) { + return http_response(status, headers_json, body); +} + +/* ── Threading ───────────────────────────────────────────────────────────── */ +/* + * Design: + * Static ElThread table (max EL_SEED_MAX_THREADS entries). + * __thread_create: pick a free slot, store fn_name + arg, launch pthread. + * Worker: dlsym(RTLD_DEFAULT, fn_name) → call as el_val_t fn(el_val_t). + * Store result string in slot.result. + * __thread_join: pthread_join, return stored result string. + * + * Thread handle is the slot index (0..EL_SEED_MAX_THREADS-1). + * Returns -1 on failure (no slots, dlsym failure, pthread_create failure). + * + * Each slot is guarded by its own mutex so join/create on different handles + * never contend. + */ + +#define EL_SEED_MAX_THREADS 64 + +typedef el_val_t (*ElFn1)(el_val_t); + +typedef struct { + int in_use; + char* fn_name; + char* arg; + char* result; + pthread_t tid; + int done; /* set to 1 by worker before exit */ +} ElThread; + +static ElThread _el_threads[EL_SEED_MAX_THREADS]; +static pthread_mutex_t _el_thread_mu = PTHREAD_MUTEX_INITIALIZER; + +typedef struct { + int slot; +} ElThreadArg; + +static void* el_thread_worker(void* raw) { + ElThreadArg* ta = (ElThreadArg*)raw; + int slot = ta->slot; + free(ta); + + ElThread* t = &_el_threads[slot]; + + /* Resolve the El function symbol in the running binary. */ + void* sym = dlsym(RTLD_DEFAULT, t->fn_name); + if (!sym) { + pthread_mutex_lock(&_el_thread_mu); + t->result = seed_strdup_persist(""); + t->done = 1; + pthread_mutex_unlock(&_el_thread_mu); + return NULL; + } + + ElFn1 fn = (ElFn1)sym; + + /* Call the El function with the string argument. */ + el_val_t arg_val = EL_STR(t->arg); + el_val_t ret = fn(arg_val); + + /* Persist the result string. */ + const char* rs = EL_CSTR(ret); + char* stored = seed_strdup_persist(rs ? rs : ""); + + pthread_mutex_lock(&_el_thread_mu); + t->result = stored; + t->done = 1; + pthread_mutex_unlock(&_el_thread_mu); + + return NULL; +} + +el_val_t __thread_create(el_val_t fn_name, el_val_t arg) { + const char* fname = EL_CSTR(fn_name); + const char* astr = EL_CSTR(arg); + if (!fname || !*fname) return (el_val_t)(int64_t)-1; + if (!astr) astr = ""; + + pthread_mutex_lock(&_el_thread_mu); + int slot = -1; + for (int i = 0; i < EL_SEED_MAX_THREADS; i++) { + if (!_el_threads[i].in_use) { slot = i; break; } + } + if (slot < 0) { + pthread_mutex_unlock(&_el_thread_mu); + return (el_val_t)(int64_t)-1; + } + + ElThread* t = &_el_threads[slot]; + t->in_use = 1; + t->done = 0; + t->fn_name = seed_strdup_persist(fname); + t->arg = seed_strdup_persist(astr); + t->result = NULL; + pthread_mutex_unlock(&_el_thread_mu); + + ElThreadArg* ta = malloc(sizeof(ElThreadArg)); + if (!ta) { + pthread_mutex_lock(&_el_thread_mu); + free(t->fn_name); free(t->arg); + t->in_use = 0; + pthread_mutex_unlock(&_el_thread_mu); + return (el_val_t)(int64_t)-1; + } + ta->slot = slot; + + if (pthread_create(&t->tid, NULL, el_thread_worker, ta) != 0) { + free(ta); + pthread_mutex_lock(&_el_thread_mu); + free(t->fn_name); free(t->arg); + t->in_use = 0; + pthread_mutex_unlock(&_el_thread_mu); + return (el_val_t)(int64_t)-1; + } + + return (el_val_t)(int64_t)slot; +} + +el_val_t __thread_join(el_val_t tid) { + int64_t slot = (int64_t)tid; + if (slot < 0 || slot >= EL_SEED_MAX_THREADS) return seed_wrap_str(seed_strdup("")); + + ElThread* t = &_el_threads[slot]; + + pthread_mutex_lock(&_el_thread_mu); + if (!t->in_use) { + pthread_mutex_unlock(&_el_thread_mu); + return seed_wrap_str(seed_strdup("")); + } + pthread_mutex_unlock(&_el_thread_mu); + + /* Wait for thread to finish. */ + pthread_join(t->tid, NULL); + + pthread_mutex_lock(&_el_thread_mu); + char* res = t->result ? t->result : ""; + char* copy = seed_strdup(res); /* arena-tracked for caller lifetime */ + free(t->fn_name); + free(t->arg); + if (t->result) { free(t->result); t->result = NULL; } + t->fn_name = NULL; t->arg = NULL; + t->in_use = 0; + t->done = 0; + pthread_mutex_unlock(&_el_thread_mu); + + return seed_wrap_str(copy); +} + +/* ── Mutex pool ──────────────────────────────────────────────────────────── */ + +#define EL_SEED_MAX_MUTEXES 256 + +static pthread_mutex_t _el_mutexes[EL_SEED_MAX_MUTEXES]; +static int _el_mutex_init[EL_SEED_MAX_MUTEXES]; +static int _el_mutex_used[EL_SEED_MAX_MUTEXES]; +static pthread_mutex_t _el_mutex_pool_mu = PTHREAD_MUTEX_INITIALIZER; + +el_val_t __mutex_new(void) { + pthread_mutex_lock(&_el_mutex_pool_mu); + int slot = -1; + for (int i = 0; i < EL_SEED_MAX_MUTEXES; i++) { + if (!_el_mutex_used[i]) { slot = i; break; } + } + if (slot < 0) { + pthread_mutex_unlock(&_el_mutex_pool_mu); + return (el_val_t)(int64_t)-1; + } + _el_mutex_used[slot] = 1; + if (!_el_mutex_init[slot]) { + pthread_mutex_init(&_el_mutexes[slot], NULL); + _el_mutex_init[slot] = 1; + } + pthread_mutex_unlock(&_el_mutex_pool_mu); + return (el_val_t)(int64_t)slot; +} + +void __mutex_lock(el_val_t m) { + int64_t slot = (int64_t)m; + if (slot < 0 || slot >= EL_SEED_MAX_MUTEXES) return; + if (!_el_mutex_init[slot]) return; + pthread_mutex_lock(&_el_mutexes[slot]); +} + +void __mutex_unlock(el_val_t m) { + int64_t slot = (int64_t)m; + if (slot < 0 || slot >= EL_SEED_MAX_MUTEXES) return; + if (!_el_mutex_init[slot]) return; + pthread_mutex_unlock(&_el_mutexes[slot]); +} + +/* ── Subprocess ──────────────────────────────────────────────────────────── */ + +el_val_t __exec(el_val_t cmd) { + const char* c = EL_CSTR(cmd); + if (!c) return seed_wrap_str(seed_strdup("")); + FILE* f = popen(c, "r"); + if (!f) return seed_wrap_str(seed_strdup("")); + size_t cap = 4096, len = 0; + char* buf = malloc(cap); + if (!buf) { pclose(f); return seed_wrap_str(seed_strdup("")); } + char tmp[4096]; + while (fgets(tmp, sizeof(tmp), f)) { + size_t n = strlen(tmp); + while (len + n + 1 > cap) { + cap *= 2; + char* g = realloc(buf, cap); + if (!g) { free(buf); pclose(f); return seed_wrap_str(seed_strdup("")); } + buf = g; + } + memcpy(buf + len, tmp, n); + len += n; + buf[len] = '\0'; + } + pclose(f); + seed_arena_track(buf); + return seed_wrap_str(buf); +} + +void __exec_bg(el_val_t cmd) { + const char* c = EL_CSTR(cmd); + if (!c) return; + /* Fork-free: run in background via system() with & appended. */ + size_t n = strlen(c); + char* s = malloc(n + 4); + if (!s) return; + memcpy(s, c, n); + memcpy(s + n, " &", 3); + system(s); + free(s); +} + +/* ── Environment and process ─────────────────────────────────────────────── */ + +el_val_t __env_get(el_val_t key) { + const char* k = EL_CSTR(key); + if (!k) return seed_wrap_str(seed_strdup("")); + const char* v = getenv(k); + return seed_wrap_str(seed_strdup(v ? v : "")); +} + +void __exit_program(el_val_t code) { + exit((int)(int64_t)code); +} + +/* ── args_json ────────────────────────────────────────────────────────────── */ + +static int _seed_argc = 0; +static char** _seed_argv = NULL; + +void el_seed_init_args(int argc, char** argv) { + _seed_argc = argc; + _seed_argv = argv; +} + +el_val_t __args_json(void) { + /* Return ["arg1","arg2",...] as a JSON string. Skip argv[0] (program name). */ + size_t cap = 256, len = 0; + char* buf = malloc(cap); + if (!buf) return seed_wrap_str(seed_strdup("[]")); + buf[len++] = '['; + int first = 1; + for (int i = 1; i < _seed_argc; i++) { + const char* a = _seed_argv[i]; + /* Estimate: each arg needs at most strlen*6+4 bytes (worst case escape) */ + size_t need = strlen(a) * 6 + 8; + while (len + need + 2 > cap) { + cap *= 2; + char* g = realloc(buf, cap); + if (!g) { free(buf); return seed_wrap_str(seed_strdup("[]")); } + buf = g; + } + if (!first) buf[len++] = ','; + first = 0; + buf[len++] = '"'; + for (const char* p = a; *p; p++) { + unsigned char c = (unsigned char)*p; + if (c == '"') { buf[len++] = '\\'; buf[len++] = '"'; } + else if (c == '\\') { buf[len++] = '\\'; buf[len++] = '\\'; } + else if (c == '\n') { buf[len++] = '\\'; buf[len++] = 'n'; } + else if (c == '\r') { buf[len++] = '\\'; buf[len++] = 'r'; } + else if (c == '\t') { buf[len++] = '\\'; buf[len++] = 't'; } + else buf[len++] = (char)c; + } + buf[len++] = '"'; + } + buf[len++] = ']'; + buf[len] = '\0'; + seed_arena_track(buf); + return seed_wrap_str(buf); +} + +/* ── Time ────────────────────────────────────────────────────────────────── */ + +el_val_t __time_now_ns(void) { + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + int64_t ns = (int64_t)ts.tv_sec * 1000000000LL + (int64_t)ts.tv_nsec; + return (el_val_t)ns; + } + struct timeval tv; + gettimeofday(&tv, NULL); + return (el_val_t)((int64_t)tv.tv_sec * 1000000000LL + (int64_t)tv.tv_usec * 1000LL); +} + +void __sleep_ms(el_val_t ms) { + int64_t m = (int64_t)ms; + if (m < 0) m = 0; + struct timespec ts; + ts.tv_sec = (time_t)(m / 1000LL); + ts.tv_nsec = (long)((m % 1000LL) * 1000000LL); + nanosleep(&ts, NULL); +} + +/* ── UUID ────────────────────────────────────────────────────────────────── */ + +static int _seed_uuid_seeded = 0; + +el_val_t __uuid_v4(void) { + if (!_seed_uuid_seeded) { + srand((unsigned)time(NULL) ^ (unsigned)(uintptr_t)&_seed_uuid_seeded); + _seed_uuid_seeded = 1; + } + unsigned char b[16]; + for (int i = 0; i < 16; i++) b[i] = (unsigned char)(rand() & 0xff); + b[6] = (b[6] & 0x0f) | 0x40; /* version 4 */ + b[8] = (b[8] & 0x3f) | 0x80; /* RFC 4122 variant */ + 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 seed_wrap_str(seed_strdup(buf)); +} + +/* ── Math ────────────────────────────────────────────────────────────────── */ + +el_val_t __sqrt_f(el_val_t f) { return el_from_float(sqrt(el_to_float(f))); } +el_val_t __log_f(el_val_t f) { return el_from_float(log10(el_to_float(f))); } +el_val_t __ln_f(el_val_t f) { return el_from_float(log(el_to_float(f))); } +el_val_t __sin_f(el_val_t f) { return el_from_float(sin(el_to_float(f))); } +el_val_t __cos_f(el_val_t f) { return el_from_float(cos(el_to_float(f))); } +el_val_t __pi_f(void) { return el_from_float(3.14159265358979323846); } + +/* ── JSON — thin wrappers around el_runtime.c implementations ────────────── */ + +el_val_t __json_get(el_val_t json, el_val_t key) { return json_get(json, key); } +el_val_t __json_get_raw(el_val_t json_str, el_val_t key) { return json_get_raw(json_str, key); } +el_val_t __json_parse(el_val_t s) { return json_parse(s); } +el_val_t __json_stringify(el_val_t v) { return json_stringify(v); } +el_val_t __json_array_len(el_val_t json_str) { return json_array_len(json_str); } +el_val_t __json_array_get(el_val_t json_str, el_val_t index) { return json_array_get(json_str, index); } +el_val_t __json_array_get_string(el_val_t json_str, el_val_t index) { return json_array_get_string(json_str, index); } +el_val_t __json_get_string(el_val_t json_str, el_val_t key) { return json_get_string(json_str, key); } +el_val_t __json_get_int(el_val_t json_str, el_val_t key) { return json_get_int(json_str, key); } +el_val_t __json_get_float(el_val_t json_str, el_val_t key) { return json_get_float(json_str, key); } +el_val_t __json_get_bool(el_val_t json_str, el_val_t key) { return json_get_bool(json_str, key); } +el_val_t __json_set(el_val_t json_str, el_val_t key, el_val_t value) { return json_set(json_str, key, value); } + +/* ── State K/V — thin wrappers ───────────────────────────────────────────── */ + +el_val_t __state_set(el_val_t key, el_val_t value) { return state_set(key, value); } +el_val_t __state_get(el_val_t key) { return state_get(key); } +el_val_t __state_del(el_val_t key) { return state_del(key); } +el_val_t __state_keys(void) { return state_keys(); } + +/* ── HTML/URL — thin wrappers ────────────────────────────────────────────── */ + +el_val_t __html_sanitize(el_val_t input_html, el_val_t allowlist_json) { + return el_html_sanitize(input_html, allowlist_json); +} + +el_val_t __url_encode(el_val_t s) { return url_encode(s); } +el_val_t __url_decode(el_val_t s) { return url_decode(s); } + +/* ── Engram — thin wrappers ──────────────────────────────────────────────── */ + +el_val_t __engram_node(el_val_t content, el_val_t node_type, el_val_t salience) { + return engram_node(content, node_type, salience); +} + +el_val_t __engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, + el_val_t salience, el_val_t importance, el_val_t confidence, + el_val_t tier, el_val_t tags) { + return engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags); +} + +el_val_t __engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label, + el_val_t salience, el_val_t certainty, el_val_t confidence, + el_val_t status, el_val_t tags, el_val_t layer_id) { + return engram_node_layered(content, node_type, label, salience, certainty, confidence, + status, tags, layer_id); +} + +el_val_t __engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible, + el_val_t transparent, el_val_t injectable) { + return engram_add_layer(name, priority, suppressible, transparent, injectable); +} + +el_val_t __engram_remove_layer(el_val_t layer_id) { return engram_remove_layer(layer_id); } +el_val_t __engram_list_layers(void) { return engram_list_layers(); } +el_val_t __engram_get_node(el_val_t id) { return engram_get_node(id); } +void __engram_strengthen(el_val_t node_id) { engram_strengthen(node_id); } +void __engram_forget(el_val_t node_id) { engram_forget(node_id); } +el_val_t __engram_node_count(void) { return engram_node_count(); } + +el_val_t __engram_search(el_val_t query, el_val_t limit) { return engram_search(query, limit); } +el_val_t __engram_scan_nodes(el_val_t limit, el_val_t offset) { return engram_scan_nodes(limit, offset); } + +void __engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation) { + engram_connect(from_id, to_id, weight, relation); +} + +el_val_t __engram_edge_between(el_val_t from_id, el_val_t to_id) { + return engram_edge_between(from_id, to_id); +} + +el_val_t __engram_neighbors(el_val_t node_id) { return engram_neighbors(node_id); } + +el_val_t __engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction) { + return engram_neighbors_filtered(node_id, max_depth, direction); +} + +el_val_t __engram_edge_count(void) { return engram_edge_count(); } + +el_val_t __engram_activate(el_val_t query, el_val_t depth) { return engram_activate(query, depth); } +el_val_t __engram_save(el_val_t path) { return engram_save(path); } +el_val_t __engram_load(el_val_t path) { return engram_load(path); } + +el_val_t __engram_get_node_json(el_val_t id) { return engram_get_node_json(id); } + +el_val_t __engram_search_json(el_val_t query, el_val_t limit) { + return engram_search_json(query, limit); +} + +el_val_t __engram_scan_nodes_json(el_val_t limit, el_val_t offset) { + return engram_scan_nodes_json(limit, offset); +} + +el_val_t __engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset) { + return engram_scan_nodes_by_type_json(node_type, limit, offset); +} + +el_val_t __engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction) { + return engram_neighbors_json(node_id, max_depth, direction); +} + +el_val_t __engram_activate_json(el_val_t query, el_val_t depth) { + return engram_activate_json(query, depth); +} + +el_val_t __engram_stats_json(void) { return engram_stats_json(); } +el_val_t __engram_list_layers_json(void) { return engram_list_layers_json(); } + +el_val_t __engram_compile_layered_json(el_val_t intent, el_val_t depth) { + return engram_compile_layered_json(intent, depth); +} diff --git a/el-compiler/runtime/el_seed.h b/el-compiler/runtime/el_seed.h new file mode 100644 index 0000000..20f01a4 --- /dev/null +++ b/el-compiler/runtime/el_seed.h @@ -0,0 +1,201 @@ +/* + * el_seed.h — El language seed runtime header + * + * Declares all OS-boundary primitives available to new-generation El programs. + * All functions use the __ prefix convention. Signatures use el_val_t (= int64_t) + * as the universal value type, consistent with el_runtime.h. + * + * This file is the clean OS boundary for the El runtime migration. All heavy + * interpreter state, legacy naming, and implicit global coupling lives in + * el_runtime.c. el_seed.c exposes only the minimal primitives that compiled + * El programs need to call into the OS. + * + * Link requirements (same as el_runtime.c): + * -lcurl — HTTP client (__http_do, __http_do_to_file) + * -lpthread — threading (__thread_create, __thread_join, __mutex_new, ...) + * + * Canonical compile: + * cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \ + * -o .c el-compiler/runtime/el_seed.c el-compiler/runtime/el_runtime.c + */ + +#pragma once + +#include "el_runtime.h" /* el_val_t, EL_STR, EL_CSTR, EL_INT, EL_NULL, el_to_float, el_from_float */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ── String primitives ───────────────────────────────────────────────────── */ + +el_val_t __str_len(el_val_t s); +el_val_t __str_char_at(el_val_t s, el_val_t i); /* returns Int (byte value) */ +el_val_t __str_alloc(el_val_t n); /* malloc(n+1), zero-init, return String */ +el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c); /* s[i]=c, return s */ +el_val_t __str_cmp(el_val_t a, el_val_t b); /* strcmp result as Int */ +el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n); /* strncmp */ +el_val_t __str_concat_raw(el_val_t a, el_val_t b); /* malloc+strcpy concat */ +el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end); /* substring copy */ +el_val_t __int_to_str(el_val_t n); +el_val_t __str_to_int(el_val_t s); +el_val_t __float_to_str(el_val_t f); /* f is bit-cast double */ +el_val_t __str_to_float(el_val_t s); /* strtod, bit-cast result */ + +/* ── I/O ─────────────────────────────────────────────────────────────────── */ + +void __println(el_val_t s); +void __print(el_val_t s); +el_val_t __readline(void); + +/* ── Filesystem ──────────────────────────────────────────────────────────── */ + +el_val_t __fs_read(el_val_t path); +el_val_t __fs_write(el_val_t path, el_val_t content); +el_val_t __fs_exists(el_val_t path); +el_val_t __fs_list_raw(el_val_t path); /* newline-separated filenames */ +el_val_t __fs_mkdir(el_val_t path); +el_val_t __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n); + +/* ── HTTP client ─────────────────────────────────────────────────────────── */ + +/* Unified HTTP call. headers_json is a JSON object of header name->value pairs + * (e.g. {"Authorization":"Bearer ...","Content-Type":"application/json"}). + * Use "" or "{}" for no extra headers. timeout_ms <= 0 uses the default. */ +el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body, + el_val_t headers_json, el_val_t timeout_ms); + +/* Stream response body directly to a file. Returns 1 on success, 0 on failure. */ +el_val_t __http_do_to_file(el_val_t method, el_val_t url, el_val_t body, + el_val_t headers_json, el_val_t out_path); + +/* ── HTTP server ─────────────────────────────────────────────────────────── */ + +/* Blocking HTTP server. handler_name is the El function name to dispatch to. + * v1 handler: (method, path, body) -> String + * v2 handler: (method, path, headers_map, body) -> String or envelope */ +void __http_serve(el_val_t port, el_val_t handler_name); +void __http_serve_v2(el_val_t port, el_val_t handler_name); + +/* Build a structured HTTP response envelope. + * headers_json: JSON object literal like {"Content-Type":"text/plain"} or "{}" */ +el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body); + +/* ── Threading ───────────────────────────────────────────────────────────── */ + +/* Create a thread that calls the named El function with a String argument. + * fn_name is resolved via dlsym(RTLD_DEFAULT, fn_name). Returns a thread + * handle Int that can be passed to __thread_join. Returns -1 on failure. */ +el_val_t __thread_create(el_val_t fn_name, el_val_t arg); + +/* Wait for thread tid (returned by __thread_create) to finish. + * Returns the thread's return value as a String. */ +el_val_t __thread_join(el_val_t tid); + +/* Allocate a new mutex. Returns a handle Int (index into internal table). */ +el_val_t __mutex_new(void); + +void __mutex_lock(el_val_t m); +void __mutex_unlock(el_val_t m); + +/* ── Subprocess ──────────────────────────────────────────────────────────── */ + +el_val_t __exec(el_val_t cmd); /* popen, capture all stdout, return String */ +void __exec_bg(el_val_t cmd); /* fire and forget */ + +/* ── Environment and process ─────────────────────────────────────────────── */ + +el_val_t __env_get(el_val_t key); /* getenv, return "" if not set */ +void __exit_program(el_val_t code); +el_val_t __args_json(void); /* CLI args as JSON array string */ + +/* ── Time ────────────────────────────────────────────────────────────────── */ + +el_val_t __time_now_ns(void); /* clock_gettime REALTIME, nanoseconds */ +void __sleep_ms(el_val_t ms); + +/* ── UUID ────────────────────────────────────────────────────────────────── */ + +el_val_t __uuid_v4(void); + +/* ── Math ────────────────────────────────────────────────────────────────── */ + +el_val_t __sqrt_f(el_val_t f); +el_val_t __log_f(el_val_t f); +el_val_t __ln_f(el_val_t f); +el_val_t __sin_f(el_val_t f); +el_val_t __cos_f(el_val_t f); +el_val_t __pi_f(void); + +/* ── JSON ────────────────────────────────────────────────────────────────── */ + +el_val_t __json_get(el_val_t json, el_val_t key); +el_val_t __json_get_raw(el_val_t json_str, el_val_t key); +el_val_t __json_parse(el_val_t s); +el_val_t __json_stringify(el_val_t v); +el_val_t __json_array_len(el_val_t json_str); +el_val_t __json_array_get(el_val_t json_str, el_val_t index); +el_val_t __json_array_get_string(el_val_t json_str, el_val_t index); +el_val_t __json_get_string(el_val_t json_str, el_val_t key); +el_val_t __json_get_int(el_val_t json_str, el_val_t key); +el_val_t __json_get_float(el_val_t json_str, el_val_t key); +el_val_t __json_get_bool(el_val_t json_str, el_val_t key); +el_val_t __json_set(el_val_t json_str, el_val_t key, el_val_t value); + +/* ── State K/V ───────────────────────────────────────────────────────────── */ + +el_val_t __state_set(el_val_t key, el_val_t value); +el_val_t __state_get(el_val_t key); +el_val_t __state_del(el_val_t key); +el_val_t __state_keys(void); + +/* ── HTML/URL ────────────────────────────────────────────────────────────── */ + +el_val_t __html_sanitize(el_val_t input_html, el_val_t allowlist_json); +el_val_t __url_encode(el_val_t s); +el_val_t __url_decode(el_val_t s); + +/* ── Engram ──────────────────────────────────────────────────────────────── */ + +el_val_t __engram_node(el_val_t content, el_val_t node_type, el_val_t salience); +el_val_t __engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, + el_val_t salience, el_val_t importance, el_val_t confidence, + el_val_t tier, el_val_t tags); +el_val_t __engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label, + el_val_t salience, el_val_t certainty, el_val_t confidence, + el_val_t status, el_val_t tags, el_val_t layer_id); +el_val_t __engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible, + el_val_t transparent, el_val_t injectable); +el_val_t __engram_remove_layer(el_val_t layer_id); +el_val_t __engram_list_layers(void); +el_val_t __engram_get_node(el_val_t id); +void __engram_strengthen(el_val_t node_id); +void __engram_forget(el_val_t node_id); +el_val_t __engram_node_count(void); +el_val_t __engram_search(el_val_t query, el_val_t limit); +el_val_t __engram_scan_nodes(el_val_t limit, el_val_t offset); +void __engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation); +el_val_t __engram_edge_between(el_val_t from_id, el_val_t to_id); +el_val_t __engram_neighbors(el_val_t node_id); +el_val_t __engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction); +el_val_t __engram_edge_count(void); +el_val_t __engram_activate(el_val_t query, el_val_t depth); +el_val_t __engram_save(el_val_t path); +el_val_t __engram_load(el_val_t path); +el_val_t __engram_get_node_json(el_val_t id); +el_val_t __engram_search_json(el_val_t query, el_val_t limit); +el_val_t __engram_scan_nodes_json(el_val_t limit, el_val_t offset); +el_val_t __engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset); +el_val_t __engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction); +el_val_t __engram_activate_json(el_val_t query, el_val_t depth); +el_val_t __engram_stats_json(void); +el_val_t __engram_list_layers_json(void); +el_val_t __engram_compile_layered_json(el_val_t intent, el_val_t depth); + +/* ── args init (called from main) ────────────────────────────────────────── */ +/* Store argc/argv for __args_json. Call once at the start of main(). */ +void el_seed_init_args(int argc, char** argv); + +#ifdef __cplusplus +} +#endif