/* * el_runtime_win32.c — Minimal Win32-compatible el runtime stub. * * Provides only the symbols that native-hello (and the el-native vessel) call * from el_runtime.h. Replaces the full el_runtime.c + el_seed.c for * cross-compiled Win32 targets where POSIX headers (pthread, curl, dlfcn, * sys/socket, etc.) are unavailable. * * Symbols provided: * Allocators: el_strdup, el_strbuf, el_wrap_str (static helpers) * I/O: println, print * String: el_str_concat, str_eq, str_starts_with, str_len, str_concat, * int_to_str, str_to_int, native_str_to_int, str_slice, str_trim, * str_contains, str_ends_with, str_to_upper, str_to_lower, * str_index_of, str_char_at, str_char_code, str_replace, * str_pad_left, str_pad_right, str_upper, str_lower * Math: el_abs, el_max, el_min, int_to_float, float_to_int * List: el_list_empty, el_list_new, el_list_len, el_list_get, * el_list_append, el_list_clone * Map: el_map_new, el_map_get, el_get_field, el_map_set * Retain/RC: el_retain, el_release * Arena: el_arena_push, el_arena_pop * JSON: json_get_string, json_get_int, json_get_float, json_get_bool, * json_get_raw, json_find_key (static), json_skip_value (static) * Process: exit_program, env, el_runtime_init_args * Stubs: All remaining el_runtime.h symbols as no-op / return-0 stubs * so the linker is satisfied even if the app doesn't call them. * * el_request_start / el_request_end are provided as empty no-ops (no HTTP * server, no per-request arena needed for a native GUI app). * * Compile: * x86_64-w64-mingw32-gcc -std=c11 -DEL_TARGET_WIN32 -I \ * -c el_runtime_win32.c -o el_runtime_win32.o */ #include "el_runtime.h" #include #include #include #include #include #include #include /* ── Arena (single-threaded; no pthread needed) ─────────────────────────── */ #define EL_ARENA_INITIAL 512 typedef struct { char** ptrs; size_t count; size_t cap; } ElArena; static ElArena _arena = {NULL, 0, 0}; static int _arena_active = 0; static void el_arena_track(char* p) { if (!_arena_active || !p) return; if (_arena.count >= _arena.cap) { size_t nc = _arena.cap == 0 ? EL_ARENA_INITIAL : _arena.cap * 2; char** g = realloc(_arena.ptrs, nc * sizeof(char*)); if (!g) return; _arena.ptrs = g; _arena.cap = nc; } _arena.ptrs[_arena.count++] = p; } void el_request_start(void) { _arena.count = 0; _arena_active = 1; } void el_request_end(void) { _arena_active = 0; for (size_t i = 0; i < _arena.count; i++) free(_arena.ptrs[i]); _arena.count = 0; } #define EL_ARENA_SCOPE_DEPTH 32 static size_t _arena_scope[EL_ARENA_SCOPE_DEPTH]; static int _arena_scope_depth = 0; el_val_t el_arena_push(void) { if (!_arena_active) _arena_active = 1; if (_arena_scope_depth < EL_ARENA_SCOPE_DEPTH) _arena_scope[_arena_scope_depth++] = _arena.count; return (el_val_t)(int64_t)_arena.count; } el_val_t el_arena_pop(el_val_t mark) { size_t save = (size_t)(int64_t)mark; if (save > _arena.count) save = 0; for (size_t i = save; i < _arena.count; i++) { if (_arena.ptrs[i]) { free(_arena.ptrs[i]); _arena.ptrs[i] = NULL; } } _arena.count = save; if (_arena_scope_depth > 0) _arena_scope_depth--; if (save == 0) _arena_active = 0; return 0; } /* ── Allocator helpers ───────────────────────────────────────────────────── */ static char* el_strdup(const char* s) { if (!s) { char* p = strdup(""); el_arena_track(p); return p; } char* p = strdup(s); el_arena_track(p); return p; } static char* el_strbuf(size_t n) { char* p = malloc(n + 1); if (!p) { fputs("el_runtime_win32: out of memory\n", stderr); exit(1); } p[0] = '\0'; el_arena_track(p); return p; } static el_val_t el_wrap_str(char* s) { return EL_STR(s); } /* ── I/O ─────────────────────────────────────────────────────────────────── */ el_val_t println(el_val_t s) { const char* str = EL_CSTR(s); puts(str ? str : ""); return 0; } el_val_t print(el_val_t s) { const char* str = EL_CSTR(s); if (str) fputs(str, stdout); return 0; } 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), 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* p = EL_CSTR(prefv); if (!s || !p) return 0; return (el_val_t)(strncmp(s, p, strlen(p)) == 0); } el_val_t str_ends_with(el_val_t sv, el_val_t sufv) { const char* s = EL_CSTR(sv); const char* suf = EL_CSTR(sufv); if (!s || !suf) return 0; size_t ls = strlen(s), lsuf = strlen(suf); if (lsuf > ls) return 0; return (el_val_t)(strcmp(s + ls - lsuf, suf) == 0); } el_val_t str_len(el_val_t sv) { const char* s = EL_CSTR(sv); return s ? (el_val_t)strlen(s) : 0; } 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 native_str_to_int(el_val_t sv) { return str_to_int(sv); } 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), lf = strlen(from), lt = strlen(to); if (lf == 0) return el_wrap_str(el_strdup(s)); /* Count occurrences */ size_t count = 0; const char* p = s; while ((p = strstr(p, from)) != NULL) { count++; p += lf; } size_t new_len = ls + count * (lt > lf ? lt - lf : 0) - count * (lf > lt ? lf - lt : 0); char* out = el_strbuf(new_len); const char* src = s; char* dst = out; while ((p = strstr(src, from)) != NULL) { size_t pre = (size_t)(p - src); memcpy(dst, src, pre); dst += pre; memcpy(dst, to, lt); dst += lt; src = p + lf; } strcpy(dst, src); 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_upper(el_val_t sv) { return str_to_upper(sv); } el_val_t str_lower(el_val_t sv) { return str_to_lower(sv); } 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); } el_val_t str_index_of(el_val_t sv, el_val_t subv) { const char* s = EL_CSTR(sv); const char* sub = EL_CSTR(subv); if (!s || !sub) return -1; const char* p = strstr(s, sub); return p ? (el_val_t)(p - s) : -1; } el_val_t str_char_at(el_val_t sv, el_val_t i) { const char* s = EL_CSTR(sv); if (!s) return 0; int64_t idx = (int64_t)i; int64_t len = (int64_t)strlen(s); if (idx < 0 || idx >= len) return 0; char buf[2] = { s[idx], '\0' }; return el_wrap_str(el_strdup(buf)); } el_val_t str_char_code(el_val_t sv, el_val_t i) { const char* s = EL_CSTR(sv); if (!s) return 0; int64_t idx = (int64_t)i; int64_t len = (int64_t)strlen(s); if (idx < 0 || idx >= len) return 0; return (el_val_t)(unsigned char)s[idx]; } el_val_t str_pad_left(el_val_t sv, el_val_t widthv, el_val_t padv) { const char* s = EL_CSTR(sv); const char* pad = EL_CSTR(padv); if (!s) s = ""; if (!pad || !*pad) pad = " "; int64_t width = (int64_t)widthv; int64_t slen = (int64_t)strlen(s); if (slen >= width) return el_wrap_str(el_strdup(s)); int64_t need = width - slen; char* out = el_strbuf((size_t)width); size_t plen = strlen(pad); for (int64_t i = 0; i < need; i++) out[i] = pad[i % plen]; memcpy(out + need, s, (size_t)slen); out[width] = '\0'; return el_wrap_str(out); } el_val_t str_pad_right(el_val_t sv, el_val_t widthv, el_val_t padv) { const char* s = EL_CSTR(sv); const char* pad = EL_CSTR(padv); if (!s) s = ""; if (!pad || !*pad) pad = " "; int64_t width = (int64_t)widthv; int64_t slen = (int64_t)strlen(s); if (slen >= width) return el_wrap_str(el_strdup(s)); int64_t need = width - slen; char* out = el_strbuf((size_t)width); memcpy(out, s, (size_t)slen); size_t plen = strlen(pad); for (int64_t i = 0; i < need; i++) out[slen + i] = pad[i % plen]; out[width] = '\0'; return el_wrap_str(out); } el_val_t str_format(el_val_t fmtv, el_val_t datav) { /* Minimal stub — just return the format string unchanged. */ return fmtv; } el_val_t str_split(el_val_t sv, el_val_t sepv) { /* Return empty list stub. */ return el_list_empty(); } el_val_t str_count(el_val_t sv, el_val_t subv) { const char* s = EL_CSTR(sv); const char* sub = EL_CSTR(subv); if (!s || !sub || !*sub) return 0; size_t lsub = strlen(sub); int64_t cnt = 0; const char* p = s; while ((p = strstr(p, sub)) != NULL) { cnt++; p += lsub; } return (el_val_t)cnt; } /* ── 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; } 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 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 str_to_float(el_val_t sv) { const char* s = EL_CSTR(sv); if (!s) return 0; return el_from_float(strtod(s, NULL)); } el_val_t format_float(el_val_t f, el_val_t decimals) { char buf[128]; int d = (int)(int64_t)decimals; if (d < 0) d = 0; if (d > 30) d = 30; 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) { return format_float(f, decimals); } 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(log10(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.14159265358979323846); } /* ── Refcounted heap objects ─────────────────────────────────────────────── */ #define EL_MAGIC_LIST 0xE15710A1u #define EL_MAGIC_MAP 0xE19A704Bu typedef struct { uint32_t magic; uint32_t refcount; } ElHeader; typedef struct { ElHeader hdr; int64_t length; int64_t capacity; el_val_t* elems; } ElList; typedef struct { ElHeader hdr; int64_t count; int64_t capacity; el_val_t* keys; el_val_t* values; } ElMap; static int looks_like_heap_obj(el_val_t v) { if (v == 0) return 0; int64_t s = (int64_t)v; if (s > -0x10000 && s < 0x10000) return 0; uintptr_t p = (uintptr_t)v; if (p < 0x10000) return 0; if (p & 0x7) return 0; return 1; } void el_retain(el_val_t v) { if (!looks_like_heap_obj(v)) return; ElHeader* h = (ElHeader*)(uintptr_t)v; if (h->magic == EL_MAGIC_LIST || h->magic == EL_MAGIC_MAP) h->refcount++; } void el_release(el_val_t v) { if (!looks_like_heap_obj(v)) return; ElHeader* h = (ElHeader*)(uintptr_t)v; if (h->magic == EL_MAGIC_LIST) { if (h->refcount > 0 && --h->refcount == 0) { ElList* l = (ElList*)h; free(l->elems); h->magic = 0; free(l); } } else if (h->magic == EL_MAGIC_MAP) { if (h->refcount > 0 && --h->refcount == 0) { ElMap* m = (ElMap*)h; free(m->keys); free(m->values); h->magic = 0; free(m); } } } /* ── List ────────────────────────────────────────────────────────────────── */ static ElList* list_alloc(int64_t cap) { if (cap < 4) cap = 4; ElList* lst = malloc(sizeof(ElList)); if (!lst) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); } lst->hdr.magic = EL_MAGIC_LIST; lst->hdr.refcount = 1; lst->length = 0; lst->capacity = cap; lst->elems = malloc((size_t)cap * sizeof(el_val_t)); if (!lst->elems) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); } 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 lv) { ElList* l = (ElList*)(uintptr_t)lv; return l ? l->length : 0; } el_val_t el_list_get(el_val_t lv, el_val_t idx) { ElList* l = (ElList*)(uintptr_t)lv; if (!l || idx < 0 || idx >= l->length) return 0; return l->elems[idx]; } el_val_t el_list_append(el_val_t lv, el_val_t elem) { ElList* old = (ElList*)(uintptr_t)lv; if (!old) { ElList* fresh = list_alloc(4); fresh->elems[0] = elem; fresh->length = 1; return EL_STR(fresh); } if (old->hdr.refcount <= 1) { if (old->length >= old->capacity) { int64_t nc = old->capacity > 0 ? old->capacity * 2 : 4; el_val_t* g = realloc(old->elems, (size_t)nc * sizeof(el_val_t)); if (!g) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); } old->elems = g; old->capacity = nc; } old->elems[old->length++] = elem; return lv; } int64_t nc = old->length + 1; if (nc < 4) nc = 4; ElList* fresh = malloc(sizeof(ElList)); if (!fresh) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); } fresh->hdr.magic = EL_MAGIC_LIST; fresh->hdr.refcount = 1; fresh->length = old->length + 1; fresh->capacity = nc; fresh->elems = malloc((size_t)nc * sizeof(el_val_t)); if (!fresh->elems) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); } if (old->length > 0) memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t)); fresh->elems[old->length] = elem; return EL_STR(fresh); } el_val_t el_list_clone(el_val_t lv) { ElList* old = (ElList*)(uintptr_t)lv; if (!old) return el_list_empty(); ElList* fresh = list_alloc(old->capacity > 0 ? old->capacity : 4); if (old->length > 0) memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t)); fresh->length = old->length; return EL_STR(fresh); } /* ── Map ─────────────────────────────────────────────────────────────────── */ static ElMap* map_alloc(int64_t cap) { if (cap < 4) cap = 4; ElMap* m = malloc(sizeof(ElMap)); if (!m) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); } m->hdr.magic = EL_MAGIC_MAP; m->hdr.refcount = 1; m->count = 0; m->capacity = cap; m->keys = malloc((size_t)cap * sizeof(el_val_t)); m->values = malloc((size_t)cap * sizeof(el_val_t)); if (!m->keys || !m->values) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); } return m; } el_val_t el_map_new(el_val_t pair_count, ...) { ElMap* m = map_alloc(pair_count > 0 ? pair_count : 4); 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); m->count = pair_count; return EL_STR(m); } el_val_t el_map_get(el_val_t mv, el_val_t kv) { ElMap* m = (ElMap*)(uintptr_t)mv; const char* key = EL_CSTR(kv); 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 mv, el_val_t kv) { return el_map_get(mv, kv); } el_val_t el_map_set(el_val_t mv, el_val_t kv, el_val_t value) { ElMap* m = (ElMap*)(uintptr_t)mv; if (!m) { ElMap* fresh = map_alloc(4); fresh->keys[0] = kv; fresh->values[0] = value; fresh->count = 1; return EL_STR(fresh); } const char* key = EL_CSTR(kv); if (key) { 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 mv; } } } if (m->count >= m->capacity) { int64_t nc = m->capacity > 0 ? m->capacity * 2 : 4; el_val_t* gk = realloc(m->keys, (size_t)nc * sizeof(el_val_t)); el_val_t* gv = realloc(m->values, (size_t)nc * sizeof(el_val_t)); if (!gk || !gv) { fputs("el_runtime_win32: OOM\n", stderr); exit(1); } m->keys = gk; m->values = gv; m->capacity = nc; } m->keys[m->count] = kv; m->values[m->count] = value; m->count++; return mv; } /* ── JSON helpers ────────────────────────────────────────────────────────── */ 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 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_win32: OOM\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': 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_win32: OOM\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 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, close = (open == '{') ? '}' : ']'; int depth = 0, in_str = 0, 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) return p; continue; } p++; } return p; } while (*p && *p != ',' && *p != '}' && *p != ']' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') p++; return p; } 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, in_str = 0, 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 == '"') { p++; in_str = 0; continue; } p++; continue; } if (c == '"') { 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 (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; } 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("")); JsonParser jp = { p, json + (json ? strlen(json) : 0), 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 || *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 || *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; return (el_val_t)(strncmp(p, "true", 4) == 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_get(el_val_t json, el_val_t key) { return json_get_raw(json, key); } /* Simple JSON number / scalar parser helpers for json_parse / json_stringify */ el_val_t json_parse(el_val_t s) { /* Minimal: return the string verbatim (caller accesses via json_get_*). */ return s; } el_val_t json_stringify(el_val_t v) { /* Basic: if it already looks like a string, return it. */ const char* s = EL_CSTR(v); return s ? v : EL_STR("{}"); } el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value) { /* Stub: returns the original JSON unchanged. */ return json_str; } el_val_t json_array_len(el_val_t json_str) { const char* s = EL_CSTR(json_str); if (!s || *s != '[') return 0; int64_t cnt = 0; int depth = 0, in_str = 0, e = 0; const char* p = s; 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 == '[' || c == '{') { if (depth++ == 0 && c == '[') { p++; continue; } } else if (c == ']' || c == '}') { if (--depth == 0) break; } else if (c == ',' && depth == 1) cnt++; p++; } /* If we saw any content, count = separators + 1 */ if (depth == 0) { /* empty or done */ } /* Simpler: count commas at depth 1 + 1 if non-empty */ /* Re-scan for simplicity */ cnt = 0; depth = 0; in_str = 0; e = 0; p = s; int has_content = 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 == '[' || c == '{') depth++; else if (c == ']' || c == '}') { depth--; if (depth == 0) break; } if (depth == 1) { if (c != '[' && c != ' ' && c != '\t' && c != '\n' && c != '\r') has_content = 1; if (c == ',') cnt++; } p++; } return has_content ? cnt + 1 : 0; } el_val_t json_array_get(el_val_t json_str, el_val_t index) { const char* s = EL_CSTR(json_str); if (!s || *s != '[') return 0; int64_t target = (int64_t)index; int64_t cur = 0; int depth = 0, in_str = 0, e = 0; const char* p = s; 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; } if (c == '[' || c == '{') depth++; else if (c == ']' || c == '}') { if (--depth == 0) break; } if (depth == 1 && (p == s + 1 || c == ',')) { if (c == ',') { cur++; p++; while (*p == ' ' || *p == '\t' || *p == '\n') p++; } else { p++; while (*p == ' ' || *p == '\t' || *p == '\n') p++; } if (cur == target) { 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); } continue; } p++; } return 0; } el_val_t json_array_get_string(el_val_t json_str, el_val_t index) { el_val_t raw = json_array_get(json_str, index); if (!raw) return el_wrap_str(el_strdup("")); const char* s = EL_CSTR(raw); if (!s || *s != '"') return raw; JsonParser jp = { s, s + strlen(s), 0 }; char* parsed = jp_parse_string_raw(&jp); return el_wrap_str(parsed); } el_val_t json_escape_string(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 * 6 + 4); char* p = out; *p++ = '"'; for (size_t i = 0; i < n; i++) { unsigned char c = (unsigned char)s[i]; switch (c) { case '"': *p++ = '\\'; *p++ = '"'; break; case '\\': *p++ = '\\'; *p++ = '\\'; break; case '\n': *p++ = '\\'; *p++ = 'n'; break; case '\r': *p++ = '\\'; *p++ = 'r'; break; case '\t': *p++ = '\\'; *p++ = 't'; break; default: if (c < 0x20) { p += sprintf(p, "\\u%04x", c); } else { *p++ = (char)c; } } } *p++ = '"'; *p = '\0'; return el_wrap_str(out); } el_val_t json_build_object(el_val_t kvs) { return kvs; } el_val_t json_build_array(el_val_t items) { return items; } /* ── Environment / process ───────────────────────────────────────────────── */ 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 : "")); } el_val_t exit_program(el_val_t code) { exit((int)(int64_t)code); return 0; } /* Args store — populated by el_runtime_init_args */ 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; } /* ── HTML helpers (stubs) ────────────────────────────────────────────────── */ el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json) { return input_html; } el_val_t html_raw(el_val_t s) { return s; } el_val_t html_escape(el_val_t s) { return s; } el_val_t url_encode(el_val_t s) { return s; } el_val_t url_decode(el_val_t s) { return s; } /* ── Filesystem stubs ────────────────────────────────────────────────────── */ el_val_t fs_read(el_val_t path) { const char* p = EL_CSTR(path); if (!p) return el_wrap_str(el_strdup("")); FILE* f = fopen(p, "rb"); if (!f) return el_wrap_str(el_strdup("")); fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f); if (sz <= 0) { fclose(f); return el_wrap_str(el_strdup("")); } char* buf = malloc((size_t)sz + 1); if (!buf) { fclose(f); return el_wrap_str(el_strdup("")); } size_t got = fread(buf, 1, (size_t)sz, f); buf[got] = '\0'; fclose(f); el_arena_track(buf); return EL_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; fputs(c, f); fclose(f); return 1; } el_val_t fs_exists(el_val_t path) { const char* p = EL_CSTR(path); if (!p) return 0; FILE* f = fopen(p, "rb"); if (!f) return 0; fclose(f); return 1; } el_val_t fs_list(el_val_t path) { return el_wrap_str(el_strdup("")); } el_val_t fs_list_json(el_val_t path) { return el_wrap_str(el_strdup("[]")); } el_val_t fs_mkdir(el_val_t path) { return 0; } el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n) { return 0; } /* ── HTTP stubs (no network in Win32 GUI app) ────────────────────────────── */ el_val_t http_get(el_val_t url) { return el_wrap_str(el_strdup("{\"error\":\"no http\"}")); } el_val_t http_post(el_val_t url, el_val_t body) { return http_get(url); } el_val_t http_post_json(el_val_t url, el_val_t body) { return http_get(url); } el_val_t http_get_with_headers(el_val_t url, el_val_t h) { return http_get(url); } el_val_t http_post_with_headers(el_val_t url, el_val_t b, el_val_t h) { return http_get(url); } el_val_t http_post_json_with_headers(el_val_t url, el_val_t h, el_val_t b) { return http_get(url); } el_val_t http_post_form_auth(el_val_t url, el_val_t b, el_val_t a) { return http_get(url); } el_val_t http_delete(el_val_t url) { return http_get(url); } el_val_t http_serve(el_val_t port, el_val_t handler) { return 0; } el_val_t http_set_handler(el_val_t name) { return 0; } el_val_t http_serve_v2(el_val_t port, el_val_t handler) { return 0; } el_val_t http_set_handler_v2(el_val_t name) { return 0; } el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) { return body; } void el_seed_set_http_conn_fd(int fd) { (void)fd; } el_val_t http_post_to_file(el_val_t url, el_val_t b, el_val_t h, el_val_t out) { return 0; } el_val_t http_get_to_file(el_val_t url, el_val_t h, el_val_t out) { return 0; } /* ── Time stubs ──────────────────────────────────────────────────────────── */ #include el_val_t time_now(void) { return (el_val_t)time(NULL); } el_val_t time_now_utc(void) { return time_now(); } el_val_t now_ns(void) { return (el_val_t)(time(NULL) * (int64_t)1000000000LL); } el_val_t el_now_instant(void) { return now_ns(); } el_val_t now(void) { return el_now_instant(); } el_val_t unix_seconds(el_val_t n) { return n * 1000000000LL; } el_val_t unix_millis(el_val_t n) { return n * 1000000LL; } el_val_t unix_timestamp(void) { return (el_val_t)time(NULL); } el_val_t sleep_secs(el_val_t secs) { return 0; } el_val_t sleep_ms(el_val_t ms) { return 0; } el_val_t el_sleep_duration(el_val_t dur) { return 0; } el_val_t time_format(el_val_t ts, el_val_t fmt) { return el_wrap_str(el_strdup("")); } el_val_t time_to_parts(el_val_t ts) { return el_wrap_str(el_strdup("{}")); } el_val_t time_from_parts(el_val_t s, el_val_t ns, el_val_t tz) { return s; } el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit) { return ts; } el_val_t time_diff(el_val_t a, el_val_t b, el_val_t unit) { return a - b; } el_val_t instant_from_iso8601(el_val_t s) { return 0; } el_val_t el_duration_from_nanos(el_val_t ns) { return ns; } el_val_t duration_seconds(el_val_t n) { return n * 1000000000LL; } el_val_t duration_millis(el_val_t n) { return n * 1000000LL; } el_val_t duration_nanos(el_val_t n) { return n; } el_val_t el_instant_add_dur(el_val_t i, el_val_t d) { return i + d; } el_val_t el_instant_sub_dur(el_val_t i, el_val_t d) { return i - d; } el_val_t el_instant_diff(el_val_t a, el_val_t b) { return a - b; } el_val_t el_duration_add(el_val_t a, el_val_t b) { return a + b; } el_val_t el_duration_sub(el_val_t a, el_val_t b) { return a - b; } el_val_t el_duration_scale(el_val_t d, el_val_t s) { return d * s; } el_val_t el_duration_div(el_val_t d, el_val_t s) { return s ? d / s : 0; } el_val_t el_instant_lt(el_val_t a, el_val_t b) { return a < b; } el_val_t el_instant_le(el_val_t a, el_val_t b) { return a <= b; } el_val_t el_instant_gt(el_val_t a, el_val_t b) { return a > b; } el_val_t el_instant_ge(el_val_t a, el_val_t b) { return a >= b; } el_val_t el_instant_eq(el_val_t a, el_val_t b) { return a == b; } el_val_t el_instant_ne(el_val_t a, el_val_t b) { return a != b; } el_val_t el_duration_lt(el_val_t a, el_val_t b) { return a < b; } el_val_t el_duration_le(el_val_t a, el_val_t b) { return a <= b; } el_val_t el_duration_gt(el_val_t a, el_val_t b) { return a > b; } el_val_t el_duration_ge(el_val_t a, el_val_t b) { return a >= b; } el_val_t el_duration_eq(el_val_t a, el_val_t b) { return a == b; } el_val_t el_duration_ne(el_val_t a, el_val_t b) { return a != b; } el_val_t instant_to_unix_seconds(el_val_t i) { return i / 1000000000LL; } el_val_t instant_to_unix_millis(el_val_t i) { return i / 1000000LL; } el_val_t instant_to_iso8601(el_val_t i) { return el_wrap_str(el_strdup("")); } el_val_t duration_to_seconds(el_val_t d) { return d / 1000000000LL; } el_val_t duration_to_millis(el_val_t d) { return d / 1000000LL; } el_val_t duration_to_nanos(el_val_t d) { return d; } /* ── Calendar stubs ──────────────────────────────────────────────────────── */ el_val_t zone(el_val_t id) { return id; } el_val_t zone_utc(void) { return EL_STR("UTC"); } el_val_t zone_local(void) { return EL_STR("local"); } el_val_t zone_offset(el_val_t h, el_val_t m) { return 0; } el_val_t earth_calendar(el_val_t z) { return 0; } el_val_t earth_calendar_default(void) { return 0; } el_val_t mars_calendar(void) { return 0; } el_val_t cycle_calendar(el_val_t p) { return 0; } el_val_t no_cycle_calendar(void) { return 0; } el_val_t relative_calendar(el_val_t e) { return 0; } el_val_t now_in(el_val_t cal) { return 0; } el_val_t in_calendar(el_val_t inst, el_val_t cal) { return 0; } el_val_t cal_format(el_val_t ct, el_val_t pattern) { return el_wrap_str(el_strdup("")); } el_val_t cal_to_instant(el_val_t ct) { return 0; } el_val_t cal_cycle_phase(el_val_t ct) { return 0; } el_val_t cal_in(el_val_t ct, el_val_t cal) { return 0; } el_val_t local_date(el_val_t y, el_val_t m, el_val_t d) { return 0; } el_val_t local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns) { return 0; } el_val_t local_datetime(el_val_t date, el_val_t time) { return 0; } el_val_t zoned(el_val_t date, el_val_t time, el_val_t cal) { return 0; } el_val_t local_date_year(el_val_t ld) { return 0; } el_val_t local_date_month(el_val_t ld) { return 0; } el_val_t local_date_day(el_val_t ld) { return 0; } el_val_t local_time_hour(el_val_t lt) { return 0; } el_val_t local_time_minute(el_val_t lt) { return 0; } el_val_t local_time_second(el_val_t lt) { return 0; } el_val_t local_time_nanos(el_val_t lt) { return 0; } el_val_t el_local_date_add_dur(el_val_t ld, el_val_t dur) { return ld; } el_val_t el_local_time_add_dur(el_val_t lt, el_val_t dur) { return lt; } el_val_t el_local_date_lt(el_val_t a, el_val_t b) { return a < b; } el_val_t el_local_date_eq(el_val_t a, el_val_t b) { return a == b; } el_val_t rhythm_cycle_start(void) { return 0; } el_val_t rhythm_cycle_phase(el_val_t p) { return 0; } el_val_t rhythm_duration(el_val_t d) { return 0; } el_val_t rhythm_session_start(void) { return 0; } el_val_t rhythm_event(el_val_t name) { return 0; } el_val_t rhythm_and(el_val_t a, el_val_t b) { return 0; } el_val_t rhythm_or(el_val_t a, el_val_t b) { return 0; } el_val_t rhythm_weekday(el_val_t day) { return 0; } el_val_t rhythm_weekly_at(el_val_t day, el_val_t h, el_val_t m) { return 0; } el_val_t rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal) { return 0; } el_val_t rhythm_matches(el_val_t r, el_val_t ct) { return 0; } /* ── UUID stubs ──────────────────────────────────────────────────────────── */ el_val_t uuid_new(void) { return el_wrap_str(el_strdup("00000000-0000-0000-0000-000000000000")); } el_val_t uuid_v4(void) { return uuid_new(); } /* ── State K/V stubs ─────────────────────────────────────────────────────── */ el_val_t state_set(el_val_t key, el_val_t value) { return value; } el_val_t state_get(el_val_t key) { return 0; } el_val_t state_del(el_val_t key) { return 0; } el_val_t state_keys(void) { return el_wrap_str(el_strdup("[]")); } el_val_t state_has(el_val_t key) { return 0; } el_val_t state_get_or(el_val_t key, el_val_t def) { return def; } /* ── TTL cache stubs ─────────────────────────────────────────────────────── */ el_val_t ttl_cache_set(el_val_t key, el_val_t value) { return value; } el_val_t ttl_cache_get(el_val_t key, el_val_t max_age) { return 0; } el_val_t ttl_cache_age(el_val_t key) { return -1; }