/* * el_runtime.h — El language C runtime header * * Declares all built-in functions available to compiled El programs. * Include this in every generated .c file. * * Value model: * All El values are represented as el_val_t (= int64_t). * On 64-bit systems a pointer fits in int64_t. * String values are cast: (el_val_t)(uintptr_t)"hello" * Integer values are stored directly. * This lets arithmetic work naturally while still passing strings around. * * Type conventions (El -> C): * String -> el_val_t (holds const char* via uintptr_t cast) * Int -> el_val_t * Bool -> el_val_t (0 = false, nonzero = true) * Any -> el_val_t * Void -> void * * Macros for convenience: * EL_STR(s) cast string literal to el_val_t * EL_CSTR(v) cast el_val_t back to const char* * EL_INT(v) identity — el_val_t is already int64_t * * Link requirements: * -lcurl — required for the HTTP client (http_get, http_post, llm_*). * -lpthread — required for the HTTP server (one detached thread per * connection, capped at 64 concurrent). * -loqs — optional; required only when liboqs is installed and the * pq_* / sha3_256_hex entry points are needed. Detected at * compile time via __has_include(). * -lcrypto — optional; pulled in alongside -loqs. Used for X25519 in * pq_hybrid_* and HKDF-SHA256 derivation. * * Canonical compile command: * cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \ * -o .c el-compiler/runtime/el_runtime.c * * With liboqs (post-quantum stack): * cc -std=c11 -I el-compiler/runtime -lcurl -lpthread -loqs -lcrypto \ * -o .c el-compiler/runtime/el_runtime.c */ #pragma once #include #include typedef int64_t el_val_t; #define EL_STR(s) ((el_val_t)(uintptr_t)(s)) #define EL_CSTR(v) ((const char*)(uintptr_t)(v)) #define EL_INT(v) (v) #define EL_NULL ((el_val_t)0) /* Float values share the el_val_t (int64) slot via a bit-cast. * The codegen emits Float literals as `el_from_float()` so the * underlying bits represent the IEEE 754 double. Float-aware builtins * (math, format, json) round-trip via these helpers. */ static inline double el_to_float(el_val_t v) { union { int64_t i; double f; } u; u.i = (int64_t)v; return u.f; } static inline el_val_t el_from_float(double f) { union { double f; int64_t i; } u; u.f = f; return (el_val_t)u.i; } #ifdef __cplusplus extern "C" { #endif /* ── I/O ──────────────────────────────────────────────────────────────────── */ void println(el_val_t s); void print(el_val_t s); el_val_t readline(void); /* ── String builtins ─────────────────────────────────────────────────────── */ el_val_t el_str_concat(el_val_t a, el_val_t b); el_val_t str_eq(el_val_t a, el_val_t b); el_val_t str_starts_with(el_val_t s, el_val_t prefix); el_val_t str_ends_with(el_val_t s, el_val_t suffix); el_val_t str_len(el_val_t s); el_val_t str_concat(el_val_t a, el_val_t b); el_val_t int_to_str(el_val_t n); el_val_t str_to_int(el_val_t s); el_val_t str_slice(el_val_t s, el_val_t start, el_val_t end); el_val_t str_contains(el_val_t s, el_val_t sub); el_val_t str_replace(el_val_t s, el_val_t from, el_val_t to); el_val_t str_to_upper(el_val_t s); el_val_t str_to_lower(el_val_t s); el_val_t str_trim(el_val_t s); /* ── Math ────────────────────────────────────────────────────────────────── */ el_val_t el_abs(el_val_t n); el_val_t el_max(el_val_t a, el_val_t b); el_val_t el_min(el_val_t a, el_val_t b); /* ── Refcount (ARC) ────────────────────────────────────────────────────────── * Lists and Maps carry a refcount. Strings and ints do not — el_retain and * el_release are safe no-ops on non-refcounted values (they sniff a magic * header at offset 0 and only act if the magic matches). * * Codegen emits these at let-binding shadowing, function entry (params), and * function exit (locals other than the returned value). The refcount lets * el_list_append and el_map_set mutate in place when uniquely owned (cheap) * and copy-on-write when shared (preserves persistent semantics across * accumulator patterns in the compiler itself). */ void el_retain(el_val_t v); void el_release(el_val_t v); /* ── List ────────────────────────────────────────────────────────────────── */ el_val_t el_list_new(el_val_t count, ...); el_val_t el_list_len(el_val_t list); el_val_t el_list_get(el_val_t list, el_val_t index); el_val_t el_list_append(el_val_t list, el_val_t elem); el_val_t el_list_empty(void); el_val_t el_list_clone(el_val_t list); /* ── Map ─────────────────────────────────────────────────────────────────── */ el_val_t el_map_new(el_val_t pair_count, ...); el_val_t el_get_field(el_val_t map, el_val_t key); el_val_t el_map_get(el_val_t map, el_val_t key); el_val_t el_map_set(el_val_t map, el_val_t key, el_val_t value); /* ── HTTP ─────────────────────────────────────────────────────────────────── */ el_val_t http_get(el_val_t url); el_val_t http_post(el_val_t url, el_val_t body); el_val_t http_post_json(el_val_t url, el_val_t json_body); el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map); el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map); el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header); el_val_t http_delete(el_val_t url); void http_serve(el_val_t port, el_val_t handler); void http_set_handler(el_val_t name); /* HTTP server v2 ───────────────────────────────────────────────────────────── * Same dispatch model as http_serve, but the handler signature is widened: * * el_val_t handler(method, path, headers_map, body) * * `headers_map` is an ElMap from lowercased header name → header value (both * Strings). Repeated headers are joined with ", " per RFC 7230. * * Response value: the handler may return either * (a) a plain body string — same auto-content-type / 200-OK behaviour as * http_serve (3-arg) — or * (b) a response envelope built with `http_response(status, headers_json, * body)`. The runtime detects the envelope discriminator * `"el_http_response":1` at the start of the returned string and * unpacks status / headers / body before sending. * * The 3-arg http_serve(port, handler) remains supported unchanged for * existing handlers (e.g. products/web/server.el): it dispatches with * (method, path, body), hardcodes 200 OK, and auto-detects content type. */ void http_serve_v2(el_val_t port, el_val_t handler); void http_set_handler_v2(el_val_t name); /* Build an HTTP response envelope. `headers_json` should be a JSON object * literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The * returned string carries the discriminator `{"el_http_response":1,...}` * which the runtime's send-path detects and unpacks. Detection happens * uniformly inside http_send_response, so a 3-arg handler may also return * an envelope. The 3-arg variant remains documented as a fixed 200-OK * auto-content-type contract for legacy handlers that return plain bodies. */ el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body); /* HTTP timeout — every libcurl request honors EL_HTTP_TIMEOUT_MS (default * 60000ms). Read lazily on first use, so setting the env var any time before * the first http_* call is sufficient. */ /* Streaming variants — write the response body straight to a file via * libcurl's CURLOPT_WRITEFUNCTION = fwrite. These bypass the el_val_t string * wrapper entirely, so binary payloads (audio/mpeg, image/png, etc.) survive * embedded NUL bytes that would truncate a strlen()-based code path. * * Both honor EL_HTTP_TIMEOUT_MS, follow redirects, and accept the same * `headers_map` shape as http_post_with_headers (ElMap of String→String). * * Return value: 1 on success (file fully written), 0 on any failure * (network, file open, partial write). On failure the output file is removed * so callers cannot mistake a partially-written file for a valid one. */ el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el_val_t output_path); el_val_t http_get_to_file(el_val_t url, el_val_t headers_map, el_val_t output_path); /* ── URL encoding ────────────────────────────────────────────────────────── */ el_val_t url_encode(el_val_t s); /* RFC 3986 unreserved set */ el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */ /* ── Filesystem ──────────────────────────────────────────────────────────── */ el_val_t fs_read(el_val_t path); el_val_t fs_write(el_val_t path, el_val_t content); el_val_t fs_list(el_val_t path); el_val_t fs_exists(el_val_t path); el_val_t fs_mkdir(el_val_t path); /* mkdir -p, mode 0755 */ /* Length-explicit binary write. `length` is an Int (el_val_t holding the * byte count). The caller knows the length from context — typically because * `bytes` came from base64_decode (which produces a magic-tagged binary * buffer with embedded NULs possible) and the caller already tracks the * decoded length, OR because the bytes came from a fixed-size source * (sha256_bytes = 32, hmac_sha256_bytes = 32). Bypasses strlen entirely. * * Returns 1 on success, 0 on failure (invalid path, can't open, partial * write, negative length). On partial-write failure, the file is removed * so callers cannot read back a truncated artefact. */ el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t length); /* ── JSON ────────────────────────────────────────────────────────────────── */ el_val_t json_get(el_val_t json, el_val_t key); el_val_t json_parse(el_val_t s); el_val_t json_stringify(el_val_t v); el_val_t json_get_string(el_val_t json_str, el_val_t key); el_val_t json_get_int(el_val_t json_str, el_val_t key); el_val_t json_get_float(el_val_t json_str, el_val_t key); el_val_t json_get_bool(el_val_t json_str, el_val_t key); el_val_t json_get_raw(el_val_t json_str, el_val_t key); el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value); el_val_t json_array_len(el_val_t json_str); el_val_t json_array_get(el_val_t json_str, el_val_t index); el_val_t json_array_get_string(el_val_t json_str, el_val_t index); /* ── Time ────────────────────────────────────────────────────────────────── */ el_val_t time_now(void); el_val_t time_now_utc(void); el_val_t sleep_secs(el_val_t secs); el_val_t sleep_ms(el_val_t ms); el_val_t time_format(el_val_t ts, el_val_t fmt); el_val_t time_to_parts(el_val_t ts); el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz); el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit); el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit); /* ── UUID ────────────────────────────────────────────────────────────────── */ el_val_t uuid_new(void); el_val_t uuid_v4(void); /* ── Environment ─────────────────────────────────────────────────────────── */ el_val_t env(el_val_t key); /* ── In-process state K/V ────────────────────────────────────────────────── */ el_val_t state_set(el_val_t key, el_val_t value); el_val_t state_get(el_val_t key); el_val_t state_del(el_val_t key); el_val_t state_keys(void); /* ── Float formatting ────────────────────────────────────────────────────── */ el_val_t float_to_str(el_val_t f); el_val_t int_to_float(el_val_t n); el_val_t float_to_int(el_val_t f); el_val_t format_float(el_val_t f, el_val_t decimals); el_val_t decimal_round(el_val_t f, el_val_t decimals); el_val_t str_to_float(el_val_t s); /* ── Math (Float-aware) ──────────────────────────────────────────────────── */ el_val_t math_sqrt(el_val_t f); el_val_t math_log(el_val_t f); el_val_t math_ln(el_val_t f); el_val_t math_sin(el_val_t f); el_val_t math_cos(el_val_t f); el_val_t math_pi(void); /* ── String additions ────────────────────────────────────────────────────── */ el_val_t str_index_of(el_val_t s, el_val_t sub); el_val_t str_split(el_val_t s, el_val_t sep); el_val_t str_char_at(el_val_t s, el_val_t i); el_val_t str_char_code(el_val_t s, el_val_t i); el_val_t str_pad_left(el_val_t s, el_val_t width, el_val_t pad); el_val_t str_pad_right(el_val_t s, el_val_t width, el_val_t pad); el_val_t str_format(el_val_t template, el_val_t data); el_val_t str_lower(el_val_t s); el_val_t str_upper(el_val_t s); /* ── List additions ──────────────────────────────────────────────────────── */ el_val_t list_push(el_val_t list, el_val_t elem); el_val_t list_push_front(el_val_t list, el_val_t elem); el_val_t list_join(el_val_t list, el_val_t sep); el_val_t list_range(el_val_t start, el_val_t end); /* ── Bool helpers ────────────────────────────────────────────────────────── */ el_val_t bool_to_str(el_val_t b); /* ── Numeric parsing ─────────────────────────────────────────────────────── */ el_val_t parse_int(el_val_t s, el_val_t default_val); /* ── Process ─────────────────────────────────────────────────────────────── */ void exit_program(el_val_t code); el_val_t getpid_now(void); /* ── CGI identity ───────────────────────────────────────────────────────────── * Called at the start of main() in CGI programs (those with a `cgi {}` block). * Records the program's DHARMA identity before any other code executes. */ void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal, el_val_t network, el_val_t engram); /* ── DHARMA network builtins ───────────────────────────────────────────────── * Available to CGI programs (declared with a `cgi {}` block). * * Peers are addressed by `dharma_id` of the form * "@" e.g. "ntn-genesis@http://localhost:7770" * If the @ portion is omitted, transport defaults to * "http://localhost:7770" (the local CGI daemon assumption). * * Wire protocol (all peers expose): * POST /dharma/recv { channel, from, content } → response body * POST /dharma/event { type, payload, source, timestamp } * POST /api/activate { query } → list of nodes * * Hosting application's responsibility: an El program with a `cgi {}` block * runs http_serve() with its own request handler; that handler should route * "/dharma/event" requests by calling el_runtime_dharma_event_arrive() so * incoming events feed dharma_field() queues. The runtime itself does not * intercept any /dharma path. */ el_val_t dharma_connect(el_val_t cgi_id); el_val_t dharma_send(el_val_t channel, el_val_t content); el_val_t dharma_activate(el_val_t query); void dharma_emit(el_val_t event_type, el_val_t payload); el_val_t dharma_field(el_val_t event_type); void dharma_strengthen(el_val_t cgi_id, el_val_t weight); el_val_t dharma_relationship(el_val_t cgi_id); el_val_t dharma_peers(void); /* Public C API: called by an El program's HTTP handler when a /dharma/event * request arrives. Pushes onto the per-event-type queue and signals any * pending dharma_field() blockers. All three arguments must be NUL-terminated * C strings (or NULL — then treated as empty). */ void el_runtime_dharma_event_arrive(const char* event_type, const char* payload, const char* source); /* ── Engram local graph primitives ─────────────────────────────────────────── * Operate on the CGI's local Engram knowledge graph. * `engram_activate` queries the local graph only; `dharma_activate` is * network-wide across all connected CGI graphs. */ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience); el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label, el_val_t salience, el_val_t importance, el_val_t confidence, el_val_t tier, el_val_t tags); el_val_t engram_get_node(el_val_t id); void engram_strengthen(el_val_t node_id); void engram_forget(el_val_t node_id); el_val_t engram_node_count(void); el_val_t engram_search(el_val_t query, el_val_t limit); el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset); void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation); el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id); el_val_t engram_neighbors(el_val_t node_id); el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction); el_val_t engram_edge_count(void); el_val_t engram_activate(el_val_t query, el_val_t depth); el_val_t engram_save(el_val_t path); el_val_t engram_load(el_val_t path); /* JSON-string accessors — return pre-serialized JSON so HTTP handlers * can pass results straight through without round-tripping ElList/ElMap * through json_stringify. */ el_val_t engram_get_node_json(el_val_t id); el_val_t engram_search_json(el_val_t query, el_val_t limit); el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset); el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction); el_val_t engram_activate_json(el_val_t query, el_val_t depth); el_val_t engram_stats_json(void); /* ── LLM (Anthropic API client) ───────────────────────────────────────────── * All functions call https://api.anthropic.com/v1/messages with the API key * from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */ el_val_t llm_call(el_val_t model, el_val_t prompt); el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt); el_val_t llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools); el_val_t llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64); el_val_t llm_models(void); /* Register a tool handler by name. The handler is looked up via dlsym * (mirroring http_set_handler), so any El `fn (input)` compiles to * a global C symbol that this function can locate at runtime. * Handler signature: `el_val_t handler(el_val_t input_json)` — receives * the tool input as a JSON-string el_val_t and returns a JSON-string * el_val_t result. Used by llm_call_agentic. */ void llm_register_tool(el_val_t name, el_val_t handler_fn_name); /* ── args() ───────────────────────────────────────────────────────────────── * Provides access to command-line arguments passed to the program. * Populated by el_runtime_init_args() before main() runs. */ el_val_t args(void); void el_runtime_init_args(int argc, char** argv); /* ── Crypto primitives ───────────────────────────────────────────────────── * SHA-256, HMAC-SHA-256, and base64 (standard + URL-safe). * Self-contained — no OpenSSL/libcrypto dependency. The implementations are * adapted from public-domain reference code (Brad Conte / RFC 4648). * * Bytes-returning variants (sha256_bytes, hmac_sha256_bytes) return a string * value whose contents are raw binary; callers usually feed these into * base64_encode. Note that el_val_t strings are NUL-terminated by convention, * so the binary payload may contain embedded NULs — pass it directly into * base64_encode (which uses an explicit length) rather than treating it as * a printable C string. * * The "base64" variants emit/accept RFC 4648 standard alphabet with padding. * The "base64url" variants use URL-safe alphabet (`-`/`_`) with no padding, * as used in JWTs. */ el_val_t sha256_hex(el_val_t input); el_val_t sha256_bytes(el_val_t input); el_val_t hmac_sha256_hex(el_val_t key, el_val_t message); el_val_t hmac_sha256_bytes(el_val_t key, el_val_t message); el_val_t base64_encode(el_val_t input); el_val_t base64_decode(el_val_t input); el_val_t base64url_encode(el_val_t input); el_val_t base64url_decode(el_val_t input); /* Length-aware variants (internal — exposed for the rare caller that already * has a known-length binary buffer and doesn't want to round-trip through * a NUL-terminated el_val_t string). Sha256_bytes and hmac_sha256_bytes feed * these implicitly. */ el_val_t el_sha256_bytes_n(const unsigned char* data, size_t len); el_val_t el_base64_encode_n(const unsigned char* data, size_t len, int url_safe); /* ── Post-quantum primitives (liboqs-backed) ──────────────────────────────── * All inputs/outputs hex-encoded. Algorithm choices: * Signature: CRYSTALS-Dilithium-3 (NIST level 3, balanced) * KEM: CRYSTALS-Kyber-768 (NIST level 3) * Hash: SHA3-256 (Keccak) (PQ-aware protocols favour SHA3 over SHA2) * * If liboqs is not linked (detected via __has_include() at compile * time), the pq_* entry points return a JSON-shaped error string so callers * fail loudly rather than silently fall back to classical schemes: * {"error":"liboqs not linked, post-quantum primitives unavailable"} * * The hybrid handshake pairs X25519 with Kyber-768 per NIST PQ guidance and * CNSA 2.0. Combined shared secret is HKDF-SHA256(x25519_ss || kyber_ss). * Even if Kyber falls, X25519 holds; if X25519 falls under quantum attack, * Kyber holds. SHA3-256 also remains usable independent of liboqs (the * Keccak permutation is PQ-OK as a primitive). */ el_val_t pq_keygen_signature(void); el_val_t pq_sign(el_val_t secret_key_hex, el_val_t message); el_val_t pq_verify(el_val_t public_key_hex, el_val_t message, el_val_t signature_hex); el_val_t pq_kem_keygen(void); el_val_t pq_kem_encaps(el_val_t public_key_hex); el_val_t pq_kem_decaps(el_val_t secret_key_hex, el_val_t ciphertext_hex); el_val_t pq_hybrid_keygen(void); el_val_t pq_hybrid_handshake(el_val_t remote_pub_combined); el_val_t sha3_256_hex(el_val_t input); /* ── Native VM builtin aliases (for compiled El source) ───────────────────── * These match the El VM's native_* builtins so that El source compiled * to C can call the same names without modification. */ el_val_t native_list_get(el_val_t list, el_val_t index); el_val_t native_list_len(el_val_t list); el_val_t native_list_append(el_val_t list, el_val_t elem); el_val_t native_list_empty(void); el_val_t native_list_clone(el_val_t list); el_val_t native_string_chars(el_val_t s); el_val_t native_int_to_str(el_val_t n); /* ── Method-call shorthand aliases ────────────────────────────────────────── * The El method-call convention `obj.method(args)` compiles to * `method(obj, args)`. These aliases expose the runtime functions under * the short names that result from method calls in El source. * * Example: `myList.append(x)` → `append(myList, x)` (calls this alias) * `myList.len()` → `len(myList)` (calls this alias) */ el_val_t append(el_val_t list, el_val_t elem); /* el_list_append */ el_val_t len(el_val_t list); /* el_list_len */ el_val_t get(el_val_t list, el_val_t index); /* el_list_get */ el_val_t map_get(el_val_t map, el_val_t key); /* el_map_get */ el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */ #ifdef __cplusplus } #endif