Files
el/lang/el-compiler/runtime/el_runtime_win32.c

1147 lines
42 KiB
C

/*
* 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<runtime> \
* -c el_runtime_win32.c -o el_runtime_win32.o
*/
#include "el_runtime.h"
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
/* ── 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 <time.h>
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; }