/* * el_seed.c — El language seed runtime: minimal C OS boundary * * This file exposes all OS-boundary primitives that El programs need, under * the __ prefix convention. It is self-contained: all allocators, arena * management, and el_request_start / el_request_end are defined here. * * Threading: __thread_create / __thread_join use dlsym(RTLD_DEFAULT) to look * up El function symbols at runtime. This is the foundation of El's parallelism. * * Link: cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \ * -o .c el_seed.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 /* ── Private allocator ───────────────────────────────────────────────────── */ /* * el_seed.c carries its own arena for per-request allocation tracking. * The arena is reset at el_request_start / el_request_end, which are defined * below and delegate to seed_request_start / seed_request_end. */ #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; } /* el_request_start / el_request_end — formerly defined in el_runtime.c. * Now self-contained in el_seed.c, delegating to the seed arena. */ void el_request_start(void) { seed_request_start(); } void el_request_end(void) { seed_request_end(); } /* 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); } /* ── HTTP SSE — Server-Sent Events streaming ─────────────────────────────── */ /* * Thread-local file descriptor stashed by http_worker_v2 before calling the * El handler. El SSE builtins read this to get the raw socket fd. * * Lifecycle: * http_worker_v2 sets _tl_http_conn_fd = fd (via el_seed_set_http_conn_fd) * El handler calls __http_conn_fd() → receives that fd * El handler calls __http_sse_open(fd) → sends SSE headers, keeps fd open * El handler calls __http_sse_send(fd, data) → writes "data: ...\n\n" * El handler calls __http_sse_close(fd) → closes the fd * El handler returns "__sse__" sentinel → http_worker_v2 does NOT close fd * * The -1 value means no current connection (guard against misuse outside * a handler context). */ static __thread int _tl_http_conn_fd = -1; /* Called by el_runtime.c's http_worker_v2 — not part of the El ABI. */ void el_seed_set_http_conn_fd(int fd) { _tl_http_conn_fd = fd; } /* __http_conn_fd() — returns the raw fd for the current HTTP connection. * Valid only inside an http_serve_v2 handler before it returns. */ el_val_t __http_conn_fd(void) { return EL_INT(_tl_http_conn_fd); } /* __http_sse_open(fd) — sends SSE response headers on fd, keeping it open. * Returns 1 on success, 0 on write failure. */ el_val_t __http_sse_open(el_val_t conn_id) { int fd = (int)(int64_t)conn_id; if (fd < 0) return 0; static const char sse_headers[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/event-stream\r\n" "Cache-Control: no-cache\r\n" "Connection: keep-alive\r\n" "Access-Control-Allow-Origin: *\r\n" "\r\n"; size_t n = sizeof(sse_headers) - 1; /* exclude NUL */ size_t sent = 0; while (sent < n) { ssize_t w = write(fd, sse_headers + sent, n - sent); if (w <= 0) return 0; sent += (size_t)w; } return 1; } /* __http_sse_send(fd, data) — writes one SSE event frame: "data: \n\n". * data must not contain newlines. Returns 1 on success, 0 on client disconnect. */ el_val_t __http_sse_send(el_val_t conn_id, el_val_t data) { int fd = (int)(int64_t)conn_id; if (fd < 0) return 0; const char* s = EL_CSTR(data); if (!s) s = ""; /* Build "data: \n\n" in a single buffer for one write call. */ size_t prefix_len = 6; /* "data: " */ size_t slen = strlen(s); size_t total = prefix_len + slen + 2; /* + "\n\n" */ char* buf = malloc(total + 1); if (!buf) return 0; memcpy(buf, "data: ", 6); memcpy(buf + 6, s, slen); buf[6 + slen] = '\n'; buf[6 + slen + 1] = '\n'; buf[total] = '\0'; size_t sent = 0; int ok = 1; while (sent < total) { ssize_t w = write(fd, buf + sent, total - sent); if (w <= 0) { ok = 0; break; } sent += (size_t)w; } free(buf); return ok ? 1 : 0; } /* __http_sse_close(fd) — closes the SSE connection fd. */ el_val_t __http_sse_close(el_val_t conn_id) { int fd = (int)(int64_t)conn_id; if (fd < 0) return 0; close(fd); return 1; } /* ── 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_parse_map(el_val_t json_str) { return json_parse(json_str); } el_val_t __json_stringify_val(el_val_t val) { return json_stringify(val); } 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_get_node_by_label(el_val_t label) { return engram_get_node_by_label(label); } 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); } /* ── Cryptographic hashing ────────────────────────────────────────────────── */ /* * SHA-256 — self-contained implementation (no OpenSSL dependency). * Based on Brad Conte's public-domain reference implementation. */ typedef struct { uint8_t data[64]; uint32_t datalen; uint64_t bitlen; uint32_t state[8]; } _seed_sha256_ctx; static const uint32_t _seed_sha256_k[64] = { 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 }; #define _SEED_ROTR32(x,n) (((x)>>(n))|((x)<<(32-(n)))) #define _SEED_CH(x,y,z) (((x)&(y))^(~(x)&(z))) #define _SEED_MAJ(x,y,z) (((x)&(y))^((x)&(z))^((y)&(z))) #define _SEED_EP0(x) (_SEED_ROTR32(x,2)^_SEED_ROTR32(x,13)^_SEED_ROTR32(x,22)) #define _SEED_EP1(x) (_SEED_ROTR32(x,6)^_SEED_ROTR32(x,11)^_SEED_ROTR32(x,25)) #define _SEED_SIG0(x) (_SEED_ROTR32(x,7)^_SEED_ROTR32(x,18)^((x)>>3)) #define _SEED_SIG1(x) (_SEED_ROTR32(x,17)^_SEED_ROTR32(x,19)^((x)>>10)) static void _seed_sha256_transform(_seed_sha256_ctx* ctx, const uint8_t* data) { uint32_t a,b,c,d,e,f,g,h,t1,t2,m[64]; for (int i=0,j=0; i<16; i++,j+=4) m[i]=(uint32_t)(data[j]<<24)|(data[j+1]<<16)|(data[j+2]<<8)|data[j+3]; for (int i=16; i<64; i++) m[i]=_SEED_SIG1(m[i-2])+m[i-7]+_SEED_SIG0(m[i-15])+m[i-16]; a=ctx->state[0]; b=ctx->state[1]; c=ctx->state[2]; d=ctx->state[3]; e=ctx->state[4]; f=ctx->state[5]; g=ctx->state[6]; h=ctx->state[7]; for (int i=0; i<64; i++) { t1=h+_SEED_EP1(e)+_SEED_CH(e,f,g)+_seed_sha256_k[i]+m[i]; t2=_SEED_EP0(a)+_SEED_MAJ(a,b,c); h=g; g=f; f=e; e=d+t1; d=c; c=b; b=a; a=t1+t2; } ctx->state[0]+=a; ctx->state[1]+=b; ctx->state[2]+=c; ctx->state[3]+=d; ctx->state[4]+=e; ctx->state[5]+=f; ctx->state[6]+=g; ctx->state[7]+=h; } static void _seed_sha256_init(_seed_sha256_ctx* ctx) { ctx->datalen=0; ctx->bitlen=0; ctx->state[0]=0x6a09e667; ctx->state[1]=0xbb67ae85; ctx->state[2]=0x3c6ef372; ctx->state[3]=0xa54ff53a; ctx->state[4]=0x510e527f; ctx->state[5]=0x9b05688c; ctx->state[6]=0x1f83d9ab; ctx->state[7]=0x5be0cd19; } static void _seed_sha256_update(_seed_sha256_ctx* ctx, const uint8_t* data, size_t len) { for (size_t i=0; idata[ctx->datalen++] = data[i]; if (ctx->datalen==64) { _seed_sha256_transform(ctx,ctx->data); ctx->bitlen+=512; ctx->datalen=0; } } } static void _seed_sha256_final(_seed_sha256_ctx* ctx, uint8_t hash[32]) { uint32_t i=ctx->datalen; ctx->data[i++]=0x80; if (ctx->datalen<56) { while(i<56) ctx->data[i++]=0; } else { while(i<64) ctx->data[i++]=0; _seed_sha256_transform(ctx,ctx->data); memset(ctx->data,0,56); } ctx->bitlen+=ctx->datalen*8; ctx->data[63]=(uint8_t)(ctx->bitlen); ctx->data[62]=(uint8_t)(ctx->bitlen>>8); ctx->data[61]=(uint8_t)(ctx->bitlen>>16); ctx->data[60]=(uint8_t)(ctx->bitlen>>24); ctx->data[59]=(uint8_t)(ctx->bitlen>>32); ctx->data[58]=(uint8_t)(ctx->bitlen>>40); ctx->data[57]=(uint8_t)(ctx->bitlen>>48); ctx->data[56]=(uint8_t)(ctx->bitlen>>56); _seed_sha256_transform(ctx,ctx->data); for (i=0; i<4; i++) { hash[i] =(uint8_t)(ctx->state[0]>>(24-i*8)); hash[i+4] =(uint8_t)(ctx->state[1]>>(24-i*8)); hash[i+8] =(uint8_t)(ctx->state[2]>>(24-i*8)); hash[i+12] =(uint8_t)(ctx->state[3]>>(24-i*8)); hash[i+16] =(uint8_t)(ctx->state[4]>>(24-i*8)); hash[i+20] =(uint8_t)(ctx->state[5]>>(24-i*8)); hash[i+24] =(uint8_t)(ctx->state[6]>>(24-i*8)); hash[i+28] =(uint8_t)(ctx->state[7]>>(24-i*8)); } } el_val_t __sha256_hex(el_val_t sv) { const char* s = EL_CSTR(sv); if (!s) s = ""; _seed_sha256_ctx ctx; _seed_sha256_init(&ctx); _seed_sha256_update(&ctx, (const uint8_t*)s, strlen(s)); uint8_t digest[32]; _seed_sha256_final(&ctx, digest); static const char hex[] = "0123456789abcdef"; char* out = malloc(65); if (!out) return EL_STR(""); for (int i=0; i<32; i++) { out[i*2] = hex[(digest[i]>>4)&0xf]; out[i*2+1] = hex[digest[i]&0xf]; } out[64] = '\0'; return EL_STR(out); }