254cbe0ac2
El SDK CI - dev / build-and-test (pull_request) Successful in 3m22s
The El compiler generates calls to __-prefixed C primitives from within El stdlib compiled code (e.g. __println, __str_len, __json_get, etc). These were absent from el_runtime.c, causing linker failures when building el-install, elb, or epm with the current compiler. Add 46 __-prefixed aliases/implementations in el_runtime.c covering: - I/O: __println, __print, __readline - String: __str_len, __str_cmp, __str_ncmp, __str_alloc, __str_set_char, __str_concat_raw, __str_slice_raw, __str_char_at, plus numeric converters - FS: __fs_read, __fs_write, __fs_exists, __fs_mkdir, __fs_list_raw, etc - HTTP: __http_do, __http_do_map, __http_serve, __http_serve_v2, __http_response, __http_sse_* (weak stubs) - JSON: __json_get, __json_set, __json_parse_map, __json_stringify_val, etc - State, env, exec, uuid, sha256, args
1208 lines
44 KiB
C
1208 lines
44 KiB
C
/*
|
|
* 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 <out> <prog>.c el_seed.c
|
|
*/
|
|
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include "el_seed.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <dlfcn.h>
|
|
#include <curl/curl.h>
|
|
|
|
/* ── 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: <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: <s>\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_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; i<len; i++) {
|
|
ctx->data[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);
|
|
}
|