0fa9e749e1
Three changes that turned the runtime into something Engram-the-server
can actually run on top of.
1. engram_*_json accessors. The runtime's engram_get_node/search/scan/
neighbors/activate return ElList/ElMap; passing those through
json_stringify hit the type-erasure wall (an ElList* has no header
that distinguishes it from a string pointer). Added pre-serialized
sibling builtins:
engram_get_node_json(id) -> JSON object
engram_search_json(query, limit) -> JSON array of node objects
engram_scan_nodes_json(limit, offset)
engram_neighbors_json(node_id, max_depth, direction)
engram_activate_json(query, depth)
engram_stats_json()
Each walks the typed C structures and serializes directly, reusing
the existing engram_emit_node_json / engram_emit_edge_json helpers
from the snapshot path.
2. http_set_handler now falls back to dlsym(RTLD_DEFAULT, name) when
the named handler isn't already in the C-level registry. El programs
that define `fn handle_request(method, path, body) -> String` can
register themselves just by calling http_set_handler("handle_request").
No C glue required. Verified live on a real El server.
3. Codegen: extended int-typed dispatch on `+` to handle Calls. New
helper is_int_call recognizes a known-int-returning builtin set:
str_len, str_index_of, str_to_int, str_char_code, native_list_len,
el_list_len, len, json_get_int, json_array_len, engram_node_count,
engram_edge_count, time_now, time_now_utc, time_diff, time_add,
time_from_parts, el_abs/max/min, float_to_int. With this,
`pos + str_len(needle)` compiles to integer arithmetic instead of
string concat. The earlier limitation noted in the previous commit
(Ident + Call returning Int) is now closed.
Also: el_to_float / el_from_float moved to el_runtime.h as static
inlines so generated programs can use them. Eliminates the unused
inline definitions that were duplicating in the .c file.
Closure verified: stage1 vs stage2 byte-identical against the new
runtime. dist/platform/elc rebuilt; .prev4 preserved.
Engram server (engram/src/server.el) end-to-end:
POST /api/nodes ×3 → 3 UUIDs returned
POST /api/edges ×2 → linkage made
GET /api/stats → {"node_count":3,"edge_count":2}
GET /api/search?q=spreading&limit=5 → 1 hit, full node JSON
POST /api/activate {"query":"Hebbian","depth":3}
→ seed node @ hop 0, strength 0.8
→ 1-hop neighbor @ strength 0.392 (= 0.8 × 0.7 weight × 0.7 decay)
GET /api/neighbors/<id>?depth=2 → {node, edge, hops} triple
POST /api/save → {"ok":true,"path":"..."}
Server stays alive across all routes.
Snapshot save/load on restart still TODO — server starts with 0 nodes
even when a snapshot exists; investigation pending.
313 lines
16 KiB
C
313 lines
16 KiB
C
/*
|
|
* 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).
|
|
*
|
|
* Canonical compile command:
|
|
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
|
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
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(<dbl>)` 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);
|
|
|
|
/* ── 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);
|
|
|
|
/* ── 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);
|
|
void http_serve(el_val_t port, el_val_t handler);
|
|
void http_set_handler(el_val_t name);
|
|
|
|
/* ── 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);
|
|
|
|
/* ── 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);
|
|
|
|
/* ── Time ────────────────────────────────────────────────────────────────── */
|
|
|
|
el_val_t time_now(void);
|
|
el_val_t time_now_utc(void);
|
|
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);
|
|
|
|
/* ── Process ─────────────────────────────────────────────────────────────── */
|
|
|
|
void exit_program(el_val_t code);
|
|
|
|
/* ── 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).
|
|
* Stubs print descriptive output and return empty/zero values.
|
|
* Full implementations are linked from the DHARMA runtime at deploy time. */
|
|
|
|
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);
|
|
|
|
/* ── 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);
|
|
|
|
/* ── 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);
|
|
|
|
/* ── 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_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
|