restructure: move el compiler content into lang/

This commit is contained in:
Will Anderson
2026-05-05 01:38:51 -05:00
parent ce68f91a38
commit 1ae68962cf
143 changed files with 0 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
// channel.el Go-style channels for El
//
// Channels are the communication primitive for concurrent El programs.
// Threads send values into a channel; other threads receive them.
// Channels are typed by convention all values are Strings.
//
// Backed by four seed primitives in el_runtime.c:
// __channel_new(capacity) -> Int create channel; cap=0 = unbounded
// __channel_send(ch, msg) push msg; blocks if bounded and full
// __channel_recv(ch) -> String pop msg; blocks until available; "" on close
// __channel_try_recv(ch) -> String non-blocking pop; "" if empty
// __channel_close(ch) mark closed; wake all blocked recvers
//
// Usage:
// let ch: Int = channel_new(10) // buffered channel, capacity 10
// spawn("producer", int_to_str(ch))
// let msg: String = channel_recv(ch)
// Core channel API
// channel_new create a channel with the given buffer capacity.
//
// capacity: 0 = unbounded (never blocks sender)
// N = bounded buffer of N messages (sender blocks when full)
//
// Returns a channel handle (Int) to pass to send/recv/close.
fn channel_new(capacity: Int) -> Int {
return __channel_new(capacity)
}
// channel_send send a message into the channel.
//
// Blocks if the channel is bounded and full.
// No-op if the channel is already closed.
fn channel_send(ch: Int, msg: String) {
__channel_send(ch, msg)
}
// channel_recv receive the next message from the channel.
//
// Blocks until a message is available.
// Returns "" when the channel is closed and all buffered messages are drained.
// The "" sentinel signals end-of-stream to consumers in a loop.
fn channel_recv(ch: Int) -> String {
return __channel_recv(ch)
}
// channel_try_recv non-blocking receive.
//
// Returns the next message if one is available, or "" if the channel is empty.
// Does not block. Callers must distinguish "" (empty) from a legitimate ""
// message by convention use a non-empty sentinel in the message protocol.
fn channel_try_recv(ch: Int) -> String {
return __channel_try_recv(ch)
}
// channel_close signal that no more messages will be sent.
//
// After close, channel_recv continues to drain buffered messages then
// returns "" on every subsequent call. channel_send on a closed channel
// is a no-op (the message is dropped).
fn channel_close(ch: Int) {
__channel_close(ch)
}
// channel_pipeline
// channel_pipeline producer/consumer pipeline with parallel workers.
//
// Reads messages from in_ch, applies fn_name to each, writes results to out_ch.
// Spawns `workers` concurrent worker threads each drains in_ch independently,
// so messages are processed in arrival order within each worker but not globally.
//
// fn_name must be an El fn with signature (String) -> String.
//
// Call channel_close(in_ch) to signal EOF. Workers exit when they receive "".
// The caller must also close out_ch after all workers finish (via join).
//
// let in_ch: Int = channel_new(0)
// let out_ch: Int = channel_new(0)
// channel_pipeline(in_ch, out_ch, "process_item", 4)
// channel_send(in_ch, "work-1")
// channel_close(in_ch)
// let result: String = channel_recv(out_ch)
fn channel_pipeline(in_ch: Int, out_ch: Int, fn_name: String, workers: Int) {
let i: Int = 0
while i < workers {
let arg: String = "{\"in_ch\":" + int_to_str(in_ch) +
",\"out_ch\":" + int_to_str(out_ch) +
",\"fn\":\"" + fn_name + "\"}"
let _tid: Int = spawn("_channel_worker", arg)
let i = i + 1
}
}
// _channel_worker internal worker for channel_pipeline.
//
// Reads messages from in_ch until it receives "" (closed+empty), applies
// fn_name to each, and writes results to out_ch. Runs in its own thread
// (spawned by channel_pipeline).
fn _channel_worker(arg: String) -> String {
let in_ch: Int = str_to_int(json_get(arg, "in_ch"))
let out_ch: Int = str_to_int(json_get(arg, "out_ch"))
let fn_name: String = json_get(arg, "fn")
let running: Bool = true
while running {
let msg: String = channel_recv(in_ch)
if str_eq(msg, "") {
let running = false
} else {
// Spawn fn_name in a child thread so it cannot block the worker loop.
let tid: Int = spawn(fn_name, msg)
let result: String = join(tid)
channel_send(out_ch, result)
}
}
return ""
}
// channel_drain
// channel_drain collect all messages from ch into a list.
//
// Reads until the channel is closed and empty (recv returns "").
// Returns a [String] of all messages received.
//
// Typical usage: close the channel from the producer side, then call
// channel_drain from the consumer to collect results.
fn channel_drain(ch: Int) -> [String] {
let results: [String] = el_list_empty()
let running: Bool = true
while running {
let msg: String = channel_recv(ch)
if str_eq(msg, "") {
let running = false
} else {
let results = el_list_append(results, msg)
}
}
return results
}
// channel_fan_out
// channel_fan_out send every item in a list into a channel.
//
// items: [String] items to send
// ch: Int destination channel
//
// Sends all items then closes the channel to signal end-of-stream.
// Intended for the producer side of a pipeline:
//
// channel_fan_out(items, in_ch)
// let results: [String] = channel_drain(out_ch)
fn channel_fan_out(items: [String], ch: Int) {
let n: Int = el_list_len(items)
let i: Int = 0
while i < n {
let item: String = el_list_get(items, i)
channel_send(ch, item)
let i = i + 1
}
channel_close(ch)
}
+86
View File
@@ -0,0 +1,86 @@
// runtime/engram.el El wrapper for the engram graph store
//
// Thin wrappers over the __engram_* seed primitives defined in el_seed.c.
// Each function delegates directly to the corresponding seed no logic here.
// The seed layer owns all storage, indexing, and graph traversal.
//
// Dependencies: runtime/string.el, runtime/json.el
// --- Node creation ---
fn engram_node(content: String, node_type: String, salience: Float) -> String {
return __engram_node(content, node_type, salience)
}
fn engram_node_full(content: String, nt: String, sal: Float, imp: Float,
source: String, lang: String, ts: Int, tags: String) -> String {
return __engram_node_full(content, nt, sal, imp, source, lang, ts, tags)
}
// --- Node retrieval ---
fn engram_get_node(id: String) -> String {
return __engram_get_node(id)
}
fn engram_node_count() -> Int {
return __engram_node_count()
}
// --- Node lifecycle ---
fn engram_strengthen(id: String) -> Bool {
return __engram_strengthen(id)
}
fn engram_forget(id: String) -> Bool {
return __engram_forget(id)
}
// --- Search and scan ---
fn engram_search(query: String, limit: Int) -> String {
return __engram_search(query, limit)
}
fn engram_scan_nodes(limit: Int, offset: Int) -> String {
return __engram_scan_nodes(limit, offset)
}
fn engram_scan_nodes_json(limit: Int, offset: Int) -> String {
return __engram_scan_nodes_json(limit, offset)
}
// --- Graph edges ---
fn engram_connect(from: String, to: String, rel: String, weight: Float) -> Bool {
return __engram_connect(from, to, rel, weight)
}
fn engram_edge_between(a: String, b: String) -> String {
return __engram_edge_between(a, b)
}
// --- Graph traversal ---
fn engram_neighbors(id: String) -> String {
return __engram_neighbors(id)
}
fn engram_neighbors_filtered(id: String, rel: String, min_w: Float) -> String {
return __engram_neighbors_filtered(id, rel, min_w)
}
fn engram_activate(query: String, depth: Int) -> String {
return __engram_activate(query, depth)
}
fn engram_activate_json(query: String, limit: Int) -> String {
return __engram_activate_json(query, limit)
}
// --- Generation ---
fn generate(form: String) -> String {
return __generate(form)
}
+105
View File
@@ -0,0 +1,105 @@
// runtime/env.el environment and process
// Covers: environment variables, command-line args, process exit, in-process
// state store, UUID generation, and list convenience helpers.
// env read an environment variable. Returns "" if the variable is not set.
fn env(key: String) -> String {
return __env_get(key)
}
// args command-line arguments as a list of strings.
// __args_json returns a JSON array (e.g. ["prog","arg1","arg2"]).
// The list is built by iterating over the array.
fn args() -> [String] {
let json: String = __args_json()
let n: Int = json_array_len(json)
let result: [String] = el_list_empty()
let i: Int = 0
while i < n {
let item: String = json_array_get_string(json, i)
let result = el_list_append(result, item)
let i = i + 1
}
return result
}
// exit_program terminate the process with the given exit code.
fn exit_program(code: Int) {
__exit_program(code)
}
// List convenience helpers
// get index into a list. Thin alias for el_list_get used throughout the
// El stdlib so call sites read like `get(lst, i)` rather than the verbose form.
fn get(lst: [String], i: Int) -> String {
return el_list_get(lst, i)
}
// len length of a list.
fn len(lst: [String]) -> Int {
return el_list_len(lst)
}
// In-process key-value state store
// state_set store a string value under key.
fn state_set(key: String, val: String) {
__state_set(key, val)
}
// state_get retrieve value for key; returns "" if key not present.
fn state_get(key: String) -> String {
return __state_get(key)
}
// state_del remove key from the store.
fn state_del(key: String) {
__state_del(key)
}
// state_keys all keys currently in the store as a JSON array string.
fn state_keys() -> String {
return __state_keys()
}
// DHARMA runtime helpers
// config read a configuration value from the environment.
// Returns "" if the variable is not set. Alias for env().
fn config(key: String) -> String {
return __env_get(key)
}
// log_info write an [INFO] log line to stdout.
fn log_info(msg: String) {
__println("[INFO] " + msg)
}
// log_warn write a [WARN] log line to stdout.
fn log_warn(msg: String) {
__println("[WARN] " + msg)
}
// list_len return the number of elements in a list. Alias for el_list_len.
fn list_len(lst: [String]) -> Int {
return el_list_len(lst)
}
// list_get return the element at index i in a list. Alias for el_list_get.
fn list_get(lst: [String], i: Int) -> String {
return el_list_get(lst, i)
}
// UUID generation
// uuid_new generate a new random UUID v4.
fn uuid_new() -> String {
return __uuid_v4()
}
// uuid_v4 alias for uuid_new(); explicit version name for callers that
// need to be precise about the UUID variant.
fn uuid_v4() -> String {
return __uuid_v4()
}
+27
View File
@@ -0,0 +1,27 @@
// runtime/exec.el subprocess execution
// All four names resolve to the same seed primitives so callers can use
// whichever name matches their mental model of the operation.
// exec run a shell command, capture stdout, return as String.
// Blocks until the subprocess exits (30-second wall-clock deadline in the
// seed layer). Returns "" on any error.
fn exec(cmd: String) -> String {
return __exec(cmd)
}
// exec_bg fire-and-forget subprocess. Returns immediately; no stdout.
fn exec_bg(cmd: String) {
__exec_bg(cmd)
}
// exec_command alias for exec(); preferred when callers care about side
// effects (e.g. invoking a build tool) rather than captured output.
fn exec_command(cmd: String) -> String {
return __exec(cmd)
}
// exec_capture alias for exec(); preferred when callers explicitly want
// to capture and process stdout.
fn exec_capture(cmd: String) -> String {
return __exec(cmd)
}
+49
View File
@@ -0,0 +1,49 @@
// runtime/fs.el filesystem operations
// Thin El wrappers over seed primitives; no logic beyond what is needed
// to present a clean API. The heavy lifting lives in el_runtime.c.
fn fs_read(path: String) -> String {
return __fs_read(path)
}
fn fs_write(path: String, content: String) -> Bool {
return __fs_write(path, content)
}
fn fs_exists(path: String) -> Bool {
return __fs_exists(path)
}
fn fs_mkdir(path: String) -> Bool {
return __fs_mkdir(path)
}
fn fs_write_bytes(path: String, bytes: String, n: Int) -> Bool {
return __fs_write_bytes(path, bytes, n)
}
// fs_list return list of filenames in a directory.
// __fs_list_raw returns a newline-separated string (possibly with a trailing
// newline); callers that need a clean list should filter empty strings.
fn fs_list(path: String) -> [String] {
let raw: String = __fs_list_raw(path)
return str_split(raw, "\n")
}
// fs_list_json return a JSON array of filenames in a directory.
// Empty strings produced by a trailing newline are stripped before encoding.
fn fs_list_json(path: String) -> String {
let items: [String] = fs_list(path)
let n: Int = el_list_len(items)
let clean: [String] = el_list_empty()
let i: Int = 0
while i < n {
let item: String = el_list_get(items, i)
let trimmed: String = str_trim(item)
if !str_eq(trimmed, "") {
let clean = el_list_append(clean, "\"" + trimmed + "\"")
}
let i = i + 1
}
return json_build_array(clean)
}
+250
View File
@@ -0,0 +1,250 @@
// runtime/http.el El HTTP client and server wrappers
//
// Thin El layer over seed primitives. All network I/O is performed by the
// seed; this file provides the public API that El programs import.
//
// Seed primitives consumed:
// __http_do(method, url, body, headers_json, timeout_ms) -> String
// __http_do_to_file(method, url, body, headers_json, out_path) -> Bool
// __http_do_map(method, url, body, headers_map, timeout_ms) -> String
// __http_do_map_to_file(method, url, body, headers_map, out_path) -> Bool
// __http_serve(port, handler_name)
// __http_serve_v2(port, handler_name)
// __http_response(status, headers_json, body) -> String
// __env_get(key) -> String
//
// NOTE FOR SEED AGENT: __http_do_map and __http_do_map_to_file must be added
// to the seed. They are identical to __http_do / __http_do_to_file except
// they accept an ElMap directly for headers instead of a pre-serialised JSON
// string. This avoids needing map iteration in El (which has no for-loop or
// map iterator primitive). The seed implementation maps to headers_from_map()
// in el_runtime.c.
//
// Other builtins used:
// str_eq(a, b) -> Bool
// str_to_int(s) -> Int
// Timeout helper
// el_http_timeout_ms returns the configured HTTP timeout in milliseconds.
// Reads EL_HTTP_TIMEOUT_MS from the environment; defaults to 60000 (60s).
// Returns 60000 if the env var is absent, empty, or non-positive.
fn el_http_timeout_ms() -> Int {
let v: String = __env_get("EL_HTTP_TIMEOUT_MS")
if str_eq(v, "") { return 60000 }
let n: Int = str_to_int(v)
if n <= 0 { return 60000 }
return n
}
// HTTP client simple variants
// http_get performs an HTTP GET request and returns the response body.
// On transport failure the seed returns an error JSON fragment.
fn http_get(url: String) -> String {
return __http_do("GET", url, "", "{}", el_http_timeout_ms())
}
// http_post performs an HTTP POST request with the given body.
// No Content-Type header is set; use http_post_json for JSON payloads.
fn http_post(url: String, body: String) -> String {
return __http_do("POST", url, body, "{}", el_http_timeout_ms())
}
// http_post_json performs an HTTP POST request with Content-Type:
// application/json. body must be a valid JSON string.
fn http_post_json(url: String, body: String) -> String {
return __http_do("POST", url, body, "{\"Content-Type\":\"application/json\"}", el_http_timeout_ms())
}
// http_delete performs an HTTP DELETE request and returns the response body.
fn http_delete(url: String) -> String {
return __http_do("DELETE", url, "", "{}", el_http_timeout_ms())
}
// HTTP client header map variants
//
// These accept a Map<String, String> of request headers. The seed's
// __http_do_map converts the ElMap to a curl_slist internally, matching
// the headers_from_map() logic in el_runtime.c.
// http_get_with_headers performs an HTTP GET with caller-supplied headers.
fn http_get_with_headers(url: String, headers: Map<String, String>) -> String {
return __http_do_map("GET", url, "", headers, el_http_timeout_ms())
}
// http_post_with_headers performs an HTTP POST with caller-supplied headers.
fn http_post_with_headers(url: String, body: String, headers: Map<String, String>) -> String {
return __http_do_map("POST", url, body, headers, el_http_timeout_ms())
}
// http_post_form_auth performs an HTTP POST with
// Content-Type: application/x-www-form-urlencoded and an Authorization
// header built from auth_header (the caller passes the full header value,
// e.g. "Bearer <token>" or "Basic <base64>").
//
// Mirrors http_post_form_auth in el_runtime.c: two headers are injected,
// Content-Type is always set; Authorization is omitted when auth_header is "".
fn http_post_form_auth(url: String, form_body: String, auth_header: String) -> String {
if str_eq(auth_header, "") {
return __http_do("POST", url, form_body, "{\"Content-Type\":\"application/x-www-form-urlencoded\"}", el_http_timeout_ms())
}
return __http_do("POST", url, form_body, "{\"Content-Type\":\"application/x-www-form-urlencoded\",\"Authorization\":\"" + auth_header + "\"}", el_http_timeout_ms())
}
// HTTP client streaming to file
//
// These route the response body directly to a file via the seed, bypassing
// the El string layer. This preserves embedded NUL bytes in binary payloads
// (audio, images, etc.) an El string would truncate at the first NUL.
// Returns true on success, false on any transport or I/O error.
// http_post_to_file performs an HTTP POST and streams the response body to
// output_path. Useful for large or binary response payloads.
fn http_post_to_file(url: String, body: String, headers: Map<String, String>, output_path: String) -> Bool {
return __http_do_map_to_file("POST", url, body, headers, output_path)
}
// http_get_to_file performs an HTTP GET and streams the response body to
// output_path. Useful for large or binary response payloads.
fn http_get_to_file(url: String, headers: Map<String, String>, output_path: String) -> Bool {
return __http_do_map_to_file("GET", url, "", headers, output_path)
}
// HTTP server
//
// El programs call http_set_handler(name) to register which El function
// handles requests, then http_serve(port, name) to start listening.
// The seed resolves handler names via dlsym every El fn compiles to a
// global C symbol with the same name, so self-registration works without
// any El-level registry.
//
// v2 widens the handler signature from
// (method, path, body) -> String
// to
// (method, path, headers_map, body) -> String
// so handlers can inspect incoming headers. Use http_serve_v2 +
// http_set_handler_v2 for v2 handlers.
// http_set_handler registers name as the active v1 request handler.
// The seed resolves the symbol via dlsym at call time; no El-level
// registration is needed. This is a no-op at the El layer.
fn http_set_handler(name: String) {
// no-op: the seed handles handler registration via dlsym
}
// http_serve starts an HTTP/1.1 server on port, dispatching every request
// to handler (a v1 handler: fn(method, path, body) -> String).
// Blocks forever. Accepts both IPv4 and IPv6 (dual-stack).
fn http_serve(port: Int, handler: String) {
__http_serve(port, handler)
}
// http_set_handler_v2 registers name as the active v2 request handler.
// No-op at the El layer; the seed uses dlsym.
fn http_set_handler_v2(name: String) {
// no-op: the seed handles handler registration via dlsym
}
// http_serve_v2 starts an HTTP/1.1 server on port, dispatching every
// request to handler (a v2 handler: fn(method, path, headers, body) ->
// String). Blocks forever. Accepts both IPv4 and IPv6 (dual-stack).
fn http_serve_v2(port: Int, handler: String) {
__http_serve_v2(port, handler)
}
// Response construction
// http_response builds a structured response envelope that the HTTP server
// runtime unpacks into a real HTTP response with the given status code and
// headers. status must be 100599 (defaults to 200 outside that range).
// headers_json must be a JSON object literal (e.g. "{}" or
// "{\"Content-Type\":\"text/html\"}"); body is the response body string.
//
// The envelope format is:
// {"el_http_response":1,"status":<n>,"headers":<obj>,"body":"<escaped>"}
// The runtime detects this prefix and unpacks it; plain string returns from
// handlers are still supported and are sent as HTTP 200 with auto-detected
// Content-Type.
fn http_response(status: Int, headers_json: String, body: String) -> String {
return __http_response(status, headers_json, body)
}
// HTTP client PATCH
// http_patch performs an HTTP PATCH request with Content-Type: application/json.
fn http_patch(url: String, body: String) -> String {
return __http_do("PATCH", url, body, "{\"Content-Type\":\"application/json\"}", el_http_timeout_ms())
}
// HTTP client Engram variants (optional API key)
//
// These are used by dharma's db.el to talk to Engram nodes.
// The key parameter is the X-API-Key header value; pass "" for no auth.
// http_post_engram performs an HTTP POST with Content-Type: application/json
// and an optional X-API-Key header. If key is "" no auth header is added.
fn http_post_engram(url: String, key: String, body: String) -> String {
if str_eq(key, "") {
return __http_do("POST", url, body, "{\"Content-Type\":\"application/json\"}", el_http_timeout_ms())
}
return __http_do("POST", url, body, "{\"Content-Type\":\"application/json\",\"X-API-Key\":\"" + key + "\"}", el_http_timeout_ms())
}
// http_get_engram performs an HTTP GET with an optional X-API-Key header.
fn http_get_engram(url: String, key: String) -> String {
if str_eq(key, "") {
return __http_do("GET", url, "", "{}", el_http_timeout_ms())
}
return __http_do("GET", url, "", "{\"X-API-Key\":\"" + key + "\"}", el_http_timeout_ms())
}
// SSE Server-Sent Events streaming
//
// Usage pattern for an SSE handler:
//
// fn my_handler(method: String, path: String, headers: Map<String, String>, body: String) -> String {
// let fd: Int = http_conn_fd()
// http_sse_open(fd)
// http_sse_send(fd, "hello")
// http_sse_send(fd, "world")
// http_sse_close(fd)
// return http_sse_sentinel()
// }
//
// The sentinel return value tells http_serve_v2 NOT to close the connection
// automatically the handler already closed it via http_sse_close.
// http_conn_fd returns the raw file descriptor for the current HTTP connection.
// Only valid inside an http_serve_v2 handler, before the handler returns.
// Use with http_sse_open / http_sse_send / http_sse_close for streaming.
fn http_conn_fd() -> Int {
return __http_conn_fd()
}
// http_sse_open sends SSE response headers on the current connection,
// keeping it open for streaming. Call once at the start of an SSE handler.
// Returns true on success.
fn http_sse_open(fd: Int) -> Bool {
return __http_sse_open(fd)
}
// http_sse_send writes one SSE event to the connection.
// data should not contain newlines (they are added automatically).
// Returns true if the write succeeded (client still connected).
fn http_sse_send(fd: Int, data: String) -> Bool {
return __http_sse_send(fd, data)
}
// http_sse_close closes the SSE connection.
fn http_sse_close(fd: Int) {
__http_sse_close(fd)
return
}
// http_sse_sentinel is the return value an SSE handler must return
// to tell the HTTP server NOT to close the connection automatically.
// The handler takes ownership of the fd and closes it via http_sse_close.
fn http_sse_sentinel() -> String {
return "__sse__"
}
+213
View File
@@ -0,0 +1,213 @@
// runtime/json.el El JSON operations
//
// Thin El wrappers over seed JSON primitives, plus pure-El builders and
// helpers. Each function here corresponds to (and replaces) a C function
// from el-compiler/runtime/legacy/el_runtime.c (lines 26923333).
//
// Seed primitives consumed by this module:
// __json_get(json, key) -> String (value as string)
// __json_get_raw(json, key) -> String (raw JSON token)
// __json_parse_map(s) -> Map<String, Any>
// __json_stringify_val(v) -> String
// __json_array_len(arr) -> Int
// __json_array_get(arr, i) -> String (element as JSON fragment)
// __json_array_get_string(arr, i) -> String (element as string value)
// __json_set(json, key, value) -> String (JSON mutation)
// __str_to_int(s) -> Int
// __str_to_float(s) -> Float
// ---------------------------------------------------------------------------
// Core thin wrappers that delegate directly to seed
// ---------------------------------------------------------------------------
// json_get extract a value from a JSON object as a string.
// Supports dot-path traversal ("a.b.c") and array indices ("items.0.name").
fn json_get(json: String, key: String) -> String {
return __json_get(json, key)
}
// json_get_raw extract a raw JSON token (the un-decoded fragment) for a key.
// Useful when the caller wants to pass a sub-object to another JSON function.
fn json_get_raw(json: String, key: String) -> String {
return __json_get_raw(json, key)
}
// json_parse parse a JSON string into a Map<String, Any>.
// Arrays become ElList; objects become ElMap; scalars are typed values.
fn json_parse(s: String) -> Map<String, Any> {
return __json_parse_map(s)
}
// json_stringify serialize an El value (ElMap, ElList, String, Int) to JSON.
fn json_stringify(v: Any) -> String {
return __json_stringify_val(v)
}
// json_array_len return the number of elements in a JSON array string.
fn json_array_len(arr: String) -> Int {
return __json_array_len(arr)
}
// json_array_get return the i-th element of a JSON array as a JSON fragment.
// Nested objects and arrays are returned verbatim. Out-of-range -> "".
fn json_array_get(arr: String, i: Int) -> String {
return __json_array_get(arr, i)
}
// json_array_get_string return the i-th element of a JSON array as a plain
// string value (quotes and escape sequences removed). Non-string elements
// and out-of-range indices yield "".
fn json_array_get_string(arr: String, i: Int) -> String {
return __json_array_get_string(arr, i)
}
// ---------------------------------------------------------------------------
// Typed extractors delegate to seed then convert
// ---------------------------------------------------------------------------
// json_get_string extract a string value for a key.
// Equivalent to json_get but named explicitly for readability.
fn json_get_string(json: String, key: String) -> String {
return __json_get(json, key)
}
// json_get_int extract an integer value for a key.
fn json_get_int(json: String, key: String) -> Int {
let s: String = __json_get(json, key)
return str_to_int(s)
}
// json_get_float extract a floating-point value for a key.
fn json_get_float(json: String, key: String) -> Float {
let s: String = __json_get(json, key)
return str_to_float(s)
}
// json_get_bool extract a boolean value for a key.
// Returns true only when the raw JSON token is the literal "true".
fn json_get_bool(json: String, key: String) -> Bool {
let s: String = __json_get(json, key)
return str_eq(s, "true")
}
// ---------------------------------------------------------------------------
// Mutation
// ---------------------------------------------------------------------------
// json_set set or insert a key/value pair in a JSON object string.
// If the key already exists its value is replaced in-place; otherwise the
// pair is appended before the closing brace. The value must already be a
// valid JSON-encoded string (e.g. a quoted string, number, or sub-object).
fn json_set(json: String, key: String, value: String) -> String {
return __json_set(json, key, value)
}
// ---------------------------------------------------------------------------
// Pure-El builders no seed call required
// ---------------------------------------------------------------------------
// json_build_object build a JSON object from alternating key/value strings.
//
// keys_and_values must contain an even number of elements laid out as:
// [key0, val0, key1, val1, ...]
//
// Both keys and values are assumed to be plain strings that will be
// double-quoted and JSON-escaped by this function. Pass a pre-encoded
// number or sub-object as the value if you need non-string JSON types.
//
// Example:
// json_build_object(["name", "alice", "role", "admin"])
// -> {"name":"alice","role":"admin"}
fn json_build_object(keys_and_values: [String]) -> String {
let n: Int = el_list_len(keys_and_values)
let result: String = "{"
let i: Int = 0
while i < n - 1 {
let key: String = el_list_get(keys_and_values, i)
let val: String = el_list_get(keys_and_values, i + 1)
let sep: String = if i == 0 { "" } else { "," }
let escaped_key: String = json_escape_string(key)
let escaped_val: String = json_escape_string(val)
let result = result + sep + "\"" + escaped_key + "\":\"" + escaped_val + "\""
let i = i + 2
}
return result + "}"
}
// json_build_array build a JSON array from a list of already-JSON-encoded
// strings.
//
// Each element in items must be a valid JSON fragment (quoted string, number,
// object, array, or literal). The function joins them with commas and wraps
// the result in brackets.
//
// Example:
// json_build_array(["\"alice\"", "\"bob\""])
// -> ["alice","bob"]
fn json_build_array(items: [String]) -> String {
let n: Int = el_list_len(items)
let result: String = "["
let i: Int = 0
while i < n {
let item: String = el_list_get(items, i)
let sep: String = if i == 0 { "" } else { "," }
let result = result + sep + item
let i = i + 1
}
return result + "]"
}
// json_array_push append a pre-encoded JSON element to a JSON array string.
// elem must be a valid JSON fragment (e.g. "\"foo\"" or "42").
// Returns a new JSON array string with elem appended.
// Example: json_array_push("[]", "\"alice\"") -> "[\"alice\"]"
fn json_array_push(arr: String, elem: String) -> String {
let n: Int = json_array_len(arr)
if n == 0 {
return "[" + elem + "]"
}
// arr ends with ']'; insert before it
let inner_end: Int = str_last_index_of(arr, "]")
if inner_end < 0 {
return "[" + elem + "]"
}
let prefix: String = str_slice(arr, 0, inner_end)
return prefix + "," + elem + "]"
}
// json_escape_string escape a raw string so it can be safely embedded as a
// JSON string value.
//
// Characters escaped: backslash, double-quote, newline, carriage return, tab.
// The returned value does NOT include surrounding double-quotes; wrap it in
// quotes if you need a complete JSON string literal.
fn json_escape_string(s: String) -> String {
let s1: String = str_replace(s, "\\", "\\\\")
let s2: String = str_replace(s1, "\"", "\\\"")
let s3: String = str_replace(s2, "\n", "\\n")
let s4: String = str_replace(s3, "\r", "\\r")
let s5: String = str_replace(s4, "\t", "\\t")
return s5
}
// ---------------------------------------------------------------------------
// DHARMA byte decoding
// ---------------------------------------------------------------------------
// bytes_to_str decode a JSON array of integer byte values back to a string.
// "[104,105]" -> "hi"
// Inverse of str_to_bytes (defined in string.el). Defined here because it
// depends on json_array_len and json_array_get_string which live in this file.
fn bytes_to_str(arr: String) -> String {
let n: Int = json_array_len(arr)
if n == 0 { return "" }
let out: String = __str_alloc(n)
let i: Int = 0
while i < n {
let elem: String = json_array_get_string(arr, i)
let b: Int = __str_to_int(elem)
out = __str_set_char(out, i, b)
i = i + 1
}
return out
}
+34
View File
@@ -0,0 +1,34 @@
// runtime/manifest.el El runtime module manifest
//
// Load order for runtime compilation. Each module may depend on modules
// listed before it. The build system concatenates these in this order,
// then compiles the combined source.
//
// Modules:
// 1. runtime/string.el string operations (no dependencies)
// 2. runtime/math.el numeric/float operations (no dependencies)
// 3. runtime/state.el in-process key-value (no dependencies)
// 4. runtime/env.el environment, process, args, uuid
// 5. runtime/fs.el filesystem operations (depends: string)
// 6. runtime/exec.el subprocess execution (depends: string)
// 7. runtime/time.el time, date, calendar (depends: string, math)
// 8. runtime/json.el JSON operations (depends: string)
// 9. runtime/http.el HTTP client+server (depends: string, json)
// 10. runtime/engram.el graph store (depends: string, json)
// 11. runtime/thread.el threading, parallel_map (depends: all above)
// 12. runtime/collections.el list/map higher-level ops (depends: string)
//
// Build command (from el/ root):
// cat runtime/string.el runtime/math.el runtime/state.el runtime/env.el \
// runtime/fs.el runtime/exec.el runtime/time.el runtime/json.el \
// runtime/http.el runtime/engram.el runtime/thread.el \
// runtime/collections.el \
// <user-program.el> > combined.el
// ./dist/platform/elc combined.el > output.c
// cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
// -o output output.c el-compiler/runtime/el_seed.c
// This file itself is not compiled it is documentation only.
fn runtime_version() -> String {
return "2.0.0-el-native"
}
+170
View File
@@ -0,0 +1,170 @@
// runtime/math.el Float math, integer utilities, and numeric conversions.
//
// Implements the math/float surface from el-compiler/runtime/legacy/el_runtime.c
// (lines 303305 for el_abs/max/min, lines 47254771 for float/format ops)
// in pure El, using seed primitives.
//
// Seed primitives consumed:
// __sqrt_f(f: Float) -> Float
// __log_f(f: Float) -> Float
// __ln_f(f: Float) -> Float
// __sin_f(f: Float) -> Float
// __cos_f(f: Float) -> Float
// __pi_f() -> Float
// __float_to_str(f: Float) -> String
// __str_to_float(s: String) -> Float
// __int_to_str(n: Int) -> String
// __str_to_int(s: String) -> Int
// ---------------------------------------------------------------------------
// Integer math el_abs, el_max, el_min.
//
// Matches legacy el_abs, el_max, el_min (lines 303305).
// ---------------------------------------------------------------------------
// el_abs absolute value of an integer.
fn el_abs(n: Int) -> Int {
if n < 0 { return -n }
return n
}
// el_max larger of two integers.
fn el_max(a: Int, b: Int) -> Int {
if a > b { return a }
return b
}
// el_min smaller of two integers.
fn el_min(a: Int, b: Int) -> Int {
if a < b { return a }
return b
}
// ---------------------------------------------------------------------------
// Float math thin wrappers over seed primitives.
//
// Matches legacy math_sqrt, math_log, math_ln, math_sin, math_cos, math_pi
// (lines 47664771).
// ---------------------------------------------------------------------------
// math_sqrt square root.
fn math_sqrt(f: Float) -> Float {
return __sqrt_f(f)
}
// math_log base-10 logarithm.
fn math_log(f: Float) -> Float {
return __log_f(f)
}
// math_ln natural logarithm.
fn math_ln(f: Float) -> Float {
return __ln_f(f)
}
// math_sin sine (radians).
fn math_sin(f: Float) -> Float {
return __sin_f(f)
}
// math_cos cosine (radians).
fn math_cos(f: Float) -> Float {
return __cos_f(f)
}
// math_pi the constant π.
fn math_pi() -> Float {
return __pi_f()
}
// ---------------------------------------------------------------------------
// Float conversions float_to_str, int_to_float, float_to_int, str_to_float.
//
// Matches legacy float_to_str, int_to_float, float_to_int, str_to_float
// (lines 47254762).
// ---------------------------------------------------------------------------
// float_to_str format a float using %g (shortest exact representation).
// Matches legacy float_to_str() snprintf "%g".
fn float_to_str(f: Float) -> String {
return __float_to_str(f)
}
// int_to_float convert an integer to a float.
// Matches legacy int_to_float() (double)(int64_t)n.
fn int_to_float(n: Int) -> Float {
return __int_to_float(n)
}
// float_to_int truncate a float to an integer (toward zero).
// Matches legacy float_to_int() (int64_t)el_to_float(f).
fn float_to_int(f: Float) -> Int {
return __float_to_int(f)
}
// str_to_float parse a float from a string. Returns 0.0 on failure.
// Matches legacy str_to_float() strtod(str, NULL).
fn str_to_float(s: String) -> Float {
return __str_to_float(s)
}
// ---------------------------------------------------------------------------
// format_float format a float to a fixed number of decimal places.
//
// decimals is clamped to [0, 30]. Matches legacy format_float() snprintf "%.*f".
// ---------------------------------------------------------------------------
fn format_float(f: Float, decimals: Int) -> String {
let d: Int = decimals
if d < 0 { d = 0 }
if d > 30 { d = 30 }
// Delegate to seed; the seed exposes __format_float(f, d) -> String.
// This matches snprintf(buf, 128, "%.*f", d, v) in the legacy runtime.
return __format_float(f, d)
}
// ---------------------------------------------------------------------------
// decimal_round round a float to d decimal places (half-away-from-zero).
//
// Matches legacy decimal_round():
// mul = pow(10, d)
// r = (v >= 0 ? floor(v*mul + 0.5) : -floor(-v*mul + 0.5)) / mul
//
// We implement pow(10, d) via a loop (d <= 15, so at most 15 multiplications).
// ---------------------------------------------------------------------------
// _pow10 10^n as a Float for n in [0, 15].
fn _pow10(n: Int) -> Float {
let result: Float = 1.0
let i: Int = 0
while i < n {
result = result * 10.0
i = i + 1
}
return result
}
// _floor_f floor of a float: largest integer <= f.
// Uses __float_to_int (truncation) with correction for negative non-integers.
fn _floor_f(f: Float) -> Float {
let t: Int = __float_to_int(f)
let tf: Float = __int_to_float(t)
// if f was negative and not already an integer, subtract 1
if f < 0.0 {
if tf > f {
return tf - 1.0
}
}
return tf
}
fn decimal_round(f: Float, decimals: Int) -> Float {
let d: Int = decimals
if d < 0 { d = 0 }
if d > 15 { d = 15 }
let mul: Float = _pow10(d)
if f >= 0.0 {
return _floor_f(f * mul + 0.5) / mul
}
return 0.0 - _floor_f((0.0 - f) * mul + 0.5) / mul
}
+61
View File
@@ -0,0 +1,61 @@
// runtime/state.el In-process key/value store.
//
// Thin El wrappers over the __state_* seed primitives. The backing store is
// a process-wide hash map maintained by the El runtime (formerly el_runtime.c
// lines 46324721: state_set, state_get, state_del, state_keys).
//
// Keys and values are Strings. Values are persistent across request boundaries
// within the same process instance (they survive individual request lifetimes).
// Concurrent access is serialized by the runtime; these wrappers are lock-free
// from El's perspective.
//
// Seed primitives consumed:
// __state_set(key: String, val: String)
// __state_get(key: String) -> String
// __state_del(key: String)
// __state_keys() -> String (JSON array of key strings)
// ---------------------------------------------------------------------------
// Core set / get / del / keys
// ---------------------------------------------------------------------------
// state_set store val under key. Overwrites any existing value.
fn state_set(key: String, val: String) {
__state_set(key, val)
}
// state_get retrieve the value for key. Returns "" if key is absent.
fn state_get(key: String) -> String {
return __state_get(key)
}
// state_del remove key from the store. No-op if key does not exist.
fn state_del(key: String) {
__state_del(key)
}
// state_keys return a JSON array string of all current keys.
// e.g. ["foo","bar","baz"]
// Matches legacy state_keys() which returns an ElList (here serialized as JSON).
fn state_keys() -> String {
return __state_keys()
}
// ---------------------------------------------------------------------------
// Convenience helpers
// ---------------------------------------------------------------------------
// state_has true if key is present (value is non-empty string).
// Note: a key set to "" is indistinguishable from absent via state_get alone.
fn state_has(key: String) -> Bool {
let v: String = state_get(key)
if str_eq(v, "") { return false }
return true
}
// state_get_or return val for key, or default_val if key is absent.
fn state_get_or(key: String, default_val: String) -> String {
let v: String = state_get(key)
if str_eq(v, "") { return default_val }
return v
}
+42
View File
@@ -0,0 +1,42 @@
// stdlib.el El standard library master import file.
//
// Import this single file to get the full El runtime in the correct
// dependency order. El programs can do:
//
// import "../foundation/el/runtime/stdlib.el"
//
// or, if El is installed via tools/install.sh:
//
// import "/usr/local/el/runtime/stdlib.el"
//
// Note: test.el is intentionally NOT included here it is dev-only
// and should be imported explicitly in test files only.
//
// Dependency order (each module may depend on earlier ones):
// string no deps
// math no deps
// time no deps
// env no deps
// fs no deps
// exec no deps
// json depends on string
// http depends on string, json
// state no deps
// thread depends on exec
// channel depends on thread, state
// engram depends on http, json, string
// manifest depends on fs, json, string
import "string.el"
import "math.el"
import "time.el"
import "env.el"
import "fs.el"
import "exec.el"
import "json.el"
import "http.el"
import "state.el"
import "thread.el"
import "channel.el"
import "engram.el"
import "manifest.el"
+907
View File
@@ -0,0 +1,907 @@
// runtime/string.el String operations implemented in El.
//
// All functions delegate character-level work to the seed primitives declared
// in el_seed.c. No C is written here; this is pure El source that compiles
// to C via the normal El pipeline.
//
// Seed primitives used (provided by el_seed.c):
// __str_len(s) -> Int
// __str_char_at(s, i) -> Int (char code at byte index i)
// __str_alloc(n) -> String (n-byte zero-filled mutable buffer)
// __str_set_char(s, i, c) -> String (mutate s[i]=c, return s)
// __str_cmp(a, b) -> Int (strcmp)
// __str_ncmp(a, b, n) -> Int (strncmp)
// __str_concat_raw(a, b) -> String
// __str_slice_raw(s, lo, hi) -> String (substring copy [lo, hi))
// __int_to_str(n) -> String
// __str_to_int(s) -> Int
// __float_to_str(f) -> String
// __str_to_float(s) -> Float
// __println(s)
// __print(s)
// __readline() -> String
// __url_encode(s) -> String
// __url_decode(s) -> String
// I/O
fn println(s: String) -> Void {
__println(s)
}
fn print(s: String) -> Void {
__print(s)
}
fn readline() -> String {
return __readline()
}
// Type conversions
fn int_to_str(n: Int) -> String {
return __int_to_str(n)
}
fn str_to_int(s: String) -> Int {
return __str_to_int(s)
}
fn float_to_str(f: Float) -> String {
return __float_to_str(f)
}
fn str_to_float(s: String) -> Float {
return __str_to_float(s)
}
fn bool_to_str(b: Bool) -> String {
if b { return "true" }
return "false"
}
// URL encoding
fn url_encode(s: String) -> String {
return __url_encode(s)
}
fn url_decode(s: String) -> String {
return __url_decode(s)
}
// Math
fn el_abs(n: Int) -> Int {
if n < 0 { return 0 - n }
return n
}
fn el_max(a: Int, b: Int) -> Int {
if a > b { return a }
return b
}
fn el_min(a: Int, b: Int) -> Int {
if a < b { return a }
return b
}
// Core string primitives
fn str_len(s: String) -> Int {
return __str_len(s)
}
fn str_eq(a: String, b: String) -> Bool {
return __str_cmp(a, b) == 0
}
fn str_concat(a: String, b: String) -> String {
return __str_concat_raw(a, b)
}
fn str_slice(s: String, start: Int, end: Int) -> String {
let slen: Int = __str_len(s)
let lo: Int = start
if lo < 0 { lo = 0 }
if lo > slen { lo = slen }
let hi: Int = end
if hi < lo { hi = lo }
if hi > slen { hi = slen }
return __str_slice_raw(s, lo, hi)
}
// Whitespace helpers (internal)
//
// _is_ws: returns true for ASCII whitespace (space, tab, \n, \r, \f, \v).
fn _is_ws(c: Int) -> Bool {
if c == 32 { return true } // space
if c == 9 { return true } // tab
if c == 10 { return true } // \n
if c == 13 { return true } // \r
if c == 12 { return true } // \f
if c == 11 { return true } // \v
return false
}
// Scan forward from index 0; return index of first byte not in whitespace,
// or n if the entire string is whitespace.
fn _find_first_non_ws(s: String, n: Int) -> Int {
let i: Int = 0
while i < n {
if !_is_ws(__str_char_at(s, i)) { return i }
i = i + 1
}
return n
}
// Scan backward from index n-1; return index of last non-whitespace byte,
// or -1 if the entire string is whitespace.
fn _find_last_non_ws(s: String, n: Int) -> Int {
let i: Int = n - 1
while i >= 0 {
if !_is_ws(__str_char_at(s, i)) { return i }
i = i - 1
}
return -1
}
// Comparison and search
fn str_starts_with(s: String, prefix: String) -> Bool {
let plen: Int = __str_len(prefix)
let slen: Int = __str_len(s)
if plen > slen { return false }
return __str_ncmp(s, prefix, plen) == 0
}
fn str_ends_with(s: String, suffix: String) -> Bool {
let slen: Int = __str_len(s)
let suflen: Int = __str_len(suffix)
if suflen > slen { return false }
let tail: String = __str_slice_raw(s, slen - suflen, slen)
return __str_cmp(tail, suffix) == 0
}
fn str_contains(s: String, sub: String) -> Bool {
let slen: Int = __str_len(s)
let sublen: Int = __str_len(sub)
if sublen == 0 { return true }
if sublen > slen { return false }
let limit: Int = slen - sublen
let i: Int = 0
while i <= limit {
let window: String = __str_slice_raw(s, i, i + sublen)
if __str_cmp(window, sub) == 0 { return true }
i = i + 1
}
return false
}
fn str_index_of(s: String, sub: String) -> Int {
let slen: Int = __str_len(s)
let sublen: Int = __str_len(sub)
if sublen == 0 { return 0 }
if sublen > slen { return -1 }
let limit: Int = slen - sublen
let i: Int = 0
while i <= limit {
let window: String = __str_slice_raw(s, i, i + sublen)
if __str_cmp(window, sub) == 0 { return i }
i = i + 1
}
return -1
}
fn str_last_index_of(s: String, sub: String) -> Int {
let slen: Int = __str_len(s)
let sublen: Int = __str_len(sub)
if sublen == 0 { return slen }
if sublen > slen { return -1 }
let last: Int = -1
let limit: Int = slen - sublen
let i: Int = 0
while i <= limit {
let window: String = __str_slice_raw(s, i, i + sublen)
if __str_cmp(window, sub) == 0 {
last = i
i = i + sublen
} else {
i = i + 1
}
}
return last
}
fn str_index_of_all(s: String, sub: String) -> [Int] {
let result: [Int] = el_list_empty()
let slen: Int = __str_len(s)
let sublen: Int = __str_len(sub)
if sublen == 0 { return result }
if sublen > slen { return result }
let limit: Int = slen - sublen
let i: Int = 0
while i <= limit {
let window: String = __str_slice_raw(s, i, i + sublen)
if __str_cmp(window, sub) == 0 {
result = el_list_append(result, i)
i = i + sublen
} else {
i = i + 1
}
}
return result
}
// Return the byte index of the first character in s that appears in any_of,
// or -1 if none found.
fn str_find_chars(s: String, any_of: String) -> Int {
let slen: Int = __str_len(s)
let alen: Int = __str_len(any_of)
if alen == 0 { return -1 }
let i: Int = 0
while i < slen {
let c: Int = __str_char_at(s, i)
let j: Int = 0
while j < alen {
if c == __str_char_at(any_of, j) { return i }
j = j + 1
}
i = i + 1
}
return -1
}
// Character access
// Return a one-character string at byte index i, or "" if out of range.
fn str_char_at(s: String, i: Int) -> String {
let slen: Int = __str_len(s)
if i < 0 { return "" }
if i >= slen { return "" }
return __str_slice_raw(s, i, i + 1)
}
// Return the char code (byte value) at byte index i, or 0 if out of range.
fn str_char_code(s: String, i: Int) -> Int {
let slen: Int = __str_len(s)
if i < 0 { return 0 }
if i >= slen { return 0 }
return __str_char_at(s, i)
}
// Case conversion
fn str_to_upper(s: String) -> String {
let n: Int = __str_len(s)
if n == 0 { return "" }
let out: String = __str_alloc(n)
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
// a-z (97-122) -> A-Z (65-90): subtract 32
if c >= 97 {
if c <= 122 { c = c - 32 }
}
out = __str_set_char(out, i, c)
i = i + 1
}
return out
}
fn str_to_lower(s: String) -> String {
let n: Int = __str_len(s)
if n == 0 { return "" }
let out: String = __str_alloc(n)
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
// A-Z (65-90) -> a-z (97-122): add 32
if c >= 65 {
if c <= 90 { c = c + 32 }
}
out = __str_set_char(out, i, c)
i = i + 1
}
return out
}
// Aliases used in existing El codebases.
fn str_lower(s: String) -> String {
return str_to_lower(s)
}
fn str_upper(s: String) -> String {
return str_to_upper(s)
}
// Whitespace trimming
fn str_trim(s: String) -> String {
let n: Int = __str_len(s)
if n == 0 { return "" }
let lo: Int = _find_first_non_ws(s, n)
if lo == n { return "" }
let hi: Int = _find_last_non_ws(s, n)
return __str_slice_raw(s, lo, hi + 1)
}
fn str_lstrip(s: String) -> String {
let n: Int = __str_len(s)
if n == 0 { return "" }
let lo: Int = _find_first_non_ws(s, n)
if lo == n { return "" }
return __str_slice_raw(s, lo, n)
}
fn str_rstrip(s: String) -> String {
let n: Int = __str_len(s)
if n == 0 { return "" }
let hi: Int = _find_last_non_ws(s, n)
if hi < 0 { return "" }
return __str_slice_raw(s, 0, hi + 1)
}
// Replacement
fn str_replace(s: String, from: String, to: String) -> String {
let slen: Int = __str_len(s)
let flen: Int = __str_len(from)
if flen == 0 { return s }
if slen == 0 { return s }
// Scan s left-to-right; emit `to` on each match, otherwise emit one byte.
let result: String = ""
let i: Int = 0
while i < slen {
// Try to match `from` at position i
if i + flen <= slen {
let window: String = __str_slice_raw(s, i, i + flen)
if __str_cmp(window, from) == 0 {
result = __str_concat_raw(result, to)
i = i + flen
} else {
let ch: String = __str_slice_raw(s, i, i + 1)
result = __str_concat_raw(result, ch)
i = i + 1
}
} else {
// Not enough bytes left for a match emit remainder and stop.
let tail: String = __str_slice_raw(s, i, slen)
result = __str_concat_raw(result, tail)
i = slen
}
}
return result
}
// Repetition and reversal
fn str_repeat(s: String, n: Int) -> String {
if n <= 0 { return "" }
let slen: Int = __str_len(s)
if slen == 0 { return "" }
let result: String = ""
let i: Int = 0
while i < n {
result = __str_concat_raw(result, s)
i = i + 1
}
return result
}
// Byte-reverse (correct for ASCII; for multi-byte UTF-8 codepoints this
// reverses bytes within a codepoint, which is intentional at this tier
// Phase 2 will add grapheme-aware reversal).
fn str_reverse(s: String) -> String {
let n: Int = __str_len(s)
if n == 0 { return "" }
let out: String = __str_alloc(n)
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
out = __str_set_char(out, n - 1 - i, c)
i = i + 1
}
return out
}
// Prefix/suffix stripping
fn str_strip_prefix(s: String, prefix: String) -> String {
let slen: Int = __str_len(s)
let plen: Int = __str_len(prefix)
if plen == 0 { return s }
if plen > slen { return s }
if __str_ncmp(s, prefix, plen) == 0 {
return __str_slice_raw(s, plen, slen)
}
return s
}
fn str_strip_suffix(s: String, suffix: String) -> String {
let slen: Int = __str_len(s)
let suflen: Int = __str_len(suffix)
if suflen == 0 { return s }
if suflen > slen { return s }
let tail: String = __str_slice_raw(s, slen - suflen, slen)
if __str_cmp(tail, suffix) == 0 {
return __str_slice_raw(s, 0, slen - suflen)
}
return s
}
// Strip leading and trailing bytes whose char code appears in `chars`.
fn str_strip_chars(s: String, chars: String) -> String {
let slen: Int = __str_len(s)
let clen: Int = __str_len(chars)
if slen == 0 { return "" }
if clen == 0 { return s }
let lo: Int = _find_first_not_in_charset(s, chars, slen, clen)
if lo == slen { return "" }
let hi: Int = _find_last_not_in_charset(s, chars, slen, clen)
return __str_slice_raw(s, lo, hi + 1)
}
// Internal: true if char code `c` is present in the charset string.
fn _char_in_set(c: Int, chars: String, clen: Int) -> Bool {
let j: Int = 0
while j < clen {
if c == __str_char_at(chars, j) { return true }
j = j + 1
}
return false
}
fn _find_first_not_in_charset(s: String, chars: String, slen: Int, clen: Int) -> Int {
let i: Int = 0
while i < slen {
if !_char_in_set(__str_char_at(s, i), chars, clen) { return i }
i = i + 1
}
return slen
}
fn _find_last_not_in_charset(s: String, chars: String, slen: Int, clen: Int) -> Int {
let i: Int = slen - 1
while i >= 0 {
if !_char_in_set(__str_char_at(s, i), chars, clen) { return i }
i = i - 1
}
return -1
}
// Padding
// Pad s on the left to `width` total chars, repeating `pad` cyclically.
fn str_pad_left(s: String, width: Int, pad: String) -> String {
let slen: Int = __str_len(s)
if slen >= width { return s }
let plen: Int = __str_len(pad)
if plen == 0 { return s }
let need: Int = width - slen
let prefix: String = ""
let i: Int = 0
while i < need {
// Select pad character at position (i mod plen)
let pad_idx: Int = i - (i / plen) * plen
let pc: String = __str_slice_raw(pad, pad_idx, pad_idx + 1)
prefix = __str_concat_raw(prefix, pc)
i = i + 1
}
return __str_concat_raw(prefix, s)
}
// Pad s on the right to `width` total chars, repeating `pad` cyclically.
fn str_pad_right(s: String, width: Int, pad: String) -> String {
let slen: Int = __str_len(s)
if slen >= width { return s }
let plen: Int = __str_len(pad)
if plen == 0 { return s }
let need: Int = width - slen
let suffix: String = ""
let i: Int = 0
while i < need {
let pad_idx: Int = i - (i / plen) * plen
let pc: String = __str_slice_raw(pad, pad_idx, pad_idx + 1)
suffix = __str_concat_raw(suffix, pc)
i = i + 1
}
return __str_concat_raw(s, suffix)
}
// Counting
// Count non-overlapping occurrences of `sub` in `s`. Empty sub returns 0.
fn str_count(s: String, sub: String) -> Int {
let slen: Int = __str_len(s)
let sublen: Int = __str_len(sub)
if sublen == 0 { return 0 }
if sublen > slen { return 0 }
let count: Int = 0
let limit: Int = slen - sublen
let i: Int = 0
while i <= limit {
let window: String = __str_slice_raw(s, i, i + sublen)
if __str_cmp(window, sub) == 0 {
count = count + 1
i = i + sublen
} else {
i = i + 1
}
}
return count
}
// Byte count alias of str_len.
fn str_count_bytes(s: String) -> Int {
return __str_len(s)
}
// UTF-8 codepoint count: count bytes that are NOT continuation bytes (10xxxxxx).
// Continuation bytes have the pattern 10xxxxxx = 0x80..0xBF (128..191).
fn str_count_chars(s: String) -> Int {
let n: Int = __str_len(s)
let count: Int = 0
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
// Continuation bytes are in range [128, 191]; skip them.
// All other bytes (< 128 ASCII, or >= 192 leading bytes) start a codepoint.
if c < 128 {
count = count + 1
} else {
if c >= 192 { count = count + 1 }
}
i = i + 1
}
return count
}
// Count newline-delimited lines. A trailing newline does NOT add an extra empty line.
fn str_count_lines(s: String) -> Int {
let n: Int = __str_len(s)
if n == 0 { return 0 }
let count: Int = 0
let has_content: Bool = false
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
has_content = true
if c == 10 { // \n
count = count + 1
has_content = false
}
i = i + 1
}
if has_content { count = count + 1 }
return count
}
// Count whitespace-delimited words (non-empty tokens).
fn str_count_words(s: String) -> Int {
let n: Int = __str_len(s)
let count: Int = 0
let in_word: Bool = false
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
if _is_ws(c) {
in_word = false
} else {
if !in_word {
in_word = true
count = count + 1
}
}
i = i + 1
}
return count
}
// Count ASCII letters [A-Za-z].
fn str_count_letters(s: String) -> Int {
let n: Int = __str_len(s)
let count: Int = 0
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
if c >= 65 {
if c <= 90 { count = count + 1 } // A-Z
}
if c >= 97 {
if c <= 122 { count = count + 1 } // a-z
}
i = i + 1
}
return count
}
// Count ASCII decimal digits [0-9].
fn str_count_digits(s: String) -> Int {
let n: Int = __str_len(s)
let count: Int = 0
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
if c >= 48 {
if c <= 57 { count = count + 1 } // '0'-'9'
}
i = i + 1
}
return count
}
// Character classification
//
// For all predicates: empty string -> false.
// Multi-char string: ALL bytes must satisfy the predicate.
fn is_letter(s: String) -> Bool {
let n: Int = __str_len(s)
if n == 0 { return false }
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
let ok: Bool = false
if c >= 65 { if c <= 90 { ok = true } } // A-Z
if c >= 97 { if c <= 122 { ok = true } } // a-z
if !ok { return false }
i = i + 1
}
return true
}
fn is_digit(s: String) -> Bool {
let n: Int = __str_len(s)
if n == 0 { return false }
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
if c < 48 { return false } // '0'
if c > 57 { return false } // '9'
i = i + 1
}
return true
}
fn is_alphanumeric(s: String) -> Bool {
let n: Int = __str_len(s)
if n == 0 { return false }
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
let ok: Bool = false
if c >= 48 { if c <= 57 { ok = true } } // 0-9
if c >= 65 { if c <= 90 { ok = true } } // A-Z
if c >= 97 { if c <= 122 { ok = true } } // a-z
if !ok { return false }
i = i + 1
}
return true
}
fn is_whitespace(s: String) -> Bool {
let n: Int = __str_len(s)
if n == 0 { return false }
let i: Int = 0
while i < n {
if !_is_ws(__str_char_at(s, i)) { return false }
i = i + 1
}
return true
}
// ASCII punctuation: 33-47, 58-64, 91-96, 123-126.
fn is_punctuation(s: String) -> Bool {
let n: Int = __str_len(s)
if n == 0 { return false }
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
let ok: Bool = false
if c >= 33 { if c <= 47 { ok = true } }
if c >= 58 { if c <= 64 { ok = true } }
if c >= 91 { if c <= 96 { ok = true } }
if c >= 123 { if c <= 126 { ok = true } }
if !ok { return false }
i = i + 1
}
return true
}
fn is_uppercase(s: String) -> Bool {
let n: Int = __str_len(s)
if n == 0 { return false }
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
if c < 65 { return false } // 'A'
if c > 90 { return false } // 'Z'
i = i + 1
}
return true
}
fn is_lowercase(s: String) -> Bool {
let n: Int = __str_len(s)
if n == 0 { return false }
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
if c < 97 { return false } // 'a'
if c > 122 { return false } // 'z'
i = i + 1
}
return true
}
// Splitting
fn str_split(s: String, sep: String) -> [String] {
let result: [String] = el_list_empty()
let slen: Int = __str_len(s)
let seplen: Int = __str_len(sep)
// Empty separator: return the whole string as a single element.
if seplen == 0 {
result = el_list_append(result, s)
return result
}
let part_start: Int = 0
let i: Int = 0
while i < slen {
if i + seplen <= slen {
let window: String = __str_slice_raw(s, i, i + seplen)
if __str_cmp(window, sep) == 0 {
let part: String = __str_slice_raw(s, part_start, i)
result = el_list_append(result, part)
i = i + seplen
part_start = i
} else {
i = i + 1
}
} else {
i = i + 1
}
}
// Append remaining tail (may be empty string if s ended with sep).
let tail: String = __str_slice_raw(s, part_start, slen)
result = el_list_append(result, tail)
return result
}
// Split into at most n parts. The nth part (index n-1) contains the remainder
// verbatim, including any further separators. n <= 0 returns []. n == 1
// returns [s].
fn str_split_n(s: String, sep: String, n: Int) -> [String] {
let result: [String] = el_list_empty()
if n <= 0 { return result }
if n == 1 {
result = el_list_append(result, s)
return result
}
let slen: Int = __str_len(s)
let seplen: Int = __str_len(sep)
if seplen == 0 {
result = el_list_append(result, s)
return result
}
let part_start: Int = 0
let parts: Int = 0
let i: Int = 0
while i < slen {
if parts >= n - 1 {
// Reached the split limit stop splitting, emit the rest below.
i = slen
} else {
if i + seplen <= slen {
let window: String = __str_slice_raw(s, i, i + seplen)
if __str_cmp(window, sep) == 0 {
let part: String = __str_slice_raw(s, part_start, i)
result = el_list_append(result, part)
i = i + seplen
part_start = i
parts = parts + 1
} else {
i = i + 1
}
} else {
i = i + 1
}
}
}
// Remainder verbatim.
let tail: String = __str_slice_raw(s, part_start, slen)
result = el_list_append(result, tail)
return result
}
// Split on newlines. \r\n is folded to \n. Trailing empty line after a
// final \n is dropped so "a\nb\n" yields ["a", "b"], not ["a", "b", ""].
fn str_split_lines(s: String) -> [String] {
let result: [String] = el_list_empty()
let n: Int = __str_len(s)
if n == 0 { return result }
let line_start: Int = 0
let i: Int = 0
while i < n {
let c: Int = __str_char_at(s, i)
if c == 10 { // \n
let lend: Int = i
// Fold \r\n: if the byte before \n is \r, exclude it.
if lend > line_start {
if __str_char_at(s, lend - 1) == 13 { lend = lend - 1 }
}
let line: String = __str_slice_raw(s, line_start, lend)
result = el_list_append(result, line)
line_start = i + 1
}
i = i + 1
}
// Trailing content with no terminating \n.
if line_start < n {
let line: String = __str_slice_raw(s, line_start, n)
result = el_list_append(result, line)
}
return result
}
// Split into a list of one-byte strings (byte-level chars).
fn str_split_chars(s: String) -> [String] {
let result: [String] = el_list_empty()
let n: Int = __str_len(s)
let i: Int = 0
while i < n {
let ch: String = __str_slice_raw(s, i, i + 1)
result = el_list_append(result, ch)
i = i + 1
}
return result
}
// Joining
// Join a list of strings with a separator between consecutive elements.
// Empty list yields "". Non-string elements should not be passed here.
fn str_join(parts: [String], sep: String) -> String {
let n: Int = el_list_len(parts)
if n == 0 { return "" }
let result: String = el_list_get(parts, 0)
let i: Int = 1
while i < n {
result = __str_concat_raw(result, sep)
result = __str_concat_raw(result, el_list_get(parts, i))
i = i + 1
}
return result
}
// DHARMA byte encoding (str_to_bytes)
//
// str_to_bytes encode a string as a JSON array of unsigned byte values.
// "hi" -> "[104,105]"
// Used by db.el to store content in Engram JSON nodes as a byte array.
// Note: bytes_to_str (the inverse) is defined in json.el because it depends
// on json_array_get_string which is defined there.
fn str_to_bytes(s: String) -> String {
let n: Int = __str_len(s)
if n == 0 { return "[]" }
let result: String = "["
let i: Int = 0
while i < n {
let b: Int = __str_char_at(s, i)
if i > 0 { result = __str_concat_raw(result, ",") }
result = __str_concat_raw(result, __int_to_str(b))
i = i + 1
}
return __str_concat_raw(result, "]")
}
// Cryptographic hashing
// hash_sha256 return the SHA-256 hex digest of a string.
// Delegates to the __sha256_hex seed primitive.
fn hash_sha256(s: String) -> String {
return __sha256_hex(s)
}
+320
View File
@@ -0,0 +1,320 @@
// runtime/test.el El test framework: assertions, registration, and runner.
//
// Provides a minimal but complete test harness for El programs. No external
// dependencies. Written entirely in El using existing runtime primitives.
//
// Quick-start
//
// 1. Write a test function with the standard test signature:
//
// fn test_str_eq_works(_: String) -> String {
// assert_true(str_eq("a", "a"), "same strings are equal")
// assert_false(str_eq("a", "b"), "different strings are not equal")
// return ""
// }
//
// 2. Register it and run:
//
// fn main() -> Void {
// test_case("str_eq works", "test_str_eq_works")
// test_run_all()
// }
//
// Test functions must have the signature (String) -> String. The argument is
// a dummy passed by the threading mechanism (see runtime/thread.el) and should
// be ignored. The return value is likewise ignored results flow through the
// state-based assertion primitives.
//
// State keys
//
// _test_cases JSON array of {"name":"...","fn":"..."}
// _test_pass_count Int as string total passing assertions
// _test_fail_count Int as string total failing assertions
// _test_failures JSON array of failure message strings
// _test_current Name of the test case currently executing
//
// Dependencies
//
// runtime/string.el str_eq, str_concat, str_contains, int_to_str, ...
// runtime/state.el state_set, state_get
// runtime/json.el json_array_len, json_array_get_string, json_get,
// json_escape_string
// runtime/thread.el spawn, join (for dynamic dispatch via dlsym)
// Internal: JSON array helpers
//
// _test_json_append append a quoted, escaped string element to a JSON array.
//
// Given an existing JSON array string (e.g. '["a","b"]') and a plain string
// value, returns a new array with the value appended (e.g. '["a","b","c"]').
//
// The array must be non-empty always init with "[]" before calling.
fn _test_json_append(arr: String, val: String) -> String {
let escaped: String = json_escape_string(val)
let inner: String = str_slice(arr, 1, str_len(arr) - 1)
if str_eq(inner, "") {
return "[\"" + escaped + "\"]"
}
return "[" + inner + ",\"" + escaped + "\"]"
}
// _test_json_obj_append append a raw JSON object string to a JSON array.
//
// Used to build the _test_cases list where each element is already a
// JSON object (not a plain string).
fn _test_json_obj_append(arr: String, obj: String) -> String {
let inner: String = str_slice(arr, 1, str_len(arr) - 1)
if str_eq(inner, "") {
return "[" + obj + "]"
}
return "[" + inner + "," + obj + "]"
}
// Test registration
// test_case register a named test case.
//
// name: Human-readable test case name (shown in output).
// fn_name: Name of a top-level El function with signature
// (String) -> String. The function should call assertion
// primitives from this module. The String arg it receives is ""
// and its return value is ignored.
//
// Test cases are stored in the _test_cases state key and executed in
// registration order by test_run_all().
fn test_case(name: String, fn_name: String) {
let arr: String = state_get("_test_cases")
if str_eq(arr, "") { arr = "[]" }
let escaped_name: String = json_escape_string(name)
let escaped_fn: String = json_escape_string(fn_name)
let obj: String = "{\"name\":\"" + escaped_name + "\",\"fn\":\"" + escaped_fn + "\"}"
let arr = _test_json_obj_append(arr, obj)
state_set("_test_cases", arr)
}
// Test state helpers
// _test_init reset all test counters and failure lists.
//
// Called at the start of test_run_all(). Safe to call multiple times.
fn _test_init() {
state_set("_test_pass_count", "0")
state_set("_test_fail_count", "0")
state_set("_test_failures", "[]")
state_set("_test_current", "")
}
// _test_inc_pass increment the global pass counter by 1.
fn _test_inc_pass() {
let n: Int = str_to_int(state_get("_test_pass_count"))
state_set("_test_pass_count", int_to_str(n + 1))
}
// _test_inc_fail increment the global fail counter by 1.
fn _test_inc_fail() {
let n: Int = str_to_int(state_get("_test_fail_count"))
state_set("_test_fail_count", int_to_str(n + 1))
}
// test_pass record a passing assertion for the current test case.
//
// Increments the pass counter. Called internally by assertions.
fn test_pass(name: String) {
_test_inc_pass()
}
// test_fail record a failing assertion for the current test case.
//
// name: assertion label or description (usually the msg parameter)
// msg: detailed failure message including expected/got values
//
// Increments the fail counter and appends the message to _test_failures.
// Also prints the failure immediately for visibility.
fn test_fail(name: String, msg: String) {
_test_inc_fail()
let failures: String = state_get("_test_failures")
if str_eq(failures, "") { failures = "[]" }
let entry: String = " " + msg
let failures = _test_json_append(failures, entry)
state_set("_test_failures", failures)
println(" FAIL: " + msg)
}
// Assertions
// assert_true assert that condition is true.
//
// condition: the boolean value to test
// msg: description shown on failure
fn assert_true(condition: Bool, msg: String) {
if condition {
test_pass(msg)
return
}
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg + ": expected true, got false")
}
// assert_false assert that condition is false.
//
// condition: the boolean value to test
// msg: description shown on failure
fn assert_false(condition: Bool, msg: String) {
if !condition {
test_pass(msg)
return
}
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg + ": expected false, got true")
}
// assert_eq assert that two strings are equal.
//
// a, b: strings to compare
// msg: description shown on failure (quoted values appended automatically)
fn assert_eq(a: String, b: String, msg: String) {
if str_eq(a, b) {
test_pass(msg)
return
}
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg + ": expected \"" + b + "\", got \"" + a + "\"")
}
// assert_int_eq assert that two integers are equal.
//
// a, b: integers to compare
// msg: description shown on failure
fn assert_int_eq(a: Int, b: Int, msg: String) {
if a == b {
test_pass(msg)
return
}
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg + ": expected " + int_to_str(b) + ", got " + int_to_str(a))
}
// assert_neq assert that two strings are NOT equal.
//
// a, b: strings to compare
// msg: description shown on failure
fn assert_neq(a: String, b: String, msg: String) {
if !str_eq(a, b) {
test_pass(msg)
return
}
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg + ": expected values to differ, but both are \"" + a + "\"")
}
// assert_contains assert that string s contains substring sub.
//
// s: haystack string
// sub: needle substring
// msg: description shown on failure
fn assert_contains(s: String, sub: String, msg: String) {
if str_contains(s, sub) {
test_pass(msg)
return
}
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg + ": \"" + s + "\" does not contain \"" + sub + "\"")
}
// assert_starts_with assert that string s starts with prefix.
//
// s: string to inspect
// prefix: expected prefix
// msg: description shown on failure
fn assert_starts_with(s: String, prefix: String, msg: String) {
if str_starts_with(s, prefix) {
test_pass(msg)
return
}
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg + ": \"" + s + "\" does not start with \"" + prefix + "\"")
}
// assert_ends_with assert that string s ends with suffix.
//
// s: string to inspect
// suffix: expected suffix
// msg: description shown on failure
fn assert_ends_with(s: String, suffix: String, msg: String) {
if str_ends_with(s, suffix) {
test_pass(msg)
return
}
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg + ": \"" + s + "\" does not end with \"" + suffix + "\"")
}
// fail unconditional test failure.
//
// msg: failure message shown in output
//
// Use when a code path that must not be reached is reached, or when an
// expected exception did not occur.
fn fail(msg: String) {
let test_name: String = state_get("_test_current")
test_fail(msg, "[" + test_name + "] " + msg)
}
// Runner
// _test_run_one execute a single registered test case by name.
//
// name: the human-readable test case name (set as _test_current)
// fn_name: the El function to invoke via the thread mechanism
//
// Sets _test_current so that assertions inside the test function know which
// test they belong to. Spawns and immediately joins the test function in a
// child thread (same dlsym mechanism as parallel_map) so dynamic dispatch
// works without needing closures.
fn _test_run_one(name: String, fn_name: String) {
state_set("_test_current", name)
let before_fail: Int = str_to_int(state_get("_test_fail_count"))
let tid: Int = __thread_create(fn_name, "")
__thread_join(tid)
let after_fail: Int = str_to_int(state_get("_test_fail_count"))
if after_fail == before_fail {
println("[test] " + name + " ... PASS")
} else {
println("[test] " + name + " ... FAIL")
}
}
// test_run_all execute all registered test cases and print a summary.
//
// Iterates through every test case registered via test_case(), runs each one,
// prints per-test PASS/FAIL status, then prints a summary line.
//
// Returns the total number of failing assertions. Exit with this value to
// signal CI failure:
//
// fn main() -> Int {
// test_case("str_eq", "test_str_eq")
// return test_run_all()
// }
fn test_run_all() -> Int {
_test_init()
let cases: String = state_get("_test_cases")
if str_eq(cases, "") { cases = "[]" }
let n: Int = json_array_len(cases)
let i: Int = 0
while i < n {
let entry: String = json_array_get(cases, i)
let name: String = json_get(entry, "name")
let fn_name: String = json_get(entry, "fn")
_test_run_one(name, fn_name)
i = i + 1
}
let pass_count: Int = str_to_int(state_get("_test_pass_count"))
let fail_count: Int = str_to_int(state_get("_test_fail_count"))
println("[test] Summary: " + int_to_str(pass_count) + " passed, " + int_to_str(fail_count) + " failed")
return fail_count
}
+225
View File
@@ -0,0 +1,225 @@
// thread.el El native threading model
//
// First-class parallelism for El. Eliminates bash fan-out hacks for parallel
// HTTP dispatch, concurrent processing pipelines, and any other workload that
// benefits from concurrent execution.
//
// Built on four seed primitives exposed by el_seed.c via dlsym+pthread:
// __thread_create(fn_name, arg) -> Int spawn thread, return tid
// __thread_join(tid) -> String join thread, return result
// __mutex_new() -> Int allocate a mutex, return handle
// __mutex_lock(m) lock mutex
// __mutex_unlock(m) unlock mutex
//
// Every El fn compiles to a global C symbol. __thread_create uses dlsym to
// look up the function by name and run it in a pthread. This means any El fn
// with signature (String) -> String is directly threadable.
// Core primitives
// spawn launch an El function in a new thread.
//
// fn_name: the name of an El fn with signature (String) -> String
// arg: the argument to pass to that fn
//
// Returns a thread id (tid) that can be passed to join().
// The El function must be a top-level fn its C symbol must be globally
// visible so dlsym can resolve it.
fn spawn(fn_name: String, arg: String) -> Int {
return __thread_create(fn_name, arg)
}
// join wait for a thread to finish and return its result.
//
// tid: the thread id returned by spawn()
//
// Blocks until the thread completes. Returns the String value the thread
// function returned.
fn join(tid: Int) -> String {
return __thread_join(tid)
}
// parallel_map
// parallel_map map an El function over a list of strings concurrently.
//
// items: [String] the input list
// fn_name: String name of an El fn with signature (String) -> String
//
// Spawns one thread per item. All threads run concurrently. Joins each thread
// in input order, so the output list preserves the same order as the input.
//
// This is the core primitive that replaces bash fan-out for parallel HTTP.
// Example dispatch to N rooms at once:
// let responses: [String] = parallel_map(room_payloads, "dispatch_to_room")
fn parallel_map(items: [String], fn_name: String) -> [String] {
let n: Int = el_list_len(items)
// Phase 1: spawn all threads and collect tids in order.
let tids: [String] = el_list_empty()
let i = 0
while i < n {
let item: String = el_list_get(items, i)
let tid: Int = spawn(fn_name, item)
// Store tid as string so we can hold it in [String].
// int_to_str is available as a builtin.
let tids = el_list_append(tids, int_to_str(tid))
let i = i + 1
}
// Phase 2: join all threads in order, collecting results.
let results: [String] = el_list_empty()
let j = 0
while j < n {
let tid_str: String = el_list_get(tids, j)
let tid: Int = str_to_int(tid_str)
let result: String = join(tid)
let results = el_list_append(results, result)
let j = j + 1
}
return results
}
// parallel_map_json
// parallel_map_json parallel_map over a JSON array string.
//
// items_json: String a JSON array of strings, e.g. '["a","b","c"]'
// fn_name: String name of an El fn with signature (String) -> String
//
// Parses the JSON array into an [String], runs parallel_map, then serialises
// the result list back to a JSON array string. Both input and output are JSON
// strings the common El inter-service format.
//
// Example:
// let out_json: String = parallel_map_json(rooms_json, "dispatch_to_room")
fn parallel_map_json(items_json: String, fn_name: String) -> String {
let n: Int = json_array_len(items_json)
// Unpack JSON array into [String].
let items: [String] = el_list_empty()
let i = 0
while i < n {
let item: String = json_array_get(items_json, i)
let items = el_list_append(items, item)
let i = i + 1
}
// Run the concurrent map.
let results: [String] = parallel_map(items, fn_name)
// Repack results into a JSON array string.
let m: Int = el_list_len(results)
let out: String = "["
let j = 0
while j < m {
let val: String = el_list_get(results, j)
if j > 0 {
let out = out + ","
}
// Each result is treated as a raw JSON value (object, array, or
// quoted string as returned by the worker fn).
let out = out + val
let j = j + 1
}
let out = out + "]"
return out
}
// parallel_filter
// parallel_filter keep items where fn_name returns "true", concurrently.
//
// items: [String] the input list
// fn_name: String name of an El fn with signature (String) -> String
// that returns "true" to keep the item or anything else
// to discard it
//
// Runs the predicate fn on all items in parallel. Collects results in order,
// preserving the relative order of kept items.
fn parallel_filter(items: [String], fn_name: String) -> [String] {
let n: Int = el_list_len(items)
// Spawn a predicate thread for every item.
let tids: [String] = el_list_empty()
let i = 0
while i < n {
let item: String = el_list_get(items, i)
let tid: Int = spawn(fn_name, item)
let tids = el_list_append(tids, int_to_str(tid))
let i = i + 1
}
// Join in order, keep item if the predicate returned "true".
let kept: [String] = el_list_empty()
let j = 0
while j < n {
let item: String = el_list_get(items, j)
let tid_str: String = el_list_get(tids, j)
let tid: Int = str_to_int(tid_str)
let verdict: String = join(tid)
if str_eq(verdict, "true") {
let kept = el_list_append(kept, item)
}
let j = j + 1
}
return kept
}
// HTTP helpers
// fire_http_post worker fn for parallel_posts.
//
// Expects arg to be a JSON object with "url" and "body" keys:
// {"url":"https://...","body":"{...}"}
//
// Returns the HTTP response body string. Registered as a global El fn so
// parallel_map can locate it via dlsym.
fn fire_http_post(arg: String) -> String {
let url: String = json_get(arg, "url")
let body: String = json_get(arg, "body")
return http_post(url, body)
}
// parallel_posts fire a list of HTTP POSTs concurrently.
//
// requests: [String] each element is a JSON object {"url":"...","body":"..."}
//
// Returns [String] of response bodies in the same order as the input.
//
// Example fan out to N room endpoints at once:
// let reqs: [String] = el_list_empty()
// let reqs = el_list_append(reqs, "{\"url\":\"http://room-a/dispatch\",\"body\":\"" + payload + "\"}")
// let reqs = el_list_append(reqs, "{\"url\":\"http://room-b/dispatch\",\"body\":\"" + payload + "\"}")
// let responses: [String] = parallel_posts(reqs)
fn parallel_posts(requests: [String]) -> [String] {
return parallel_map(requests, "fire_http_post")
}
// Mutex helpers
// with_mutex call fn_name(arg) while holding mutex m.
//
// m: Int mutex handle returned by __mutex_new()
// fn_name: String name of an El fn with signature (String) -> String
// arg: String argument to pass to fn_name
//
// Locks the mutex, spawns fn_name(arg) in a child thread, joins to collect
// the result, then unlocks. The mutex is held across the entire duration of
// fn_name's execution, serializing concurrent callers.
//
// Note: fn_name must NOT itself acquire the same mutex that would deadlock.
// This is the standard reentrant-mutex caveat.
//
// Usage:
// let m: Int = __mutex_new()
// let result: String = with_mutex(m, "update_shared_state", payload)
fn with_mutex(m: Int, fn_name: String, arg: String) -> String {
__mutex_lock(m)
let tid: Int = spawn(fn_name, arg)
let result: String = join(tid)
__mutex_unlock(m)
return result
}
+425
View File
@@ -0,0 +1,425 @@
// runtime/time.el Time operations, sleep, and formatting.
//
// Implements the time surface from el-compiler/runtime/legacy/el_runtime.c
// (lines 33343440, 34713656) in pure El, using seed primitives.
//
// Seed primitives consumed:
// __time_now_ns() -> Int (nanoseconds since Unix epoch)
// __sleep_ms(n: Int)
// __int_to_str(n: Int) -> String
// __str_to_int(s: String) -> Int
// __float_to_str(f: Float) -> String
// ---------------------------------------------------------------------------
// Core now / sleep
// ---------------------------------------------------------------------------
// time_now milliseconds since Unix epoch (UTC). Matches legacy time_now().
fn time_now() -> Int {
return __time_now_ns() / 1000000
}
// time_now_utc same as time_now; UTC alias kept for compatibility.
fn time_now_utc() -> Int {
return __time_now_ns() / 1000000
}
// now_ns nanoseconds since Unix epoch. Matches el_now_instant().
fn now_ns() -> Int {
return __time_now_ns()
}
// unix_timestamp whole seconds since Unix epoch. Matches unix_timestamp().
fn unix_timestamp() -> Int {
return __time_now_ns() / 1000000000
}
// sleep_secs block for n seconds. Clamps negatives to 0.
fn sleep_secs(n: Int) {
if n < 0 {
__sleep_ms(0)
} else {
__sleep_ms(n * 1000)
}
}
// sleep_ms block for n milliseconds. Clamps negatives to 0.
fn sleep_ms(n: Int) {
if n < 0 {
__sleep_ms(0)
} else {
__sleep_ms(n)
}
}
// ---------------------------------------------------------------------------
// Gregorian decomposition helpers pure integer arithmetic.
//
// Algorithm: civil date from days since Unix epoch (1970-01-01).
// Based on Howard Hinnant's public-domain civil_from_days formula
// (http://howardhinnant.github.io/date_algorithms.html), which the legacy
// gmtime_r call performs under the hood.
//
// We expose the pieces as private helpers (leading underscore convention).
// ---------------------------------------------------------------------------
// _is_leap 1 if year y is a Gregorian leap year, 0 otherwise.
fn _is_leap(y: Int) -> Int {
if y % 400 == 0 { return 1 }
if y % 100 == 0 { return 0 }
if y % 4 == 0 { return 1 }
return 0
}
// _days_in_month number of days in month m of year y (m: 1..12).
fn _days_in_month(y: Int, m: Int) -> Int {
if m == 1 { return 31 }
if m == 2 {
if _is_leap(y) == 1 { return 29 }
return 28
}
if m == 3 { return 31 }
if m == 4 { return 30 }
if m == 5 { return 31 }
if m == 6 { return 30 }
if m == 7 { return 31 }
if m == 8 { return 31 }
if m == 9 { return 30 }
if m == 10 { return 31 }
if m == 11 { return 30 }
return 31
}
// _pad2 zero-pad an integer to at least 2 digits.
fn _pad2(n: Int) -> String {
if n < 10 { return "0" + __int_to_str(n) }
return __int_to_str(n)
}
// _pad4 zero-pad an integer to at least 4 digits.
fn _pad4(n: Int) -> String {
if n < 10 { return "000" + __int_to_str(n) }
if n < 100 { return "00" + __int_to_str(n) }
if n < 1000 { return "0" + __int_to_str(n) }
return __int_to_str(n)
}
// _pad3 zero-pad an integer to at least 3 digits (milliseconds).
fn _pad3(n: Int) -> String {
if n < 10 { return "00" + __int_to_str(n) }
if n < 100 { return "0" + __int_to_str(n) }
return __int_to_str(n)
}
// _civil_year_month_day decompose days-since-epoch (z, may be negative)
// into year/month/day using the civil_from_days algorithm.
// Returns a JSON object: {"year":Y,"month":M,"day":D}
fn _civil_ymd(z: Int) -> String {
// shift epoch to 0000-03-01 (makes leap-day math clean)
let zz: Int = z + 719468
// era: 400-year block
let era: Int = zz / 146097
if zz < 0 {
era = (zz - 146096) / 146097
}
let doe: Int = zz - era * 146097 // day-of-era [0, 146096]
let yoe: Int = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365 // year-of-era [0, 399]
let y: Int = yoe + era * 400
let doy: Int = doe - (365 * yoe + yoe / 4 - yoe / 100) // day-of-year [0, 365]
let mp: Int = (5 * doy + 2) / 153 // month in [0, 11] from March
let d: Int = doy - (153 * mp + 2) / 5 + 1 // day [1, 31]
let m: Int = mp + 3
if mp >= 10 { m = mp - 9 }
if mp >= 10 { y = y + 1 }
return "{\"year\":" + __int_to_str(y) + ",\"month\":" + __int_to_str(m) + ",\"day\":" + __int_to_str(d) + "}"
}
// ---------------------------------------------------------------------------
// time_to_parts decompose a millisecond timestamp into UTC components.
//
// Returns a JSON string:
// {"year":Y,"month":M,"day":D,"hour":H,"minute":M,"second":S,"ms":MS}
//
// Matches legacy time_to_parts() which returns an ElMap with the same keys.
// ---------------------------------------------------------------------------
fn time_to_parts(ts: Int) -> String {
let ms_raw: Int = ts % 1000
let ms: Int = ms_raw
if ms_raw < 0 { ms = ms_raw + 1000 }
let s_raw: Int = ts / 1000
let s: Int = s_raw
if ms_raw < 0 { s = s_raw - 1 }
// seconds within the day and days since epoch
let sec_of_day: Int = s % 86400
let day_z: Int = s / 86400
// handle negative: floor division
if sec_of_day < 0 {
sec_of_day = sec_of_day + 86400
day_z = day_z - 1
}
let hour: Int = sec_of_day / 3600
let rem: Int = sec_of_day % 3600
let minute: Int = rem / 60
let second: Int = rem % 60
// date components via civil decomposition
let ymd: String = _civil_ymd(day_z)
let year: Int = __str_to_int(json_get(ymd, "year"))
let month: Int = __str_to_int(json_get(ymd, "month"))
let day: Int = __str_to_int(json_get(ymd, "day"))
return "{\"year\":" + __int_to_str(year) +
",\"month\":" + __int_to_str(month) +
",\"day\":" + __int_to_str(day) +
",\"hour\":" + __int_to_str(hour) +
",\"minute\":" + __int_to_str(minute) +
",\"second\":" + __int_to_str(second) +
",\"ms\":" + __int_to_str(ms) + "}"
}
// ---------------------------------------------------------------------------
// time_format format a millisecond timestamp as a string.
//
// fmt "ISO" (or empty) "YYYY-MM-DDTHH:MM:SS.mmmZ" (ISO 8601 UTC)
// Other fmt values are passed as a strftime-style hint; the El runtime
// implements the most common tokens. Unsupported tokens are passed through.
//
// Matches legacy time_format().
// ---------------------------------------------------------------------------
fn time_format(ts: Int, fmt: String) -> String {
let parts: String = time_to_parts(ts)
let y: Int = __str_to_int(json_get(parts, "year"))
let mo: Int = __str_to_int(json_get(parts, "month"))
let d: Int = __str_to_int(json_get(parts, "day"))
let h: Int = __str_to_int(json_get(parts, "hour"))
let mi: Int = __str_to_int(json_get(parts, "minute"))
let s: Int = __str_to_int(json_get(parts, "second"))
let ms: Int = __str_to_int(json_get(parts, "ms"))
// ISO 8601 UTC: YYYY-MM-DDTHH:MM:SS.mmmZ
if str_eq(fmt, "ISO") {
return _pad4(y) + "-" + _pad2(mo) + "-" + _pad2(d) +
"T" + _pad2(h) + ":" + _pad2(mi) + ":" + _pad2(s) +
"." + _pad3(ms) + "Z"
}
if str_eq(fmt, "") {
return _pad4(y) + "-" + _pad2(mo) + "-" + _pad2(d) +
"T" + _pad2(h) + ":" + _pad2(mi) + ":" + _pad2(s) +
"." + _pad3(ms) + "Z"
}
// strftime-subset: replace common tokens
let out: String = fmt
let out = str_replace(out, "%Y", _pad4(y))
let out = str_replace(out, "%m", _pad2(mo))
let out = str_replace(out, "%d", _pad2(d))
let out = str_replace(out, "%H", _pad2(h))
let out = str_replace(out, "%M", _pad2(mi))
let out = str_replace(out, "%S", _pad2(s))
let out = str_replace(out, "%3N", _pad3(ms))
return out
}
// ---------------------------------------------------------------------------
// time_from_parts construct a ms timestamp from seconds + nanosecond offset.
//
// Matches legacy time_from_parts(secs, ns, tz) tz is ignored (UTC assumed).
// ---------------------------------------------------------------------------
fn time_from_parts(secs: Int, ns: Int, tz: String) -> Int {
return secs * 1000 + ns / 1000000
}
// ---------------------------------------------------------------------------
// time_add add a duration to a millisecond timestamp.
//
// unit: "ms" | "sec" | "min" | "hour" | "day"
// Matches legacy time_add().
// ---------------------------------------------------------------------------
fn time_add(ts: Int, n: Int, unit: String) -> Int {
if str_eq(unit, "ms") { return ts + n }
if str_eq(unit, "sec") { return ts + n * 1000 }
if str_eq(unit, "min") { return ts + n * 60000 }
if str_eq(unit, "hour") { return ts + n * 3600000 }
if str_eq(unit, "day") { return ts + n * 86400000 }
// default: treat as ms
return ts + n
}
// ---------------------------------------------------------------------------
// time_diff compute the difference between two millisecond timestamps.
//
// Returns ts2 - ts1 in the given unit.
// unit: "ms" | "sec" | "min" | "hour" | "day"
// Matches legacy time_diff().
// ---------------------------------------------------------------------------
fn time_diff(ts1: Int, ts2: Int, unit: String) -> Int {
let d: Int = ts2 - ts1
if str_eq(unit, "ms") { return d }
if str_eq(unit, "sec") { return d / 1000 }
if str_eq(unit, "min") { return d / 60000 }
if str_eq(unit, "hour") { return d / 3600000 }
if str_eq(unit, "day") { return d / 86400000 }
return d
}
// ---------------------------------------------------------------------------
// Instant / Duration nanosecond-precision temporal types.
//
// These match the el_now_instant, duration_seconds, duration_millis, etc.
// family from legacy lines 34713656. Both Instant and Duration are Int
// (nanoseconds); the type distinction is at the call-site convention level.
// ---------------------------------------------------------------------------
// now current Instant in nanoseconds. Alias for __time_now_ns().
fn now() -> Int {
return __time_now_ns()
}
// unix_seconds Instant from whole seconds since epoch.
fn unix_seconds(n: Int) -> Int {
return n * 1000000000
}
// unix_millis Instant from milliseconds since epoch.
fn unix_millis(n: Int) -> Int {
return n * 1000000
}
// instant_to_unix_seconds convert Instant nanoseconds to whole seconds.
fn instant_to_unix_seconds(i: Int) -> Int {
return i / 1000000000
}
// instant_to_unix_millis convert Instant nanoseconds to milliseconds.
fn instant_to_unix_millis(i: Int) -> Int {
return i / 1000000
}
// instant_to_iso8601 format an Instant (nanoseconds) as ISO 8601 UTC.
fn instant_to_iso8601(i: Int) -> String {
let ms: Int = i / 1000000
return time_format(ms, "ISO")
}
// duration_seconds Duration from n whole seconds.
fn duration_seconds(n: Int) -> Int {
return n * 1000000000
}
// duration_millis Duration from n milliseconds.
fn duration_millis(n: Int) -> Int {
return n * 1000000
}
// duration_nanos Duration from n nanoseconds (identity).
fn duration_nanos(n: Int) -> Int {
return n
}
// duration_to_seconds convert a Duration (nanoseconds) to whole seconds.
fn duration_to_seconds(d: Int) -> Int {
return d / 1000000000
}
// duration_to_millis convert a Duration (nanoseconds) to milliseconds.
fn duration_to_millis(d: Int) -> Int {
return d / 1000000
}
// duration_to_nanos return the Duration as nanoseconds (identity).
fn duration_to_nanos(d: Int) -> Int {
return d
}
// sleep_duration sleep for a Duration (nanoseconds). Clamps negatives to 0.
fn sleep_duration(dur: Int) {
let ms: Int = dur / 1000000
if ms < 0 {
__sleep_ms(0)
} else {
__sleep_ms(ms)
}
}
// ---------------------------------------------------------------------------
// TTL cache time-bounded key/value backed by state.
//
// Matches legacy ttl_cache_set / ttl_cache_get / ttl_cache_age (lines 36633717).
// max_age is a Duration (nanoseconds).
// ---------------------------------------------------------------------------
// ttl_cache_set store a value and record the current Instant for TTL checks.
fn ttl_cache_set(key: String, value: String) {
state_set(key, value)
let stamp_key: String = "__ttl_at:" + key
let now_str: String = __int_to_str(__time_now_ns())
state_set(stamp_key, now_str)
}
// ttl_cache_get return value if age < max_age (nanoseconds), else "".
fn ttl_cache_get(key: String, max_age: Int) -> String {
let stamp_key: String = "__ttl_at:" + key
let sv: String = state_get(stamp_key)
if str_eq(sv, "") { return "" }
let set_at: Int = __str_to_int(sv)
let now_ns: Int = __time_now_ns()
let age: Int = now_ns - set_at
if age < 0 { return "" }
if age > max_age { return "" }
return state_get(key)
}
// ttl_cache_age nanoseconds since a key was last set (INT_MAX sentinel if missing).
fn ttl_cache_age(key: String) -> Int {
let stamp_key: String = "__ttl_at:" + key
let sv: String = state_get(stamp_key)
if str_eq(sv, "") { return 9223372036854775807 }
let set_at: Int = __str_to_int(sv)
return __time_now_ns() - set_at
}
// ---------------------------------------------------------------------------
// uuid_new / uuid_v4 generate a UUID v4 string.
//
// Delegates to the __uuid_v4() seed primitive.
// Matches legacy uuid_new() / uuid_v4() (lines 46024621).
// ---------------------------------------------------------------------------
fn uuid_new() -> String {
return __uuid_v4()
}
fn uuid_v4() -> String {
return __uuid_v4()
}
// ---------------------------------------------------------------------------
// DHARMA-compatible aliases millisecond-precision timestamps.
//
// now_millis, unix_timestamp_ms, and time_now_ms all return the same value:
// milliseconds since the Unix epoch. They exist because different parts of
// the dharma codebase use different names for the same concept.
// ---------------------------------------------------------------------------
// now_millis milliseconds since Unix epoch. Alias for time_now().
fn now_millis() -> Int {
return __time_now_ns() / 1000000
}
// unix_timestamp_ms same as now_millis.
fn unix_timestamp_ms() -> Int {
return __time_now_ns() / 1000000
}
// time_now_ms same as now_millis.
fn time_now_ms() -> Int {
return __time_now_ns() / 1000000
}