Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b2a1246e7 | |||
| f78da81aa4 | |||
| 2597a092bb | |||
| 226b798407 | |||
| cfe8cb1c80 | |||
| 688b8508fb | |||
| 59cea116c5 | |||
| deb0520551 | |||
| da116b2884 | |||
| 58753a88d7 | |||
| edff25180e | |||
| 6271cb42b2 | |||
| 3da9181deb | |||
| 192241c7c1 | |||
| e7c2dc7734 | |||
| f7bd99ae45 | |||
| 93d36fddb1 | |||
| 2d751890ea |
+3
-3
@@ -1,7 +1,7 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn sem_get(json: String, key: String) -> String
|
||||
extern fn generate_frame(frame: Any) -> String
|
||||
extern fn generate_frame_lang(frame: Any, lang_code: String) -> String
|
||||
extern fn build_form_from_json(semantic_form_json: String, lang_code: String) -> Any
|
||||
extern fn generate_frame(frame: [String]) -> String
|
||||
extern fn generate_frame_lang(frame: [String], lang_code: String) -> String
|
||||
extern fn build_form_from_json(semantic_form_json: String, lang_code: String) -> [String]
|
||||
extern fn generate(semantic_form_json: String) -> String
|
||||
extern fn generate_lang(semantic_form_json: String, lang_code: String) -> String
|
||||
|
||||
+28
-28
@@ -1,22 +1,22 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
extern fn slots_get(slots: Any, key: String) -> String
|
||||
extern fn slots_set(slots: Any, key: String, val: String) -> Any
|
||||
extern fn make_slots(k0: String, v0: String) -> Any
|
||||
extern fn make_slots2(k0: String, v0: String, k1: String, v1: String) -> Any
|
||||
extern fn make_slots3(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String) -> Any
|
||||
extern fn make_slots4(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String) -> Any
|
||||
extern fn make_slots5(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String, k4: String, v4: String) -> Any
|
||||
extern fn rule_id(rule: Any) -> String
|
||||
extern fn rule_lhs(rule: Any) -> String
|
||||
extern fn rule_rhs_len(rule: Any) -> Int
|
||||
extern fn rule_rhs(rule: Any, idx: Int) -> String
|
||||
extern fn make_rule(id: String, lhs: String, r0: String) -> Any
|
||||
extern fn make_rule2(id: String, lhs: String, r0: String, r1: String) -> Any
|
||||
extern fn make_rule3(id: String, lhs: String, r0: String, r1: String, r2: String) -> Any
|
||||
extern fn make_rule4(id: String, lhs: String, r0: String, r1: String, r2: String, r3: String) -> Any
|
||||
extern fn build_rules() -> Any
|
||||
extern fn get_rules() -> Any
|
||||
extern fn find_rule(rule_id_str: String) -> Any
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn slots_get(slots: [String], key: String) -> String
|
||||
extern fn slots_set(slots: [String], key: String, val: String) -> [String]
|
||||
extern fn make_slots(k0: String, v0: String) -> [String]
|
||||
extern fn make_slots2(k0: String, v0: String, k1: String, v1: String) -> [String]
|
||||
extern fn make_slots3(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String) -> [String]
|
||||
extern fn make_slots4(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String) -> [String]
|
||||
extern fn make_slots5(k0: String, v0: String, k1: String, v1: String, k2: String, v2: String, k3: String, v3: String, k4: String, v4: String) -> [String]
|
||||
extern fn rule_id(rule: [String]) -> String
|
||||
extern fn rule_lhs(rule: [String]) -> String
|
||||
extern fn rule_rhs_len(rule: [String]) -> Int
|
||||
extern fn rule_rhs(rule: [String], idx: Int) -> String
|
||||
extern fn make_rule(id: String, lhs: String, r0: String) -> [String]
|
||||
extern fn make_rule2(id: String, lhs: String, r0: String, r1: String) -> [String]
|
||||
extern fn make_rule3(id: String, lhs: String, r0: String, r1: String, r2: String) -> [String]
|
||||
extern fn make_rule4(id: String, lhs: String, r0: String, r1: String, r2: String, r3: String) -> [String]
|
||||
extern fn build_rules() -> [[String]]
|
||||
extern fn get_rules() -> [[String]]
|
||||
extern fn find_rule(rule_id_str: String) -> [String]
|
||||
extern fn make_leaf(label: String, word: String) -> String
|
||||
extern fn make_node1(label: String, child0: String) -> String
|
||||
extern fn make_node2(label: String, child0: String, child1: String) -> String
|
||||
@@ -24,15 +24,15 @@ extern fn make_node3(label: String, child0: String, child1: String, child2: Stri
|
||||
extern fn make_node4(label: String, child0: String, child1: String, child2: String, child3: String) -> String
|
||||
extern fn nlg_is_ws(c: String) -> Bool
|
||||
extern fn skip_ws(s: String, pos: Int) -> Int
|
||||
extern fn scan_token(s: String, start: Int) -> Any
|
||||
extern fn scan_token(s: String, start: Int) -> [String]
|
||||
extern fn render_tree(tree: String) -> String
|
||||
extern fn gram_word_order(profile: Any) -> String
|
||||
extern fn gram_order_constituents(subj: String, verb: String, obj: String, profile: Any) -> String
|
||||
extern fn gram_build_vp(verb: String, aux: String, profile: Any) -> String
|
||||
extern fn gram_question_strategy(profile: Any) -> String
|
||||
extern fn gram_word_order(profile: [String]) -> String
|
||||
extern fn gram_order_constituents(subj: String, verb: String, obj: String, profile: [String]) -> String
|
||||
extern fn gram_build_vp(verb: String, aux: String, profile: [String]) -> String
|
||||
extern fn gram_question_strategy(profile: [String]) -> String
|
||||
extern fn is_pronoun(word: String) -> Bool
|
||||
extern fn build_np(referent: String, slots: Any) -> String
|
||||
extern fn build_np(referent: String, slots: [String]) -> String
|
||||
extern fn build_pp(loc: String) -> String
|
||||
extern fn build_vp_body(slots: Any) -> String
|
||||
extern fn build_vp_from_slots(slots: Any) -> String
|
||||
extern fn generate_tree(rule_id_str: String, slots: Any) -> String
|
||||
extern fn build_vp_body(slots: [String]) -> String
|
||||
extern fn build_vp_from_slots(slots: [String]) -> String
|
||||
extern fn generate_tree(rule_id_str: String, slots: [String]) -> String
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn agent_person(agent: String) -> String
|
||||
extern fn agent_number(agent: String) -> String
|
||||
extern fn realize_np(referent: String, number: String) -> String
|
||||
extern fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: Any) -> Any
|
||||
extern fn realize_question_lang(predicate: String, tense: String, aspect: String, person: String, number: String, agent: String, patient: String, location: String, profile: Any) -> String
|
||||
extern fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String]
|
||||
extern fn realize_question_lang(predicate: String, tense: String, aspect: String, person: String, number: String, agent: String, patient: String, location: String, profile: [String]) -> String
|
||||
extern fn capitalize_first(s: String) -> String
|
||||
extern fn add_punct(s: String, intent: String) -> String
|
||||
extern fn realize_lang(form: Any, profile: Any) -> String
|
||||
extern fn realize(form: Any) -> String
|
||||
extern fn realize_lang(form: [String], profile: [String]) -> String
|
||||
extern fn realize(form: [String]) -> String
|
||||
|
||||
+15
-15
@@ -1,18 +1,18 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
extern fn sem_frame(intent: String, subject: String, obj: String, modifiers: String) -> Any
|
||||
extern fn sem_frame_lang(intent: String, subject: String, obj: String, modifiers: String, lang_code: String) -> Any
|
||||
extern fn sem_frame_simple(intent: String, subject: String) -> Any
|
||||
extern fn sem_frame_obj(intent: String, subject: String, obj: String) -> Any
|
||||
extern fn sem_intent(frame: Any) -> String
|
||||
extern fn sem_subject(frame: Any) -> String
|
||||
extern fn sem_object(frame: Any) -> String
|
||||
extern fn sem_modifiers(frame: Any) -> String
|
||||
extern fn sem_lang(frame: Any) -> String
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn sem_frame(intent: String, subject: String, obj: String, modifiers: String) -> [String]
|
||||
extern fn sem_frame_lang(intent: String, subject: String, obj: String, modifiers: String, lang_code: String) -> [String]
|
||||
extern fn sem_frame_simple(intent: String, subject: String) -> [String]
|
||||
extern fn sem_frame_obj(intent: String, subject: String, obj: String) -> [String]
|
||||
extern fn sem_intent(frame: [String]) -> String
|
||||
extern fn sem_subject(frame: [String]) -> String
|
||||
extern fn sem_object(frame: [String]) -> String
|
||||
extern fn sem_modifiers(frame: [String]) -> String
|
||||
extern fn sem_lang(frame: [String]) -> String
|
||||
extern fn sem_first_modifier(mods: String) -> String
|
||||
extern fn sem_intent_to_realize(intent: String) -> String
|
||||
extern fn sem_to_spec(frame: Any) -> Any
|
||||
extern fn sem_to_spec_full(frame: Any, verb: String, tense: String, aspect: String) -> Any
|
||||
extern fn sem_to_spec(frame: [String]) -> [String]
|
||||
extern fn sem_to_spec_full(frame: [String], verb: String, tense: String, aspect: String) -> [String]
|
||||
extern fn sem_realize_greet(subject: String) -> String
|
||||
extern fn sem_realize(frame: Any) -> String
|
||||
extern fn sem_realize_full(frame: Any, verb: String, tense: String, aspect: String) -> String
|
||||
extern fn sem_realize_lang(frame: Any, lang_code: String) -> String
|
||||
extern fn sem_realize(frame: [String]) -> String
|
||||
extern fn sem_realize_full(frame: [String], verb: String, tense: String, aspect: String) -> String
|
||||
extern fn sem_realize_lang(frame: [String], lang_code: String) -> String
|
||||
|
||||
BIN
Binary file not shown.
Vendored
+38
-2
@@ -20,6 +20,8 @@ el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_strengthen(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_create_ise(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body);
|
||||
@@ -115,7 +117,7 @@ el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body) {
|
||||
node_type = EL_STR("Memory");
|
||||
}
|
||||
el_val_t salience = json_get_float(body, EL_STR("salience"));
|
||||
if (str_eq(salience, el_from_float(0.0))) {
|
||||
if (salience == el_from_float(0.0)) {
|
||||
salience = el_from_float(0.5);
|
||||
}
|
||||
el_val_t id = engram_node(content, node_type, salience);
|
||||
@@ -205,7 +207,7 @@ el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body) {
|
||||
relation = EL_STR("associates");
|
||||
}
|
||||
el_val_t weight = json_get_float(body, EL_STR("weight"));
|
||||
if (str_eq(weight, el_from_float(0.0))) {
|
||||
if (weight == el_from_float(0.0)) {
|
||||
weight = el_from_float(0.5);
|
||||
}
|
||||
engram_connect(from_id, to_id, weight, relation);
|
||||
@@ -243,6 +245,34 @@ el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_create_ise(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t content = json_get_string(body, EL_STR("content"));
|
||||
if (str_eq(content, EL_STR(""))) {
|
||||
return err_json(EL_STR("missing content"));
|
||||
}
|
||||
el_val_t sal = el_from_float(0.3);
|
||||
el_val_t imp = el_from_float(0.3);
|
||||
el_val_t conf = el_from_float(0.8);
|
||||
el_val_t id = engram_node_full(content, EL_STR("InternalStateEvent"), EL_STR("state-event"), sal, imp, conf, EL_STR("Episodic"), EL_STR("[\"internal-state\",\"InternalStateEvent\"]"));
|
||||
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
if (str_eq(dir, EL_STR(""))) {
|
||||
dir = EL_STR("/tmp/engram");
|
||||
}
|
||||
el_val_t snap_path = el_str_concat(dir, EL_STR("/sync-export.json"));
|
||||
engram_save(snap_path);
|
||||
el_val_t snap = fs_read(snap_path);
|
||||
if (str_eq(snap, EL_STR(""))) {
|
||||
return EL_STR("{\"nodes\":[],\"edges\":[]}");
|
||||
}
|
||||
return snap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t p = json_get_string(body, EL_STR("path"));
|
||||
if (str_eq(p, EL_STR(""))) {
|
||||
@@ -299,6 +329,9 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
||||
return route_health(method, path, body);
|
||||
}
|
||||
}
|
||||
if (str_eq(method, EL_STR("POST")) && str_starts_with(clean, EL_STR("/api/neuron/state-events"))) {
|
||||
return route_create_ise(method, path, body);
|
||||
}
|
||||
if (!check_auth_ok(method, body)) {
|
||||
return err_json(EL_STR("unauthorized"));
|
||||
}
|
||||
@@ -341,6 +374,9 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/strengthen")) || str_eq(clean, EL_STR("/strengthen")))) {
|
||||
return route_strengthen(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/sync")) || str_eq(clean, EL_STR("/sync")))) {
|
||||
return route_sync(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/save")) || str_eq(clean, EL_STR("/save")))) {
|
||||
return route_save(method, path, body);
|
||||
}
|
||||
|
||||
@@ -206,6 +206,59 @@ fn route_health(method: String, path: String, body: String) -> String {
|
||||
"{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}"
|
||||
}
|
||||
|
||||
// route_sync — return a snapshot of non-ISE/non-Working nodes for the soul daemon
|
||||
// to merge into its in-process graph via engram_load_merge.
|
||||
//
|
||||
// The soul calls GET /api/sync every SOUL_REFRESH_MS (default 10 min) to pull
|
||||
// new Knowledge/Memory/BacklogItem nodes from the authoritative HTTP Engram into
|
||||
// its in-process working store. Previously this returned 404 "not found", causing
|
||||
// the soul to write the error JSON to a temp file and attempt an empty merge.
|
||||
//
|
||||
// Strategy: save the current snapshot to disk, read it back, return the full
|
||||
// snapshot JSON. The soul's engram_load_merge handles large files gracefully
|
||||
// (it skips nodes already present by ID). Auth-exempt: same-host internal call.
|
||||
// (2026-06-27 self-review: added this route to fix silent 10-min sync failures)
|
||||
fn route_sync(method: String, path: String, body: String) -> String {
|
||||
let dir: String = env("ENGRAM_DATA_DIR")
|
||||
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
||||
let snap_path: String = dir + "/snapshot.json"
|
||||
engram_save(snap_path)
|
||||
let snap: String = fs_read(snap_path)
|
||||
if str_eq(snap, "") { return "{\"nodes\":[],\"edges\":[]}" }
|
||||
return snap
|
||||
}
|
||||
|
||||
// route_emit_ise — write an InternalStateEvent node from the soul daemon.
|
||||
//
|
||||
// Endpoint: POST /api/neuron/state-events
|
||||
// Body: {"content": "<json-string>"}
|
||||
//
|
||||
// Auth: exempt (internal endpoint, soul daemon on same host, no _auth needed).
|
||||
// The soul's ise_post() sends {"content":"..."} without _auth; enforcing auth
|
||||
// here would silently drop all heartbeat/curiosity ISEs. Unauthenticated POST
|
||||
// to this endpoint is acceptable: ISE writes are observability-only, append-only,
|
||||
// and come from a trusted process on localhost.
|
||||
//
|
||||
// Salience/importance set to match engram_node_full ISE defaults used by the
|
||||
// in-process fallback path in awareness.el (salience=0.3, importance=0.3,
|
||||
// confidence=0.8, tier=Episodic). High temporal_decay_rate (1.617) — ISEs
|
||||
// are inherently transient; they should decay faster than structural knowledge.
|
||||
// (2026-06-26 self-review: added this route after discovering ise_post was
|
||||
// silently failing — the soul posts here but the endpoint didn't exist.)
|
||||
fn route_emit_ise(method: String, path: String, body: String) -> String {
|
||||
let content: String = json_get_string(body, "content")
|
||||
if str_eq(content, "") { return err_json("missing content") }
|
||||
let sal: Float = 0.3
|
||||
let imp: Float = 0.3
|
||||
let conf: Float = 0.8
|
||||
let id: String = engram_node_full(
|
||||
content, "InternalStateEvent", "state-event",
|
||||
sal, imp, conf,
|
||||
"Episodic", "[\"internal-state\",\"InternalStateEvent\"]"
|
||||
)
|
||||
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
||||
}
|
||||
|
||||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn check_auth_ok(method: String, body: String) -> Bool {
|
||||
@@ -232,6 +285,11 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// ISE posting is auth-exempt (internal soul daemon, same host, no _auth key)
|
||||
if str_eq(method, "POST") && str_eq(clean, "/api/neuron/state-events") {
|
||||
return route_emit_ise(method, path, body)
|
||||
}
|
||||
|
||||
// Auth (when ENGRAM_API_KEY is set)
|
||||
if !check_auth_ok(method, body) {
|
||||
return err_json("unauthorized")
|
||||
@@ -294,6 +352,11 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
return route_load(method, path, body)
|
||||
}
|
||||
|
||||
// Sync — soul daemon periodic pull of non-ISE knowledge into in-process graph
|
||||
if str_eq(method, "GET") && str_eq(clean, "/api/sync") {
|
||||
return route_sync(method, path, body)
|
||||
}
|
||||
|
||||
"{\"error\":\"not found\",\"path\":\"" + clean + "\"}"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,711 @@
|
||||
/*
|
||||
* ElBridge.java — Android Java companion to el_android.c.
|
||||
*
|
||||
* All public methods are static. The C JNI layer calls these to create views,
|
||||
* set properties, and manage the widget tree. Views are identified by integer
|
||||
* slot indices matching the C-side handle values.
|
||||
*
|
||||
* Threading: every method that touches a View dispatches to the UI thread
|
||||
* using Activity.runOnUiThread(Runnable) and blocks with a CountDownLatch
|
||||
* until the UI thread completes the operation. This mirrors the AppKit
|
||||
* dispatch_sync(main_queue, ^{}) pattern in el_appkit.m.
|
||||
*
|
||||
* Callbacks: Java sets listeners on views that call back into C via:
|
||||
* nativeOnClick(int slot)
|
||||
* nativeOnChange(int slot, String text)
|
||||
* nativeOnSubmit(int slot, String text)
|
||||
* These are declared native and implemented in el_android.c.
|
||||
*
|
||||
* Usage (in your Activity.onCreate):
|
||||
* System.loadLibrary("elruntime");
|
||||
* ElBridge.init(this);
|
||||
*
|
||||
* The native library calls __native_init() which calls nativeRegisterActivity
|
||||
* via the C side; alternatively call ElBridge.init(this) directly from Java.
|
||||
*
|
||||
* Compile requirements:
|
||||
* Android minSdkVersion 21 (Lollipop) or higher.
|
||||
* No third-party dependencies — uses only android.* framework classes.
|
||||
* For image loading from arbitrary file paths, BitmapFactory is used.
|
||||
* To replace with Glide/Picasso, edit createImageView only.
|
||||
*/
|
||||
|
||||
package com.neuron.el;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class ElBridge {
|
||||
|
||||
/* ── Native callbacks (implemented in el_android.c) ─────────────────── */
|
||||
|
||||
public static native void nativeOnClick(int slot);
|
||||
public static native void nativeOnChange(int slot, String text);
|
||||
public static native void nativeOnSubmit(int slot, String text);
|
||||
public static native void nativeRegisterActivity(Activity activity);
|
||||
|
||||
/* ── State ───────────────────────────────────────────────────────────── */
|
||||
|
||||
private static final int MAX_SLOTS = 4096;
|
||||
|
||||
private static Activity sActivity;
|
||||
private static Handler sUiHandler;
|
||||
private static View[] sViews = new View[MAX_SLOTS];
|
||||
private static int sNextSlot = 1; /* slot 0 reserved / null */
|
||||
|
||||
/* ── Init ────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Must be called from the Activity before any widget operations.
|
||||
* Typically called from Activity.onCreate after System.loadLibrary.
|
||||
*/
|
||||
public static void init(Activity activity) {
|
||||
sActivity = activity;
|
||||
sUiHandler = new Handler(Looper.getMainLooper());
|
||||
nativeRegisterActivity(activity);
|
||||
}
|
||||
|
||||
/* ── Slot management ─────────────────────────────────────────────────── */
|
||||
|
||||
private static int allocSlot(View v) {
|
||||
/* Find a free slot starting from sNextSlot, wrap around. */
|
||||
for (int i = 0; i < MAX_SLOTS - 1; i++) {
|
||||
int idx = ((sNextSlot - 1 + i) % (MAX_SLOTS - 1)) + 1;
|
||||
if (sViews[idx] == null) {
|
||||
sViews[idx] = v;
|
||||
sNextSlot = (idx % (MAX_SLOTS - 1)) + 1;
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
android.util.Log.e("ElBridge", "allocSlot: slot table full");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static View getView(int slot) {
|
||||
if (slot <= 0 || slot >= MAX_SLOTS) return null;
|
||||
return sViews[slot];
|
||||
}
|
||||
|
||||
/* ── UI-thread dispatch helper ───────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Dispatch r on the UI thread and block until it completes.
|
||||
* Safe to call from the UI thread itself (runs inline without posting).
|
||||
*/
|
||||
private static void runSync(final Runnable r) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
r.run();
|
||||
} else {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
sUiHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
try { r.run(); } finally { latch.countDown(); }
|
||||
}
|
||||
});
|
||||
try { latch.await(); } catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Integer slot returning runSync helper ───────────────────────────── */
|
||||
|
||||
private interface IntSupplier { int get(); }
|
||||
|
||||
private static int runSyncInt(final IntSupplier s) {
|
||||
final int[] result = { -1 };
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() { result[0] = s.get(); }
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
/* ── Context accessor ────────────────────────────────────────────────── */
|
||||
|
||||
private static Context ctx() { return sActivity; }
|
||||
|
||||
/* ── View creation ───────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Create a LinearLayout.
|
||||
* @param orientation 1=VERTICAL, 0=HORIZONTAL
|
||||
* @param spacing gap between children in dp; applied as bottom/right margin
|
||||
*/
|
||||
public static int createLinearLayout(final int orientation, final int spacing) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
LinearLayout ll = new LinearLayout(ctx());
|
||||
ll.setOrientation(orientation == 1
|
||||
? LinearLayout.VERTICAL
|
||||
: LinearLayout.HORIZONTAL);
|
||||
ll.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
/* Spacing is stored so addChild can apply margins. */
|
||||
ll.setTag(R_TAG_SPACING, spacing);
|
||||
return allocSlot(ll);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a FrameLayout (ZStack equivalent). */
|
||||
public static int createFrameLayout() {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
FrameLayout fl = new FrameLayout(ctx());
|
||||
fl.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(fl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a ScrollView. */
|
||||
public static int createScrollView() {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
ScrollView sv = new ScrollView(ctx());
|
||||
sv.setLayoutParams(new ScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
sv.setFillViewport(true);
|
||||
return allocSlot(sv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a TextView with initial text. */
|
||||
public static int createTextView(final String text) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
TextView tv = new TextView(ctx());
|
||||
tv.setText(text != null ? text : "");
|
||||
tv.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(tv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a Button with a label. */
|
||||
public static int createButton(final String label) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
Button btn = new Button(ctx());
|
||||
btn.setText(label != null ? label : "");
|
||||
btn.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(btn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EditText.
|
||||
* @param placeholder hint text
|
||||
* @param singleLine true = single-line text field; false = multi-line text area
|
||||
*/
|
||||
public static int createEditText(final String placeholder, final boolean singleLine) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
EditText et = new EditText(ctx());
|
||||
et.setHint(placeholder != null ? placeholder : "");
|
||||
if (singleLine) {
|
||||
et.setInputType(InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
et.setMaxLines(1);
|
||||
et.setSingleLine(true);
|
||||
} else {
|
||||
et.setInputType(InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
et.setMinLines(3);
|
||||
et.setSingleLine(false);
|
||||
et.setGravity(Gravity.TOP | Gravity.START);
|
||||
}
|
||||
et.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(et);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ImageView, loading from a file path via BitmapFactory.
|
||||
* If path is null/empty the ImageView is created with no image.
|
||||
*/
|
||||
public static int createImageView(final String path) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
ImageView iv = new ImageView(ctx());
|
||||
iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
iv.setAdjustViewBounds(true);
|
||||
if (path != null && !path.isEmpty()) {
|
||||
Bitmap bmp = BitmapFactory.decodeFile(path);
|
||||
if (bmp != null) {
|
||||
iv.setImageBitmap(bmp);
|
||||
} else {
|
||||
android.util.Log.w("ElBridge",
|
||||
"createImageView: failed to decode " + path);
|
||||
}
|
||||
}
|
||||
iv.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(iv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Window operations ───────────────────────────────────────────────── */
|
||||
|
||||
/** Set the Activity's content view to the view at slot. */
|
||||
public static void setContentView(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null && sActivity != null) {
|
||||
sActivity.setContentView(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set the Activity title. */
|
||||
public static void setTitle(final String title) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
if (sActivity != null) {
|
||||
sActivity.setTitle(title != null ? title : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Tree operations ─────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Add child view to parent view.
|
||||
* LinearLayout: child added as arranged child with spacing margin.
|
||||
* ScrollView: child replaces current document view.
|
||||
* FrameLayout / other ViewGroup: plain addView.
|
||||
*/
|
||||
public static void addChild(final int parentSlot, final int childSlot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View parent = getView(parentSlot);
|
||||
View child = getView(childSlot);
|
||||
if (parent == null || child == null) return;
|
||||
|
||||
/* Remove child from existing parent first. */
|
||||
if (child.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) child.getParent()).removeView(child);
|
||||
}
|
||||
|
||||
if (parent instanceof LinearLayout) {
|
||||
LinearLayout ll = (LinearLayout) parent;
|
||||
Object tag = ll.getTag(R_TAG_SPACING);
|
||||
int spacing = (tag instanceof Integer) ? (Integer) tag : 0;
|
||||
LinearLayout.LayoutParams lp;
|
||||
Object existingLp = child.getLayoutParams();
|
||||
if (existingLp instanceof LinearLayout.LayoutParams) {
|
||||
lp = (LinearLayout.LayoutParams) existingLp;
|
||||
} else {
|
||||
lp = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
/* Apply spacing as margin on the leading/top edge (after first child). */
|
||||
if (ll.getChildCount() > 0 && spacing > 0) {
|
||||
int px = dpToPx(spacing);
|
||||
if (ll.getOrientation() == LinearLayout.VERTICAL) {
|
||||
lp.topMargin = px;
|
||||
} else {
|
||||
lp.leftMargin = px;
|
||||
}
|
||||
}
|
||||
child.setLayoutParams(lp);
|
||||
ll.addView(child);
|
||||
} else if (parent instanceof ScrollView) {
|
||||
ScrollView sv = (ScrollView) parent;
|
||||
sv.removeAllViews();
|
||||
sv.addView(child);
|
||||
} else if (parent instanceof ViewGroup) {
|
||||
((ViewGroup) parent).addView(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove child from its parent. */
|
||||
public static void removeChild(final int parentSlot, final int childSlot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View parent = getView(parentSlot);
|
||||
View child = getView(childSlot);
|
||||
if (parent instanceof ViewGroup && child != null) {
|
||||
((ViewGroup) parent).removeView(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove the view from its parent and release the slot. */
|
||||
public static void destroyView(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
if (v.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) v.getParent()).removeView(v);
|
||||
}
|
||||
sViews[slot] = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Property setters ────────────────────────────────────────────────── */
|
||||
|
||||
public static void setText(final int slot, final String text) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
String s = text != null ? text : "";
|
||||
if (v instanceof EditText) {
|
||||
((EditText) v).setText(s);
|
||||
} else if (v instanceof Button) {
|
||||
((Button) v).setText(s);
|
||||
} else if (v instanceof TextView) {
|
||||
((TextView) v).setText(s);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static String getText(final int slot) {
|
||||
final String[] result = { "" };
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v instanceof TextView) {
|
||||
CharSequence cs = ((TextView) v).getText();
|
||||
result[0] = cs != null ? cs.toString() : "";
|
||||
}
|
||||
}
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
/** Set foreground text color. Components in [0,1]. */
|
||||
public static void setTextColor(final int slot, final float r, final float g,
|
||||
final float b, final float a) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v instanceof TextView) {
|
||||
((TextView) v).setTextColor(floatToArgb(r, g, b, a));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set background color using a GradientDrawable so corner radius is preserved. */
|
||||
public static void setBackgroundColor(final int slot, final float r, final float g,
|
||||
final float b, final float a) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ensureGradientBackground(v);
|
||||
GradientDrawable gd = (GradientDrawable) v.getBackground();
|
||||
gd.setColor(floatToArgb(r, g, b, a));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set font family and size.
|
||||
* family: "system" or null → system default; otherwise tries to load by name.
|
||||
* bold: if true uses Typeface.BOLD.
|
||||
*/
|
||||
public static void setFont(final int slot, final String family,
|
||||
final int sizeSp, final boolean bold) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof TextView)) return;
|
||||
TextView tv = (TextView) v;
|
||||
Typeface tf;
|
||||
if (family != null && !family.isEmpty()
|
||||
&& !family.equals("system")) {
|
||||
Typeface base = Typeface.create(family,
|
||||
bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
tf = (base != null) ? base
|
||||
: Typeface.defaultFromStyle(bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
} else {
|
||||
tf = Typeface.defaultFromStyle(bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
}
|
||||
tv.setTypeface(tf);
|
||||
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, sizeSp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set padding in dp. */
|
||||
public static void setPadding(final int slot, final int top, final int right,
|
||||
final int bottom, final int left) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) {
|
||||
v.setPadding(dpToPx(left), dpToPx(top), dpToPx(right), dpToPx(bottom));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set explicit width in dp. Passes MATCH_PARENT for negative values. */
|
||||
public static void setWidth(final int slot, final int widthDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp == null) lp = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.width = widthDp < 0
|
||||
? ViewGroup.LayoutParams.MATCH_PARENT
|
||||
: dpToPx(widthDp);
|
||||
v.setLayoutParams(lp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set explicit height in dp. */
|
||||
public static void setHeight(final int slot, final int heightDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp == null) lp = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.height = heightDp < 0
|
||||
? ViewGroup.LayoutParams.MATCH_PARENT
|
||||
: dpToPx(heightDp);
|
||||
v.setLayoutParams(lp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flex weight on a child of a LinearLayout.
|
||||
* flex > 0 → weight = flex, width/height = 0dp (expand).
|
||||
* flex == 0 → weight = 0, wrap_content (shrink to content).
|
||||
*/
|
||||
public static void setFlex(final int slot, final int flex) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp instanceof LinearLayout.LayoutParams) {
|
||||
LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams) lp;
|
||||
if (flex > 0) {
|
||||
llp.weight = (float) flex;
|
||||
/* Determine orientation from parent to set 0dp on the right axis. */
|
||||
if (v.getParent() instanceof LinearLayout) {
|
||||
LinearLayout parent = (LinearLayout) v.getParent();
|
||||
if (parent.getOrientation() == LinearLayout.VERTICAL) {
|
||||
llp.height = 0;
|
||||
} else {
|
||||
llp.width = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
llp.weight = 0f;
|
||||
}
|
||||
v.setLayoutParams(llp);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set corner radius in dp using a GradientDrawable background. */
|
||||
public static void setCornerRadius(final int slot, final float radiusDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ensureGradientBackground(v);
|
||||
GradientDrawable gd = (GradientDrawable) v.getBackground();
|
||||
gd.setCornerRadius(dpToPxF(radiusDp));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setEnabled(final int slot, final boolean enabled) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) v.setEnabled(enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide a view.
|
||||
* @param visible true = VISIBLE, false = GONE (matches AppKit setHidden semantics)
|
||||
*/
|
||||
public static void setVisibility(final int slot, final boolean visible) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) v.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Event listener registration ─────────────────────────────────────── */
|
||||
|
||||
/** Register an OnClickListener that calls back into C nativeOnClick. */
|
||||
public static void setOnClickListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
final int capturedSlot = slot;
|
||||
v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override public void onClick(View view) {
|
||||
nativeOnClick(capturedSlot);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a TextWatcher on an EditText that calls back nativeOnChange
|
||||
* for every text change.
|
||||
*/
|
||||
public static void setOnChangeListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof EditText)) return;
|
||||
final int capturedSlot = slot;
|
||||
((EditText) v).addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start,
|
||||
int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start,
|
||||
int before, int count) {}
|
||||
@Override public void afterTextChanged(Editable s) {
|
||||
nativeOnChange(capturedSlot, s != null ? s.toString() : "");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an OnEditorActionListener on a single-line EditText that calls
|
||||
* nativeOnSubmit when the user presses the action/enter key.
|
||||
*/
|
||||
public static void setOnSubmitListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof EditText)) return;
|
||||
final int capturedSlot = slot;
|
||||
((EditText) v).setOnEditorActionListener(
|
||||
new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView tv, int actionId,
|
||||
android.view.KeyEvent event) {
|
||||
nativeOnSubmit(capturedSlot,
|
||||
tv.getText() != null ? tv.getText().toString() : "");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Internal helpers ─────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Tag key used to stash the spacing value on LinearLayouts so addChild
|
||||
* can apply the correct margin between children.
|
||||
* We use a stable integer resource-id-like value; because we do not have
|
||||
* a resources file here we use View.generateViewId() lazily.
|
||||
*/
|
||||
private static int sSpacingTagKey = 0;
|
||||
|
||||
private static int R_TAG_SPACING;
|
||||
static {
|
||||
R_TAG_SPACING = View.generateViewId();
|
||||
}
|
||||
|
||||
/** Convert dp to pixels using the Activity's display metrics. */
|
||||
private static int dpToPx(float dp) {
|
||||
if (sActivity == null) return (int) dp;
|
||||
float density = sActivity.getResources().getDisplayMetrics().density;
|
||||
return Math.round(dp * density);
|
||||
}
|
||||
|
||||
private static float dpToPxF(float dp) {
|
||||
if (sActivity == null) return dp;
|
||||
float density = sActivity.getResources().getDisplayMetrics().density;
|
||||
return dp * density;
|
||||
}
|
||||
|
||||
/** Convert RGBA float components (0–1) to an Android ARGB int. */
|
||||
private static int floatToArgb(float r, float g, float b, float a) {
|
||||
int ai = Math.round(a * 255f);
|
||||
int ri = Math.round(r * 255f);
|
||||
int gi = Math.round(g * 255f);
|
||||
int bi = Math.round(b * 255f);
|
||||
return Color.argb(ai, ri, gi, bi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the view has a GradientDrawable background so that both color
|
||||
* and corner radius can be set independently. If the current background
|
||||
* is already a GradientDrawable it is reused; otherwise a new transparent
|
||||
* one is installed.
|
||||
*/
|
||||
private static void ensureGradientBackground(View v) {
|
||||
if (!(v.getBackground() instanceof GradientDrawable)) {
|
||||
GradientDrawable gd = new GradientDrawable();
|
||||
gd.setColor(Color.TRANSPARENT);
|
||||
v.setBackground(gd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
# Platform Bridge Specification — el-native
|
||||
|
||||
This document is the authoritative reference for anyone implementing a new platform bridge for the el-native widget system. Read it top to bottom before writing a single line of code.
|
||||
|
||||
---
|
||||
|
||||
## What a Platform Bridge Is
|
||||
|
||||
The el compiler (`elc`) emits C code. That C code calls `__`-prefixed functions for everything OS-related: printing, file I/O, threading, and — when building a native UI app — widget creation and event handling. These `__` functions are the *OS boundary*.
|
||||
|
||||
For native UI, the bridge is the translation layer between that fixed C API and whatever platform toolkit you are targeting. The bridge owns:
|
||||
|
||||
1. A **slot table** of up to 4096 widget objects, indexed by `int64_t` handle.
|
||||
2. Implementations of all 33 `__*` widget functions declared in `el_native_target.h`.
|
||||
3. Callback dispatch from platform events back into El function symbols resolved via `dlsym` (or a platform equivalent).
|
||||
|
||||
The thin wrappers in `el_seed.c` (`#ifdef EL_TARGET_*` blocks) marshal between `el_val_t` and native C types, then call through to the bridge. The bridge itself never touches `el_val_t` — it works only with plain C types (`int64_t`, `const char*`, `int`, `float`).
|
||||
|
||||
**Existing bridges:**
|
||||
|
||||
| File | Platform | Toolkit | Language |
|
||||
|------|----------|---------|----------|
|
||||
| `el_appkit.m` | macOS | AppKit | ObjC (MRC) |
|
||||
| `el_gtk4.c` | Linux | GTK4 | C |
|
||||
| `el_win32.c` | Windows | Win32/ComCtl | C |
|
||||
| `el_uikit.m` | iOS | UIKit | ObjC (MRC) |
|
||||
| `el_android.c` + `ElBridge.java` | Android | View/JNI | C + Java |
|
||||
| `el_sdl2.c` | Embedded Linux / Pi | SDL2 | C |
|
||||
| `el_lvgl.c` | Microcontrollers | LVGL | C |
|
||||
|
||||
---
|
||||
|
||||
## The Slot System
|
||||
|
||||
Every widget — window, button, label, container, image — is stored in a static array:
|
||||
|
||||
```c
|
||||
#define EL_<PLATFORM>_MAX_WIDGETS 4096
|
||||
|
||||
typedef struct {
|
||||
ElWidgetKind kind;
|
||||
/* platform-specific object reference (pointer, handle, ID...) */
|
||||
/* callback names */
|
||||
char* cb_click;
|
||||
char* cb_change;
|
||||
} ElWidget;
|
||||
|
||||
static ElWidget _el_widgets[EL_<PLATFORM>_MAX_WIDGETS];
|
||||
```
|
||||
|
||||
Rules:
|
||||
- **Slot 0 is never valid.** Scan starts at index 1. This ensures 0 is never a valid handle.
|
||||
- **Handle = slot index.** An `int64_t` value returned to El code is a direct index into `_el_widgets[]`.
|
||||
- **-1 = invalid.** All create functions return -1 on failure (table full, platform API error).
|
||||
- **Slot is FREE until allocated, FREE again after destroy.** Use an `ElWidgetKind` enum where `0 = EL_WIDGET_FREE` to track liveness.
|
||||
- **4096 slots is the system-wide maximum.** This is intentional and sufficient for any realistic UI. Do not increase it without a compelling reason.
|
||||
|
||||
### Slot allocation and release pattern
|
||||
|
||||
```c
|
||||
static int64_t el_widget_alloc(ElWidgetKind kind, /* platform object ref */) {
|
||||
for (int i = 1; i < EL_<PLATFORM>_MAX_WIDGETS; i++) {
|
||||
if (_el_widgets[i].kind == EL_WIDGET_FREE) {
|
||||
_el_widgets[i].kind = kind;
|
||||
/* store platform object ref — retain/addref if needed */
|
||||
_el_widgets[i].cb_click = NULL;
|
||||
_el_widgets[i].cb_change = NULL;
|
||||
return (int64_t)i;
|
||||
}
|
||||
}
|
||||
return -1; /* table full */
|
||||
}
|
||||
|
||||
static ElWidget* el_widget_get(int64_t handle) {
|
||||
if (handle <= 0 || handle >= EL_<PLATFORM>_MAX_WIDGETS) return NULL;
|
||||
if (_el_widgets[handle].kind == EL_WIDGET_FREE) return NULL;
|
||||
return &_el_widgets[handle];
|
||||
}
|
||||
|
||||
static void el_widget_free(int64_t handle) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
/* release platform object ref */
|
||||
w->kind = EL_WIDGET_FREE;
|
||||
free(w->cb_click); w->cb_click = NULL;
|
||||
free(w->cb_change); w->cb_change = NULL;
|
||||
}
|
||||
```
|
||||
|
||||
**NULL handle guard:** `el_widget_get` must return `NULL` for any handle that is 0, negative, out of range, or points to a `FREE` slot. Every `__` function that takes a handle must null-check the result of `el_widget_get` before doing anything. Failing to do so causes crashes or corruption when El code passes an uninitialized handle.
|
||||
|
||||
---
|
||||
|
||||
## The 33 Required Functions
|
||||
|
||||
Every bridge must implement all 33 functions listed below. They are grouped by category. The signatures shown are from `el_native_target.h` and from `el_seed.c`'s wrapper layer; the bridge itself uses plain C types (the wrappers do the `el_val_t` ↔ C-type conversion).
|
||||
|
||||
### Internal C signatures (what the bridge implements)
|
||||
|
||||
These are what your `.c` / `.m` file defines. The `el_seed.c` wrappers call these.
|
||||
|
||||
#### Lifecycle (2 functions)
|
||||
|
||||
```c
|
||||
void el_<platform>_init(void);
|
||||
```
|
||||
**Purpose:** Initialize the platform UI toolkit. Must be idempotent (safe to call more than once). Called once from `__native_init` before any widget creation.
|
||||
**Edge cases:** On platforms where the toolkit must be initialized before a display connection is established (X11, Wayland), this is where that happens. On Android, this is a no-op because ElBridge.java calls init from Java.
|
||||
|
||||
```c
|
||||
void el_<platform>_run_loop(void);
|
||||
```
|
||||
**Purpose:** Start the platform event loop. On most platforms this **never returns**. Exceptions: Android (the loop is Java-managed — this must be a no-op) and headless test builds.
|
||||
**Edge cases:** Must be called from the main thread. On iOS/UIKit, calls `UIApplicationMain` which never returns; El code must set `el_main_entry_fn` before calling this.
|
||||
|
||||
#### Window (3 functions)
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_window_create(const char* title, int w, int h, int mw, int mh);
|
||||
```
|
||||
**Purpose:** Create a top-level window. `w`/`h` = initial size in logical pixels. `mw`/`mh` = minimum size (0 = no minimum).
|
||||
**Returns:** Slot handle, or -1 on failure.
|
||||
**Edge cases:** NULL or empty `title` must be handled gracefully (use `""`). On mobile (iOS, Android), the concept of a "window" maps to the root view controller / activity root view — create that here.
|
||||
|
||||
```c
|
||||
void el_<platform>_window_show(int64_t handle);
|
||||
```
|
||||
**Purpose:** Make the window visible. On some platforms windows are hidden at creation; this makes them appear.
|
||||
**Edge cases:** NULL handle → return silently. Calling on an already-visible window is a no-op.
|
||||
|
||||
```c
|
||||
void el_<platform>_window_set_title(int64_t handle, const char* title);
|
||||
```
|
||||
**Purpose:** Update the window's title bar text at runtime.
|
||||
**Edge cases:** NULL handle or NULL title → no-op.
|
||||
|
||||
#### Layout containers (4 functions)
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_vstack_create(int spacing);
|
||||
int64_t el_<platform>_hstack_create(int spacing);
|
||||
```
|
||||
**Purpose:** Create a vertical/horizontal linear container. `spacing` = gap between children in logical pixels.
|
||||
**Returns:** Slot handle, or -1.
|
||||
**Edge cases:** spacing = 0 is valid and common.
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_zstack_create(void);
|
||||
```
|
||||
**Purpose:** Create a z-axis layered container (children overlap, no stacking direction). Used for overlays.
|
||||
**Returns:** Slot handle, or -1.
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_scroll_create(void);
|
||||
```
|
||||
**Purpose:** Create a scrollable container. Scrolls vertically by default. Only the first child added is the scrollable content.
|
||||
**Returns:** Slot handle, or -1.
|
||||
|
||||
#### Leaf widgets (5 functions)
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_label_create(const char* text);
|
||||
```
|
||||
**Purpose:** Create a non-editable text label.
|
||||
**Edge cases:** NULL/empty text → label with empty string, not a crash.
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_button_create(const char* label);
|
||||
```
|
||||
**Purpose:** Create a clickable button. The `label` is the button's visible text.
|
||||
**Edge cases:** The button must wire up an action target at creation time so that click callbacks registered later via `el_<platform>_widget_on_click` will fire. On platforms with target-action (AppKit, UIKit), allocate the delegate object here.
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_text_field_create(const char* placeholder);
|
||||
```
|
||||
**Purpose:** Create a single-line text input. `placeholder` is the hint text shown when empty.
|
||||
**Edge cases:** NULL placeholder → no hint text displayed.
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_text_area_create(const char* placeholder);
|
||||
```
|
||||
**Purpose:** Create a multi-line text input (scrollable).
|
||||
**Edge cases:** Same as text_field_create. On AppKit, this wraps NSTextView inside NSScrollView — the slot's `obj` points to the scroll view, not the text view.
|
||||
|
||||
```c
|
||||
int64_t el_<platform>_image_create(const char* path_or_name);
|
||||
```
|
||||
**Purpose:** Create an image widget. `path_or_name` can be a filesystem path or a platform resource name. Try path first, fall back to named resource.
|
||||
**Edge cases:** Non-existent path → create an empty image widget (do not crash). NULL → same.
|
||||
|
||||
#### Widget properties (12 functions)
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_text(int64_t handle, const char* text);
|
||||
```
|
||||
**Purpose:** Update the text content of a label, button, text field, text area, or window title. Must dispatch on `kind` to use the correct API.
|
||||
**Edge cases:** NULL handle → no-op. NULL text → treat as `""`.
|
||||
|
||||
```c
|
||||
const char* el_<platform>_widget_get_text(int64_t handle);
|
||||
```
|
||||
**Purpose:** Return the current text of a widget. Returns a `const char*` that the `el_seed.c` wrapper wraps into an `el_val_t` string.
|
||||
**Edge cases:** NULL handle → return `""` (never NULL). The caller in `el_seed.c` handles the `strdup` lifetime issue — see the AppKit reference implementation notes.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_color(int64_t h, float r, float g, float b, float a);
|
||||
void el_<platform>_widget_set_bg_color(int64_t h, float r, float g, float b, float a);
|
||||
```
|
||||
**Purpose:** Set foreground (text) color and background color respectively. All channels are normalized floats [0.0, 1.0].
|
||||
**Edge cases:** For containers, `set_color` may be a no-op (no text); `set_bg_color` should set the layer/surface background. On platforms without alpha compositing, clamp alpha to 0 or 1.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_font(int64_t h, const char* family, int size, int bold);
|
||||
```
|
||||
**Purpose:** Set the font on a text-bearing widget. `family` = font family name or `"system"` for the platform default. `size` = point size. `bold` = 0 or 1.
|
||||
**Edge cases:** If `family` is not found, fall back to the system font. Non-text widgets (containers, images) → no-op.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_padding(int64_t h, int top, int right, int bottom, int left);
|
||||
```
|
||||
**Purpose:** Set internal padding/insets for a container or text area.
|
||||
**Edge cases:** On platforms where padding is per-view (not per-container), map to the nearest equivalent (margin, insets, text container inset). For leaf widgets other than text areas, this may be a partial no-op.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_width(int64_t h, int width);
|
||||
void el_<platform>_widget_set_height(int64_t h, int height);
|
||||
```
|
||||
**Purpose:** Apply a fixed-size constraint. `width`/`height` in logical pixels.
|
||||
**Edge cases:** On platforms with Auto Layout or constraint systems, add a fixed-size constraint. Do not apply to windows (size is set at creation). Calling multiple times should override the previous constraint, not add another.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_flex(int64_t h, int flex);
|
||||
```
|
||||
**Purpose:** Set the flex/expansion factor. `flex > 0` → the widget expands to fill available space. `flex == 0` → hugs content size.
|
||||
**Edge cases:** Maps to content-hugging priority (AppKit), `GtkWidget::hexpand`/`vexpand` (GTK4), or layout weight (Android). For windows → no-op.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_corner_radius(int64_t h, int radius);
|
||||
```
|
||||
**Purpose:** Apply rounded corners to the widget's visual layer.
|
||||
**Edge cases:** Requires backing layer / GPU surface. On platforms without layer compositing (Win32 without DX), this may be a no-op or require manual painting. Radius in logical pixels.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_disabled(int64_t h, int disabled);
|
||||
```
|
||||
**Purpose:** Enable or disable user interaction. `disabled = 1` → greyed out, non-interactive.
|
||||
**Edge cases:** Only meaningful for interactive widgets (button, text field). For containers/labels → no-op.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_set_hidden(int64_t h, int hidden);
|
||||
```
|
||||
**Purpose:** Show or hide the widget. `hidden = 1` → invisible but still in layout.
|
||||
**Edge cases:** For windows, map to `orderOut`/`hide` or equivalent. For views, use `setHidden`/`gtk_widget_set_visible` or equivalent.
|
||||
|
||||
#### Tree management (3 functions)
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_add_child(int64_t parent, int64_t child);
|
||||
```
|
||||
**Purpose:** Attach a child widget to a parent container. Dispatch on parent kind:
|
||||
- Window → add to root content view/container
|
||||
- VStack/HStack → add as arranged/linear child
|
||||
- ZStack → add as overlapping subview
|
||||
- Scroll → set as document/content view (first child only)
|
||||
- Other → add as plain subview
|
||||
|
||||
**Edge cases:** NULL parent or child handle → no-op. Attempting to add a window as a child → no-op. Adding the same child twice is platform-defined behavior (tolerate it).
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_remove_child(int64_t parent, int64_t child);
|
||||
```
|
||||
**Purpose:** Detach child from its parent. The child slot remains allocated; the widget is not destroyed.
|
||||
**Edge cases:** NULL handles → no-op. Child not currently attached → no-op.
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_destroy(int64_t handle);
|
||||
```
|
||||
**Purpose:** Destroy a widget: remove from superview/parent, release the platform object, release callback strings, mark slot as FREE.
|
||||
**Edge cases:** For windows, close the window. Free any delegate/target objects stored in side tables. After destroy, the handle is invalid — El code must not use it again (this is the caller's responsibility, not enforced here).
|
||||
|
||||
#### Event registration (3 functions)
|
||||
|
||||
```c
|
||||
void el_<platform>_widget_on_click(int64_t h, const char* fn_name);
|
||||
void el_<platform>_widget_on_change(int64_t h, const char* fn_name);
|
||||
void el_<platform>_widget_on_submit(int64_t h, const char* fn_name);
|
||||
```
|
||||
**Purpose:** Register an El callback function by symbol name.
|
||||
- `on_click` → button press
|
||||
- `on_change` → text field value change (keystroke-level)
|
||||
- `on_submit` → text field Enter key (text field only; stored in `cb_click` slot in AppKit)
|
||||
|
||||
The implementation stores `strdup(fn_name)` in the widget's `cb_click` or `cb_change` field. The platform event handler calls `el_<platform>_invoke_cb` (see callback ABI section).
|
||||
**Edge cases:** NULL or empty `fn_name` → clear the callback (`free` + set NULL). Calling on a non-interactive widget (label, image) → no-op or store silently (harmless).
|
||||
|
||||
#### Manifest reader (1 function)
|
||||
|
||||
```c
|
||||
/* Note: __manifest_read is implemented in el_seed.c, not in the bridge. */
|
||||
/* Bridges do NOT need to implement this. */
|
||||
```
|
||||
|
||||
`__manifest_read` is handled entirely in the platform-independent section of `el_seed.c`. It reads a JSON/EL manifest file from the path in the `EL_MANIFEST` environment variable. Bridge authors do not need to implement this.
|
||||
|
||||
---
|
||||
|
||||
## The Callback ABI
|
||||
|
||||
When a platform event fires (button clicked, text changed), the bridge must call back into the El runtime. The mechanism:
|
||||
|
||||
```c
|
||||
typedef void (*ElCb2)(int64_t handle, int64_t data);
|
||||
|
||||
static void el_<platform>_invoke_cb(const char* fn_name, int64_t handle, const char* data) {
|
||||
if (!fn_name || !*fn_name) return;
|
||||
void* sym = dlsym(RTLD_DEFAULT, fn_name);
|
||||
if (!sym) return;
|
||||
ElCb2 fn = (ElCb2)sym;
|
||||
fn(handle, (int64_t)(uintptr_t)(data ? data : ""));
|
||||
}
|
||||
```
|
||||
|
||||
The El callback signature (in El source):
|
||||
```
|
||||
fn handler(handle: Int, data: String) -> Void
|
||||
```
|
||||
|
||||
Which compiles to:
|
||||
```c
|
||||
void handler(int64_t handle, int64_t data)
|
||||
```
|
||||
|
||||
Where `data` is a `const char*` cast to `int64_t` (the el `String` representation). For click events, `data` is `""`. For change/submit events, `data` is the current widget text.
|
||||
|
||||
**On platforms without `dlsym`** (Windows, some embedded systems): use `GetProcAddress(GetModuleHandle(NULL), fn_name)` on Windows, or maintain a manual symbol registration table for embedded targets where dynamic linking is unavailable.
|
||||
|
||||
**Thread safety for callbacks:** Callbacks fired from a background thread must be marshalled to the UI thread before calling into El code. El code may call `__widget_set_text` or other UI functions synchronously from within the callback — those must run on the UI thread.
|
||||
|
||||
---
|
||||
|
||||
## Thread Safety Contract
|
||||
|
||||
**All platform UI operations must execute on the main/UI thread.** This is a hard requirement on every platform (AppKit, UIKit, GTK4, Win32, Android View, SDL2 main thread rule).
|
||||
|
||||
The reference pattern (AppKit):
|
||||
```c
|
||||
static void el_appkit_sync_main(void (^block)(void)) {
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_sync(dispatch_get_main_queue(), block);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For other platforms:
|
||||
- **GTK4:** `g_main_context_invoke` or `g_idle_add` + semaphore for synchronous dispatch
|
||||
- **Win32:** `SendMessage(hwnd, WM_APP, ...)` or `PostMessage` + wait
|
||||
- **Android:** `Activity.runOnUiThread`
|
||||
- **SDL2:** All ops must be called from the thread that initialized SDL (the main thread)
|
||||
- **LVGL:** `lv_lock()` / `lv_unlock()` for thread-safe access
|
||||
|
||||
El program flow is: `main()` → build UI (on main thread) → `__native_run_loop()`. Because UI is built before the run loop starts, most widget creation calls are already on the main thread. The sync dispatch wrapper exists to handle callbacks that arrive on worker threads (e.g., network callbacks that update UI).
|
||||
|
||||
---
|
||||
|
||||
## Integration Pattern
|
||||
|
||||
### In `el_native_target.h`
|
||||
|
||||
Add an `#ifdef EL_TARGET_<PLATFORM>` block declaring all 33 `__*` functions with their `el_val_t` signatures (identical to the existing blocks for MACOS, LINUX, etc.):
|
||||
|
||||
```c
|
||||
#ifdef EL_TARGET_<PLATFORM>
|
||||
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_<PLATFORM> */
|
||||
```
|
||||
|
||||
### In `el_seed.c`
|
||||
|
||||
Add an `#ifdef EL_TARGET_<PLATFORM>` block containing:
|
||||
1. `extern` declarations of all `el_<platform>_*` functions (your bridge's C API)
|
||||
2. Thin wrapper functions that marshal `el_val_t` ↔ C types and call through
|
||||
|
||||
The wrapper pattern (copy from the `EL_TARGET_MACOS` block and substitute the platform name):
|
||||
|
||||
```c
|
||||
#ifdef EL_TARGET_<PLATFORM>
|
||||
|
||||
/* Forward declarations — implemented in el_<platform>.c */
|
||||
extern void el_<platform>_init(void);
|
||||
extern void el_<platform>_run_loop(void);
|
||||
extern int64_t el_<platform>_window_create(const char* title, int w, int h, int mw, int mh);
|
||||
/* ... all others ... */
|
||||
|
||||
/* Wrappers */
|
||||
void __native_init(void) { el_<platform>_init(); }
|
||||
void __native_run_loop(void) { el_<platform>_run_loop(); }
|
||||
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height) {
|
||||
return (el_val_t)el_<platform>_window_create(
|
||||
EL_CSTR(title),
|
||||
(int)(int64_t)width, (int)(int64_t)height,
|
||||
(int)(int64_t)min_width, (int)(int64_t)min_height);
|
||||
}
|
||||
|
||||
void __window_show(el_val_t h) { el_<platform>_window_show((int64_t)h); }
|
||||
void __window_set_title(el_val_t h, el_val_t t) { el_<platform>_window_set_title((int64_t)h, EL_CSTR(t)); }
|
||||
|
||||
/* ... continue for all 33 functions ... */
|
||||
|
||||
#endif /* EL_TARGET_<PLATFORM> */
|
||||
```
|
||||
|
||||
Key marshalling rules:
|
||||
- `el_val_t` → `int64_t` handle: `(int64_t)h`
|
||||
- `el_val_t` → `int`: `(int)(int64_t)value`
|
||||
- `el_val_t` → `const char*`: `EL_CSTR(value)`
|
||||
- `el_val_t` → `float`: `(float)el_to_float(value)` (for color channels)
|
||||
- `int64_t` handle → `el_val_t`: `(el_val_t)handle`
|
||||
- `const char*` → `el_val_t`: `EL_STR(str)` — but read the get_text note below
|
||||
|
||||
**`__widget_get_text` note:** The bridge returns `const char*`. The `el_seed.c` wrapper wraps it with `EL_STR(s)`. The returned pointer must remain valid until the El program is done with it. The AppKit implementation returns a `strdup`'d string — the caller (seed wrapper) stores it without tracking it in the arena. This is a known lifetime edge; be consistent with the platform's existing pattern.
|
||||
|
||||
---
|
||||
|
||||
## How el Strings Work
|
||||
|
||||
Inside El compiled C code, strings are `el_val_t` values where the value is the `uintptr_t` cast of a `const char*`:
|
||||
|
||||
```c
|
||||
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
||||
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
||||
```
|
||||
|
||||
To construct a string result in `el_seed.c`:
|
||||
```c
|
||||
static char* s = strdup("hello");
|
||||
return EL_STR(s);
|
||||
```
|
||||
|
||||
To read a string argument passed from El:
|
||||
```c
|
||||
const char* text = EL_CSTR(some_el_val_t_argument);
|
||||
```
|
||||
|
||||
The `EL_STR` / `EL_CSTR` macros are defined in `el_seed.h` and are available in `el_seed.c`. Bridge files (`el_<platform>.c`) do not use these macros — they only deal in `const char*` at their API boundary.
|
||||
|
||||
---
|
||||
|
||||
## Known Gotchas
|
||||
|
||||
### Duplicate symbols with `el_runtime.c`
|
||||
|
||||
`el_runtime.c` defines many of the same `__`-prefixed symbols as `el_seed.c`. When linking both (required for the native-hello example), the linker will reject duplicate definitions. The build system uses `nmedit` (macOS) to hide the `el_runtime.c` copies of symbols that `el_seed.c` already defines, keeping `el_seed.c` canonical.
|
||||
|
||||
If you are writing a build script for a new platform and see duplicate symbol link errors involving `__println`, `__print`, `__str_len`, etc., apply the same nmedit / `objcopy --weaken-symbol` / `strip --strip-symbol` trick from the macOS build to your platform's object file handling.
|
||||
|
||||
### The `nmedit` trick on macOS
|
||||
|
||||
```bash
|
||||
# Build a keep-list: symbols defined in el_seed.o but also in el_runtime.o
|
||||
nm el_seed.o | awk '/^[0-9a-f]+ T _/{print $3}' | sort > .seed_T.txt
|
||||
nm el_runtime.o | awk '/^[0-9a-f]+ T _/{print $3}' | sort > .rt_T.txt
|
||||
# Keep only symbols unique to el_runtime.o
|
||||
comm -23 .rt_T.txt .seed_T.txt > .rt_keep.txt
|
||||
nmedit -s .rt_keep.txt el_runtime.o
|
||||
```
|
||||
|
||||
On Linux, use `objcopy` with `--weaken-symbol` for each duplicate, or link `el_seed.o` before `el_runtime.o` and use `--allow-multiple-definition` if your use case permits it.
|
||||
|
||||
### ObjC bridges must use MRC, not ARC
|
||||
|
||||
Bridges that use Objective-C (AppKit, UIKit) **must** compile without ARC (`-fno-objc-arc`). The reason: the widget table stores `id` values in a plain C struct. ARC cannot insert retain/release through C struct boundaries, and will reject explicit `[obj retain]` / `[obj release]` calls in ARC mode. Use:
|
||||
|
||||
```bash
|
||||
clang -ObjC -fno-objc-arc -framework Cocoa -c el_appkit.m
|
||||
```
|
||||
|
||||
### Widget table struct — don't put `id` fields in C structs under ARC
|
||||
|
||||
If you ever add ARC-managed object fields to the `ElWidget` struct, you will get a compile error. Keep the struct C-only (pointers as `void*` if you must, cast when using) or compile as MRC.
|
||||
|
||||
### The `el_seed.c` `__manifest_read` is platform-independent
|
||||
|
||||
`__manifest_read` is already implemented in `el_seed.c` (in the section compiled unconditionally). You do not implement it in your bridge. You do need to declare it in the `el_native_target.h` block for your platform (matching the other platforms), but `el_seed.c` already has the implementation.
|
||||
|
||||
---
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
Before declaring your bridge ready for integration:
|
||||
|
||||
- [ ] All 33 `__*` functions implemented (including `__manifest_read` declaration, implementation is in seed)
|
||||
- [ ] Slot table with 4096 entries, scan starting at index 1
|
||||
- [ ] `el_widget_get` returns NULL for handle 0, negative, out-of-range, and FREE slots
|
||||
- [ ] All functions null-check `el_widget_get` result before use
|
||||
- [ ] `el_<platform>_init` is idempotent
|
||||
- [ ] `el_<platform>_run_loop` either never returns or is documented as a no-op (Android)
|
||||
- [ ] Callback dispatch via `dlsym` (or platform equivalent) implemented
|
||||
- [ ] All UI operations dispatched to the main/UI thread
|
||||
- [ ] `el_native_target.h` updated with `#ifdef EL_TARGET_<PLATFORM>` block
|
||||
- [ ] `el_seed.c` updated with `#ifdef EL_TARGET_<PLATFORM>` extern + wrapper block
|
||||
- [ ] Bridge compiles cleanly with no warnings: `cc -DEL_TARGET_<PLATFORM> -Wall -Wextra -c el_<platform>.c`
|
||||
- [ ] Bridge links cleanly with `el_seed.o` and `el_runtime.o` (duplicate symbol check)
|
||||
- [ ] `detect-platforms` script updated with detection logic for the new platform
|
||||
- [ ] Basic smoke test: create window → add label → show window → run loop
|
||||
Executable
+208
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env bash
|
||||
# detect-platforms — probe available platform bridge dependencies
|
||||
#
|
||||
# Reports which el-native platform bridges can be built on this machine,
|
||||
# with install instructions for anything that is missing.
|
||||
#
|
||||
# Usage: ./detect-platforms
|
||||
# ./build.sh platforms (from native-hello)
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
PASS="[+]"
|
||||
FAIL="[ ]"
|
||||
|
||||
_ok() { printf " ${PASS} %-22s %s\n" "$1" "$2"; }
|
||||
_miss() { printf " ${FAIL} %-22s %s\n" "$1" "$2"; }
|
||||
|
||||
# ── Header ────────────────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "==> el-native platform detection"
|
||||
echo ""
|
||||
echo " Checking build dependencies for each platform bridge..."
|
||||
echo ""
|
||||
|
||||
AVAILABLE=0
|
||||
MISSING=0
|
||||
|
||||
# ── macOS / AppKit ────────────────────────────────────────────────────────────
|
||||
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
if xcrun --find clang &>/dev/null && xcrun --find xcodebuild &>/dev/null 2>/dev/null || \
|
||||
xcrun --find cc &>/dev/null; then
|
||||
CLT_INFO="Xcode CLT $(xcode-select -p 2>/dev/null | sed 's|/Developer||' || echo '')"
|
||||
_ok "macOS/AppKit" "-DEL_TARGET_MACOS ${CLT_INFO}"
|
||||
AVAILABLE=$((AVAILABLE + 1))
|
||||
else
|
||||
_miss "macOS/AppKit" "-DEL_TARGET_MACOS (Xcode CLT missing — xcode-select --install)"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
else
|
||||
_miss "macOS/AppKit" "-DEL_TARGET_MACOS (not on macOS)"
|
||||
fi
|
||||
|
||||
# ── iOS / UIKit ───────────────────────────────────────────────────────────────
|
||||
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
if xcrun --sdk iphoneos --show-sdk-path &>/dev/null 2>&1; then
|
||||
SDK_VER=$(xcrun --sdk iphoneos --show-sdk-version 2>/dev/null || echo "")
|
||||
_ok "iOS/UIKit" "-DEL_TARGET_IOS SDK ${SDK_VER} (requires Xcode.app)"
|
||||
AVAILABLE=$((AVAILABLE + 1))
|
||||
else
|
||||
_miss "iOS/UIKit" "-DEL_TARGET_IOS (iOS SDK not found — install Xcode.app)"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
else
|
||||
_miss "iOS/UIKit" "-DEL_TARGET_IOS (not on macOS — requires Xcode)"
|
||||
fi
|
||||
|
||||
# ── Linux / GTK4 ──────────────────────────────────────────────────────────────
|
||||
|
||||
if pkg-config --exists gtk4 2>/dev/null; then
|
||||
GTK_VER=$(pkg-config --modversion gtk4 2>/dev/null)
|
||||
_ok "Linux/GTK4" "-DEL_TARGET_LINUX gtk4 ${GTK_VER}"
|
||||
AVAILABLE=$((AVAILABLE + 1))
|
||||
else
|
||||
_miss "Linux/GTK4" "-DEL_TARGET_LINUX (gtk4 not found)"
|
||||
echo " Install: apt install libgtk-4-dev"
|
||||
echo " or: dnf install gtk4-devel"
|
||||
echo " or: brew install gtk4"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
|
||||
# ── SDL2 / Embedded Linux / Pi ────────────────────────────────────────────────
|
||||
|
||||
SDL2_OK=0
|
||||
if pkg-config --exists sdl2 2>/dev/null; then
|
||||
SDL2_VER=$(pkg-config --modversion sdl2 2>/dev/null)
|
||||
# Also check for SDL2_ttf (needed for text rendering)
|
||||
if pkg-config --exists SDL2_ttf 2>/dev/null; then
|
||||
TTF_VER=$(pkg-config --modversion SDL2_ttf 2>/dev/null)
|
||||
_ok "SDL2/Embedded" "-DEL_TARGET_SDL2 sdl2 ${SDL2_VER}, SDL2_ttf ${TTF_VER}"
|
||||
else
|
||||
_ok "SDL2/Embedded" "-DEL_TARGET_SDL2 sdl2 ${SDL2_VER} (SDL2_ttf missing — needed for text)"
|
||||
echo " Install: apt install libsdl2-ttf-dev"
|
||||
fi
|
||||
SDL2_OK=1
|
||||
AVAILABLE=$((AVAILABLE + 1))
|
||||
elif command -v sdl2-config &>/dev/null; then
|
||||
SDL2_VER=$(sdl2-config --version 2>/dev/null || echo "")
|
||||
_ok "SDL2/Embedded" "-DEL_TARGET_SDL2 sdl2 ${SDL2_VER} (via sdl2-config)"
|
||||
SDL2_OK=1
|
||||
AVAILABLE=$((AVAILABLE + 1))
|
||||
fi
|
||||
|
||||
if [[ $SDL2_OK -eq 0 ]]; then
|
||||
_miss "SDL2/Embedded" "-DEL_TARGET_SDL2 (sdl2 not found)"
|
||||
echo " Install: apt install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev"
|
||||
echo " or: brew install sdl2 sdl2_ttf sdl2_image"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
|
||||
# ── LVGL / Microcontrollers ───────────────────────────────────────────────────
|
||||
|
||||
LVGL_OK=0
|
||||
LVGL_WHERE=""
|
||||
|
||||
if [ -f "${SCRIPT_DIR}/lvgl/lvgl.h" ]; then
|
||||
LVGL_WHERE="./lvgl/lvgl.h"
|
||||
LVGL_OK=1
|
||||
elif [ -f "${SCRIPT_DIR}/../lvgl/lvgl.h" ]; then
|
||||
LVGL_WHERE="adjacent lvgl/"
|
||||
LVGL_OK=1
|
||||
elif [ -f "/usr/include/lvgl/lvgl.h" ]; then
|
||||
LVGL_WHERE="/usr/include/lvgl"
|
||||
LVGL_OK=1
|
||||
elif [ -f "/usr/local/include/lvgl/lvgl.h" ]; then
|
||||
LVGL_WHERE="/usr/local/include/lvgl"
|
||||
LVGL_OK=1
|
||||
elif pkg-config --exists lvgl 2>/dev/null; then
|
||||
LVGL_WHERE="pkg-config ($(pkg-config --modversion lvgl 2>/dev/null))"
|
||||
LVGL_OK=1
|
||||
fi
|
||||
|
||||
if [[ $LVGL_OK -eq 1 ]]; then
|
||||
_ok "LVGL/MCU" "-DEL_TARGET_LVGL ${LVGL_WHERE}"
|
||||
AVAILABLE=$((AVAILABLE + 1))
|
||||
else
|
||||
_miss "LVGL/MCU" "-DEL_TARGET_LVGL (lvgl.h not found)"
|
||||
echo " Install: git clone https://github.com/lvgl/lvgl"
|
||||
echo " (place lvgl/ next to el-compiler/runtime/)"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
|
||||
# ── Android / JNI ─────────────────────────────────────────────────────────────
|
||||
|
||||
ANDROID_OK=0
|
||||
ANDROID_WHERE=""
|
||||
|
||||
if [ -n "${ANDROID_NDK_HOME:-}" ] && [ -d "${ANDROID_NDK_HOME}" ]; then
|
||||
NDK_VER=""
|
||||
if [ -f "${ANDROID_NDK_HOME}/source.properties" ]; then
|
||||
NDK_VER=$(grep "Pkg.Revision" "${ANDROID_NDK_HOME}/source.properties" \
|
||||
2>/dev/null | cut -d= -f2 | tr -d ' ' || echo "")
|
||||
fi
|
||||
ANDROID_WHERE="NDK ${NDK_VER:-(version unknown)} at \$ANDROID_NDK_HOME"
|
||||
ANDROID_OK=1
|
||||
elif command -v ndk-build &>/dev/null; then
|
||||
ANDROID_WHERE="ndk-build in PATH"
|
||||
ANDROID_OK=1
|
||||
elif [ -n "${ANDROID_HOME:-}" ] && [ -d "${ANDROID_HOME}/ndk" ]; then
|
||||
ANDROID_WHERE="NDK via \$ANDROID_HOME/ndk"
|
||||
ANDROID_OK=1
|
||||
fi
|
||||
|
||||
if [[ $ANDROID_OK -eq 1 ]]; then
|
||||
_ok "Android/JNI" "-DEL_TARGET_ANDROID ${ANDROID_WHERE}"
|
||||
AVAILABLE=$((AVAILABLE + 1))
|
||||
else
|
||||
_miss "Android/JNI" "-DEL_TARGET_ANDROID (ANDROID_NDK_HOME not set)"
|
||||
echo " Install: https://developer.android.com/studio/releases/ndk"
|
||||
echo " Then: export ANDROID_NDK_HOME=/path/to/ndk"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
|
||||
# ── Windows / Win32 (cross or native) ─────────────────────────────────────────
|
||||
|
||||
WIN32_OK=0
|
||||
WIN32_WHERE=""
|
||||
|
||||
if [[ "$(uname)" == MINGW* ]] || [[ "$(uname)" == CYGWIN* ]] || \
|
||||
[[ "$(uname)" == MSYS* ]]; then
|
||||
WIN32_WHERE="native Windows ($(uname))"
|
||||
WIN32_OK=1
|
||||
elif command -v x86_64-w64-mingw32-gcc &>/dev/null; then
|
||||
MINGW_VER=$(x86_64-w64-mingw32-gcc --version 2>/dev/null | head -1 || echo "")
|
||||
WIN32_WHERE="mingw cross-compiler — ${MINGW_VER}"
|
||||
WIN32_OK=1
|
||||
elif command -v i686-w64-mingw32-gcc &>/dev/null; then
|
||||
WIN32_WHERE="mingw 32-bit cross-compiler"
|
||||
WIN32_OK=1
|
||||
fi
|
||||
|
||||
if [[ $WIN32_OK -eq 1 ]]; then
|
||||
_ok "Windows/Win32" "-DEL_TARGET_WIN32 ${WIN32_WHERE}"
|
||||
AVAILABLE=$((AVAILABLE + 1))
|
||||
else
|
||||
_miss "Windows/Win32" "-DEL_TARGET_WIN32 (mingw cross-compiler not found)"
|
||||
echo " Install: brew install mingw-w64"
|
||||
echo " or: apt install gcc-mingw-w64"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo " ${AVAILABLE} platform(s) available, ${MISSING} unavailable on this machine."
|
||||
echo ""
|
||||
|
||||
if [[ -x "${SCRIPT_DIR}/new-platform" ]]; then
|
||||
echo " Scaffold a new bridge: ${SCRIPT_DIR}/new-platform <name>"
|
||||
fi
|
||||
echo " Bridge contract: ${SCRIPT_DIR}/PLATFORM_BRIDGE_SPEC.md"
|
||||
echo ""
|
||||
@@ -0,0 +1,949 @@
|
||||
/*
|
||||
* el_android.c — Android JNI backend for the el native widget system.
|
||||
*
|
||||
* This file implements the Android widget layer that el_seed.c calls through
|
||||
* to when EL_TARGET_ANDROID is defined. It is the exact Android counterpart
|
||||
* to el_appkit.m and presents the same C API surface.
|
||||
*
|
||||
* Architecture:
|
||||
* el program (el code)
|
||||
* → __widget_* C builtins in el_seed.c
|
||||
* → el_android_* C-callable functions declared here
|
||||
* → ElBridge static methods in Java via JNI
|
||||
* → android.view.View subclasses on the UI thread
|
||||
*
|
||||
* Widget handles: every widget (window root, view, control) is assigned an
|
||||
* int64_t slot index into view_slots[]. The el program holds these as opaque
|
||||
* Int values. Slot 0 is never valid (null handle = -1 convention).
|
||||
*
|
||||
* Threading: Android requires all UI operations to run on the main (UI) thread.
|
||||
* Every JNI call that mutates a View is dispatched through
|
||||
* Activity.runOnUiThread(Runnable) if the current thread is not the UI thread.
|
||||
* el_android_run_loop is a no-op — Android lifecycle is driven by the Activity.
|
||||
*
|
||||
* Callback mechanism: when a widget fires an event Java calls
|
||||
* nativeOnClick / nativeOnChange / nativeOnSubmit
|
||||
* The C side looks up the registered El function name for that slot, then:
|
||||
* dlsym(RTLD_DEFAULT, fn_name)(widget_handle, event_data_string)
|
||||
* This matches the __thread_create pattern in el_seed.c exactly.
|
||||
*
|
||||
* Compile / link (as part of libelruntime.so):
|
||||
* Compiled by the Android Gradle NDK build system with -DEL_TARGET_ANDROID.
|
||||
* Link flags: -landroid -llog -ldl
|
||||
*
|
||||
* Java companion: ElBridge.java in the same directory must be compiled into
|
||||
* the Android application's APK (package com.neuron.el).
|
||||
*/
|
||||
|
||||
#ifdef EL_TARGET_ANDROID
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
/* ── Logging ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_TAG "ElAndroid"
|
||||
#define EL_LOGI(...) __android_log_print(ANDROID_LOG_INFO, EL_TAG, __VA_ARGS__)
|
||||
#define EL_LOGW(...) __android_log_print(ANDROID_LOG_WARN, EL_TAG, __VA_ARGS__)
|
||||
#define EL_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, EL_TAG, __VA_ARGS__)
|
||||
|
||||
/* ── JNI global state ────────────────────────────────────────────────────── */
|
||||
|
||||
static JavaVM *g_jvm = NULL;
|
||||
static jobject g_activity = NULL; /* global ref to Activity */
|
||||
static jclass g_bridge_class = NULL; /* global ref to ElBridge class */
|
||||
|
||||
/* Cached method IDs on ElBridge — filled in el_android_init(). */
|
||||
static jmethodID g_mid_createLinearLayout = NULL;
|
||||
static jmethodID g_mid_createFrameLayout = NULL;
|
||||
static jmethodID g_mid_createScrollView = NULL;
|
||||
static jmethodID g_mid_createTextView = NULL;
|
||||
static jmethodID g_mid_createButton = NULL;
|
||||
static jmethodID g_mid_createEditText = NULL;
|
||||
static jmethodID g_mid_createImageView = NULL;
|
||||
static jmethodID g_mid_setContentView = NULL;
|
||||
static jmethodID g_mid_setTitle = NULL;
|
||||
static jmethodID g_mid_addChild = NULL;
|
||||
static jmethodID g_mid_removeChild = NULL;
|
||||
static jmethodID g_mid_destroyView = NULL;
|
||||
static jmethodID g_mid_setText = NULL;
|
||||
static jmethodID g_mid_getText = NULL;
|
||||
static jmethodID g_mid_setTextColor = NULL;
|
||||
static jmethodID g_mid_setBackgroundColor = NULL;
|
||||
static jmethodID g_mid_setFont = NULL;
|
||||
static jmethodID g_mid_setPadding = NULL;
|
||||
static jmethodID g_mid_setWidth = NULL;
|
||||
static jmethodID g_mid_setHeight = NULL;
|
||||
static jmethodID g_mid_setFlex = NULL;
|
||||
static jmethodID g_mid_setCornerRadius = NULL;
|
||||
static jmethodID g_mid_setEnabled = NULL;
|
||||
static jmethodID g_mid_setVisibility = NULL;
|
||||
static jmethodID g_mid_setOnClickListener = NULL;
|
||||
static jmethodID g_mid_setOnChangeListener = NULL;
|
||||
static jmethodID g_mid_setOnSubmitListener = NULL;
|
||||
static jmethodID g_mid_runOnUiThread = NULL; /* Activity.runOnUiThread */
|
||||
|
||||
/* ── Widget table ─────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_ANDROID_MAX_WIDGETS 4096
|
||||
|
||||
typedef enum {
|
||||
EL_WIDGET_FREE = 0,
|
||||
EL_WIDGET_WINDOW = 1,
|
||||
EL_WIDGET_VSTACK = 2,
|
||||
EL_WIDGET_HSTACK = 3,
|
||||
EL_WIDGET_ZSTACK = 4,
|
||||
EL_WIDGET_SCROLL = 5,
|
||||
EL_WIDGET_LABEL = 6,
|
||||
EL_WIDGET_BUTTON = 7,
|
||||
EL_WIDGET_TEXTFIELD = 8,
|
||||
EL_WIDGET_TEXTAREA = 9,
|
||||
EL_WIDGET_IMAGE = 10,
|
||||
} ElWidgetKind;
|
||||
|
||||
typedef struct {
|
||||
ElWidgetKind kind;
|
||||
jint slot; /* Java-side slot index (matches C index) */
|
||||
char *cb_click; /* El function name for click / submit events */
|
||||
char *cb_change; /* El function name for value-change events */
|
||||
} ElWidget;
|
||||
|
||||
static ElWidget _el_widgets[EL_ANDROID_MAX_WIDGETS];
|
||||
|
||||
static int64_t el_widget_alloc(ElWidgetKind kind, jint slot) {
|
||||
for (int i = 1; i < EL_ANDROID_MAX_WIDGETS; i++) {
|
||||
if (_el_widgets[i].kind == EL_WIDGET_FREE) {
|
||||
_el_widgets[i].kind = kind;
|
||||
_el_widgets[i].slot = slot;
|
||||
_el_widgets[i].cb_click = NULL;
|
||||
_el_widgets[i].cb_change = NULL;
|
||||
return (int64_t)i;
|
||||
}
|
||||
}
|
||||
EL_LOGE("el_widget_alloc: slot table full");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ElWidget *el_widget_get(int64_t handle) {
|
||||
if (handle <= 0 || handle >= EL_ANDROID_MAX_WIDGETS) return NULL;
|
||||
if (_el_widgets[handle].kind == EL_WIDGET_FREE) return NULL;
|
||||
return &_el_widgets[handle];
|
||||
}
|
||||
|
||||
static void el_widget_free(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->kind = EL_WIDGET_FREE;
|
||||
w->slot = -1;
|
||||
free(w->cb_click); w->cb_click = NULL;
|
||||
free(w->cb_change); w->cb_change = NULL;
|
||||
}
|
||||
|
||||
/* ── JNI environment helpers ─────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Obtain a JNIEnv for the calling thread. Attaches the thread to the JVM if
|
||||
* needed (detaches in el_jni_detach_if_attached — call in pairs).
|
||||
*/
|
||||
static int g_was_attached = 0; /* thread-local would be cleaner but this is
|
||||
safe for single-threaded el programs */
|
||||
|
||||
static JNIEnv *el_jni_env(void) {
|
||||
if (!g_jvm) return NULL;
|
||||
JNIEnv *env = NULL;
|
||||
jint rc = (*g_jvm)->GetEnv(g_jvm, (void **)&env, JNI_VERSION_1_6);
|
||||
if (rc == JNI_OK) { g_was_attached = 0; return env; }
|
||||
if (rc == JNI_EDETACHED) {
|
||||
if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) == JNI_OK) {
|
||||
g_was_attached = 1;
|
||||
return env;
|
||||
}
|
||||
}
|
||||
EL_LOGE("el_jni_env: failed to obtain JNIEnv");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void el_jni_detach_if_attached(void) {
|
||||
if (g_was_attached && g_jvm) {
|
||||
(*g_jvm)->DetachCurrentThread(g_jvm);
|
||||
g_was_attached = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── UI-thread dispatch ──────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Most ElBridge static methods already dispatch to the UI thread internally
|
||||
* (they call Activity.runOnUiThread). The helper below is available for
|
||||
* cases where the caller needs to be sure the call has completed before
|
||||
* returning (ElBridge methods marked "sync" use a CountDownLatch internally).
|
||||
*
|
||||
* For the current implementation we call ElBridge methods directly; ElBridge
|
||||
* itself marshals to the UI thread via Activity.runOnUiThread + latch.
|
||||
* This keeps the C side simple and mirrors the AppKit dispatch_sync pattern.
|
||||
*/
|
||||
|
||||
/* ── JNI_OnLoad ──────────────────────────────────────────────────────────── */
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
(void)reserved;
|
||||
g_jvm = vm;
|
||||
EL_LOGI("JNI_OnLoad: el Android bridge loaded");
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/* ── el_android_init ─────────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Called from __native_init(). The Activity must have already called
|
||||
* ElBridge.registerActivity(activity) from Java before this runs, which sets
|
||||
* g_activity via the nativeRegisterActivity JNI method below.
|
||||
*
|
||||
* Caches all method IDs used later so individual widget calls avoid repeated
|
||||
* FindClass / GetStaticMethodID lookups.
|
||||
*/
|
||||
void el_android_init(void) {
|
||||
static int done = 0;
|
||||
if (done) return;
|
||||
done = 1;
|
||||
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env) { EL_LOGE("el_android_init: no JNIEnv"); return; }
|
||||
|
||||
jclass cls = (*env)->FindClass(env, "com/neuron/el/ElBridge");
|
||||
if (!cls) { EL_LOGE("el_android_init: ElBridge class not found"); return; }
|
||||
g_bridge_class = (*env)->NewGlobalRef(env, cls);
|
||||
(*env)->DeleteLocalRef(env, cls);
|
||||
|
||||
#define CACHE_STATIC(var, name, sig) \
|
||||
var = (*env)->GetStaticMethodID(env, g_bridge_class, name, sig); \
|
||||
if (!var) EL_LOGW("el_android_init: method not found: %s %s", name, sig)
|
||||
|
||||
CACHE_STATIC(g_mid_createLinearLayout, "createLinearLayout", "(II)I");
|
||||
CACHE_STATIC(g_mid_createFrameLayout, "createFrameLayout", "()I");
|
||||
CACHE_STATIC(g_mid_createScrollView, "createScrollView", "()I");
|
||||
CACHE_STATIC(g_mid_createTextView, "createTextView", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_createButton, "createButton", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_createEditText, "createEditText", "(Ljava/lang/String;Z)I");
|
||||
CACHE_STATIC(g_mid_createImageView, "createImageView", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_setContentView, "setContentView", "(I)V");
|
||||
CACHE_STATIC(g_mid_setTitle, "setTitle", "(Ljava/lang/String;)V");
|
||||
CACHE_STATIC(g_mid_addChild, "addChild", "(II)V");
|
||||
CACHE_STATIC(g_mid_removeChild, "removeChild", "(II)V");
|
||||
CACHE_STATIC(g_mid_destroyView, "destroyView", "(I)V");
|
||||
CACHE_STATIC(g_mid_setText, "setText", "(ILjava/lang/String;)V");
|
||||
CACHE_STATIC(g_mid_getText, "getText", "(I)Ljava/lang/String;");
|
||||
CACHE_STATIC(g_mid_setTextColor, "setTextColor", "(IFFFF)V");
|
||||
CACHE_STATIC(g_mid_setBackgroundColor, "setBackgroundColor", "(IFFFF)V");
|
||||
CACHE_STATIC(g_mid_setFont, "setFont", "(ILjava/lang/String;IZ)V");
|
||||
CACHE_STATIC(g_mid_setPadding, "setPadding", "(IIIII)V");
|
||||
CACHE_STATIC(g_mid_setWidth, "setWidth", "(II)V");
|
||||
CACHE_STATIC(g_mid_setHeight, "setHeight", "(II)V");
|
||||
CACHE_STATIC(g_mid_setFlex, "setFlex", "(II)V");
|
||||
CACHE_STATIC(g_mid_setCornerRadius, "setCornerRadius", "(IF)V");
|
||||
CACHE_STATIC(g_mid_setEnabled, "setEnabled", "(IZ)V");
|
||||
CACHE_STATIC(g_mid_setVisibility, "setVisibility", "(IZ)V");
|
||||
CACHE_STATIC(g_mid_setOnClickListener, "setOnClickListener", "(I)V");
|
||||
CACHE_STATIC(g_mid_setOnChangeListener, "setOnChangeListener", "(I)V");
|
||||
CACHE_STATIC(g_mid_setOnSubmitListener, "setOnSubmitListener", "(I)V");
|
||||
#undef CACHE_STATIC
|
||||
|
||||
el_jni_detach_if_attached();
|
||||
EL_LOGI("el_android_init: complete");
|
||||
}
|
||||
|
||||
/* ── JNI: Activity registration ─────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Called from Java: ElBridge.registerActivity(activity) calls back here.
|
||||
* Stores a global reference to the Activity so C code can dispatch to it.
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeRegisterActivity(JNIEnv *env, jclass cls,
|
||||
jobject activity) {
|
||||
(void)cls;
|
||||
if (g_activity) {
|
||||
(*env)->DeleteGlobalRef(env, g_activity);
|
||||
g_activity = NULL;
|
||||
}
|
||||
if (activity) {
|
||||
g_activity = (*env)->NewGlobalRef(env, activity);
|
||||
EL_LOGI("nativeRegisterActivity: activity registered");
|
||||
}
|
||||
}
|
||||
|
||||
/* ── El callback invocation ──────────────────────────────────────────────── */
|
||||
/*
|
||||
* Invoke an El callback by symbol name.
|
||||
* Signature matches AppKit: fn(handle: Int, data: String) -> Void
|
||||
* compiled to: void fn(el_val_t handle, el_val_t data)
|
||||
*/
|
||||
typedef void (*ElCb2)(int64_t handle, int64_t data);
|
||||
|
||||
static void el_android_invoke_cb(const char *fn_name, int64_t handle,
|
||||
const char *data) {
|
||||
if (!fn_name || !*fn_name) return;
|
||||
void *sym = dlsym(RTLD_DEFAULT, fn_name);
|
||||
if (!sym) { EL_LOGW("invoke_cb: symbol not found: %s", fn_name); return; }
|
||||
ElCb2 fn = (ElCb2)sym;
|
||||
fn(handle, (int64_t)(uintptr_t)(data ? data : ""));
|
||||
}
|
||||
|
||||
/* ── JNI: callbacks from Java → C ───────────────────────────────────────── */
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnClick(JNIEnv *env, jclass cls, jint slot) {
|
||||
(void)env; (void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_click) {
|
||||
el_android_invoke_cb(w->cb_click, handle, "");
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnChange(JNIEnv *env, jclass cls,
|
||||
jint slot, jstring text) {
|
||||
(void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_change) {
|
||||
const char *ctext = text ? (*env)->GetStringUTFChars(env, text, NULL) : "";
|
||||
el_android_invoke_cb(w->cb_change, handle, ctext);
|
||||
if (text) (*env)->ReleaseStringUTFChars(env, text, ctext);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnSubmit(JNIEnv *env, jclass cls,
|
||||
jint slot, jstring text) {
|
||||
(void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_click) { /* submit stored in cb_click, same as AppKit */
|
||||
const char *ctext = text ? (*env)->GetStringUTFChars(env, text, NULL) : "";
|
||||
el_android_invoke_cb(w->cb_click, handle, ctext);
|
||||
if (text) (*env)->ReleaseStringUTFChars(env, text, ctext);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Helper: jstring from C string ──────────────────────────────────────── */
|
||||
|
||||
static jstring el_jstr(JNIEnv *env, const char *s) {
|
||||
return (*env)->NewStringUTF(env, s ? s : "");
|
||||
}
|
||||
|
||||
/* ── Window ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_android_window_create — on Android a "window" is the root LinearLayout
|
||||
* set as the Activity's content view. We create a vertical LinearLayout and
|
||||
* store it. el_android_window_show calls setContentView on the Activity.
|
||||
*/
|
||||
int64_t el_android_window_create(const char *title, int width, int height,
|
||||
int min_width, int min_height) {
|
||||
(void)width; (void)height; (void)min_width; (void)min_height;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
|
||||
/* VERTICAL LinearLayout with no spacing (spacing added via margins in Java) */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)1 /* VERTICAL */, (jint)0);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1;
|
||||
}
|
||||
|
||||
/* Set activity title */
|
||||
if (g_mid_setTitle && title) {
|
||||
jstring jtitle = el_jstr(env, title);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTitle, jtitle);
|
||||
(*env)->DeleteLocalRef(env, jtitle);
|
||||
}
|
||||
|
||||
int64_t handle = el_widget_alloc(EL_WIDGET_WINDOW, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return handle;
|
||||
}
|
||||
|
||||
void el_android_window_show(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w || w->kind != EL_WIDGET_WINDOW) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setContentView,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_window_set_title(int64_t handle, const char *title) {
|
||||
(void)handle;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jtitle = el_jstr(env, title);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTitle, jtitle);
|
||||
(*env)->DeleteLocalRef(env, jtitle);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Layout containers ───────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_android_vstack_create(int spacing) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)1 /* VERTICAL */, (jint)spacing);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_VSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_hstack_create(int spacing) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)0 /* HORIZONTAL */, (jint)spacing);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_HSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_zstack_create(void) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createFrameLayout);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_ZSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_scroll_create(void) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createScrollView);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_SCROLL, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
/* ── Widget factories ─────────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_android_label_create(const char *text) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jt = el_jstr(env, text);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createTextView, jt);
|
||||
(*env)->DeleteLocalRef(env, jt);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_LABEL, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_button_create(const char *label) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jl = el_jstr(env, label);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createButton, jl);
|
||||
(*env)->DeleteLocalRef(env, jl);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_BUTTON, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_text_field_create(const char *placeholder) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, placeholder);
|
||||
/* singleLine = true */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createEditText, jp, (jboolean)JNI_TRUE);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_TEXTFIELD, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_text_area_create(const char *placeholder) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, placeholder);
|
||||
/* singleLine = false → multiline EditText */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createEditText, jp, (jboolean)JNI_FALSE);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_TEXTAREA, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_image_create(const char *path) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, path);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createImageView, jp);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_IMAGE, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
/* ── Widget property setters ─────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_set_text(int64_t handle, const char *text) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jt = el_jstr(env, text);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setText,
|
||||
(jint)w->slot, jt);
|
||||
(*env)->DeleteLocalRef(env, jt);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
const char *el_android_widget_get_text(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return "";
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return "";
|
||||
jstring js = (jstring)(*env)->CallStaticObjectMethod(env, g_bridge_class,
|
||||
g_mid_getText,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return ""; }
|
||||
const char *result = "";
|
||||
if (js) {
|
||||
const char *cstr = (*env)->GetStringUTFChars(env, js, NULL);
|
||||
result = cstr ? strdup(cstr) : "";
|
||||
if (cstr) (*env)->ReleaseStringUTFChars(env, js, cstr);
|
||||
(*env)->DeleteLocalRef(env, js);
|
||||
}
|
||||
el_jni_detach_if_attached();
|
||||
return result;
|
||||
}
|
||||
|
||||
void el_android_widget_set_color(int64_t handle, float r, float g, float b, float a) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTextColor,
|
||||
(jint)w->slot, (jfloat)r, (jfloat)g,
|
||||
(jfloat)b, (jfloat)a);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_bg_color(int64_t handle, float r, float g, float b, float a) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setBackgroundColor,
|
||||
(jint)w->slot, (jfloat)r, (jfloat)g,
|
||||
(jfloat)b, (jfloat)a);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_font(int64_t handle, const char *family, int size, int bold) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jfam = el_jstr(env, family);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setFont,
|
||||
(jint)w->slot, jfam, (jint)size,
|
||||
(jboolean)(bold ? JNI_TRUE : JNI_FALSE));
|
||||
(*env)->DeleteLocalRef(env, jfam);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_padding(int64_t handle, int top, int right, int bottom, int left) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setPadding,
|
||||
(jint)w->slot, (jint)top, (jint)right,
|
||||
(jint)bottom, (jint)left);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_width(int64_t handle, int width) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setWidth,
|
||||
(jint)w->slot, (jint)width);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_height(int64_t handle, int height) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setHeight,
|
||||
(jint)w->slot, (jint)height);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_flex(int64_t handle, int flex) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setFlex,
|
||||
(jint)w->slot, (jint)flex);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_corner_radius(int64_t handle, int radius) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setCornerRadius,
|
||||
(jint)w->slot, (jfloat)radius);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_disabled(int64_t handle, int disabled) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setEnabled,
|
||||
(jint)w->slot,
|
||||
(jboolean)(disabled ? JNI_FALSE : JNI_TRUE));
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_hidden(int64_t handle, int hidden) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
/* visible=true means NOT hidden */
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setVisibility,
|
||||
(jint)w->slot,
|
||||
(jboolean)(hidden ? JNI_FALSE : JNI_TRUE));
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Child management ─────────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_add_child(int64_t parent, int64_t child) {
|
||||
ElWidget *pw = el_widget_get(parent);
|
||||
ElWidget *cw = el_widget_get(child);
|
||||
if (!pw || !cw) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_addChild,
|
||||
(jint)pw->slot, (jint)cw->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_remove_child(int64_t parent, int64_t child) {
|
||||
ElWidget *pw = el_widget_get(parent);
|
||||
ElWidget *cw = el_widget_get(child);
|
||||
if (!pw || !cw) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_removeChild,
|
||||
(jint)pw->slot, (jint)cw->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Event registration ───────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_on_click(int64_t handle, const char *fn_name) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_click);
|
||||
w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
if (!w->cb_click) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnClickListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_on_change(int64_t handle, const char *fn_name) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_change);
|
||||
w->cb_change = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
if (!w->cb_change) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnChangeListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_on_submit(int64_t handle, const char *fn_name) {
|
||||
/* Submit stored in cb_click, same as AppKit. */
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_click);
|
||||
w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
if (!w->cb_click) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnSubmitListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Widget destroy ───────────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_destroy(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (env && g_bridge_class) {
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_destroyView,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
}
|
||||
el_widget_free(handle);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Manifest reader ─────────────────────────────────────────────────────── */
|
||||
/*
|
||||
* __manifest_read: parse the app{} block from a manifest file.
|
||||
* Returns the raw file contents as an el_val_t (const char* cast).
|
||||
* The caller (el program) parses the returned string.
|
||||
* Reads from the filesystem; for APK assets use the AssetManager path instead.
|
||||
*/
|
||||
static char *el_read_file(const char *path) {
|
||||
if (!path || !*path) return NULL;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (len <= 0) { fclose(f); return NULL; }
|
||||
char *buf = (char *)malloc((size_t)len + 1);
|
||||
if (!buf) { fclose(f); return NULL; }
|
||||
fread(buf, 1, (size_t)len, f);
|
||||
buf[len] = '\0';
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
el_val_t el_android_manifest_read(const char *path) {
|
||||
char *contents = el_read_file(path);
|
||||
if (!contents) return (el_val_t)(uintptr_t)"";
|
||||
return (el_val_t)(uintptr_t)contents; /* caller owns allocation */
|
||||
}
|
||||
|
||||
/* ── __widget_* C API (called from el_seed.c) ────────────────────────────── */
|
||||
/*
|
||||
* These are the functions declared in el_native_target.h under EL_TARGET_ANDROID.
|
||||
* They forward to the el_android_* internal functions above.
|
||||
*
|
||||
* The el_val_t / int64_t ABI matches the AppKit functions exactly:
|
||||
* - Integer params passed as int64_t, extracted with (int)
|
||||
* - String params passed as int64_t, extracted with (const char*)(uintptr_t)
|
||||
* - Float params (r,g,b,a) passed as int64_t bit-cast from double; extracted
|
||||
* with el_to_float / bit-cast union
|
||||
*/
|
||||
|
||||
static inline float el_val_to_float(el_val_t v) {
|
||||
union { double d; int64_t i; } u;
|
||||
u.i = v;
|
||||
return (float)u.d;
|
||||
}
|
||||
|
||||
void __native_init(void) {
|
||||
el_android_init();
|
||||
}
|
||||
|
||||
void __native_run_loop(void) {
|
||||
/* No-op on Android — lifecycle is driven by the Activity. */
|
||||
}
|
||||
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height) {
|
||||
return (el_val_t)el_android_window_create(
|
||||
(const char *)(uintptr_t)title,
|
||||
(int)width, (int)height, (int)min_width, (int)min_height);
|
||||
}
|
||||
|
||||
void __window_show(el_val_t handle) {
|
||||
el_android_window_show((int64_t)handle);
|
||||
}
|
||||
|
||||
void __window_set_title(el_val_t handle, el_val_t title) {
|
||||
el_android_window_set_title((int64_t)handle,
|
||||
(const char *)(uintptr_t)title);
|
||||
}
|
||||
|
||||
el_val_t __vstack_create(el_val_t spacing) {
|
||||
return (el_val_t)el_android_vstack_create((int)spacing);
|
||||
}
|
||||
|
||||
el_val_t __hstack_create(el_val_t spacing) {
|
||||
return (el_val_t)el_android_hstack_create((int)spacing);
|
||||
}
|
||||
|
||||
el_val_t __zstack_create(void) {
|
||||
return (el_val_t)el_android_zstack_create();
|
||||
}
|
||||
|
||||
el_val_t __scroll_create(void) {
|
||||
return (el_val_t)el_android_scroll_create();
|
||||
}
|
||||
|
||||
el_val_t __label_create(el_val_t text) {
|
||||
return (el_val_t)el_android_label_create((const char *)(uintptr_t)text);
|
||||
}
|
||||
|
||||
el_val_t __button_create(el_val_t label) {
|
||||
return (el_val_t)el_android_button_create((const char *)(uintptr_t)label);
|
||||
}
|
||||
|
||||
el_val_t __text_field_create(el_val_t placeholder) {
|
||||
return (el_val_t)el_android_text_field_create((const char *)(uintptr_t)placeholder);
|
||||
}
|
||||
|
||||
el_val_t __text_area_create(el_val_t placeholder) {
|
||||
return (el_val_t)el_android_text_area_create((const char *)(uintptr_t)placeholder);
|
||||
}
|
||||
|
||||
el_val_t __image_create(el_val_t path_or_name) {
|
||||
return (el_val_t)el_android_image_create((const char *)(uintptr_t)path_or_name);
|
||||
}
|
||||
|
||||
void __widget_set_text(el_val_t handle, el_val_t text) {
|
||||
el_android_widget_set_text((int64_t)handle,
|
||||
(const char *)(uintptr_t)text);
|
||||
}
|
||||
|
||||
el_val_t __widget_get_text(el_val_t handle) {
|
||||
return (el_val_t)(uintptr_t)el_android_widget_get_text((int64_t)handle);
|
||||
}
|
||||
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a) {
|
||||
el_android_widget_set_color((int64_t)handle,
|
||||
el_val_to_float(r), el_val_to_float(g),
|
||||
el_val_to_float(b), el_val_to_float(a));
|
||||
}
|
||||
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a) {
|
||||
el_android_widget_set_bg_color((int64_t)handle,
|
||||
el_val_to_float(r), el_val_to_float(g),
|
||||
el_val_to_float(b), el_val_to_float(a));
|
||||
}
|
||||
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold) {
|
||||
el_android_widget_set_font((int64_t)handle,
|
||||
(const char *)(uintptr_t)family,
|
||||
(int)size, (int)bold);
|
||||
}
|
||||
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left) {
|
||||
el_android_widget_set_padding((int64_t)handle,
|
||||
(int)top, (int)right, (int)bottom, (int)left);
|
||||
}
|
||||
|
||||
void __widget_set_width(el_val_t handle, el_val_t width) {
|
||||
el_android_widget_set_width((int64_t)handle, (int)width);
|
||||
}
|
||||
|
||||
void __widget_set_height(el_val_t handle, el_val_t height) {
|
||||
el_android_widget_set_height((int64_t)handle, (int)height);
|
||||
}
|
||||
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
el_android_widget_set_flex((int64_t)handle, (int)flex);
|
||||
}
|
||||
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
el_android_widget_set_corner_radius((int64_t)handle, (int)radius);
|
||||
}
|
||||
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_android_widget_set_disabled((int64_t)handle, (int)disabled);
|
||||
}
|
||||
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_android_widget_set_hidden((int64_t)handle, (int)hidden);
|
||||
}
|
||||
|
||||
void __widget_add_child(el_val_t parent, el_val_t child) {
|
||||
el_android_widget_add_child((int64_t)parent, (int64_t)child);
|
||||
}
|
||||
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
el_android_widget_remove_child((int64_t)parent, (int64_t)child);
|
||||
}
|
||||
|
||||
void __widget_destroy(el_val_t handle) {
|
||||
el_android_widget_destroy((int64_t)handle);
|
||||
}
|
||||
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_click((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_change((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_submit((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
el_val_t __manifest_read(el_val_t path) {
|
||||
return el_android_manifest_read((const char *)(uintptr_t)path);
|
||||
}
|
||||
|
||||
#endif /* EL_TARGET_ANDROID */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,844 @@
|
||||
/*
|
||||
* el_lvgl.c — LVGL v9 backend for the el native widget system.
|
||||
*
|
||||
* This file implements the microcontroller/embedded widget layer that el_seed.c
|
||||
* calls through to when EL_TARGET_LVGL is defined.
|
||||
*
|
||||
* Architecture:
|
||||
* el program (el code)
|
||||
* → __widget_* C builtins in el_seed.c
|
||||
* → el_lvgl_* C functions defined here
|
||||
* → lv_obj_t widgets via LVGL v9
|
||||
*
|
||||
* Target platforms: ESP32, STM32, industrial panels, any system with 256KB+
|
||||
* RAM and an LVGL-compatible display driver. No OS required.
|
||||
*
|
||||
* Widget handles: every widget is assigned an int64_t slot index into
|
||||
* g_widgets[]. The el program holds these as opaque Int values.
|
||||
* Slot 0 is reserved; -1 = invalid handle.
|
||||
*
|
||||
* Window model: on embedded there is one screen. __window_create configures
|
||||
* lv_scr_act() as the root container. __window_show is a no-op (the screen
|
||||
* is always visible). __native_run_loop calls lv_task_handler() in a tight
|
||||
* loop — on RTOS this runs inside a dedicated task; on bare metal it IS the
|
||||
* main loop. The host application is responsible for initialising the display
|
||||
* driver and calling lv_tick_inc() before calling __native_run_loop.
|
||||
*
|
||||
* Callback dispatch:
|
||||
* When EL_LVGL_NO_DLSYM is NOT defined (hosted Linux, testing):
|
||||
* dlsym(RTLD_DEFAULT, fn_name) resolves the El function symbol at runtime.
|
||||
* When EL_LVGL_NO_DLSYM IS defined (bare-metal ESP32/STM32):
|
||||
* The caller must provide:
|
||||
* el_val_t el_lvgl_dispatch(const char *fn, el_val_t a, el_val_t b);
|
||||
* which maps function names to function pointers via a compile-time table.
|
||||
*
|
||||
* Font mapping: LVGL v9 ships Montserrat in discrete sizes. __widget_set_font
|
||||
* maps the requested point size to the nearest available Montserrat variant.
|
||||
* Bold is approximated by stepping up two sizes (no separate bold face in the
|
||||
* default LVGL font set). Define EL_LVGL_CUSTOM_FONT to override font_select().
|
||||
*
|
||||
* Compile (hosted test build):
|
||||
* gcc -DEL_TARGET_LVGL -I./lvgl el_lvgl.c -c -o el_lvgl.o
|
||||
* # Then link with lvgl.a / lvgl source tree.
|
||||
*
|
||||
* Compile (bare-metal, no dynamic linker):
|
||||
* arm-none-eabi-gcc -DEL_TARGET_LVGL -DEL_LVGL_NO_DLSYM \
|
||||
* -I./lvgl el_lvgl.c -c -o el_lvgl.o
|
||||
*/
|
||||
|
||||
#ifdef EL_TARGET_LVGL
|
||||
|
||||
#include "lvgl/lvgl.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef EL_LVGL_NO_DLSYM
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#include "el_runtime.h"
|
||||
|
||||
/* ── Callback dispatch macro ─────────────────────────────────────────────── */
|
||||
|
||||
#ifdef EL_LVGL_NO_DLSYM
|
||||
/*
|
||||
* Bare-metal path. The application provides this function:
|
||||
* el_val_t el_lvgl_dispatch(const char *fn, el_val_t a, el_val_t b);
|
||||
* It maps string names → function pointers, typically via a switch on a hash
|
||||
* or a sorted table of {name, fn_ptr} pairs generated by elc.
|
||||
*/
|
||||
extern el_val_t el_lvgl_dispatch(const char *fn, el_val_t a, el_val_t b);
|
||||
#define EL_LVGL_CALL(fn_name, a, b) el_lvgl_dispatch((fn_name), (a), (b))
|
||||
#else
|
||||
/*
|
||||
* Hosted path. dlsym resolves the El symbol at call time.
|
||||
* We use a compound-statement expression (GCC/Clang extension) to avoid
|
||||
* executing dlsym more than once per call.
|
||||
*/
|
||||
#define EL_LVGL_CALL(fn_name, a, b) \
|
||||
({ \
|
||||
typedef el_val_t (*_el_fn_t)(el_val_t, el_val_t); \
|
||||
_el_fn_t _fn = (_el_fn_t)(uintptr_t)dlsym(RTLD_DEFAULT, (fn_name)); \
|
||||
_fn ? _fn((a), (b)) : (el_val_t)0; \
|
||||
})
|
||||
#endif
|
||||
|
||||
/* ── Widget table ─────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_LVGL_MAX_WIDGETS 4096
|
||||
|
||||
/*
|
||||
* Widget kinds — mirrors AppKit/GTK4 backends so future tooling can stay
|
||||
* consistent across all targets.
|
||||
*/
|
||||
typedef enum {
|
||||
EL_LVGL_FREE = 0,
|
||||
EL_LVGL_WINDOW = 1,
|
||||
EL_LVGL_VSTACK = 2,
|
||||
EL_LVGL_HSTACK = 3,
|
||||
EL_LVGL_ZSTACK = 4,
|
||||
EL_LVGL_SCROLL = 5,
|
||||
EL_LVGL_LABEL = 6,
|
||||
EL_LVGL_BUTTON = 7, /* lv_btn_create; inner label at slot_btn_label */
|
||||
EL_LVGL_TEXTFIELD = 8, /* lv_textarea, one-line */
|
||||
EL_LVGL_TEXTAREA = 9, /* lv_textarea, multiline */
|
||||
EL_LVGL_IMAGE = 10,
|
||||
EL_LVGL_DIVIDER = 11,
|
||||
EL_LVGL_SPACER = 12,
|
||||
} ElLvglKind;
|
||||
|
||||
/*
|
||||
* Per-slot state. Callback names are stored inline (256 bytes each) to avoid
|
||||
* heap allocation on targets with no malloc or fragmented heaps.
|
||||
*/
|
||||
typedef struct {
|
||||
ElLvglKind kind;
|
||||
lv_obj_t *obj; /* primary LVGL object */
|
||||
lv_obj_t *btn_label; /* for EL_LVGL_BUTTON: inner lv_label child */
|
||||
char cb_click[256];
|
||||
char cb_change[256];
|
||||
char cb_submit[256];
|
||||
} ElLvglWidget;
|
||||
|
||||
static ElLvglWidget g_widgets[EL_LVGL_MAX_WIDGETS];
|
||||
|
||||
/* ── Slot helpers ─────────────────────────────────────────────────────────── */
|
||||
|
||||
static int64_t lvgl_slot_alloc(ElLvglKind kind, lv_obj_t *obj) {
|
||||
for (int i = 1; i < EL_LVGL_MAX_WIDGETS; i++) {
|
||||
if (g_widgets[i].kind == EL_LVGL_FREE) {
|
||||
g_widgets[i].kind = kind;
|
||||
g_widgets[i].obj = obj;
|
||||
g_widgets[i].btn_label = NULL;
|
||||
g_widgets[i].cb_click[0] = '\0';
|
||||
g_widgets[i].cb_change[0] = '\0';
|
||||
g_widgets[i].cb_submit[0] = '\0';
|
||||
return (int64_t)i;
|
||||
}
|
||||
}
|
||||
return -1; /* table full */
|
||||
}
|
||||
|
||||
static ElLvglWidget *lvgl_slot_get(int64_t handle) {
|
||||
if (handle <= 0 || handle >= EL_LVGL_MAX_WIDGETS) return NULL;
|
||||
if (g_widgets[handle].kind == EL_LVGL_FREE) return NULL;
|
||||
return &g_widgets[handle];
|
||||
}
|
||||
|
||||
static void lvgl_slot_free(int64_t handle) {
|
||||
if (handle <= 0 || handle >= EL_LVGL_MAX_WIDGETS) return;
|
||||
ElLvglWidget *w = &g_widgets[handle];
|
||||
w->kind = EL_LVGL_FREE;
|
||||
w->obj = NULL;
|
||||
w->btn_label = NULL;
|
||||
w->cb_click[0] = '\0';
|
||||
w->cb_change[0] = '\0';
|
||||
w->cb_submit[0] = '\0';
|
||||
}
|
||||
|
||||
/* ── Font selection ───────────────────────────────────────────────────────── */
|
||||
/*
|
||||
* LVGL ships Montserrat in the following sizes (subset enabled by lv_conf.h):
|
||||
* 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48
|
||||
*
|
||||
* We map the requested size to the nearest available size. Bold is
|
||||
* approximated by stepping up two sizes (no separate bold face in the default
|
||||
* font set). Define EL_LVGL_CUSTOM_FONT to replace this function entirely.
|
||||
*/
|
||||
#ifndef EL_LVGL_CUSTOM_FONT
|
||||
|
||||
static const lv_font_t *font_select(int size, int bold) {
|
||||
/* Step up two sizes for bold approximation. */
|
||||
if (bold) size += 4;
|
||||
|
||||
/* Clamp to available range. */
|
||||
if (size < 8) size = 8;
|
||||
if (size > 48) size = 48;
|
||||
|
||||
/* Round to nearest even size >= 8. */
|
||||
if (size % 2 != 0) size++;
|
||||
|
||||
switch (size) {
|
||||
#if LV_FONT_MONTSERRAT_8
|
||||
case 8: return &lv_font_montserrat_8;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_10
|
||||
case 10: return &lv_font_montserrat_10;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_12
|
||||
case 12: return &lv_font_montserrat_12;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_14
|
||||
case 14: return &lv_font_montserrat_14;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_16
|
||||
case 16: return &lv_font_montserrat_16;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_18
|
||||
case 18: return &lv_font_montserrat_18;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_20
|
||||
case 20: return &lv_font_montserrat_20;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_22
|
||||
case 22: return &lv_font_montserrat_22;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_24
|
||||
case 24: return &lv_font_montserrat_24;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_26
|
||||
case 26: return &lv_font_montserrat_26;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_28
|
||||
case 28: return &lv_font_montserrat_28;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_30
|
||||
case 30: return &lv_font_montserrat_30;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_32
|
||||
case 32: return &lv_font_montserrat_32;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_34
|
||||
case 34: return &lv_font_montserrat_34;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_36
|
||||
case 36: return &lv_font_montserrat_36;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_38
|
||||
case 38: return &lv_font_montserrat_38;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_40
|
||||
case 40: return &lv_font_montserrat_40;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_42
|
||||
case 42: return &lv_font_montserrat_42;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_44
|
||||
case 44: return &lv_font_montserrat_44;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_46
|
||||
case 46: return &lv_font_montserrat_46;
|
||||
#endif
|
||||
#if LV_FONT_MONTSERRAT_48
|
||||
case 48: return &lv_font_montserrat_48;
|
||||
#endif
|
||||
default:
|
||||
/*
|
||||
* Requested size is not compiled in. Fall back to the default
|
||||
* theme font, which is guaranteed to be present.
|
||||
*/
|
||||
return LV_FONT_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* EL_LVGL_CUSTOM_FONT */
|
||||
|
||||
/* ── Event callback ───────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Single LVGL event callback used for all widget events. The user_data is
|
||||
* the slot index cast to (void*) via intptr_t — avoids heap allocation.
|
||||
*
|
||||
* Three event codes are handled:
|
||||
* LV_EVENT_CLICKED → cb_click (buttons, any tappable widget)
|
||||
* LV_EVENT_VALUE_CHANGED → cb_change (textarea, checkbox, etc.)
|
||||
* LV_EVENT_READY → cb_submit (Enter pressed in textarea one-line mode)
|
||||
*/
|
||||
static void el_lvgl_event_cb(lv_event_t *e) {
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
intptr_t slot = (intptr_t)lv_event_get_user_data(e);
|
||||
ElLvglWidget *w = lvgl_slot_get((int64_t)slot);
|
||||
if (!w) return;
|
||||
|
||||
if (code == LV_EVENT_CLICKED && w->cb_click[0]) {
|
||||
EL_LVGL_CALL(w->cb_click, (el_val_t)slot, (el_val_t)0);
|
||||
}
|
||||
|
||||
if (code == LV_EVENT_VALUE_CHANGED && w->cb_change[0]) {
|
||||
/*
|
||||
* Retrieve current text for textarea/textfield widgets so the handler
|
||||
* receives the updated value as its second argument.
|
||||
*/
|
||||
const char *txt = "";
|
||||
lv_obj_t *target = lv_event_get_target(e);
|
||||
if (w->kind == EL_LVGL_TEXTFIELD || w->kind == EL_LVGL_TEXTAREA) {
|
||||
txt = lv_textarea_get_text(target);
|
||||
if (!txt) txt = "";
|
||||
}
|
||||
EL_LVGL_CALL(w->cb_change, (el_val_t)slot,
|
||||
(el_val_t)(uintptr_t)txt);
|
||||
}
|
||||
|
||||
if (code == LV_EVENT_READY && w->cb_submit[0]) {
|
||||
/* LV_EVENT_READY fires when Enter is pressed in a one-line textarea. */
|
||||
EL_LVGL_CALL(w->cb_submit, (el_val_t)slot, (el_val_t)0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Initialisation ───────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_lvgl_init — call lv_init(). The host must have already initialised the
|
||||
* display driver and input driver before this, or immediately after. Idempotent.
|
||||
*/
|
||||
void el_lvgl_init(void) {
|
||||
static int done = 0;
|
||||
if (done) return;
|
||||
done = 1;
|
||||
lv_init();
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_run_loop — drive lv_task_handler() indefinitely.
|
||||
*
|
||||
* On RTOS: this function should run inside a dedicated FreeRTOS/Zephyr task.
|
||||
* On bare metal: call this as the last statement of main().
|
||||
*
|
||||
* The 5 ms delay between handler calls matches the LVGL documentation
|
||||
* recommendation for a ~200 Hz refresh budget.
|
||||
*
|
||||
* On hosted Linux (EL_LVGL_SDL or similar), usleep(5000) is used. On RTOS
|
||||
* targets define EL_LVGL_RTOS_DELAY(ms) to map to vTaskDelay/k_sleep/etc.
|
||||
*/
|
||||
void el_lvgl_run_loop(void) {
|
||||
for (;;) {
|
||||
lv_task_handler();
|
||||
#if defined(EL_LVGL_RTOS_DELAY)
|
||||
EL_LVGL_RTOS_DELAY(5);
|
||||
#elif defined(__linux__) || defined(__APPLE__)
|
||||
{
|
||||
/* Hosted test build — usleep available. */
|
||||
#include <unistd.h>
|
||||
usleep(5000);
|
||||
}
|
||||
#endif
|
||||
/* Bare-metal without a delay macro: the HAL tick increment loop
|
||||
* is the caller's responsibility. No sleep needed if lv_tick_inc()
|
||||
* is driven from a hardware timer ISR. */
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Window ───────────────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_lvgl_window_create — configure lv_scr_act() as a vertical flex container
|
||||
* and return a slot handle wrapping it. The title is stored for informational
|
||||
* purposes (e.g., a status bar widget the host might create). Width/height
|
||||
* are ignored on embedded targets because the screen size is fixed by the
|
||||
* display driver; they are accepted for API compatibility with other backends.
|
||||
*/
|
||||
int64_t el_lvgl_window_create(const char *title, int width, int height,
|
||||
int min_width, int min_height) {
|
||||
(void)width; (void)height; (void)min_width; (void)min_height;
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
/* Configure the active screen as a vertical flex container so that
|
||||
* widgets added via __widget_add_child stack naturally. */
|
||||
lv_obj_set_flex_flow(scr, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_size(scr, LV_PCT(100), LV_PCT(100));
|
||||
|
||||
/* Store the window title in a user-data string on the screen object
|
||||
* so host code can retrieve it if it wants to render a title bar. */
|
||||
if (title && *title) {
|
||||
/* lv_obj_set_user_data stores a void* — we cast the string pointer.
|
||||
* The string must outlive the screen object; for literals this is
|
||||
* always true. For dynamic titles use el_lvgl_window_set_title. */
|
||||
lv_obj_set_user_data(scr, (void *)(uintptr_t)title);
|
||||
}
|
||||
|
||||
/* Allocate a slot for the screen object. */
|
||||
int64_t h = lvgl_slot_alloc(EL_LVGL_WINDOW, scr);
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_window_show — no-op on embedded. The screen is always visible.
|
||||
*/
|
||||
void el_lvgl_window_show(int64_t handle) {
|
||||
(void)handle;
|
||||
/* On multi-screen setups, load the screen: */
|
||||
/* ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
* if (w) lv_scr_load(w->obj); */
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_window_set_title — update the user_data pointer on the screen object.
|
||||
* On embedded, "title" is typically only used by a custom host title bar.
|
||||
*/
|
||||
void el_lvgl_window_set_title(int64_t handle, const char *title) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w || w->kind != EL_LVGL_WINDOW) return;
|
||||
lv_obj_set_user_data(w->obj, (void *)(uintptr_t)(title ? title : ""));
|
||||
}
|
||||
|
||||
/* ── Layout containers ────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_lvgl_vstack_create — vertical flex column with inter-item gap = spacing.
|
||||
*/
|
||||
int64_t el_lvgl_vstack_create(int spacing) {
|
||||
lv_obj_t *obj = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(obj, (lv_coord_t)spacing, 0);
|
||||
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
/* Remove default LVGL border and background so containers are transparent
|
||||
* by default, matching the AppKit/GTK4 backends. */
|
||||
lv_obj_set_style_border_width(obj, 0, 0);
|
||||
lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_pad_all(obj, 0, 0);
|
||||
return lvgl_slot_alloc(EL_LVGL_VSTACK, obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_hstack_create — horizontal flex row with inter-item gap = spacing.
|
||||
*/
|
||||
int64_t el_lvgl_hstack_create(int spacing) {
|
||||
lv_obj_t *obj = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_style_pad_column(obj, (lv_coord_t)spacing, 0);
|
||||
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_border_width(obj, 0, 0);
|
||||
lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_pad_all(obj, 0, 0);
|
||||
return lvgl_slot_alloc(EL_LVGL_HSTACK, obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_zstack_create — plain container, children positioned absolutely.
|
||||
* No flex flow is set; callers use lv_obj_set_pos() on children directly,
|
||||
* or rely on their natural 0,0 origin.
|
||||
*/
|
||||
int64_t el_lvgl_zstack_create(void) {
|
||||
lv_obj_t *obj = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_border_width(obj, 0, 0);
|
||||
lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_pad_all(obj, 0, 0);
|
||||
return lvgl_slot_alloc(EL_LVGL_ZSTACK, obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_scroll_create — vertically scrollable container.
|
||||
*/
|
||||
int64_t el_lvgl_scroll_create(void) {
|
||||
lv_obj_t *obj = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_scroll_dir(obj, LV_DIR_VER);
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_border_width(obj, 0, 0);
|
||||
lv_obj_set_style_pad_all(obj, 0, 0);
|
||||
return lvgl_slot_alloc(EL_LVGL_SCROLL, obj);
|
||||
}
|
||||
|
||||
/* ── Widget factories ─────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_lvgl_label_create — static text label.
|
||||
*/
|
||||
int64_t el_lvgl_label_create(const char *text) {
|
||||
lv_obj_t *obj = lv_label_create(lv_scr_act());
|
||||
lv_label_set_text(obj, text ? text : "");
|
||||
return lvgl_slot_alloc(EL_LVGL_LABEL, obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_button_create — pressable button with a child label.
|
||||
*
|
||||
* LVGL buttons are containers; text is placed in an inner lv_label child.
|
||||
* We store the child label pointer in btn_label so set_text / get_text can
|
||||
* reach it without searching the object tree at runtime.
|
||||
*/
|
||||
int64_t el_lvgl_button_create(const char *label) {
|
||||
lv_obj_t *btn = lv_btn_create(lv_scr_act());
|
||||
lv_obj_t *lbl = lv_label_create(btn);
|
||||
lv_label_set_text(lbl, label ? label : "");
|
||||
lv_obj_center(lbl);
|
||||
|
||||
int64_t h = lvgl_slot_alloc(EL_LVGL_BUTTON, btn);
|
||||
if (h >= 0) {
|
||||
g_widgets[h].btn_label = lbl;
|
||||
/* Register click callback immediately so button responds when a
|
||||
* callback name is registered later via __widget_on_click. */
|
||||
lv_obj_add_event_cb(btn, el_lvgl_event_cb, LV_EVENT_CLICKED,
|
||||
(void *)(intptr_t)h);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_text_field_create — single-line text input.
|
||||
*/
|
||||
int64_t el_lvgl_text_field_create(const char *placeholder) {
|
||||
lv_obj_t *obj = lv_textarea_create(lv_scr_act());
|
||||
lv_textarea_set_one_line(obj, true);
|
||||
if (placeholder && *placeholder) {
|
||||
lv_textarea_set_placeholder_text(obj, placeholder);
|
||||
}
|
||||
int64_t h = lvgl_slot_alloc(EL_LVGL_TEXTFIELD, obj);
|
||||
if (h >= 0) {
|
||||
lv_obj_add_event_cb(obj, el_lvgl_event_cb, LV_EVENT_VALUE_CHANGED,
|
||||
(void *)(intptr_t)h);
|
||||
lv_obj_add_event_cb(obj, el_lvgl_event_cb, LV_EVENT_READY,
|
||||
(void *)(intptr_t)h);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_text_area_create — multi-line text input.
|
||||
*/
|
||||
int64_t el_lvgl_text_area_create(const char *placeholder) {
|
||||
lv_obj_t *obj = lv_textarea_create(lv_scr_act());
|
||||
lv_textarea_set_one_line(obj, false);
|
||||
if (placeholder && *placeholder) {
|
||||
lv_textarea_set_placeholder_text(obj, placeholder);
|
||||
}
|
||||
int64_t h = lvgl_slot_alloc(EL_LVGL_TEXTAREA, obj);
|
||||
if (h >= 0) {
|
||||
lv_obj_add_event_cb(obj, el_lvgl_event_cb, LV_EVENT_VALUE_CHANGED,
|
||||
(void *)(intptr_t)h);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_image_create — image widget.
|
||||
*
|
||||
* On hosted Linux (SDL backend), path is a filesystem path.
|
||||
* On embedded with SPIFFS/LittleFS, path is a SPIFFS URI: "S:/image.bin".
|
||||
* LVGL image decoders are registered separately by the host application.
|
||||
*/
|
||||
int64_t el_lvgl_image_create(const char *path_or_name) {
|
||||
lv_obj_t *obj = lv_img_create(lv_scr_act());
|
||||
if (path_or_name && *path_or_name) {
|
||||
lv_img_set_src(obj, path_or_name);
|
||||
}
|
||||
return lvgl_slot_alloc(EL_LVGL_IMAGE, obj);
|
||||
}
|
||||
|
||||
/* ── Widget property setters ─────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_text — update visible text.
|
||||
*
|
||||
* Dispatch per kind:
|
||||
* LABEL → lv_label_set_text
|
||||
* BUTTON → lv_label_set_text on inner btn_label child
|
||||
* TEXTFIELD / TEXTAREA → lv_textarea_set_text
|
||||
* WINDOW → lv_obj_set_user_data (stores title string)
|
||||
*/
|
||||
void el_lvgl_widget_set_text(int64_t handle, const char *text) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
const char *t = text ? text : "";
|
||||
switch (w->kind) {
|
||||
case EL_LVGL_LABEL:
|
||||
lv_label_set_text(w->obj, t);
|
||||
break;
|
||||
case EL_LVGL_BUTTON:
|
||||
if (w->btn_label) lv_label_set_text(w->btn_label, t);
|
||||
break;
|
||||
case EL_LVGL_TEXTFIELD:
|
||||
case EL_LVGL_TEXTAREA:
|
||||
lv_textarea_set_text(w->obj, t);
|
||||
break;
|
||||
case EL_LVGL_WINDOW:
|
||||
lv_obj_set_user_data(w->obj, (void *)(uintptr_t)t);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_get_text — retrieve visible text.
|
||||
*
|
||||
* Returns a pointer into LVGL's internal storage — valid until the next LVGL
|
||||
* operation that modifies the widget. Callers that need to hold the value
|
||||
* across LVGL calls must strdup() it.
|
||||
*/
|
||||
const char *el_lvgl_widget_get_text(int64_t handle) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return "";
|
||||
switch (w->kind) {
|
||||
case EL_LVGL_LABEL:
|
||||
return lv_label_get_text(w->obj);
|
||||
case EL_LVGL_BUTTON:
|
||||
return w->btn_label ? lv_label_get_text(w->btn_label) : "";
|
||||
case EL_LVGL_TEXTFIELD:
|
||||
case EL_LVGL_TEXTAREA:
|
||||
return lv_textarea_get_text(w->obj);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_color — foreground (text) colour.
|
||||
*
|
||||
* r/g/b are 0.0–1.0 floats bit-cast as el_val_t (see el_runtime.h).
|
||||
* LVGL lv_color_make takes uint8_t 0–255 components.
|
||||
*/
|
||||
void el_lvgl_widget_set_color(int64_t handle,
|
||||
float r, float g, float b, float a) {
|
||||
(void)a; /* LVGL text colour has no per-glyph alpha channel */
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
lv_color_t c = lv_color_make(
|
||||
(uint8_t)(r * 255.0f + 0.5f),
|
||||
(uint8_t)(g * 255.0f + 0.5f),
|
||||
(uint8_t)(b * 255.0f + 0.5f));
|
||||
lv_obj_set_style_text_color(w->obj, c, 0);
|
||||
if (w->kind == EL_LVGL_BUTTON && w->btn_label) {
|
||||
lv_obj_set_style_text_color(w->btn_label, c, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_bg_color — background fill colour + opacity.
|
||||
*/
|
||||
void el_lvgl_widget_set_bg_color(int64_t handle,
|
||||
float r, float g, float b, float a) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
lv_color_t c = lv_color_make(
|
||||
(uint8_t)(r * 255.0f + 0.5f),
|
||||
(uint8_t)(g * 255.0f + 0.5f),
|
||||
(uint8_t)(b * 255.0f + 0.5f));
|
||||
lv_opa_t opa = (lv_opa_t)(a * 255.0f + 0.5f);
|
||||
lv_obj_set_style_bg_color(w->obj, c, 0);
|
||||
lv_obj_set_style_bg_opa(w->obj, opa, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_font — apply font to text-bearing widget.
|
||||
*
|
||||
* The `family` parameter is accepted for API compatibility but LVGL uses
|
||||
* compiled-in fonts only. Only the size and bold flag have effect unless
|
||||
* EL_LVGL_CUSTOM_FONT is defined by the host.
|
||||
*/
|
||||
void el_lvgl_widget_set_font(int64_t handle,
|
||||
const char *family, int size, int bold) {
|
||||
(void)family; /* ignored; LVGL uses compiled-in Montserrat fonts */
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
const lv_font_t *font = font_select(size, bold);
|
||||
if (!font) return;
|
||||
lv_obj_set_style_text_font(w->obj, font, 0);
|
||||
if (w->kind == EL_LVGL_BUTTON && w->btn_label) {
|
||||
lv_obj_set_style_text_font(w->btn_label, font, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_padding — set per-side padding (top/right/bottom/left).
|
||||
*/
|
||||
void el_lvgl_widget_set_padding(int64_t handle,
|
||||
int top, int right, int bottom, int left) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
lv_obj_set_style_pad_top(w->obj, (lv_coord_t)top, 0);
|
||||
lv_obj_set_style_pad_right(w->obj, (lv_coord_t)right, 0);
|
||||
lv_obj_set_style_pad_bottom(w->obj, (lv_coord_t)bottom, 0);
|
||||
lv_obj_set_style_pad_left(w->obj, (lv_coord_t)left, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_width — set explicit pixel width.
|
||||
*/
|
||||
void el_lvgl_widget_set_width(int64_t handle, int width) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
lv_obj_set_width(w->obj, (lv_coord_t)width);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_height — set explicit pixel height.
|
||||
*/
|
||||
void el_lvgl_widget_set_height(int64_t handle, int height) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
lv_obj_set_height(w->obj, (lv_coord_t)height);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_flex — set flex grow factor.
|
||||
*
|
||||
* flex > 0 → lv_obj_set_flex_grow(obj, flex): object expands to fill
|
||||
* remaining space proportional to its grow factor.
|
||||
* flex == 0 → lv_obj_set_flex_grow(obj, 0): object uses natural size.
|
||||
*/
|
||||
void el_lvgl_widget_set_flex(int64_t handle, int flex) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
lv_obj_set_flex_grow(w->obj, (uint8_t)(flex > 0 ? flex : 0));
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_corner_radius — set border radius.
|
||||
*/
|
||||
void el_lvgl_widget_set_corner_radius(int64_t handle, int radius) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
lv_obj_set_style_radius(w->obj, (lv_coord_t)radius, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_disabled — enable/disable interactive state.
|
||||
*
|
||||
* LV_STATE_DISABLED greys out the widget and prevents input events.
|
||||
*/
|
||||
void el_lvgl_widget_set_disabled(int64_t handle, int disabled) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
if (disabled) {
|
||||
lv_obj_add_state(w->obj, LV_STATE_DISABLED);
|
||||
} else {
|
||||
lv_obj_clear_state(w->obj, LV_STATE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_set_hidden — show/hide widget.
|
||||
*
|
||||
* LV_OBJ_FLAG_HIDDEN hides the widget and removes it from layout flow.
|
||||
*/
|
||||
void el_lvgl_widget_set_hidden(int64_t handle, int hidden) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
if (hidden) {
|
||||
lv_obj_add_flag(w->obj, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_clear_flag(w->obj, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Tree operations ──────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_add_child — attach child widget to parent.
|
||||
*
|
||||
* lv_obj_set_parent() reparents the child object inside the LVGL tree.
|
||||
* For WINDOW parents we use the screen object itself as the parent, since
|
||||
* lv_scr_act() IS the root container.
|
||||
*/
|
||||
void el_lvgl_widget_add_child(int64_t parent, int64_t child) {
|
||||
ElLvglWidget *pw = lvgl_slot_get(parent);
|
||||
ElLvglWidget *cw = lvgl_slot_get(child);
|
||||
if (!pw || !cw) return;
|
||||
lv_obj_set_parent(cw->obj, pw->obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_remove_child — detach child from its current parent.
|
||||
*
|
||||
* LVGL has no explicit "remove from parent without deleting" operation.
|
||||
* We reparent the child back to the active screen (making it a root-level
|
||||
* floating widget) and then hide it. The widget still occupies a slot and
|
||||
* can be re-attached or destroyed later.
|
||||
*/
|
||||
void el_lvgl_widget_remove_child(int64_t parent, int64_t child) {
|
||||
(void)parent;
|
||||
ElLvglWidget *cw = lvgl_slot_get(child);
|
||||
if (!cw) return;
|
||||
/* Move to screen root and hide. */
|
||||
lv_obj_set_parent(cw->obj, lv_scr_act());
|
||||
lv_obj_add_flag(cw->obj, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
/*
|
||||
* el_lvgl_widget_destroy — delete widget and its children from the LVGL tree,
|
||||
* then free the slot.
|
||||
*
|
||||
* lv_obj_del() recursively deletes the object and all children. After this
|
||||
* call the handle is invalid and must not be used.
|
||||
*/
|
||||
void el_lvgl_widget_destroy(int64_t handle) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
lv_obj_del(w->obj);
|
||||
lvgl_slot_free(handle);
|
||||
}
|
||||
|
||||
/* ── Event registration ───────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Event registration stores the El function name in the widget slot. The
|
||||
* actual lv_obj_add_event_cb() call is made here (or was made in the factory
|
||||
* for buttons/textfields where we know the relevant event codes upfront).
|
||||
*
|
||||
* For widgets that did not register their event callback in the factory (e.g.
|
||||
* labels receiving a click handler), we add the LVGL event binding now.
|
||||
*/
|
||||
|
||||
void el_lvgl_widget_on_click(int64_t handle, const char *fn_name) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
strncpy(w->cb_click, fn_name ? fn_name : "", 255);
|
||||
w->cb_click[255] = '\0';
|
||||
/*
|
||||
* Buttons already have LV_EVENT_CLICKED registered in the factory.
|
||||
* For other widget kinds (labels, containers used as tap targets), add
|
||||
* the click flag and register the callback.
|
||||
*/
|
||||
if (w->kind != EL_LVGL_BUTTON) {
|
||||
lv_obj_add_flag(w->obj, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(w->obj, el_lvgl_event_cb, LV_EVENT_CLICKED,
|
||||
(void *)(intptr_t)handle);
|
||||
}
|
||||
}
|
||||
|
||||
void el_lvgl_widget_on_change(int64_t handle, const char *fn_name) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
strncpy(w->cb_change, fn_name ? fn_name : "", 255);
|
||||
w->cb_change[255] = '\0';
|
||||
/*
|
||||
* Textfield/textarea factories already register VALUE_CHANGED.
|
||||
* For other kinds (e.g. a custom toggle), add the binding now.
|
||||
*/
|
||||
if (w->kind != EL_LVGL_TEXTFIELD && w->kind != EL_LVGL_TEXTAREA) {
|
||||
lv_obj_add_event_cb(w->obj, el_lvgl_event_cb, LV_EVENT_VALUE_CHANGED,
|
||||
(void *)(intptr_t)handle);
|
||||
}
|
||||
}
|
||||
|
||||
void el_lvgl_widget_on_submit(int64_t handle, const char *fn_name) {
|
||||
ElLvglWidget *w = lvgl_slot_get(handle);
|
||||
if (!w) return;
|
||||
strncpy(w->cb_submit, fn_name ? fn_name : "", 255);
|
||||
w->cb_submit[255] = '\0';
|
||||
/*
|
||||
* LV_EVENT_READY fires when Enter is pressed in a one-line textarea.
|
||||
* Textfield factories already register READY. For other kinds, add it.
|
||||
*/
|
||||
if (w->kind != EL_LVGL_TEXTFIELD) {
|
||||
lv_obj_add_event_cb(w->obj, el_lvgl_event_cb, LV_EVENT_READY,
|
||||
(void *)(intptr_t)handle);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* EL_TARGET_LVGL */
|
||||
@@ -0,0 +1,574 @@
|
||||
/*
|
||||
* el_native_target.h — Native widget declarations for el programs targeting
|
||||
* native desktop UI (AppKit / GTK4 / Win32).
|
||||
*
|
||||
* This header is designed to be included AFTER el_runtime.h without conflict:
|
||||
* - It does NOT redefine el_to_float, el_from_float, or any el_runtime.h
|
||||
* static inlines.
|
||||
* - It does NOT redeclare __println, __print, or other functions whose
|
||||
* return types differ between el_seed.h and el_runtime.h.
|
||||
* - It adds: native widget builtins + float arithmetic helpers that the
|
||||
* current el_runtime.h omits but elc still emits calls to.
|
||||
*
|
||||
* Usage:
|
||||
* Inject via -include at compile time, OR #include it after el_runtime.h.
|
||||
*
|
||||
* clang -DEL_TARGET_MACOS -include el_native_target.h -c my_app.c ...
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* el_val_t must already be defined by el_runtime.h or el_seed.h. */
|
||||
#ifndef EL_VAL_T_DEFINED
|
||||
typedef int64_t el_val_t;
|
||||
#endif
|
||||
|
||||
/* ── Float arithmetic helpers ───────────────────────────────────────────────
|
||||
* elc emits calls to float_div / float_mul etc. for Float-typed expressions.
|
||||
* These were in el_runtime.c through v1.0.0-20260501 but are missing from the
|
||||
* current el_runtime.h. Redeclared here as static inline to avoid link deps.
|
||||
* Only defined if not already declared (old runtimes that still have them). */
|
||||
|
||||
#ifndef EL_FLOAT_OPS_DEFINED
|
||||
#define EL_FLOAT_OPS_DEFINED
|
||||
|
||||
/* el_to_float / el_from_float — bit-cast between el_val_t and double.
|
||||
* Defined as static inline in both el_runtime.h and el_seed.h; we do NOT
|
||||
* redefine them here. We rely on one of those headers being included first. */
|
||||
|
||||
static inline el_val_t float_div(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b;
|
||||
ur.d = (ub.d != 0.0) ? (ua.d / ub.d) : 0.0;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_mul(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d * ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_add(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d + ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_sub(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d - ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_lt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d < ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d > ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_lte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d <= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d >= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_eq(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d == ub.d);
|
||||
}
|
||||
|
||||
#endif /* EL_FLOAT_OPS_DEFINED */
|
||||
|
||||
/* ── Native widget system (macOS AppKit) ────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_MACOS and linked with el_appkit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid. */
|
||||
|
||||
#ifdef EL_TARGET_MACOS
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_set_data(el_val_t handle, el_val_t data_str);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_MACOS */
|
||||
|
||||
/* ── Native widget system (Linux GTK4) ──────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_LINUX and linked with el_gtk4.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* All functions have the same signatures as EL_TARGET_MACOS above. */
|
||||
|
||||
#ifdef EL_TARGET_LINUX
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as EL_TARGET_MACOS */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LINUX */
|
||||
|
||||
/* ── Native widget system (Windows Win32) ───────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_WIN32 and linked with el_win32.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* Link: el_win32.obj comctl32.lib user32.lib gdi32.lib */
|
||||
|
||||
#ifdef EL_TARGET_WIN32
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_WIN32 */
|
||||
|
||||
/* ── Native widget system (iOS UIKit) ───────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_IOS and linked with el_uikit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* iOS lifecycle note: UIApplicationMain never returns. The el program must
|
||||
* store its UI-build logic in a void(*)(void) function pointer, assign it to
|
||||
* el_main_entry_fn, then call __native_run_loop. ElAppDelegate invokes
|
||||
* el_main_entry_fn inside didFinishLaunchingWithOptions.
|
||||
* Call el_uikit_set_args(argc, argv) from main() before __native_run_loop. */
|
||||
|
||||
#ifdef EL_TARGET_IOS
|
||||
|
||||
/* Lifecycle entry-function hook — set before calling __native_run_loop. */
|
||||
extern void (*el_main_entry_fn)(void);
|
||||
|
||||
/* Forward argc/argv from main() to UIApplicationMain. */
|
||||
void el_uikit_set_args(int argc, char** argv);
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_IOS */
|
||||
|
||||
/* ── Native widget system (Android JNI) ─────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_ANDROID and linked with
|
||||
* libelruntime.so (which includes el_android.c compiled by the NDK build).
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Java companion: ElBridge.java (package com.neuron.el) must be compiled into
|
||||
* the APK. The Activity must call ElBridge.init(this) before any widget ops.
|
||||
*
|
||||
* Link flags (in Android.mk or CMakeLists.txt):
|
||||
* -landroid -llog -ldl */
|
||||
|
||||
#ifdef EL_TARGET_ANDROID
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void); /* no-op on Android */
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_ANDROID */
|
||||
|
||||
/* ── Native widget system (LVGL v9 — embedded / microcontroller) ─────────────
|
||||
* Available when compiled with -DEL_TARGET_LVGL and linked with el_lvgl.c
|
||||
* and the LVGL library (lvgl.a or lvgl source tree).
|
||||
*
|
||||
* Target platforms: ESP32, STM32, industrial panels. Any system with 256KB+
|
||||
* RAM and an LVGL-compatible display driver. No OS required.
|
||||
*
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Bare-metal / no dynamic linker:
|
||||
* Compile with -DEL_LVGL_NO_DLSYM and provide:
|
||||
* el_val_t el_lvgl_dispatch(const char *fn, el_val_t a, el_val_t b);
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_LVGL -I./lvgl el_lvgl.c -c -o el_lvgl.o
|
||||
* # Then link with lvgl.a. */
|
||||
|
||||
#ifdef EL_TARGET_LVGL
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as all other native targets */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LVGL */
|
||||
|
||||
/* ── Native widget system (SDL2 — embedded / Pi) ────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_SDL2 and linked with el_sdl2.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Target: Raspberry Pi Zero, embedded Linux, any system with a framebuffer
|
||||
* and SDL2 available. No GTK, no desktop environment required.
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_SDL2 $(sdl2-config --cflags) -c el_sdl2.c -o el_sdl2.o
|
||||
* Link:
|
||||
* $(sdl2-config --libs) -lSDL2_ttf -lSDL2_image -ldl */
|
||||
|
||||
#ifdef EL_TARGET_SDL2
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_SDL2 */
|
||||
@@ -190,6 +190,7 @@ el_val_t println(el_val_t s) {
|
||||
const char* str = EL_CSTR(s);
|
||||
if (str) puts(str);
|
||||
else puts("");
|
||||
fflush(stdout); /* prevent startup logs from silently buffering when stdout→file */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -709,6 +710,7 @@ static void jb_init(JsonBuf* b);
|
||||
static void jb_putc(JsonBuf* b, char c);
|
||||
static void jb_puts(JsonBuf* b, const char* s);
|
||||
static void jb_emit_escaped(JsonBuf* b, const char* s);
|
||||
static char* jb_finish(JsonBuf* b);
|
||||
static int looks_like_string(el_val_t v);
|
||||
static const char* json_find_key(const char* s, const char* key);
|
||||
static const char* json_skip_value(const char* p);
|
||||
@@ -2013,7 +2015,7 @@ el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) {
|
||||
jb_puts(&out, ",\"body\":");
|
||||
jb_emit_escaped(&out, b);
|
||||
jb_putc(&out, '}');
|
||||
return el_wrap_str(out.buf);
|
||||
return el_wrap_str(jb_finish(&out));
|
||||
}
|
||||
|
||||
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||
@@ -2120,7 +2122,7 @@ el_val_t exec_capture(el_val_t cmdv) {
|
||||
char buf[4096];
|
||||
while (fgets(buf, sizeof(buf), f)) jb_puts(&b, buf);
|
||||
pclose(f);
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
// exec — run a shell command via /bin/sh, capture stdout, return as String.
|
||||
@@ -2144,7 +2146,7 @@ el_val_t exec(el_val_t cmdv) {
|
||||
jb_puts(&b, buf);
|
||||
}
|
||||
pclose(f);
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
// exec_bg — run a shell command in background, return PID as String.
|
||||
@@ -3291,6 +3293,23 @@ static void jb_puts(JsonBuf* b, const char* s) {
|
||||
b->buf[b->len] = '\0';
|
||||
}
|
||||
|
||||
/* jb_finish — call exactly once, at the point a JsonBuf's buffer is handed
|
||||
* off as an el_val_t return value (via el_wrap_str). JsonBuf allocates through
|
||||
* raw malloc/realloc (jb_init/jb_reserve), NOT el_strdup/el_strbuf, so unlike
|
||||
* those helpers it was never registered with the per-request arena — every
|
||||
* JsonBuf-built JSON string leaked unconditionally, whether or not a request
|
||||
* arena was active, because nothing ever freed it. el_arena_track() is a
|
||||
* documented no-op when no arena is active, so this is safe to call from any
|
||||
* context (HTTP request handler, CLI arena-scope, or no arena at all — the
|
||||
* last case still leaks, same as before, until the caller's context is itself
|
||||
* arena-scoped). Do NOT call this on a JsonBuf whose ->buf is consumed by
|
||||
* another builder (jb_puts(&other, x.buf)) and freed manually — only on the
|
||||
* one actually returned via el_wrap_str. */
|
||||
static char* jb_finish(JsonBuf* b) {
|
||||
el_arena_track(b->buf);
|
||||
return b->buf;
|
||||
}
|
||||
|
||||
static void jb_emit_escaped(JsonBuf* b, const char* s) {
|
||||
jb_putc(b, '"');
|
||||
const unsigned char* p = (const unsigned char*)s;
|
||||
@@ -3399,7 +3418,7 @@ static void jb_emit_value(JsonBuf* b, el_val_t v) {
|
||||
el_val_t json_stringify(el_val_t v) {
|
||||
JsonBuf b; jb_init(&b);
|
||||
jb_emit_value(&b, v);
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
/* ── JSON substring accessors ────────────────────────────────────────────── */
|
||||
@@ -3586,7 +3605,7 @@ el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value) {
|
||||
jb_putc(&b, ':');
|
||||
jb_puts(&b, raw_val);
|
||||
jb_putc(&b, '}');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
const char* existing = json_find_key(json, k);
|
||||
JsonBuf b; jb_init(&b);
|
||||
@@ -3600,7 +3619,7 @@ el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value) {
|
||||
b.buf[b.len] = '\0';
|
||||
jb_puts(&b, raw_val);
|
||||
jb_puts(&b, end);
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
/* Insert before closing '}'. Find last '}' */
|
||||
size_t jl = strlen(json);
|
||||
@@ -3632,7 +3651,7 @@ el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value) {
|
||||
jb_puts(&b, raw_val);
|
||||
/* Append from close_idx onward */
|
||||
jb_puts(&b, json + close_idx);
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
el_val_t json_array_len(el_val_t json_str) {
|
||||
@@ -3770,7 +3789,7 @@ el_val_t json_build_object(el_val_t kvs) {
|
||||
jb_putc(&b, '"');
|
||||
}
|
||||
jb_putc(&b, '}');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
/* json_build_array — build a JSON array from a list of raw JSON values.
|
||||
@@ -3788,7 +3807,7 @@ el_val_t json_build_array(el_val_t items) {
|
||||
jb_puts(&b, vs);
|
||||
}
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
/* ── Time ────────────────────────────────────────────────────────────────── */
|
||||
@@ -5192,7 +5211,7 @@ el_val_t state_keys(void) {
|
||||
}
|
||||
jb_putc(&b, ']');
|
||||
pthread_mutex_unlock(&_state_mu);
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
/* Returns 1 (true) if the key is present in the state store, else 0 (false). */
|
||||
@@ -5398,7 +5417,7 @@ el_val_t str_format(el_val_t fmt, el_val_t data) {
|
||||
jb_putc(&b, *p);
|
||||
p++;
|
||||
}
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
el_val_t str_lower(el_val_t s) { return str_to_lower(s); }
|
||||
@@ -5814,7 +5833,7 @@ el_val_t list_join(el_val_t listv, el_val_t sep) {
|
||||
jb_puts(&b, tmp);
|
||||
}
|
||||
}
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
el_val_t list_range(el_val_t start, el_val_t end) {
|
||||
@@ -5880,6 +5899,10 @@ el_val_t getpid_now(void) {
|
||||
* Returns 0 always (the only non-return path is the exit() branch).
|
||||
*/
|
||||
el_val_t el_mem_check(void) {
|
||||
#ifdef _WIN32
|
||||
/* getrusage is POSIX-only — memory guard disabled on Windows. */
|
||||
return 0;
|
||||
#else
|
||||
/* Read limit from env; default 512 MB. */
|
||||
long limit_mb = 512;
|
||||
const char *env_val = getenv("ELC_MAX_MEM_MB");
|
||||
@@ -5905,6 +5928,7 @@ el_val_t el_mem_check(void) {
|
||||
exit(1);
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ── args() — command-line argument access ──────────────────────────────────
|
||||
@@ -6106,6 +6130,10 @@ static double engram_type_threshold(const char* node_type, const char* tier) {
|
||||
if (strcmp(tier, "Lesson") == 0) return 0.25;
|
||||
}
|
||||
if (node_type) {
|
||||
/* Knowledge nodes: Canonical/Lesson handled by tier checks above.
|
||||
* Procedural-tier Knowledge (activation_count>=50 migration): 0.20.
|
||||
* (2026-06-29 self-review — mirrors release runtime fix) */
|
||||
if (strcmp(node_type, "Knowledge") == 0) return 0.20;
|
||||
if (strcmp(node_type, "Belief") == 0) return 0.30;
|
||||
if (strcmp(node_type, "Entity") == 0) return 0.30;
|
||||
}
|
||||
@@ -6455,12 +6483,12 @@ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
|
||||
n->id = engram_new_id();
|
||||
const char* c = EL_CSTR(content);
|
||||
const char* nt = EL_CSTR(node_type);
|
||||
n->content = el_strdup(c ? c : "");
|
||||
n->node_type = el_strdup(nt && *nt ? nt : "Memory");
|
||||
n->label = engram_first_n_chars(c, 60);
|
||||
n->tier = el_strdup("Working");
|
||||
n->tags = el_strdup("");
|
||||
n->metadata = el_strdup("{}");
|
||||
n->content = el_strdup_persist(c ? c : "");
|
||||
n->node_type = el_strdup_persist(nt && *nt ? nt : "Memory");
|
||||
{ char* _lb = engram_first_n_chars(c, 60); n->label = el_strdup_persist(_lb); } /* persist: stored field must outlive request arena */
|
||||
n->tier = el_strdup_persist("Working");
|
||||
n->tags = el_strdup_persist("");
|
||||
n->metadata = el_strdup_persist("{}");
|
||||
n->salience = engram_decode_score(salience);
|
||||
if (n->salience <= 0.0 || n->salience > 1.0) n->salience = 0.5;
|
||||
n->importance = 0.5;
|
||||
@@ -6476,6 +6504,39 @@ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
|
||||
return el_wrap_str(el_strdup(n->id));
|
||||
}
|
||||
|
||||
/* engram_is_valid_utf8 — return 1 if s is valid UTF-8, 0 if it contains invalid bytes.
|
||||
* Rejects overlong encodings, surrogate halves, and byte sequences > 4 bytes. */
|
||||
static int engram_is_valid_utf8(const char* s) {
|
||||
if (!s) return 1;
|
||||
const unsigned char* p = (const unsigned char*)s;
|
||||
while (*p) {
|
||||
if (*p < 0x80) {
|
||||
/* ASCII */
|
||||
p++;
|
||||
} else if ((*p & 0xE0) == 0xC0) {
|
||||
/* 2-byte sequence */
|
||||
if ((p[1] & 0xC0) != 0x80) return 0;
|
||||
if ((*p & 0xFE) == 0xC0) return 0; /* overlong */
|
||||
p += 2;
|
||||
} else if ((*p & 0xF0) == 0xE0) {
|
||||
/* 3-byte sequence */
|
||||
if ((p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) return 0;
|
||||
if (*p == 0xE0 && (p[1] & 0xE0) == 0x80) return 0; /* overlong */
|
||||
if (*p == 0xED && (p[1] & 0xE0) == 0xA0) return 0; /* surrogate */
|
||||
p += 3;
|
||||
} else if ((*p & 0xF8) == 0xF0) {
|
||||
/* 4-byte sequence */
|
||||
if ((p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80) return 0;
|
||||
if (*p == 0xF0 && (p[1] & 0xF0) == 0x80) return 0; /* overlong */
|
||||
if (*p > 0xF4) return 0; /* above U+10FFFF */
|
||||
p += 4;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t importance, el_val_t confidence,
|
||||
el_val_t tier, el_val_t tags) {
|
||||
@@ -6489,6 +6550,13 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
const char* lb = EL_CSTR(label);
|
||||
const char* ti = EL_CSTR(tier);
|
||||
const char* tg = EL_CSTR(tags);
|
||||
/* UTF-8 guard: reject content with invalid UTF-8 bytes. Persisting invalid
|
||||
* UTF-8 garbles JSON snapshots and corrupts every subsequent node read. */
|
||||
if (c && !engram_is_valid_utf8(c)) {
|
||||
fprintf(stderr, "[engram] REJECTED node write — content contains invalid UTF-8 (label=%s)\n",
|
||||
lb ? lb : "(null)");
|
||||
return EL_STR("");
|
||||
}
|
||||
/* Persistent (el_strdup_persist, NOT el_strdup): these strings are owned by the
|
||||
* persistent global node store. el_strdup tracks into the per-request arena, which
|
||||
* el_request_end() frees when the creating HTTP request completes — leaving the
|
||||
@@ -6547,11 +6615,11 @@ el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t labe
|
||||
const char* lb = EL_CSTR(label);
|
||||
const char* tg = EL_CSTR(tags);
|
||||
const char* st = EL_CSTR(status);
|
||||
n->content = el_strdup(c ? c : "");
|
||||
n->node_type = el_strdup(nt && *nt ? nt : "Memory");
|
||||
n->label = el_strdup(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : ""));
|
||||
n->tier = el_strdup("Working");
|
||||
n->tags = el_strdup(tg ? tg : "");
|
||||
n->content = el_strdup_persist(c ? c : "");
|
||||
n->node_type = el_strdup_persist(nt && *nt ? nt : "Memory");
|
||||
n->label = el_strdup_persist(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : ""));
|
||||
n->tier = el_strdup_persist("Working");
|
||||
n->tags = el_strdup_persist(tg ? tg : "");
|
||||
if (st && *st) {
|
||||
/* Minimal metadata payload: {"status":"..."}. Keep it cheap so
|
||||
* callers using `status` don't pay JSON parse cost on every read. */
|
||||
@@ -6560,7 +6628,7 @@ el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t labe
|
||||
snprintf(meta, sl, "{\"status\":\"%s\"}", st);
|
||||
n->metadata = meta;
|
||||
} else {
|
||||
n->metadata = el_strdup("{}");
|
||||
n->metadata = el_strdup_persist("{}");
|
||||
}
|
||||
n->salience = engram_decode_score(salience);
|
||||
n->importance = engram_decode_score(certainty);
|
||||
@@ -6833,10 +6901,10 @@ void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t
|
||||
EngramEdge* e = &g->edges[g->edge_count];
|
||||
memset(e, 0, sizeof(*e));
|
||||
e->id = engram_new_id();
|
||||
e->from_id = el_strdup(f);
|
||||
e->to_id = el_strdup(t);
|
||||
e->relation = el_strdup(r && *r ? r : "associate");
|
||||
e->metadata = el_strdup("{}");
|
||||
e->from_id = el_strdup_persist(f);
|
||||
e->to_id = el_strdup_persist(t);
|
||||
e->relation = el_strdup_persist(r && *r ? r : "associate");
|
||||
e->metadata = el_strdup_persist("{}");
|
||||
e->weight = engram_decode_score(weight);
|
||||
if (e->weight <= 0.0 || e->weight > 1.0) e->weight = 0.5;
|
||||
e->confidence = 1.0;
|
||||
@@ -7469,11 +7537,11 @@ el_val_t engram_save(el_val_t path) {
|
||||
/* Helper: extract a string field from a JSON object substring. */
|
||||
static char* eg_get_str_field(const char* obj, const char* key) {
|
||||
const char* p = json_find_key(obj, key);
|
||||
if (!p) return el_strdup("");
|
||||
if (*p != '"') return el_strdup("");
|
||||
if (!p) return el_strdup_persist("");
|
||||
if (*p != '"') return el_strdup_persist("");
|
||||
JsonParser jp = { .p = p, .end = p + strlen(p), .err = 0 };
|
||||
char* out = jp_parse_string_raw(&jp);
|
||||
if (jp.err) { free(out); return el_strdup(""); }
|
||||
if (jp.err) { free(out); return el_strdup_persist(""); }
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -7547,7 +7615,7 @@ el_val_t engram_load(el_val_t path) {
|
||||
nn->tier = eg_get_str_field(obj, "tier");
|
||||
nn->tags = eg_get_str_field(obj, "tags");
|
||||
nn->metadata = eg_get_str_field(obj, "metadata");
|
||||
if (!nn->metadata || !*nn->metadata) { free(nn->metadata); nn->metadata = el_strdup("{}"); }
|
||||
if (!nn->metadata || !*nn->metadata) { free(nn->metadata); nn->metadata = el_strdup_persist("{}"); }
|
||||
nn->salience = eg_get_num_field(obj, "salience");
|
||||
nn->importance = eg_get_num_field(obj, "importance");
|
||||
nn->confidence = eg_get_num_field(obj, "confidence");
|
||||
@@ -7598,7 +7666,7 @@ el_val_t engram_load(el_val_t path) {
|
||||
ee->to_id = eg_get_str_field(obj, "to_id");
|
||||
ee->relation = eg_get_str_field(obj, "relation");
|
||||
ee->metadata = eg_get_str_field(obj, "metadata");
|
||||
if (!ee->metadata || !*ee->metadata) { free(ee->metadata); ee->metadata = el_strdup("{}"); }
|
||||
if (!ee->metadata || !*ee->metadata) { free(ee->metadata); ee->metadata = el_strdup_persist("{}"); }
|
||||
ee->weight = eg_get_num_field(obj, "weight");
|
||||
ee->confidence = eg_get_num_field(obj, "confidence");
|
||||
ee->created_at = eg_get_int_field(obj, "created_at");
|
||||
@@ -7690,7 +7758,7 @@ el_val_t engram_get_node_json(el_val_t id) {
|
||||
if (!n) return el_wrap_str(el_strdup("{}"));
|
||||
JsonBuf b; jb_init(&b);
|
||||
engram_emit_node_json(&b, n);
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
el_val_t engram_search_json(el_val_t query, el_val_t limit) {
|
||||
@@ -7718,7 +7786,7 @@ el_val_t engram_search_json(el_val_t query, el_val_t limit) {
|
||||
}
|
||||
}
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset) {
|
||||
@@ -7727,9 +7795,9 @@ el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset) {
|
||||
int64_t off = (int64_t)offset; if (off < 0) off = 0;
|
||||
JsonBuf b; jb_init(&b);
|
||||
jb_putc(&b, '[');
|
||||
if (g->node_count == 0) { jb_putc(&b, ']'); return el_wrap_str(b.buf); }
|
||||
if (g->node_count == 0) { jb_putc(&b, ']'); return el_wrap_str(jb_finish(&b)); }
|
||||
int64_t* idx = malloc((size_t)g->node_count * sizeof(int64_t));
|
||||
if (!idx) { jb_putc(&b, ']'); return el_wrap_str(b.buf); }
|
||||
if (!idx) { jb_putc(&b, ']'); return el_wrap_str(jb_finish(&b)); }
|
||||
/* Skip transparent layers — introspection filter, same as engram_scan_nodes. */
|
||||
int64_t live = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
@@ -7747,7 +7815,7 @@ el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset) {
|
||||
}
|
||||
free(idx);
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
/* engram_scan_nodes_by_type_json — filter by node_type before paginating.
|
||||
@@ -7763,9 +7831,9 @@ el_val_t engram_scan_nodes_by_type_json(el_val_t type_v, el_val_t limit, el_val_
|
||||
int64_t off = (int64_t)offset; if (off < 0) off = 0;
|
||||
JsonBuf b; jb_init(&b);
|
||||
jb_putc(&b, '[');
|
||||
if (g->node_count == 0) { jb_putc(&b, ']'); return el_wrap_str(b.buf); }
|
||||
if (g->node_count == 0) { jb_putc(&b, ']'); return el_wrap_str(jb_finish(&b)); }
|
||||
int64_t* idx = malloc((size_t)g->node_count * sizeof(int64_t));
|
||||
if (!idx) { jb_putc(&b, ']'); return el_wrap_str(b.buf); }
|
||||
if (!idx) { jb_putc(&b, ']'); return el_wrap_str(jb_finish(&b)); }
|
||||
int64_t live = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (engram_layer_is_transparent(g->nodes[i].layer_id)) continue;
|
||||
@@ -7784,7 +7852,7 @@ el_val_t engram_scan_nodes_by_type_json(el_val_t type_v, el_val_t limit, el_val_
|
||||
}
|
||||
free(idx);
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction) {
|
||||
@@ -7799,7 +7867,7 @@ el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t di
|
||||
int allow_in = (strcmp(dir, "in") == 0) || (strcmp(dir, "both") == 0);
|
||||
JsonBuf b; jb_init(&b);
|
||||
jb_putc(&b, '[');
|
||||
if (!sid || !*sid) { jb_putc(&b, ']'); return el_wrap_str(b.buf); }
|
||||
if (!sid || !*sid) { jb_putc(&b, ']'); return el_wrap_str(jb_finish(&b)); }
|
||||
|
||||
/* Frontier of (node_id, hops). Cap to a sane size. */
|
||||
char** frontier = calloc(1024, sizeof(char*));
|
||||
@@ -7809,7 +7877,7 @@ el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t di
|
||||
int64_t vc = 0;
|
||||
if (!frontier || !frontier_h || !visited) {
|
||||
free(frontier); free(frontier_h); free(visited);
|
||||
jb_putc(&b, ']'); return el_wrap_str(b.buf);
|
||||
jb_putc(&b, ']'); return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
/* Use plain strdup (not el_strdup) so arena doesn't track these pointers.
|
||||
* The BFS loop manually frees them below — arena would double-free them. */
|
||||
@@ -7852,7 +7920,7 @@ el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t di
|
||||
for (int64_t i = 0; i < vc; i++) free(visited[i]);
|
||||
free(frontier); free(frontier_h); free(visited);
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
el_val_t engram_activate_json(el_val_t query, el_val_t depth) {
|
||||
@@ -7893,7 +7961,7 @@ el_val_t engram_activate_json(el_val_t query, el_val_t depth) {
|
||||
}
|
||||
}
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
el_val_t engram_stats_json(void) {
|
||||
@@ -7913,7 +7981,7 @@ el_val_t engram_list_layers_json(void) {
|
||||
jb_putc(&b, '[');
|
||||
/* Build a sorted index over live layers. */
|
||||
size_t* idx = malloc((g->layer_count + 1) * sizeof(size_t));
|
||||
if (!idx) { jb_putc(&b, ']'); return el_wrap_str(b.buf); }
|
||||
if (!idx) { jb_putc(&b, ']'); return el_wrap_str(jb_finish(&b)); }
|
||||
size_t live = 0;
|
||||
for (size_t i = 0; i < g->layer_count; i++) {
|
||||
if (g->layers[i].name) idx[live++] = i;
|
||||
@@ -7950,7 +8018,7 @@ el_val_t engram_list_layers_json(void) {
|
||||
}
|
||||
free(idx);
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
/* engram_compile_layered_json — produce a prompt-ready context block split
|
||||
@@ -8036,7 +8104,7 @@ el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth) {
|
||||
free(b.buf);
|
||||
return el_wrap_str(el_strdup(""));
|
||||
}
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
/* engram_query_range — temporal range query.
|
||||
@@ -8081,7 +8149,7 @@ el_val_t engram_query_range(el_val_t start_ms_v, el_val_t end_ms_v) {
|
||||
}
|
||||
jb_putc(&b, ']');
|
||||
free(idx);
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
/* engram_load_merge — like engram_load but WITHOUT resetting the store.
|
||||
@@ -8332,7 +8400,7 @@ el_val_t engram_wm_top_json(el_val_t n_v) {
|
||||
}
|
||||
free(idx);
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
return el_wrap_str(jb_finish(&b));
|
||||
}
|
||||
|
||||
#ifdef HAVE_CURL
|
||||
@@ -8728,13 +8796,13 @@ static int64_t dharma_find_or_create_relation_edge(const char* peer_base, int cr
|
||||
engram_grow_nodes();
|
||||
EngramNode* n = &g->nodes[g->node_count];
|
||||
memset(n, 0, sizeof(*n));
|
||||
n->id = el_strdup(self_id);
|
||||
n->content = el_strdup(_el_cgi_dharma_id ? _el_cgi_dharma_id : "(self)");
|
||||
n->node_type = el_strdup("DharmaSelf");
|
||||
n->label = el_strdup("dharma:self");
|
||||
n->tier = el_strdup("Working");
|
||||
n->tags = el_strdup("dharma");
|
||||
n->metadata = el_strdup("{}");
|
||||
n->id = el_strdup_persist(self_id);
|
||||
n->content = el_strdup_persist(_el_cgi_dharma_id ? _el_cgi_dharma_id : "(self)");
|
||||
n->node_type = el_strdup_persist("DharmaSelf");
|
||||
n->label = el_strdup_persist("dharma:self");
|
||||
n->tier = el_strdup_persist("Working");
|
||||
n->tags = el_strdup_persist("dharma");
|
||||
n->metadata = el_strdup_persist("{}");
|
||||
n->salience = 1.0; n->importance = 1.0; n->confidence = 1.0;
|
||||
int64_t now = engram_now_ms();
|
||||
n->created_at = now; n->updated_at = now; n->last_activated = now;
|
||||
@@ -8745,13 +8813,13 @@ static int64_t dharma_find_or_create_relation_edge(const char* peer_base, int cr
|
||||
engram_grow_nodes();
|
||||
EngramNode* n = &g->nodes[g->node_count];
|
||||
memset(n, 0, sizeof(*n));
|
||||
n->id = el_strdup(peer_node);
|
||||
n->content = el_strdup(peer_base);
|
||||
n->node_type = el_strdup("DharmaPeer");
|
||||
n->label = el_strdup(peer_node);
|
||||
n->tier = el_strdup("Working");
|
||||
n->tags = el_strdup("dharma");
|
||||
n->metadata = el_strdup("{}");
|
||||
n->id = el_strdup_persist(peer_node);
|
||||
n->content = el_strdup_persist(peer_base);
|
||||
n->node_type = el_strdup_persist("DharmaPeer");
|
||||
n->label = el_strdup_persist(peer_node);
|
||||
n->tier = el_strdup_persist("Working");
|
||||
n->tags = el_strdup_persist("dharma");
|
||||
n->metadata = el_strdup_persist("{}");
|
||||
n->salience = 0.5; n->importance = 0.5; n->confidence = 1.0;
|
||||
int64_t now = engram_now_ms();
|
||||
n->created_at = now; n->updated_at = now; n->last_activated = now;
|
||||
@@ -8763,10 +8831,10 @@ static int64_t dharma_find_or_create_relation_edge(const char* peer_base, int cr
|
||||
EngramEdge* e = &g->edges[g->edge_count];
|
||||
memset(e, 0, sizeof(*e));
|
||||
e->id = engram_new_id();
|
||||
e->from_id = el_strdup(self_id);
|
||||
e->to_id = el_strdup(peer_node);
|
||||
e->relation = el_strdup("dharma-relation");
|
||||
e->metadata = el_strdup("{}");
|
||||
e->from_id = el_strdup_persist(self_id);
|
||||
e->to_id = el_strdup_persist(peer_node);
|
||||
e->relation = el_strdup_persist("dharma-relation");
|
||||
e->metadata = el_strdup_persist("{}");
|
||||
e->weight = 0.0;
|
||||
e->confidence = 1.0;
|
||||
int64_t now = engram_now_ms();
|
||||
@@ -9059,7 +9127,7 @@ static el_val_t llm_extract_text(el_val_t resp_val) {
|
||||
free(obj);
|
||||
p = end;
|
||||
}
|
||||
return el_wrap_str(out.buf);
|
||||
return el_wrap_str(jb_finish(&out));
|
||||
}
|
||||
|
||||
el_val_t llm_call(el_val_t model, el_val_t prompt) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Executable
+711
@@ -0,0 +1,711 @@
|
||||
#!/usr/bin/env bash
|
||||
# new-platform — scaffold a new el-native platform bridge
|
||||
#
|
||||
# Usage: ./new-platform <platform-name>
|
||||
#
|
||||
# Example:
|
||||
# ./new-platform myplatform
|
||||
#
|
||||
# Creates el_myplatform.c with all 33 required __* functions stubbed out,
|
||||
# the ElWidget slot table, and the dlsym callback dispatcher.
|
||||
#
|
||||
# After running, follow the printed instructions to wire the bridge into
|
||||
# el_native_target.h and el_seed.c.
|
||||
#
|
||||
# See PLATFORM_BRIDGE_SPEC.md for the full bridge contract.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ── Argument validation ───────────────────────────────────────────────────────
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <platform-name>" >&2
|
||||
echo "" >&2
|
||||
echo " <platform-name> lowercase identifier, e.g. myrtos, wayland, qt6" >&2
|
||||
echo "" >&2
|
||||
echo "This creates el_<platform-name>.c in the current directory." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PLATFORM_LOWER="${1,,}" # force lowercase
|
||||
PLATFORM_UPPER="${PLATFORM_LOWER^^}" # force uppercase
|
||||
|
||||
# Validate: letters, digits, underscores only
|
||||
if [[ ! "${PLATFORM_LOWER}" =~ ^[a-z][a-z0-9_]*$ ]]; then
|
||||
echo "Error: platform name must start with a letter and contain only a-z, 0-9, _" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUTPUT_FILE="${SCRIPT_DIR}/el_${PLATFORM_LOWER}.c"
|
||||
|
||||
if [[ -f "${OUTPUT_FILE}" ]]; then
|
||||
echo "Error: ${OUTPUT_FILE} already exists." >&2
|
||||
echo " Remove it first if you want to regenerate it." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Generate the bridge file ──────────────────────────────────────────────────
|
||||
|
||||
cat > "${OUTPUT_FILE}" << BRIDGE_FILE
|
||||
/*
|
||||
* el_${PLATFORM_LOWER}.c — el-native platform bridge for ${PLATFORM_UPPER}.
|
||||
*
|
||||
* Generated by new-platform. Replace the TODO stubs with real implementations.
|
||||
* See PLATFORM_BRIDGE_SPEC.md for the full contract.
|
||||
*
|
||||
* ── Slot system ──────────────────────────────────────────────────────────────
|
||||
* Every widget (window, button, label, container, image) is stored in a static
|
||||
* array of ElWidget structs. The el program holds an int64_t "handle" which is
|
||||
* a direct index into that array. Rules:
|
||||
* - Slot 0 is NEVER valid. Scans start at index 1.
|
||||
* - Handle -1 means "invalid" / "create failed".
|
||||
* - Maximum 4096 concurrent widgets.
|
||||
* - el_widget_get() returns NULL for 0, negative, out-of-range, or FREE slots.
|
||||
*
|
||||
* ── Callback ABI ─────────────────────────────────────────────────────────────
|
||||
* When a platform event fires (click, text change), call el_${PLATFORM_LOWER}_invoke_cb:
|
||||
*
|
||||
* el_${PLATFORM_LOWER}_invoke_cb(w->cb_click, slot_index, "");
|
||||
*
|
||||
* The El callback signature:
|
||||
* fn handler(handle: Int, data: String) -> Void
|
||||
* compiles to:
|
||||
* void handler(int64_t handle, int64_t data)
|
||||
* where data is a const char* cast to int64_t (el String representation).
|
||||
*
|
||||
* ── Thread safety ────────────────────────────────────────────────────────────
|
||||
* ALL platform UI calls must run on the main/UI thread. If your platform
|
||||
* delivers events on background threads (e.g., from a network callback that
|
||||
* updates a label), marshal to the main thread before calling any widget op.
|
||||
*
|
||||
* ── Compile ──────────────────────────────────────────────────────────────────
|
||||
* cc -DEL_TARGET_${PLATFORM_UPPER} \\
|
||||
* \$(pkg-config --cflags <your-toolkit>) \\
|
||||
* -c el_${PLATFORM_LOWER}.c -o el_${PLATFORM_LOWER}.o
|
||||
*
|
||||
* ── Link ─────────────────────────────────────────────────────────────────────
|
||||
* cc el_${PLATFORM_LOWER}.o el_seed.o el_runtime.o -o myapp \\
|
||||
* \$(pkg-config --libs <your-toolkit>) -ldl -lpthread
|
||||
*/
|
||||
|
||||
#ifdef EL_TARGET_${PLATFORM_UPPER}
|
||||
|
||||
/* ── TODO: add platform-specific includes here ──────────────────────────────
|
||||
* Examples:
|
||||
* #include <gtk/gtk.h> // GTK4
|
||||
* #include <SDL2/SDL.h> // SDL2
|
||||
* #include "lvgl/lvgl.h" // LVGL
|
||||
* #include <windows.h> // Win32
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h> /* dlsym — replace with GetProcAddress on Windows */
|
||||
|
||||
/* ── Widget table ─────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_${PLATFORM_UPPER}_MAX_WIDGETS 4096
|
||||
|
||||
typedef enum {
|
||||
EL_WIDGET_FREE = 0,
|
||||
EL_WIDGET_WINDOW = 1,
|
||||
EL_WIDGET_VSTACK = 2,
|
||||
EL_WIDGET_HSTACK = 3,
|
||||
EL_WIDGET_ZSTACK = 4,
|
||||
EL_WIDGET_SCROLL = 5,
|
||||
EL_WIDGET_LABEL = 6,
|
||||
EL_WIDGET_BUTTON = 7,
|
||||
EL_WIDGET_TEXTFIELD = 8,
|
||||
EL_WIDGET_TEXTAREA = 9,
|
||||
EL_WIDGET_IMAGE = 10,
|
||||
} ElWidgetKind;
|
||||
|
||||
typedef struct {
|
||||
ElWidgetKind kind;
|
||||
|
||||
/* TODO: add your platform's native widget reference here.
|
||||
* Examples:
|
||||
* GtkWidget* widget; // GTK4
|
||||
* SDL_Rect rect; // SDL2 (no object, just geometry)
|
||||
* lv_obj_t* obj; // LVGL
|
||||
* HWND hwnd; // Win32
|
||||
* void* native; // generic pointer
|
||||
*/
|
||||
void* native; /* platform widget reference — replace as needed */
|
||||
|
||||
/* Text content (cached for platforms that don't provide a get-text API) */
|
||||
char* text;
|
||||
|
||||
/* Foreground / background color (RGBA, 0.0-1.0) */
|
||||
float fg_r, fg_g, fg_b, fg_a;
|
||||
float bg_r, bg_g, bg_b, bg_a;
|
||||
|
||||
/* Geometry */
|
||||
int width; /* 0 = not set */
|
||||
int height; /* 0 = not set */
|
||||
int flex; /* 0 = hug content, >0 = expand */
|
||||
|
||||
/* Padding (top, right, bottom, left) */
|
||||
int pad_top, pad_right, pad_bottom, pad_left;
|
||||
|
||||
/* Corner radius */
|
||||
int corner_radius;
|
||||
|
||||
/* State */
|
||||
int disabled; /* 0 = enabled, 1 = disabled */
|
||||
int hidden; /* 0 = visible, 1 = hidden */
|
||||
|
||||
/* Event callbacks — El function name resolved at event time via dlsym */
|
||||
char* cb_click; /* on_click / on_submit */
|
||||
char* cb_change; /* on_change */
|
||||
} ElWidget;
|
||||
|
||||
static ElWidget _el_widgets[EL_${PLATFORM_UPPER}_MAX_WIDGETS];
|
||||
|
||||
/* ── Slot management ──────────────────────────────────────────────────────── */
|
||||
|
||||
static int64_t el_widget_alloc(ElWidgetKind kind, void* native) {
|
||||
for (int i = 1; i < EL_${PLATFORM_UPPER}_MAX_WIDGETS; i++) {
|
||||
if (_el_widgets[i].kind == EL_WIDGET_FREE) {
|
||||
memset(&_el_widgets[i], 0, sizeof(ElWidget));
|
||||
_el_widgets[i].kind = kind;
|
||||
_el_widgets[i].native = native;
|
||||
return (int64_t)i;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "el_${PLATFORM_LOWER}: widget table full (max %d)\n",
|
||||
EL_${PLATFORM_UPPER}_MAX_WIDGETS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ElWidget* el_widget_get(int64_t handle) {
|
||||
if (handle <= 0 || handle >= EL_${PLATFORM_UPPER}_MAX_WIDGETS) return NULL;
|
||||
if (_el_widgets[handle].kind == EL_WIDGET_FREE) return NULL;
|
||||
return &_el_widgets[handle];
|
||||
}
|
||||
|
||||
static void el_widget_free(int64_t handle) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
/* TODO: release w->native (toolkit-specific) */
|
||||
free(w->text);
|
||||
free(w->cb_click);
|
||||
free(w->cb_change);
|
||||
memset(w, 0, sizeof(ElWidget)); /* sets kind = EL_WIDGET_FREE (0) */
|
||||
}
|
||||
|
||||
/* ── Callback dispatcher ─────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Invoke an El function by symbol name. The El function must have the
|
||||
* compiled signature: void fn(int64_t handle, int64_t data)
|
||||
* where data is a const char* cast to int64_t (el String representation).
|
||||
*
|
||||
* On platforms without dlsym (e.g., Windows), replace with:
|
||||
* GetProcAddress(GetModuleHandle(NULL), fn_name)
|
||||
* On embedded targets without dynamic linking, maintain a manual symbol table.
|
||||
*/
|
||||
typedef void (*ElCb2)(int64_t handle, int64_t data);
|
||||
|
||||
static void el_${PLATFORM_LOWER}_invoke_cb(const char* fn_name,
|
||||
int64_t handle,
|
||||
const char* data) {
|
||||
if (!fn_name || !*fn_name) return;
|
||||
void* sym = dlsym(RTLD_DEFAULT, fn_name);
|
||||
if (!sym) {
|
||||
fprintf(stderr, "el_${PLATFORM_LOWER}: callback symbol not found: %s\n", fn_name);
|
||||
return;
|
||||
}
|
||||
ElCb2 fn = (ElCb2)sym;
|
||||
fn(handle, (int64_t)(uintptr_t)(data ? data : ""));
|
||||
}
|
||||
|
||||
/* ── Lifecycle ────────────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_${PLATFORM_LOWER}_init — initialize the platform toolkit.
|
||||
* Must be idempotent (safe to call more than once).
|
||||
* Called once from __native_init before any widget creation.
|
||||
*/
|
||||
void el_${PLATFORM_LOWER}_init(void) {
|
||||
static int done = 0;
|
||||
if (done) return;
|
||||
done = 1;
|
||||
|
||||
/* TODO: initialize your platform toolkit here.
|
||||
* Examples:
|
||||
* gtk_init(NULL, NULL); // GTK4
|
||||
* SDL_Init(SDL_INIT_VIDEO); // SDL2
|
||||
* lv_init(); // LVGL
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* el_${PLATFORM_LOWER}_run_loop — start the platform event loop.
|
||||
* On most platforms this NEVER returns. Exceptions: Android (no-op).
|
||||
* Must be called from the main thread.
|
||||
*/
|
||||
void el_${PLATFORM_LOWER}_run_loop(void) {
|
||||
/* TODO: start the platform event/render loop.
|
||||
* Examples:
|
||||
* gtk_main(); // GTK4
|
||||
* while (1) { SDL_PollEvent(...); render(); SDL_Delay(16); } // SDL2
|
||||
* while (1) { lv_task_handler(); usleep(5000); } // LVGL
|
||||
*/
|
||||
}
|
||||
|
||||
/* ── Window ───────────────────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_window_create(const char* title, int w, int h,
|
||||
int mw, int mh) {
|
||||
/* TODO: create a top-level window.
|
||||
* title may be NULL — treat as "".
|
||||
* mw/mh are minimum dimensions (0 = no minimum).
|
||||
* Return slot handle on success, -1 on failure.
|
||||
*/
|
||||
(void)title; (void)w; (void)h; (void)mw; (void)mh;
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_window_show(int64_t handle) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
/* TODO: make the window visible. */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_window_set_title(int64_t handle, const char* title) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
/* TODO: update the window title. title may be NULL — treat as "". */
|
||||
(void)title;
|
||||
}
|
||||
|
||||
/* ── Layout containers ────────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_vstack_create(int spacing) {
|
||||
/* TODO: create a vertical linear container with the given spacing (px). */
|
||||
(void)spacing;
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_hstack_create(int spacing) {
|
||||
/* TODO: create a horizontal linear container with the given spacing (px). */
|
||||
(void)spacing;
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_zstack_create(void) {
|
||||
/* TODO: create a z-axis overlay container (children overlap). */
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_scroll_create(void) {
|
||||
/* TODO: create a scrollable container (vertical scroll, first child = content). */
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
/* ── Leaf widgets ─────────────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_label_create(const char* text) {
|
||||
/* TODO: create a non-editable text label. text may be NULL — treat as "". */
|
||||
(void)text;
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_button_create(const char* label) {
|
||||
/* TODO: create a clickable button with the given label text.
|
||||
* Wire up the platform event handler so on_click callbacks fire later. */
|
||||
(void)label;
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_text_field_create(const char* placeholder) {
|
||||
/* TODO: create a single-line text input. placeholder may be NULL. */
|
||||
(void)placeholder;
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_text_area_create(const char* placeholder) {
|
||||
/* TODO: create a multi-line text input (scrollable). placeholder may be NULL. */
|
||||
(void)placeholder;
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
int64_t el_${PLATFORM_LOWER}_image_create(const char* path_or_name) {
|
||||
/* TODO: create an image widget.
|
||||
* Try path_or_name as a filesystem path first, then as a named resource.
|
||||
* If neither resolves, create an empty image widget (do not crash). */
|
||||
(void)path_or_name;
|
||||
return -1; /* TODO: implement */
|
||||
}
|
||||
|
||||
/* ── Widget property setters ─────────────────────────────────────────────── */
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_text(int64_t handle, const char* text) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
/* TODO: update text on label, button, text field, text area, or window title.
|
||||
* Dispatch on w->kind. text may be NULL — treat as "".
|
||||
* Cache in w->text if the platform has no get-text API. */
|
||||
free(w->text);
|
||||
w->text = strdup(text ? text : "");
|
||||
}
|
||||
|
||||
const char* el_${PLATFORM_LOWER}_widget_get_text(int64_t handle) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return "";
|
||||
/* TODO: return the current text of the widget.
|
||||
* If the platform provides a get-text API, use it.
|
||||
* Otherwise return w->text (populated by set_text).
|
||||
* NEVER return NULL — return "" on failure. */
|
||||
return w->text ? w->text : "";
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_color(int64_t handle,
|
||||
float r, float g, float b, float a) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->fg_r = r; w->fg_g = g; w->fg_b = b; w->fg_a = a;
|
||||
/* TODO: apply foreground (text) color to the platform widget. */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_bg_color(int64_t handle,
|
||||
float r, float g, float b, float a) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->bg_r = r; w->bg_g = g; w->bg_b = b; w->bg_a = a;
|
||||
/* TODO: apply background color to the platform widget. */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_font(int64_t handle,
|
||||
const char* family, int size, int bold) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
/* TODO: set font on text-bearing widgets (label, button, text field, text area).
|
||||
* family may be "system" — use the platform default font in that case.
|
||||
* Fall back to system font if family is not found. */
|
||||
(void)family; (void)size; (void)bold;
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_padding(int64_t handle,
|
||||
int top, int right,
|
||||
int bottom, int left) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->pad_top = top; w->pad_right = right;
|
||||
w->pad_bottom = bottom; w->pad_left = left;
|
||||
/* TODO: apply padding/insets to the platform container or text widget. */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_width(int64_t handle, int width) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->width = width;
|
||||
/* TODO: apply a fixed-width constraint to the platform widget. */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_height(int64_t handle, int height) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->height = height;
|
||||
/* TODO: apply a fixed-height constraint to the platform widget. */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_flex(int64_t handle, int flex) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->flex = flex;
|
||||
/* TODO: set the flex/expand factor.
|
||||
* flex > 0 → widget expands to fill available space.
|
||||
* flex == 0 → widget hugs content size.
|
||||
* Maps to: content hugging priority (AppKit), hexpand/vexpand (GTK4),
|
||||
* layout_weight (Android), stretch factor (SDL2 manual layout). */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_corner_radius(int64_t handle, int radius) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->corner_radius = radius;
|
||||
/* TODO: apply rounded corners (requires GPU layer / backing surface on most platforms). */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_disabled(int64_t handle, int disabled) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->disabled = disabled;
|
||||
/* TODO: enable or disable the widget (buttons, text fields). No-op for containers/labels. */
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_set_hidden(int64_t handle, int hidden) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->hidden = hidden;
|
||||
/* TODO: show or hide the widget.
|
||||
* hidden=1 → invisible but still occupies layout space (visibility:hidden semantics).
|
||||
* For windows: minimize or hide. */
|
||||
}
|
||||
|
||||
/* ── Tree management ─────────────────────────────────────────────────────── */
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_add_child(int64_t parent, int64_t child) {
|
||||
ElWidget* pw = el_widget_get(parent);
|
||||
ElWidget* cw = el_widget_get(child);
|
||||
if (!pw || !cw) return;
|
||||
|
||||
/* TODO: attach child to parent. Dispatch on pw->kind:
|
||||
* EL_WIDGET_WINDOW → add to root content view/container
|
||||
* EL_WIDGET_VSTACK → add as vertical child
|
||||
* EL_WIDGET_HSTACK → add as horizontal child
|
||||
* EL_WIDGET_ZSTACK → add as overlapping subview
|
||||
* EL_WIDGET_SCROLL → set as the scrollable content view (first child only)
|
||||
* default → add as plain subview
|
||||
* Do NOT add a window widget as a child. */
|
||||
(void)pw; (void)cw;
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_remove_child(int64_t parent, int64_t child) {
|
||||
ElWidget* pw = el_widget_get(parent);
|
||||
ElWidget* cw = el_widget_get(child);
|
||||
if (!pw || !cw) return;
|
||||
/* TODO: detach child from parent. The child slot remains allocated (not destroyed). */
|
||||
(void)pw; (void)cw;
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_destroy(int64_t handle) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
/* TODO: remove the widget from its parent/superview.
|
||||
* For windows: close the window.
|
||||
* Free any platform delegate/target objects stored in side tables.
|
||||
* Then free the slot: */
|
||||
el_widget_free(handle);
|
||||
}
|
||||
|
||||
/* ── Event registration ───────────────────────────────────────────────────── */
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_on_click(int64_t handle, const char* fn_name) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_click);
|
||||
w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_on_change(int64_t handle, const char* fn_name) {
|
||||
ElWidget* w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_change);
|
||||
w->cb_change = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
}
|
||||
|
||||
void el_${PLATFORM_LOWER}_widget_on_submit(int64_t handle, const char* fn_name) {
|
||||
/* Submit (Enter key in text field) reuses the cb_click slot, matching AppKit convention. */
|
||||
el_${PLATFORM_LOWER}_widget_on_click(handle, fn_name);
|
||||
}
|
||||
|
||||
/* ── Example event handler (adapt to your platform's callback mechanism) ─── */
|
||||
/*
|
||||
* When a button is clicked in your platform event loop, call:
|
||||
*
|
||||
* ElWidget* w = el_widget_get(slot_index);
|
||||
* if (w && w->cb_click) {
|
||||
* el_${PLATFORM_LOWER}_invoke_cb(w->cb_click, slot_index, "");
|
||||
* }
|
||||
*
|
||||
* When a text field changes:
|
||||
*
|
||||
* ElWidget* w = el_widget_get(slot_index);
|
||||
* if (w && w->cb_change) {
|
||||
* el_${PLATFORM_LOWER}_invoke_cb(w->cb_change, slot_index, current_text);
|
||||
* }
|
||||
*/
|
||||
|
||||
#endif /* EL_TARGET_${PLATFORM_UPPER} */
|
||||
BRIDGE_FILE
|
||||
|
||||
chmod 644 "${OUTPUT_FILE}"
|
||||
|
||||
# ── Print next-steps instructions ────────────────────────────────────────────
|
||||
|
||||
UPPER="${PLATFORM_UPPER}"
|
||||
LOWER="${PLATFORM_LOWER}"
|
||||
|
||||
cat << INSTRUCTIONS
|
||||
|
||||
Created: el_${LOWER}.c
|
||||
|
||||
Next steps:
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
1. Add to el_native_target.h (inside a new #ifdef EL_TARGET_${UPPER} block):
|
||||
|
||||
#ifdef EL_TARGET_${UPPER}
|
||||
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_${UPPER} */
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
2. Add to el_seed.c (copy the EL_TARGET_MACOS block as a template and
|
||||
substitute el_appkit_ → el_${LOWER}_):
|
||||
|
||||
#ifdef EL_TARGET_${UPPER}
|
||||
|
||||
/* Forward declarations — implemented in el_${LOWER}.c */
|
||||
extern void el_${LOWER}_init(void);
|
||||
extern void el_${LOWER}_run_loop(void);
|
||||
extern int64_t el_${LOWER}_window_create(const char* title, int w, int h, int mw, int mh);
|
||||
extern void el_${LOWER}_window_show(int64_t handle);
|
||||
extern void el_${LOWER}_window_set_title(int64_t handle, const char* title);
|
||||
extern int64_t el_${LOWER}_vstack_create(int spacing);
|
||||
extern int64_t el_${LOWER}_hstack_create(int spacing);
|
||||
extern int64_t el_${LOWER}_zstack_create(void);
|
||||
extern int64_t el_${LOWER}_scroll_create(void);
|
||||
extern int64_t el_${LOWER}_label_create(const char* text);
|
||||
extern int64_t el_${LOWER}_button_create(const char* label);
|
||||
extern int64_t el_${LOWER}_text_field_create(const char* placeholder);
|
||||
extern int64_t el_${LOWER}_text_area_create(const char* placeholder);
|
||||
extern int64_t el_${LOWER}_image_create(const char* path_or_name);
|
||||
extern void el_${LOWER}_widget_set_text(int64_t handle, const char* text);
|
||||
extern const char* el_${LOWER}_widget_get_text(int64_t handle);
|
||||
extern void el_${LOWER}_widget_set_color(int64_t h, float r, float g, float b, float a);
|
||||
extern void el_${LOWER}_widget_set_bg_color(int64_t h, float r, float g, float b, float a);
|
||||
extern void el_${LOWER}_widget_set_font(int64_t h, const char* family, int size, int bold);
|
||||
extern void el_${LOWER}_widget_set_padding(int64_t h, int top, int right, int bottom, int left);
|
||||
extern void el_${LOWER}_widget_set_width(int64_t h, int width);
|
||||
extern void el_${LOWER}_widget_set_height(int64_t h, int height);
|
||||
extern void el_${LOWER}_widget_set_flex(int64_t h, int flex);
|
||||
extern void el_${LOWER}_widget_set_corner_radius(int64_t h, int radius);
|
||||
extern void el_${LOWER}_widget_set_disabled(int64_t h, int disabled);
|
||||
extern void el_${LOWER}_widget_set_hidden(int64_t h, int hidden);
|
||||
extern void el_${LOWER}_widget_add_child(int64_t parent, int64_t child);
|
||||
extern void el_${LOWER}_widget_remove_child(int64_t parent, int64_t child);
|
||||
extern void el_${LOWER}_widget_destroy(int64_t handle);
|
||||
extern void el_${LOWER}_widget_on_click(int64_t h, const char* fn_name);
|
||||
extern void el_${LOWER}_widget_on_change(int64_t h, const char* fn_name);
|
||||
extern void el_${LOWER}_widget_on_submit(int64_t h, const char* fn_name);
|
||||
|
||||
void __native_init(void) { el_${LOWER}_init(); }
|
||||
void __native_run_loop(void) { el_${LOWER}_run_loop(); }
|
||||
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height) {
|
||||
return (el_val_t)el_${LOWER}_window_create(
|
||||
EL_CSTR(title),
|
||||
(int)(int64_t)width, (int)(int64_t)height,
|
||||
(int)(int64_t)min_width, (int)(int64_t)min_height);
|
||||
}
|
||||
void __window_show(el_val_t h) { el_${LOWER}_window_show((int64_t)h); }
|
||||
void __window_set_title(el_val_t h, el_val_t t) { el_${LOWER}_window_set_title((int64_t)h, EL_CSTR(t)); }
|
||||
|
||||
el_val_t __vstack_create(el_val_t s) { return (el_val_t)el_${LOWER}_vstack_create((int)(int64_t)s); }
|
||||
el_val_t __hstack_create(el_val_t s) { return (el_val_t)el_${LOWER}_hstack_create((int)(int64_t)s); }
|
||||
el_val_t __zstack_create(void) { return (el_val_t)el_${LOWER}_zstack_create(); }
|
||||
el_val_t __scroll_create(void) { return (el_val_t)el_${LOWER}_scroll_create(); }
|
||||
|
||||
el_val_t __label_create(el_val_t t) { return (el_val_t)el_${LOWER}_label_create(EL_CSTR(t)); }
|
||||
el_val_t __button_create(el_val_t l) { return (el_val_t)el_${LOWER}_button_create(EL_CSTR(l)); }
|
||||
el_val_t __text_field_create(el_val_t p) { return (el_val_t)el_${LOWER}_text_field_create(EL_CSTR(p)); }
|
||||
el_val_t __text_area_create(el_val_t p) { return (el_val_t)el_${LOWER}_text_area_create(EL_CSTR(p)); }
|
||||
el_val_t __image_create(el_val_t p) { return (el_val_t)el_${LOWER}_image_create(EL_CSTR(p)); }
|
||||
|
||||
void __widget_set_text(el_val_t h, el_val_t t) { el_${LOWER}_widget_set_text((int64_t)h, EL_CSTR(t)); }
|
||||
el_val_t __widget_get_text(el_val_t h) {
|
||||
const char* s = el_${LOWER}_widget_get_text((int64_t)h);
|
||||
return EL_STR(s ? s : "");
|
||||
}
|
||||
void __widget_set_color(el_val_t h, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
el_${LOWER}_widget_set_color((int64_t)h,
|
||||
(float)el_to_float(r), (float)el_to_float(g),
|
||||
(float)el_to_float(b), (float)el_to_float(a));
|
||||
}
|
||||
void __widget_set_bg_color(el_val_t h, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
el_${LOWER}_widget_set_bg_color((int64_t)h,
|
||||
(float)el_to_float(r), (float)el_to_float(g),
|
||||
(float)el_to_float(b), (float)el_to_float(a));
|
||||
}
|
||||
void __widget_set_font(el_val_t h, el_val_t family, el_val_t size, el_val_t bold) {
|
||||
el_${LOWER}_widget_set_font((int64_t)h, EL_CSTR(family),
|
||||
(int)(int64_t)size, (int)(int64_t)bold);
|
||||
}
|
||||
void __widget_set_padding(el_val_t h, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left) {
|
||||
el_${LOWER}_widget_set_padding((int64_t)h,
|
||||
(int)(int64_t)top, (int)(int64_t)right,
|
||||
(int)(int64_t)bottom, (int)(int64_t)left);
|
||||
}
|
||||
void __widget_set_width(el_val_t h, el_val_t w) { el_${LOWER}_widget_set_width((int64_t)h, (int)(int64_t)w); }
|
||||
void __widget_set_height(el_val_t h, el_val_t ht) { el_${LOWER}_widget_set_height((int64_t)h, (int)(int64_t)ht); }
|
||||
void __widget_set_flex(el_val_t h, el_val_t f) { el_${LOWER}_widget_set_flex((int64_t)h, (int)(int64_t)f); }
|
||||
void __widget_set_corner_radius(el_val_t h, el_val_t r) { el_${LOWER}_widget_set_corner_radius((int64_t)h, (int)(int64_t)r); }
|
||||
void __widget_set_disabled(el_val_t h, el_val_t d) { el_${LOWER}_widget_set_disabled((int64_t)h, (int)(int64_t)d); }
|
||||
void __widget_set_hidden(el_val_t h, el_val_t hid) { el_${LOWER}_widget_set_hidden((int64_t)h, (int)(int64_t)hid); }
|
||||
void __widget_add_child(el_val_t p, el_val_t c) { el_${LOWER}_widget_add_child((int64_t)p, (int64_t)c); }
|
||||
void __widget_remove_child(el_val_t p, el_val_t c) { el_${LOWER}_widget_remove_child((int64_t)p, (int64_t)c); }
|
||||
void __widget_destroy(el_val_t h) { el_${LOWER}_widget_destroy((int64_t)h); }
|
||||
void __widget_on_click(el_val_t h, el_val_t fn) { el_${LOWER}_widget_on_click((int64_t)h, EL_CSTR(fn)); }
|
||||
void __widget_on_change(el_val_t h, el_val_t fn) { el_${LOWER}_widget_on_change((int64_t)h, EL_CSTR(fn)); }
|
||||
void __widget_on_submit(el_val_t h, el_val_t fn) { el_${LOWER}_widget_on_submit((int64_t)h, EL_CSTR(fn)); }
|
||||
|
||||
#endif /* EL_TARGET_${UPPER} */
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────
|
||||
3. Implement each TODO in el_${LOWER}.c.
|
||||
|
||||
4. Compile:
|
||||
cc -DEL_TARGET_${UPPER} -Wall \\
|
||||
\$(pkg-config --cflags <your-toolkit> 2>/dev/null) \\
|
||||
-c el_${LOWER}.c -o el_${LOWER}.o
|
||||
|
||||
5. Link:
|
||||
cc el_${LOWER}.o el_seed.o el_runtime.o -o myapp \\
|
||||
\$(pkg-config --libs <your-toolkit> 2>/dev/null) \\
|
||||
-ldl -lpthread
|
||||
|
||||
See PLATFORM_BRIDGE_SPEC.md for the full bridge contract and gotchas.
|
||||
INSTRUCTIONS
|
||||
@@ -5454,8 +5454,12 @@ void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
#define ENGRAM_DECAY_LAMBDA 0.693147
|
||||
|
||||
/* Two-layer activation constants.
|
||||
* ENGRAM_WM_THRESHOLD: minimum background_activation for a node to be
|
||||
* considered for working-memory promotion (layer 2 candidate gate).
|
||||
* ENGRAM_WM_THRESHOLD: SUPERSEDED — defined here for legacy reference only.
|
||||
* The actual per-call threshold is computed by engram_type_threshold() which
|
||||
* returns per-node-type values (0.05 Safety/DharmaSelf, 0.15 Canonical,
|
||||
* 0.25 Lesson, 0.30 Belief/Entity, 0.40 Note/Memory/Working). This constant
|
||||
* is NOT used in engram_activate(); it matches the Canonical tier value only
|
||||
* by coincidence. (2026-07-01 self-review: clarified stale doc)
|
||||
* ENGRAM_WM_DECAY: per-turn decay applied to working_memory_weight for
|
||||
* nodes NOT re-activated in the current turn (conversational thread
|
||||
* continuity: a node promoted in turn N persists with reduced weight
|
||||
@@ -5470,9 +5474,35 @@ void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
#define ENGRAM_WM_THRESHOLD 0.15
|
||||
#define ENGRAM_WM_DECAY 0.7
|
||||
#define ENGRAM_SUPPRESSION_BREAKTHROUGH 5
|
||||
#define ENGRAM_BREAKTHROUGH_WEIGHT 0.25
|
||||
/* ENGRAM_BREAKTHROUGH_WEIGHT: lowered 0.25→0.10 (2026-06-30 self-review, porting
|
||||
* fix from self-review 2026-06-26 branch). With 0.25, Knowledge nodes (threshold
|
||||
* 0.15) promoted at ~0.21 decay in one call to ~0.147, fall below the 0.25 floor,
|
||||
* and immediately lose their WM slot to fresh breakthrough candidates at 0.25.
|
||||
* Natural promotion was invisible: live data showed 524/525 WM nodes at 0.25
|
||||
* breakthrough floor. With 0.10, all per-type thresholds (minimum 0.15 Canonical)
|
||||
* exceed the floor, so naturally-promoted nodes survive multiple decay cycles.
|
||||
* Invariant maintained: BREAKTHROUGH_WEIGHT < min(type_thresholds). */
|
||||
#define ENGRAM_BREAKTHROUGH_WEIGHT 0.10
|
||||
/* ENGRAM_WM_CAP: hard limit on concurrent working-memory nodes (2026-06-30
|
||||
* self-review, porting fix from self-review 2026-06-26 branch). Without this,
|
||||
* broad curiosity seeds like "knowledge" promote 500+ nodes simultaneously —
|
||||
* wm_avg_weight collapses to the breakthrough floor, goal-bias differentiation
|
||||
* is lost, and heartbeat ISEs show useless WM composition data. Cognitive
|
||||
* basis: WM capacity is ~4 chunks (Cowan 2001); 24 allows richer multi-topic
|
||||
* context while preventing flooding. Enforced in Pass 4 (per-call) and Pass 5
|
||||
* (global across prior-promoted nodes). */
|
||||
#define ENGRAM_WM_CAP 24
|
||||
#define ENGRAM_INHIBITION_FACTOR 0.1
|
||||
|
||||
/* qsort comparator — descending double, used by WM cap enforcement. */
|
||||
static int engram_cmp_double_desc(const void* a, const void* b) {
|
||||
double da = *(const double*)a;
|
||||
double db = *(const double*)b;
|
||||
if (da > db) return -1;
|
||||
if (da < db) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Layered consciousness architecture ──────────────────────────────────────
|
||||
*
|
||||
* The engram graph is stratified into LAYERS that gate which suppressions
|
||||
@@ -5627,6 +5657,20 @@ typedef struct EngramLayer {
|
||||
int injectable; /* can be added/removed at runtime? */
|
||||
} EngramLayer;
|
||||
|
||||
/* ID → index hash map. Open-addressing with linear probing.
|
||||
* Slots hold a strdup'd key and the array index of that node.
|
||||
* Tombstones (deleted entries) use key=ENGRAM_IDMAP_TOMB and idx=-1.
|
||||
* Rebuild required after engram_forget (shift-delete changes all indices
|
||||
* above the deleted position). */
|
||||
#define ENGRAM_IDMAP_TOMB ((char*)1) /* sentinel pointer, never dereferenced */
|
||||
#define ENGRAM_IDMAP_LOAD_NUM 3 /* grow when count*3 >= capacity*2 */
|
||||
#define ENGRAM_IDMAP_LOAD_DEN 2
|
||||
|
||||
typedef struct {
|
||||
char* key; /* NULL = empty, ENGRAM_IDMAP_TOMB = deleted, else strdup'd */
|
||||
int64_t idx;
|
||||
} EngramIdSlot;
|
||||
|
||||
typedef struct EngramStore {
|
||||
EngramNode* nodes;
|
||||
int64_t node_count;
|
||||
@@ -5642,6 +5686,22 @@ typedef struct EngramStore {
|
||||
EngramLayer* layers;
|
||||
size_t layer_count;
|
||||
size_t layer_capacity;
|
||||
/* O(1) node-id lookup: open-addressing hash map over node IDs.
|
||||
* Maintained in sync with the nodes array. Null until first use. */
|
||||
EngramIdSlot* id_map;
|
||||
size_t id_map_cap; /* power-of-2 slot count */
|
||||
size_t id_map_used; /* live entries (excluding tombstones) */
|
||||
/* Per-node adjacency index: for each node i, adj_from[i] lists edges
|
||||
* where nodes[i] is the 'from' end; adj_to[i] lists edges where it is
|
||||
* the 'to' end. Both store edge indices into g->edges[].
|
||||
* Rebuilt lazily via engram_adj_rebuild() before any BFS call. Set
|
||||
* adj_dirty=1 whenever an edge is added, deleted, or nodes shift. */
|
||||
int** adj_from; /* adj_from[node_idx] → int* array of edge indices */
|
||||
int* adj_from_len;
|
||||
int** adj_to;
|
||||
int* adj_to_len;
|
||||
int adj_dirty; /* 1 = rebuild needed before next BFS */
|
||||
int64_t adj_node_count; /* node_count at time of last adj_rebuild */
|
||||
} EngramStore;
|
||||
|
||||
static EngramStore* engram_global = NULL;
|
||||
@@ -5774,18 +5834,245 @@ static int64_t engram_now_ms(void) {
|
||||
return (int64_t)tv.tv_sec * 1000LL + (int64_t)tv.tv_usec / 1000LL;
|
||||
}
|
||||
|
||||
/* Forward declaration: engram_find_node_index is defined after the id_map
|
||||
* helpers but called here. Without this, C99 -Wimplicit-function-declaration
|
||||
* treats the call as an implicit non-static declaration, then conflicts with
|
||||
* the later `static` definition. (2026-07-01 self-review: pre-existing) */
|
||||
static int64_t engram_find_node_index(const char* id);
|
||||
|
||||
static EngramNode* engram_find_node(const char* id) {
|
||||
if (!id) return NULL;
|
||||
EngramStore* g = engram_get();
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (g->nodes[i].id && strcmp(g->nodes[i].id, id) == 0) return &g->nodes[i];
|
||||
}
|
||||
int64_t idx = engram_find_node_index(id);
|
||||
if (idx >= 0) return &g->nodes[idx];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ── ID hash map helpers ─────────────────────────────────────────────────────
|
||||
* Open-addressing, linear-probing hash map. Keys are node-id C strings.
|
||||
* Values are int64_t indices into g->nodes[].
|
||||
*
|
||||
* Rules:
|
||||
* - id_map is NULL until the first insertion (lazy init).
|
||||
* - Capacity is always a power of two.
|
||||
* - Load factor kept below 2/3: when used*3 >= cap*2, rehash to 2*cap.
|
||||
* - Deletion uses ENGRAM_IDMAP_TOMB sentinels (key == (char*)1).
|
||||
* - After engram_forget (shift-delete) the whole map is rebuilt from
|
||||
* scratch because all indices above the deleted position change.
|
||||
*/
|
||||
|
||||
static uint64_t engram_id_hash(const char* s) {
|
||||
/* FNV-1a 64-bit */
|
||||
uint64_t h = 14695981039346656037ULL;
|
||||
while (*s) { h ^= (unsigned char)*s++; h *= 1099511628211ULL; }
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Allocate a zeroed id_map of `cap` slots (cap must be power-of-two). */
|
||||
static EngramIdSlot* engram_idmap_alloc(size_t cap) {
|
||||
return calloc(cap, sizeof(EngramIdSlot));
|
||||
}
|
||||
|
||||
/* Low-level insert (no rehash check, no free of existing). Used during
|
||||
* rehash and initial build where we know the load is controlled. */
|
||||
static void engram_idmap_put_raw(EngramIdSlot* map, size_t cap,
|
||||
char* key, int64_t idx) {
|
||||
size_t mask = cap - 1;
|
||||
size_t slot = (size_t)engram_id_hash(key) & mask;
|
||||
while (map[slot].key != NULL && map[slot].key != ENGRAM_IDMAP_TOMB) {
|
||||
slot = (slot + 1) & mask;
|
||||
}
|
||||
map[slot].key = key;
|
||||
map[slot].idx = idx;
|
||||
}
|
||||
|
||||
/* Insert or update id → idx into the store's id_map. Rehashes if needed. */
|
||||
static void engram_idmap_put(EngramStore* g, const char* id, int64_t idx) {
|
||||
if (!id || !*id) return;
|
||||
/* Lazy init */
|
||||
if (!g->id_map) {
|
||||
g->id_map_cap = 64;
|
||||
g->id_map_used = 0;
|
||||
g->id_map = engram_idmap_alloc(g->id_map_cap);
|
||||
if (!g->id_map) return; /* OOM: fall back to linear scan */
|
||||
}
|
||||
/* Rehash if load factor would exceed 2/3 */
|
||||
if ((g->id_map_used + 1) * ENGRAM_IDMAP_LOAD_NUM >= g->id_map_cap * ENGRAM_IDMAP_LOAD_DEN) {
|
||||
size_t new_cap = g->id_map_cap * 2;
|
||||
EngramIdSlot* new_map = engram_idmap_alloc(new_cap);
|
||||
if (!new_map) return; /* OOM: keep old map, insert below */
|
||||
for (size_t s = 0; s < g->id_map_cap; s++) {
|
||||
if (g->id_map[s].key && g->id_map[s].key != ENGRAM_IDMAP_TOMB) {
|
||||
engram_idmap_put_raw(new_map, new_cap,
|
||||
g->id_map[s].key, g->id_map[s].idx);
|
||||
}
|
||||
}
|
||||
free(g->id_map);
|
||||
g->id_map = new_map;
|
||||
g->id_map_cap = new_cap;
|
||||
}
|
||||
/* Probe for existing key or empty/tomb slot */
|
||||
size_t mask = g->id_map_cap - 1;
|
||||
size_t slot = (size_t)engram_id_hash(id) & mask;
|
||||
size_t tomb_slot = SIZE_MAX;
|
||||
while (g->id_map[slot].key != NULL) {
|
||||
if (g->id_map[slot].key == ENGRAM_IDMAP_TOMB) {
|
||||
if (tomb_slot == SIZE_MAX) tomb_slot = slot;
|
||||
} else if (strcmp(g->id_map[slot].key, id) == 0) {
|
||||
g->id_map[slot].idx = idx; /* update */
|
||||
return;
|
||||
}
|
||||
slot = (slot + 1) & mask;
|
||||
}
|
||||
/* Use tombstone slot if found (avoids growing used count unnecessarily) */
|
||||
if (tomb_slot != SIZE_MAX) slot = tomb_slot;
|
||||
g->id_map[slot].key = el_strdup(id);
|
||||
g->id_map[slot].idx = idx;
|
||||
g->id_map_used++;
|
||||
}
|
||||
|
||||
/* Look up id in the store's id_map. Returns index or -1 if not found. */
|
||||
static int64_t engram_idmap_get(const EngramStore* g, const char* id) {
|
||||
if (!g->id_map || !id || !*id) return -1;
|
||||
size_t mask = g->id_map_cap - 1;
|
||||
size_t slot = (size_t)engram_id_hash(id) & mask;
|
||||
while (g->id_map[slot].key != NULL) {
|
||||
if (g->id_map[slot].key != ENGRAM_IDMAP_TOMB &&
|
||||
strcmp(g->id_map[slot].key, id) == 0) {
|
||||
return g->id_map[slot].idx;
|
||||
}
|
||||
slot = (slot + 1) & mask;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Free and null-out the id_map (called on full reset). */
|
||||
static void engram_idmap_free(EngramStore* g) {
|
||||
if (!g->id_map) return;
|
||||
for (size_t s = 0; s < g->id_map_cap; s++) {
|
||||
if (g->id_map[s].key && g->id_map[s].key != ENGRAM_IDMAP_TOMB)
|
||||
free(g->id_map[s].key);
|
||||
}
|
||||
free(g->id_map);
|
||||
g->id_map = NULL;
|
||||
g->id_map_cap = 0;
|
||||
g->id_map_used = 0;
|
||||
}
|
||||
|
||||
/* Rebuild id_map from scratch after a structural change (e.g. shift-delete).
|
||||
* Frees old map and constructs a fresh one. */
|
||||
static void engram_idmap_rebuild(EngramStore* g) {
|
||||
engram_idmap_free(g);
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (g->nodes[i].id && *g->nodes[i].id)
|
||||
engram_idmap_put(g, g->nodes[i].id, i);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Adjacency index helpers ─────────────────────────────────────────────────
|
||||
* Per-node adjacency lists: adj_from[i] holds edge indices where
|
||||
* g->edges[ei].from_id == g->nodes[i].id, adj_to[i] for the 'to' side.
|
||||
* BFS uses these instead of scanning all edges on every hop.
|
||||
* Called once per activation call when adj_dirty != 0.
|
||||
*/
|
||||
static void engram_adj_free(EngramStore* g) {
|
||||
int64_t old_nc = g->adj_node_count;
|
||||
if (g->adj_from) {
|
||||
for (int64_t i = 0; i < old_nc; i++) free(g->adj_from[i]);
|
||||
free(g->adj_from); g->adj_from = NULL;
|
||||
free(g->adj_from_len); g->adj_from_len = NULL;
|
||||
}
|
||||
if (g->adj_to) {
|
||||
for (int64_t i = 0; i < old_nc; i++) free(g->adj_to[i]);
|
||||
free(g->adj_to); g->adj_to = NULL;
|
||||
free(g->adj_to_len); g->adj_to_len = NULL;
|
||||
}
|
||||
g->adj_node_count = 0;
|
||||
g->adj_dirty = 1;
|
||||
}
|
||||
|
||||
static void engram_adj_rebuild(EngramStore* g) {
|
||||
/* Free old adjacency arrays */
|
||||
if (g->adj_from) {
|
||||
/* Use adj_node_count (count at build time) not current node_count —
|
||||
* nodes may have been added since the last rebuild, and adj arrays
|
||||
* only have adj_node_count entries. */
|
||||
int64_t old_nc = g->adj_node_count;
|
||||
for (int64_t i = 0; i < old_nc; i++) {
|
||||
free(g->adj_from[i]); free(g->adj_to[i]);
|
||||
}
|
||||
free(g->adj_from); free(g->adj_from_len);
|
||||
free(g->adj_to); free(g->adj_to_len);
|
||||
}
|
||||
g->adj_from = NULL; g->adj_from_len = NULL;
|
||||
g->adj_to = NULL; g->adj_to_len = NULL;
|
||||
g->adj_node_count = 0;
|
||||
if (g->node_count == 0) { g->adj_dirty = 0; return; }
|
||||
|
||||
/* Count degree per node */
|
||||
int* from_cnt = calloc((size_t)g->node_count, sizeof(int));
|
||||
int* to_cnt = calloc((size_t)g->node_count, sizeof(int));
|
||||
if (!from_cnt || !to_cnt) { free(from_cnt); free(to_cnt); return; }
|
||||
for (int64_t ei = 0; ei < g->edge_count; ei++) {
|
||||
EngramEdge* e = &g->edges[ei];
|
||||
if (!e->from_id || !e->to_id) continue;
|
||||
int64_t fi = engram_idmap_get(g, e->from_id);
|
||||
int64_t ti = engram_idmap_get(g, e->to_id);
|
||||
if (fi >= 0) from_cnt[fi]++;
|
||||
if (ti >= 0) to_cnt[ti]++;
|
||||
}
|
||||
/* Allocate per-node arrays */
|
||||
g->adj_from = calloc((size_t)g->node_count, sizeof(int*));
|
||||
g->adj_from_len = calloc((size_t)g->node_count, sizeof(int));
|
||||
g->adj_to = calloc((size_t)g->node_count, sizeof(int*));
|
||||
g->adj_to_len = calloc((size_t)g->node_count, sizeof(int));
|
||||
if (!g->adj_from || !g->adj_from_len || !g->adj_to || !g->adj_to_len) {
|
||||
free(from_cnt); free(to_cnt);
|
||||
free(g->adj_from); g->adj_from = NULL;
|
||||
free(g->adj_from_len); g->adj_from_len = NULL;
|
||||
free(g->adj_to); g->adj_to = NULL;
|
||||
free(g->adj_to_len); g->adj_to_len = NULL;
|
||||
return;
|
||||
}
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (from_cnt[i] > 0)
|
||||
g->adj_from[i] = malloc((size_t)from_cnt[i] * sizeof(int));
|
||||
if (to_cnt[i] > 0)
|
||||
g->adj_to[i] = malloc((size_t)to_cnt[i] * sizeof(int));
|
||||
}
|
||||
/* Fill */
|
||||
int* from_pos = calloc((size_t)g->node_count, sizeof(int));
|
||||
int* to_pos = calloc((size_t)g->node_count, sizeof(int));
|
||||
if (!from_pos || !to_pos) {
|
||||
free(from_cnt); free(to_cnt); free(from_pos); free(to_pos); return;
|
||||
}
|
||||
for (int64_t ei = 0; ei < g->edge_count; ei++) {
|
||||
EngramEdge* e = &g->edges[ei];
|
||||
if (!e->from_id || !e->to_id) continue;
|
||||
int64_t fi = engram_idmap_get(g, e->from_id);
|
||||
int64_t ti = engram_idmap_get(g, e->to_id);
|
||||
if (fi >= 0 && g->adj_from[fi])
|
||||
g->adj_from[fi][from_pos[fi]++] = (int)ei;
|
||||
if (ti >= 0 && g->adj_to[ti])
|
||||
g->adj_to[ti][to_pos[ti]++] = (int)ei;
|
||||
}
|
||||
/* Copy counts */
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
g->adj_from_len[i] = from_cnt[i];
|
||||
g->adj_to_len[i] = to_cnt[i];
|
||||
}
|
||||
free(from_cnt); free(to_cnt); free(from_pos); free(to_pos);
|
||||
g->adj_node_count = g->node_count;
|
||||
g->adj_dirty = 0;
|
||||
}
|
||||
|
||||
static int64_t engram_find_node_index(const char* id) {
|
||||
if (!id) return -1;
|
||||
EngramStore* g = engram_get();
|
||||
/* Fast O(1) path via id_map */
|
||||
int64_t fast = engram_idmap_get(g, id);
|
||||
if (fast >= 0) return fast;
|
||||
/* Fallback linear scan (id_map not yet built or OOM) */
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (g->nodes[i].id && strcmp(g->nodes[i].id, id) == 0) return i;
|
||||
}
|
||||
@@ -5896,7 +6183,10 @@ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
|
||||
n->created_at = now;
|
||||
n->updated_at = now;
|
||||
n->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||
int64_t new_idx = g->node_count;
|
||||
g->node_count++;
|
||||
engram_idmap_put(g, n->id, new_idx);
|
||||
g->adj_dirty = 1;
|
||||
return el_wrap_str(el_strdup(n->id));
|
||||
}
|
||||
|
||||
@@ -5932,7 +6222,10 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
n->created_at = now;
|
||||
n->updated_at = now;
|
||||
n->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||
int64_t new_idx_full = g->node_count;
|
||||
g->node_count++;
|
||||
engram_idmap_put(g, n->id, new_idx_full);
|
||||
g->adj_dirty = 1;
|
||||
return el_wrap_str(el_strdup(n->id));
|
||||
}
|
||||
|
||||
@@ -5999,7 +6292,10 @@ el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t labe
|
||||
if (lid < 0) lid = (int64_t)ENGRAM_LAYER_DEFAULT;
|
||||
if (!engram_find_layer((uint32_t)lid)) lid = (int64_t)ENGRAM_LAYER_DEFAULT;
|
||||
n->layer_id = (uint32_t)lid;
|
||||
int64_t new_idx_layered = g->node_count;
|
||||
g->node_count++;
|
||||
engram_idmap_put(g, n->id, new_idx_layered);
|
||||
g->adj_dirty = 1;
|
||||
return el_wrap_str(el_strdup(n->id));
|
||||
}
|
||||
|
||||
@@ -6162,6 +6458,10 @@ void engram_forget(el_val_t node_id) {
|
||||
}
|
||||
}
|
||||
g->edge_count = w;
|
||||
/* Shift-delete changed all indices above the removed position.
|
||||
* Rebuild id_map and mark adjacency index dirty. */
|
||||
engram_idmap_rebuild(g);
|
||||
engram_adj_free(g);
|
||||
}
|
||||
|
||||
el_val_t engram_node_count(void) {
|
||||
@@ -6265,6 +6565,7 @@ void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t
|
||||
e->last_fired = 0;
|
||||
e->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||
g->edge_count++;
|
||||
g->adj_dirty = 1;
|
||||
}
|
||||
|
||||
el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id) {
|
||||
@@ -6524,6 +6825,12 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
el_val_t out = el_list_empty();
|
||||
if (!q || g->node_count == 0) return out;
|
||||
|
||||
/* Rebuild adjacency index if the edge/node topology changed since the
|
||||
* last activation call. This is O(E) one-time cost vs O(E) per BFS step
|
||||
* without the index. On a 40K-edge graph this drops BFS from O(frontier
|
||||
* * E) to O(frontier * avg_degree). (2026-07-01 self-review) */
|
||||
if (g->adj_dirty || !g->adj_from) engram_adj_rebuild(g);
|
||||
|
||||
int64_t now_ms = engram_now_ms();
|
||||
|
||||
/* Per-node layer-1 tracking. */
|
||||
@@ -6586,22 +6893,47 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
while (fhead < ftail) {
|
||||
Frontier f = fr[fhead++];
|
||||
if (f.hops >= max_depth) continue;
|
||||
const char* cur_id = g->nodes[f.idx].id;
|
||||
for (int64_t ei = 0; ei < g->edge_count; ei++) {
|
||||
int64_t cur = f.idx;
|
||||
int64_t new_hops = f.hops + 1;
|
||||
/* Use adjacency index: iterate only edges incident to `cur`.
|
||||
* adj_from[cur] holds edge indices where cur is the 'from' node;
|
||||
* adj_to[cur] holds edge indices where cur is the 'to' node.
|
||||
* If adj index is unavailable (OOM during rebuild), fall back to
|
||||
* full edge scan so activation is never silently wrong. */
|
||||
int use_adj = (g->adj_from != NULL && g->adj_to != NULL);
|
||||
int from_len = use_adj ? g->adj_from_len[cur] : 0;
|
||||
int to_len = use_adj ? g->adj_to_len[cur] : 0;
|
||||
int edge_scan_count = use_adj ? (from_len + to_len) : (int)g->edge_count;
|
||||
for (int scan_i = 0; scan_i < edge_scan_count; scan_i++) {
|
||||
int64_t ei;
|
||||
int64_t oi;
|
||||
if (use_adj) {
|
||||
ei = (scan_i < from_len)
|
||||
? g->adj_from[cur][scan_i]
|
||||
: g->adj_to[cur][scan_i - from_len];
|
||||
EngramEdge* e = &g->edges[ei];
|
||||
oi = (scan_i < from_len)
|
||||
? engram_idmap_get(g, e->to_id)
|
||||
: engram_idmap_get(g, e->from_id);
|
||||
} else {
|
||||
/* Fallback: linear scan */
|
||||
ei = scan_i;
|
||||
EngramEdge* e = &g->edges[ei];
|
||||
const char* other = NULL;
|
||||
const char* cur_id = g->nodes[cur].id;
|
||||
if (e->from_id && strcmp(e->from_id, cur_id) == 0) other = e->to_id;
|
||||
else if (e->to_id && strcmp(e->to_id, cur_id) == 0) other = e->from_id;
|
||||
else continue;
|
||||
oi = engram_find_node_index(other);
|
||||
}
|
||||
if (oi < 0 || oi >= g->node_count) continue;
|
||||
EngramEdge* e = &g->edges[ei];
|
||||
const char* other = NULL;
|
||||
if (e->from_id && strcmp(e->from_id, cur_id) == 0) other = e->to_id;
|
||||
else if (e->to_id && strcmp(e->to_id, cur_id) == 0) other = e->from_id;
|
||||
else continue;
|
||||
int64_t oi = engram_find_node_index(other);
|
||||
if (oi < 0) continue;
|
||||
EngramNode* on = &g->nodes[oi];
|
||||
double tbonus = engram_temporal_proximity_bonus(on->created_at, seed_epoch);
|
||||
double tdecay = engram_temporal_decay(on, now_ms);
|
||||
double dampen = engram_activation_dampen(on);
|
||||
double new_act = f.act * e->weight * SPREAD_DECAY * (1.0 + tbonus)
|
||||
* tdecay * dampen;
|
||||
int64_t new_hops = f.hops + 1;
|
||||
if (!reached[oi] || new_act > best_bg[oi]) {
|
||||
best_bg[oi] = new_act;
|
||||
best_hops[oi] = new_hops;
|
||||
@@ -6659,6 +6991,19 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (!reached[i] || best_bg[i] <= 0.0) continue;
|
||||
EngramNode* n = &g->nodes[i];
|
||||
/* InternalStateEvent nodes are observability-only — never admit to WM.
|
||||
* Their JSON content (curiosity seeds, heartbeat payloads) contains common
|
||||
* words that trigger lexical seeding (e.g. "knowledge" in curiosity ISEs),
|
||||
* leading to repeated suppression and eventual breakthrough at the floor.
|
||||
* ISEs surfacing in context compilation are noise, not signal. Clear their
|
||||
* suppression_count so they don't build toward breakthrough, then skip.
|
||||
* (2026-06-30 self-review: porting fix from 2026-06-26 branch; SYNAPSE
|
||||
* paper confirms WM should hold only semantically relevant content.) */
|
||||
if (n->node_type && strcmp(n->node_type, "InternalStateEvent") == 0) {
|
||||
n->suppression_count = 0;
|
||||
wm_weights[i] = 0.0;
|
||||
continue;
|
||||
}
|
||||
/* Per-type threshold: safety nodes break through more easily. */
|
||||
double type_threshold = engram_type_threshold(n->node_type, n->tier);
|
||||
/* Goal bias weights the node's relevance to current intent. */
|
||||
@@ -6710,9 +7055,123 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
|
||||
n->suppression_count = 0;
|
||||
}
|
||||
|
||||
/* Persist working_memory_weight (post Pass 3) to node store. */
|
||||
/* ── PASS 4: WM capacity cap (per-call) ─────────────────────────────────
|
||||
* Enforce ENGRAM_WM_CAP as a hard upper bound on nodes promoted in this
|
||||
* activation call. Without this, broad curiosity seeds like "knowledge"
|
||||
* promote 500+ nodes simultaneously — wm_avg_weight collapses to the
|
||||
* breakthrough floor, goal-bias differentiation is lost, and working memory
|
||||
* becomes useless. (Ported from 2026-06-26 self-review branch; observed
|
||||
* 525 promoted for "knowledge", 524 at breakthrough floor 0.25, 1 natural.) */
|
||||
{
|
||||
int64_t cap_count = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (wm_weights[i] > 0.0) cap_count++;
|
||||
}
|
||||
if (cap_count > ENGRAM_WM_CAP) {
|
||||
double* cap_vals = malloc((size_t)cap_count * sizeof(double));
|
||||
if (cap_vals) {
|
||||
int64_t ci = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (wm_weights[i] > 0.0) cap_vals[ci++] = wm_weights[i];
|
||||
}
|
||||
qsort(cap_vals, (size_t)cap_count, sizeof(double),
|
||||
engram_cmp_double_desc);
|
||||
/* cap_vals[ENGRAM_WM_CAP-1] is the lowest weight that still
|
||||
* fits inside the cap when sorted descending. */
|
||||
double cutoff = cap_vals[ENGRAM_WM_CAP - 1];
|
||||
free(cap_vals);
|
||||
/* Count strictly above cutoff to handle ties correctly. */
|
||||
int64_t above = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (wm_weights[i] > cutoff) above++;
|
||||
}
|
||||
int64_t at_cutoff_slots = ENGRAM_WM_CAP - above;
|
||||
/* Evict nodes that don't make the cut. */
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (wm_weights[i] <= 0.0) continue; /* not promoted */
|
||||
if (wm_weights[i] > cutoff) continue; /* above cutoff */
|
||||
if (at_cutoff_slots > 0) {
|
||||
at_cutoff_slots--;
|
||||
continue; /* fills a slot */
|
||||
}
|
||||
wm_weights[i] = 0.0; /* over cap: evict */
|
||||
}
|
||||
}
|
||||
/* If malloc failed, skip cap — WM unbounded this call, no corruption. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Persist working_memory_weight (post Pass 4) to node store.
|
||||
*
|
||||
* Conversational thread continuity (ENGRAM_WM_DECAY):
|
||||
* Nodes promoted in a previous turn but NOT reached by the current BFS
|
||||
* fan-out retain a decayed weight rather than being zeroed. This models
|
||||
* the brain's ability to maintain recent context across successive turns
|
||||
* without requiring explicit re-activation. A node that was relevant one
|
||||
* query ago stays weakly present in working memory; a node from two
|
||||
* queries ago retains 0.7² ≈ 0.49 of its original weight; after ~5 quiet
|
||||
* turns it falls below 0.01 and is effectively evicted (set to 0.0).
|
||||
*
|
||||
* NOTE: this was documented in the ENGRAM_WM_DECAY constant comment since
|
||||
* the two-layer architecture was introduced, but was never implemented —
|
||||
* unreached nodes were always zeroed unconditionally. Fixed 2026-06-30
|
||||
* self-review. */
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
g->nodes[i].working_memory_weight = wm_weights[i];
|
||||
if (!reached[i] && g->nodes[i].working_memory_weight > 0.0) {
|
||||
/* Carry-over decay: node held WM weight from prior activation but
|
||||
* the current query's BFS fan-out did not reach it. Apply decay
|
||||
* rather than zero so recently-active context persists. */
|
||||
double decayed = g->nodes[i].working_memory_weight * ENGRAM_WM_DECAY;
|
||||
g->nodes[i].working_memory_weight = (decayed < 0.01) ? 0.0 : decayed;
|
||||
} else {
|
||||
g->nodes[i].working_memory_weight = wm_weights[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* ── PASS 5: Global WM cap enforcement ───────────────────────────────────
|
||||
* Pass 4 capped this call's new candidates. But nodes already in WM from
|
||||
* prior calls retain their persisted working_memory_weight (via the decay
|
||||
* carry-over above). Over multiple activation calls total WM can grow well
|
||||
* above ENGRAM_WM_CAP. This pass enforces the cap globally across ALL
|
||||
* nodes in the store, keeping only the top ENGRAM_WM_CAP by current weight.
|
||||
* Correct cognitive model: WM capacity is global (Cowan 2001); more recent
|
||||
* activations outcompete older decayed ones. (Ported from 2026-06-26
|
||||
* self-review branch.) */
|
||||
{
|
||||
int64_t global_wm_count = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (g->nodes[i].working_memory_weight > 0.0) global_wm_count++;
|
||||
}
|
||||
if (global_wm_count > ENGRAM_WM_CAP) {
|
||||
double* gvals = malloc((size_t)global_wm_count * sizeof(double));
|
||||
if (gvals) {
|
||||
int64_t gi = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (g->nodes[i].working_memory_weight > 0.0)
|
||||
gvals[gi++] = g->nodes[i].working_memory_weight;
|
||||
}
|
||||
qsort(gvals, (size_t)global_wm_count, sizeof(double),
|
||||
engram_cmp_double_desc);
|
||||
double gcutoff = gvals[ENGRAM_WM_CAP - 1];
|
||||
free(gvals);
|
||||
int64_t gabove = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (g->nodes[i].working_memory_weight > gcutoff) gabove++;
|
||||
}
|
||||
int64_t gslots_at_cutoff = ENGRAM_WM_CAP - gabove;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
EngramNode* n = &g->nodes[i];
|
||||
if (n->working_memory_weight <= 0.0) continue;
|
||||
if (n->working_memory_weight > gcutoff) continue;
|
||||
if (gslots_at_cutoff > 0) {
|
||||
gslots_at_cutoff--;
|
||||
continue; /* fills a slot */
|
||||
}
|
||||
n->working_memory_weight = 0.0; /* evict: over global cap */
|
||||
}
|
||||
}
|
||||
/* If malloc failed, skip — WM over cap this call, no data corruption. */
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Collect all background-activated nodes for the return value ────
|
||||
@@ -6927,6 +7386,8 @@ el_val_t engram_load(el_val_t path) {
|
||||
free(g->edges[i].relation); free(g->edges[i].metadata);
|
||||
}
|
||||
g->edge_count = 0;
|
||||
engram_idmap_free(g);
|
||||
engram_adj_free(g);
|
||||
|
||||
/* Walk nodes array */
|
||||
const char* nodes_p = json_find_key(data, "nodes");
|
||||
@@ -6973,7 +7434,9 @@ el_val_t engram_load(el_val_t path) {
|
||||
} else {
|
||||
nn->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||
}
|
||||
int64_t load_idx = g->node_count;
|
||||
g->node_count++;
|
||||
if (nn->id && *nn->id) engram_idmap_put(g, nn->id, load_idx);
|
||||
free(obj);
|
||||
nodes_p = end;
|
||||
nodes_p = eg_skip_ws(nodes_p);
|
||||
@@ -6981,6 +7444,7 @@ el_val_t engram_load(el_val_t path) {
|
||||
}
|
||||
}
|
||||
}
|
||||
g->adj_dirty = 1;
|
||||
/* Walk edges array */
|
||||
const char* edges_p = json_find_key(data, "edges");
|
||||
if (edges_p) {
|
||||
@@ -7081,6 +7545,159 @@ el_val_t engram_load(el_val_t path) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* engram_load_merge — like engram_load but WITHOUT resetting the store.
|
||||
* Reads a JSON snapshot from `path` and adds any nodes/edges not already
|
||||
* present in the in-memory graph. Dedup is by node id (for nodes) and by
|
||||
* (from_id, to_id, relation) tuple (for edges).
|
||||
*
|
||||
* Returns (as an EL int) the count of new nodes added. Used by the soul
|
||||
* daemon's periodic refresh cycle to keep its in-process Engram in sync
|
||||
* with the HTTP Engram store without losing current working memory state.
|
||||
* Ported from el-compiler/runtime on 2026-06-30 self-review. */
|
||||
el_val_t engram_load_merge(el_val_t path) {
|
||||
const char* p = EL_CSTR(path);
|
||||
if (!p || !*p) return 0;
|
||||
FILE* f = fopen(p, "rb");
|
||||
if (!f) return 0;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long sz = ftell(f);
|
||||
rewind(f);
|
||||
if (sz <= 0) { fclose(f); return 0; }
|
||||
char* data = malloc((size_t)sz + 1);
|
||||
if (!data) { fclose(f); return 0; }
|
||||
size_t got = fread(data, 1, (size_t)sz, f);
|
||||
fclose(f);
|
||||
data[got] = '\0';
|
||||
|
||||
EngramStore* g = engram_get();
|
||||
int64_t added_nodes = 0;
|
||||
|
||||
/* Walk nodes array — skip any node whose id already exists */
|
||||
const char* nodes_p = json_find_key(data, "nodes");
|
||||
if (nodes_p) {
|
||||
nodes_p = eg_skip_ws(nodes_p);
|
||||
if (*nodes_p == '[') {
|
||||
nodes_p++;
|
||||
nodes_p = eg_skip_ws(nodes_p);
|
||||
while (*nodes_p && *nodes_p != ']') {
|
||||
if (*nodes_p != '{') { nodes_p++; continue; }
|
||||
const char* end = json_skip_value(nodes_p);
|
||||
size_t n = (size_t)(end - nodes_p);
|
||||
char* obj = malloc(n + 1);
|
||||
memcpy(obj, nodes_p, n); obj[n] = '\0';
|
||||
char* nid = eg_get_str_field(obj, "id");
|
||||
int already = (nid && *nid && engram_find_node(nid) != NULL);
|
||||
free(nid);
|
||||
if (!already) {
|
||||
engram_grow_nodes();
|
||||
EngramNode* nn = &g->nodes[g->node_count];
|
||||
memset(nn, 0, sizeof(*nn));
|
||||
nn->id = eg_get_str_field(obj, "id");
|
||||
nn->content = eg_get_str_field(obj, "content");
|
||||
nn->node_type = eg_get_str_field(obj, "node_type");
|
||||
nn->label = eg_get_str_field(obj, "label");
|
||||
nn->tier = eg_get_str_field(obj, "tier");
|
||||
nn->tags = eg_get_str_field(obj, "tags");
|
||||
nn->metadata = eg_get_str_field(obj, "metadata");
|
||||
if (!nn->metadata || !*nn->metadata) { free(nn->metadata); nn->metadata = strdup("{}"); }
|
||||
nn->salience = eg_get_num_field(obj, "salience");
|
||||
nn->importance = eg_get_num_field(obj, "importance");
|
||||
nn->confidence = eg_get_num_field(obj, "confidence");
|
||||
nn->temporal_decay_rate = eg_get_num_field(obj, "temporal_decay_rate");
|
||||
nn->activation_count = eg_get_int_field(obj, "activation_count");
|
||||
nn->last_activated = eg_get_int_field(obj, "last_activated");
|
||||
nn->created_at = eg_get_int_field(obj, "created_at");
|
||||
nn->updated_at = eg_get_int_field(obj, "updated_at");
|
||||
nn->background_activation = eg_get_num_field(obj, "background_activation");
|
||||
nn->working_memory_weight = eg_get_num_field(obj, "working_memory_weight");
|
||||
if (!isfinite(nn->working_memory_weight) || nn->working_memory_weight < 0.0 || nn->working_memory_weight > 1.0)
|
||||
nn->working_memory_weight = 0.0;
|
||||
nn->suppression_count = (int32_t)eg_get_int_field(obj, "suppression_count");
|
||||
if (json_find_key(obj, "layer_id")) {
|
||||
nn->layer_id = (uint32_t)eg_get_int_field(obj, "layer_id");
|
||||
} else {
|
||||
nn->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||
}
|
||||
int64_t merge_idx = g->node_count;
|
||||
g->node_count++;
|
||||
added_nodes++;
|
||||
if (nn->id && *nn->id) engram_idmap_put(g, nn->id, merge_idx);
|
||||
g->adj_dirty = 1;
|
||||
}
|
||||
free(obj);
|
||||
nodes_p = end;
|
||||
nodes_p = eg_skip_ws(nodes_p);
|
||||
if (*nodes_p == ',') { nodes_p++; nodes_p = eg_skip_ws(nodes_p); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Walk edges array — skip if (from_id, to_id, relation) already present */
|
||||
const char* edges_p = json_find_key(data, "edges");
|
||||
if (edges_p) {
|
||||
edges_p = eg_skip_ws(edges_p);
|
||||
if (*edges_p == '[') {
|
||||
edges_p++;
|
||||
edges_p = eg_skip_ws(edges_p);
|
||||
while (*edges_p && *edges_p != ']') {
|
||||
if (*edges_p != '{') { edges_p++; continue; }
|
||||
const char* end = json_skip_value(edges_p);
|
||||
size_t n = (size_t)(end - edges_p);
|
||||
char* obj = malloc(n + 1);
|
||||
memcpy(obj, edges_p, n); obj[n] = '\0';
|
||||
char* efrom = eg_get_str_field(obj, "from_id");
|
||||
char* eto = eg_get_str_field(obj, "to_id");
|
||||
char* erel = eg_get_str_field(obj, "relation");
|
||||
int dup = 0;
|
||||
if (efrom && eto && erel) {
|
||||
for (int64_t ei = 0; ei < g->edge_count; ei++) {
|
||||
EngramEdge* ex = &g->edges[ei];
|
||||
if (ex->from_id && ex->to_id && ex->relation &&
|
||||
strcmp(ex->from_id, efrom) == 0 &&
|
||||
strcmp(ex->to_id, eto) == 0 &&
|
||||
strcmp(ex->relation, erel) == 0) {
|
||||
dup = 1; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dup) {
|
||||
engram_grow_edges();
|
||||
EngramEdge* ee = &g->edges[g->edge_count];
|
||||
memset(ee, 0, sizeof(*ee));
|
||||
ee->id = eg_get_str_field(obj, "id");
|
||||
ee->from_id = efrom ? efrom : strdup("");
|
||||
ee->to_id = eto ? eto : strdup("");
|
||||
ee->relation = erel ? erel : strdup("");
|
||||
ee->metadata = eg_get_str_field(obj, "metadata");
|
||||
if (!ee->metadata || !*ee->metadata) { free(ee->metadata); ee->metadata = strdup("{}"); }
|
||||
ee->weight = eg_get_num_field(obj, "weight");
|
||||
ee->confidence = eg_get_num_field(obj, "confidence");
|
||||
ee->created_at = eg_get_int_field(obj, "created_at");
|
||||
ee->updated_at = eg_get_int_field(obj, "updated_at");
|
||||
ee->last_fired = eg_get_int_field(obj, "last_fired");
|
||||
ee->inhibitory = (int)eg_get_int_field(obj, "inhibitory");
|
||||
if (json_find_key(obj, "layer_id")) {
|
||||
ee->layer_id = (uint32_t)eg_get_int_field(obj, "layer_id");
|
||||
} else {
|
||||
ee->layer_id = ENGRAM_LAYER_DEFAULT;
|
||||
}
|
||||
g->edge_count++;
|
||||
efrom = NULL; eto = NULL; erel = NULL;
|
||||
} else {
|
||||
free(efrom); free(eto); free(erel);
|
||||
}
|
||||
free(obj);
|
||||
edges_p = end;
|
||||
edges_p = eg_skip_ws(edges_p);
|
||||
if (*edges_p == ',') { edges_p++; edges_p = eg_skip_ws(edges_p); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(data);
|
||||
return (el_val_t)added_nodes;
|
||||
}
|
||||
|
||||
/* ── Engram JSON-string accessors ─────────────────────────────────────────
|
||||
* These return pre-serialized JSON strings so callers (especially HTTP
|
||||
* handlers) don't have to round-trip ElList/ElMap through json_stringify
|
||||
@@ -7097,6 +7714,34 @@ el_val_t engram_get_node_json(el_val_t id) {
|
||||
return el_wrap_str(b.buf);
|
||||
}
|
||||
|
||||
/* engram_get_node_by_label — find the first node whose label field exactly
|
||||
* matches the given string. Returns the node as a JSON object string, or "{}"
|
||||
* if no match is found.
|
||||
*
|
||||
* Used by chat.el to retrieve well-known nodes (e.g. "conv:history",
|
||||
* "session:summary") by their stable label rather than by ID, which is immune
|
||||
* to vector index drift across restarts.
|
||||
*
|
||||
* Exact match (strcmp, not istr_contains) because labels like "conv:history"
|
||||
* must not collide with nodes whose content happens to contain that substring.
|
||||
*
|
||||
* Added 2026-07-01 self-review: was called in chat.el but never defined,
|
||||
* causing build failure since June 30. */
|
||||
el_val_t engram_get_node_by_label(el_val_t label) {
|
||||
const char* lbl = EL_CSTR(label);
|
||||
if (!lbl || !*lbl) return el_wrap_str(el_strdup("{}"));
|
||||
EngramStore* g = engram_get();
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
EngramNode* n = &g->nodes[i];
|
||||
if (n->label && strcmp(n->label, lbl) == 0) {
|
||||
JsonBuf b; jb_init(&b);
|
||||
engram_emit_node_json(&b, n);
|
||||
return el_wrap_str(b.buf);
|
||||
}
|
||||
}
|
||||
return el_wrap_str(el_strdup("{}"));
|
||||
}
|
||||
|
||||
el_val_t engram_search_json(el_val_t query, el_val_t limit) {
|
||||
EngramStore* g = engram_get();
|
||||
const char* q = EL_CSTR(query);
|
||||
@@ -7298,6 +7943,95 @@ el_val_t engram_activate_json(el_val_t query, el_val_t depth) {
|
||||
return el_wrap_str(b.buf);
|
||||
}
|
||||
|
||||
/* ── Working memory introspection helpers ────────────────────────────────────
|
||||
*
|
||||
* These three functions give the soul daemon visibility into WM composition
|
||||
* without re-running activation. Used in heartbeat ISEs and curiosity scans.
|
||||
* Ported from el-compiler/runtime to releases/v1.0.0-20260501 on 2026-06-30
|
||||
* self-review (they were missing from the release build, breaking soul daemon
|
||||
* compilation). */
|
||||
|
||||
el_val_t engram_wm_count(void) {
|
||||
EngramStore* g = engram_get();
|
||||
int64_t count = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (g->nodes[i].working_memory_weight > 0.0) count++;
|
||||
}
|
||||
return (el_val_t)count;
|
||||
}
|
||||
|
||||
/* Average working_memory_weight across all promoted nodes (wm > 0).
|
||||
* Returns the float bit-pattern via el_from_float so EL can use it with
|
||||
* float_to_str / float_gt. Returns 0.0 when no nodes are promoted.
|
||||
* Useful in heartbeat ISEs to distinguish "many weak activations" from
|
||||
* "few strong activations". Added 2026-06-04 self-review. */
|
||||
el_val_t engram_wm_avg_weight(void) {
|
||||
EngramStore* g = engram_get();
|
||||
double sum = 0.0;
|
||||
int64_t count = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
double w = g->nodes[i].working_memory_weight;
|
||||
/* Skip corrupt/out-of-range values so a single bad snapshot node
|
||||
* doesn't produce a garbage average. */
|
||||
if (w > 0.0 && w <= 1.0 && isfinite(w)) { sum += w; count++; }
|
||||
}
|
||||
double avg = (count > 0) ? (sum / (double)count) : 0.0;
|
||||
return el_from_float(avg);
|
||||
}
|
||||
|
||||
/* engram_wm_top_json — return top N working-memory nodes (by wm weight) as a
|
||||
* compact JSON array for ISE heartbeat reporting.
|
||||
* Each element: {"label":"...","node_type":"...","tier":"...","wm":0.42}
|
||||
* InternalStateEvent nodes are excluded — they're observation artifacts that
|
||||
* would bury substantive WM content. Added 2026-06-05 self-review. */
|
||||
el_val_t engram_wm_top_json(el_val_t n_v) {
|
||||
int64_t top_n = (int64_t)n_v;
|
||||
if (top_n <= 0) top_n = 10;
|
||||
if (top_n > 50) top_n = 50;
|
||||
EngramStore* g = engram_get();
|
||||
int64_t* idx = malloc((size_t)(g->node_count + 1) * sizeof(int64_t));
|
||||
if (!idx) return el_wrap_str(el_strdup("[]"));
|
||||
int64_t mc = 0;
|
||||
for (int64_t i = 0; i < g->node_count; i++) {
|
||||
if (g->nodes[i].working_memory_weight > 0.0) {
|
||||
const char* nt = g->nodes[i].node_type;
|
||||
if (nt && strcmp(nt, "InternalStateEvent") == 0) continue;
|
||||
idx[mc++] = i;
|
||||
}
|
||||
}
|
||||
/* Insertion-sort descending by wm weight (mc is typically small). */
|
||||
for (int64_t i = 1; i < mc; i++) {
|
||||
int64_t key = idx[i];
|
||||
double kw = g->nodes[key].working_memory_weight;
|
||||
int64_t j = i;
|
||||
while (j > 0 && g->nodes[idx[j-1]].working_memory_weight < kw) {
|
||||
idx[j] = idx[j-1]; j--;
|
||||
}
|
||||
idx[j] = key;
|
||||
}
|
||||
int64_t emit = mc < top_n ? mc : top_n;
|
||||
JsonBuf b; jb_init(&b);
|
||||
jb_putc(&b, '[');
|
||||
for (int64_t k = 0; k < emit; k++) {
|
||||
EngramNode* n = &g->nodes[idx[k]];
|
||||
if (k > 0) jb_putc(&b, ',');
|
||||
jb_putc(&b, '{');
|
||||
jb_puts(&b, "\"label\":");
|
||||
jb_emit_escaped(&b, n->label ? n->label : "");
|
||||
jb_puts(&b, ",\"node_type\":");
|
||||
jb_emit_escaped(&b, n->node_type ? n->node_type : "");
|
||||
jb_puts(&b, ",\"tier\":");
|
||||
jb_emit_escaped(&b, n->tier ? n->tier : "");
|
||||
char tmp[48];
|
||||
snprintf(tmp, sizeof(tmp), ",\"wm\":%.3f", n->working_memory_weight);
|
||||
jb_puts(&b, tmp);
|
||||
jb_putc(&b, '}');
|
||||
}
|
||||
free(idx);
|
||||
jb_putc(&b, ']');
|
||||
return el_wrap_str(b.buf);
|
||||
}
|
||||
|
||||
el_val_t engram_stats_json(void) {
|
||||
EngramStore* g = engram_get();
|
||||
char buf[128];
|
||||
|
||||
@@ -601,6 +601,13 @@ el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t d
|
||||
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_stats_json(void);
|
||||
el_val_t engram_list_layers_json(void);
|
||||
/* Working memory introspection — count, mean weight, and top-N snapshot.
|
||||
* Ported from el-compiler/runtime on 2026-06-30 self-review. */
|
||||
el_val_t engram_wm_count(void);
|
||||
el_val_t engram_wm_avg_weight(void);
|
||||
el_val_t engram_wm_top_json(el_val_t n);
|
||||
/* Merge-load: add nodes/edges from a snapshot without resetting the store. */
|
||||
el_val_t engram_load_merge(el_val_t path);
|
||||
/* engram_compile_layered_json — produce a prompt-ready text block split
|
||||
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
||||
* and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
local.properties
|
||||
.gradle/
|
||||
build/
|
||||
app/build/
|
||||
*.iml
|
||||
.idea/
|
||||
@@ -0,0 +1,55 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.neuron.el'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.neuron.el"
|
||||
minSdk 21
|
||||
targetSdk 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags ""
|
||||
arguments "-DANDROID_STL=c++_shared"
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
// Build for the two most relevant ABIs. Add x86/x86_64 for emulator.
|
||||
abiFilters "arm64-v8a", "armeabi-v7a", "x86_64"
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/jni/CMakeLists.txt"
|
||||
version "3.22.1"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
jniDebuggable true
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// No third-party dependencies — el-native uses only android.* framework classes.
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# Keep ElBridge and MainActivity so JNI symbol names stay intact.
|
||||
-keep class com.neuron.el.ElBridge { *; }
|
||||
-keep class com.neuron.el.MainActivity { *; }
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:name=".ElApp"
|
||||
android:label="el-native"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:allowBackup="false"
|
||||
android:supportsRtl="true">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.neuron.el;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
/**
|
||||
* ElApp — Application subclass for native-hello-android.
|
||||
*
|
||||
* Currently minimal: exists as an anchor for future app-level initialisation
|
||||
* (crash reporting, global state, etc.). Listed in AndroidManifest.xml as
|
||||
* android:name=".ElApp".
|
||||
*/
|
||||
public class ElApp extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,711 @@
|
||||
/*
|
||||
* ElBridge.java — Android Java companion to el_android.c.
|
||||
*
|
||||
* All public methods are static. The C JNI layer calls these to create views,
|
||||
* set properties, and manage the widget tree. Views are identified by integer
|
||||
* slot indices matching the C-side handle values.
|
||||
*
|
||||
* Threading: every method that touches a View dispatches to the UI thread
|
||||
* using Activity.runOnUiThread(Runnable) and blocks with a CountDownLatch
|
||||
* until the UI thread completes the operation. This mirrors the AppKit
|
||||
* dispatch_sync(main_queue, ^{}) pattern in el_appkit.m.
|
||||
*
|
||||
* Callbacks: Java sets listeners on views that call back into C via:
|
||||
* nativeOnClick(int slot)
|
||||
* nativeOnChange(int slot, String text)
|
||||
* nativeOnSubmit(int slot, String text)
|
||||
* These are declared native and implemented in el_android.c.
|
||||
*
|
||||
* Usage (in your Activity.onCreate):
|
||||
* System.loadLibrary("elruntime");
|
||||
* ElBridge.init(this);
|
||||
*
|
||||
* The native library calls __native_init() which calls nativeRegisterActivity
|
||||
* via the C side; alternatively call ElBridge.init(this) directly from Java.
|
||||
*
|
||||
* Compile requirements:
|
||||
* Android minSdkVersion 21 (Lollipop) or higher.
|
||||
* No third-party dependencies — uses only android.* framework classes.
|
||||
* For image loading from arbitrary file paths, BitmapFactory is used.
|
||||
* To replace with Glide/Picasso, edit createImageView only.
|
||||
*/
|
||||
|
||||
package com.neuron.el;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class ElBridge {
|
||||
|
||||
/* ── Native callbacks (implemented in el_android.c) ─────────────────── */
|
||||
|
||||
public static native void nativeOnClick(int slot);
|
||||
public static native void nativeOnChange(int slot, String text);
|
||||
public static native void nativeOnSubmit(int slot, String text);
|
||||
public static native void nativeRegisterActivity(Activity activity);
|
||||
|
||||
/* ── State ───────────────────────────────────────────────────────────── */
|
||||
|
||||
private static final int MAX_SLOTS = 4096;
|
||||
|
||||
private static Activity sActivity;
|
||||
private static Handler sUiHandler;
|
||||
private static View[] sViews = new View[MAX_SLOTS];
|
||||
private static int sNextSlot = 1; /* slot 0 reserved / null */
|
||||
|
||||
/* ── Init ────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Must be called from the Activity before any widget operations.
|
||||
* Typically called from Activity.onCreate after System.loadLibrary.
|
||||
*/
|
||||
public static void init(Activity activity) {
|
||||
sActivity = activity;
|
||||
sUiHandler = new Handler(Looper.getMainLooper());
|
||||
nativeRegisterActivity(activity);
|
||||
}
|
||||
|
||||
/* ── Slot management ─────────────────────────────────────────────────── */
|
||||
|
||||
private static int allocSlot(View v) {
|
||||
/* Find a free slot starting from sNextSlot, wrap around. */
|
||||
for (int i = 0; i < MAX_SLOTS - 1; i++) {
|
||||
int idx = ((sNextSlot - 1 + i) % (MAX_SLOTS - 1)) + 1;
|
||||
if (sViews[idx] == null) {
|
||||
sViews[idx] = v;
|
||||
sNextSlot = (idx % (MAX_SLOTS - 1)) + 1;
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
android.util.Log.e("ElBridge", "allocSlot: slot table full");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static View getView(int slot) {
|
||||
if (slot <= 0 || slot >= MAX_SLOTS) return null;
|
||||
return sViews[slot];
|
||||
}
|
||||
|
||||
/* ── UI-thread dispatch helper ───────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Dispatch r on the UI thread and block until it completes.
|
||||
* Safe to call from the UI thread itself (runs inline without posting).
|
||||
*/
|
||||
private static void runSync(final Runnable r) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
r.run();
|
||||
} else {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
sUiHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
try { r.run(); } finally { latch.countDown(); }
|
||||
}
|
||||
});
|
||||
try { latch.await(); } catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Integer slot returning runSync helper ───────────────────────────── */
|
||||
|
||||
private interface IntSupplier { int get(); }
|
||||
|
||||
private static int runSyncInt(final IntSupplier s) {
|
||||
final int[] result = { -1 };
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() { result[0] = s.get(); }
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
/* ── Context accessor ────────────────────────────────────────────────── */
|
||||
|
||||
private static Context ctx() { return sActivity; }
|
||||
|
||||
/* ── View creation ───────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Create a LinearLayout.
|
||||
* @param orientation 1=VERTICAL, 0=HORIZONTAL
|
||||
* @param spacing gap between children in dp; applied as bottom/right margin
|
||||
*/
|
||||
public static int createLinearLayout(final int orientation, final int spacing) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
LinearLayout ll = new LinearLayout(ctx());
|
||||
ll.setOrientation(orientation == 1
|
||||
? LinearLayout.VERTICAL
|
||||
: LinearLayout.HORIZONTAL);
|
||||
ll.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
/* Spacing is stored so addChild can apply margins. */
|
||||
ll.setTag(R_TAG_SPACING, spacing);
|
||||
return allocSlot(ll);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a FrameLayout (ZStack equivalent). */
|
||||
public static int createFrameLayout() {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
FrameLayout fl = new FrameLayout(ctx());
|
||||
fl.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(fl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a ScrollView. */
|
||||
public static int createScrollView() {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
ScrollView sv = new ScrollView(ctx());
|
||||
sv.setLayoutParams(new ScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
sv.setFillViewport(true);
|
||||
return allocSlot(sv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a TextView with initial text. */
|
||||
public static int createTextView(final String text) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
TextView tv = new TextView(ctx());
|
||||
tv.setText(text != null ? text : "");
|
||||
tv.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(tv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a Button with a label. */
|
||||
public static int createButton(final String label) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
Button btn = new Button(ctx());
|
||||
btn.setText(label != null ? label : "");
|
||||
btn.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(btn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EditText.
|
||||
* @param placeholder hint text
|
||||
* @param singleLine true = single-line text field; false = multi-line text area
|
||||
*/
|
||||
public static int createEditText(final String placeholder, final boolean singleLine) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
EditText et = new EditText(ctx());
|
||||
et.setHint(placeholder != null ? placeholder : "");
|
||||
if (singleLine) {
|
||||
et.setInputType(InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
et.setMaxLines(1);
|
||||
et.setSingleLine(true);
|
||||
} else {
|
||||
et.setInputType(InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
et.setMinLines(3);
|
||||
et.setSingleLine(false);
|
||||
et.setGravity(Gravity.TOP | Gravity.START);
|
||||
}
|
||||
et.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(et);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ImageView, loading from a file path via BitmapFactory.
|
||||
* If path is null/empty the ImageView is created with no image.
|
||||
*/
|
||||
public static int createImageView(final String path) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
ImageView iv = new ImageView(ctx());
|
||||
iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
iv.setAdjustViewBounds(true);
|
||||
if (path != null && !path.isEmpty()) {
|
||||
Bitmap bmp = BitmapFactory.decodeFile(path);
|
||||
if (bmp != null) {
|
||||
iv.setImageBitmap(bmp);
|
||||
} else {
|
||||
android.util.Log.w("ElBridge",
|
||||
"createImageView: failed to decode " + path);
|
||||
}
|
||||
}
|
||||
iv.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(iv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Window operations ───────────────────────────────────────────────── */
|
||||
|
||||
/** Set the Activity's content view to the view at slot. */
|
||||
public static void setContentView(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null && sActivity != null) {
|
||||
sActivity.setContentView(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set the Activity title. */
|
||||
public static void setTitle(final String title) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
if (sActivity != null) {
|
||||
sActivity.setTitle(title != null ? title : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Tree operations ─────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Add child view to parent view.
|
||||
* LinearLayout: child added as arranged child with spacing margin.
|
||||
* ScrollView: child replaces current document view.
|
||||
* FrameLayout / other ViewGroup: plain addView.
|
||||
*/
|
||||
public static void addChild(final int parentSlot, final int childSlot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View parent = getView(parentSlot);
|
||||
View child = getView(childSlot);
|
||||
if (parent == null || child == null) return;
|
||||
|
||||
/* Remove child from existing parent first. */
|
||||
if (child.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) child.getParent()).removeView(child);
|
||||
}
|
||||
|
||||
if (parent instanceof LinearLayout) {
|
||||
LinearLayout ll = (LinearLayout) parent;
|
||||
Object tag = ll.getTag(R_TAG_SPACING);
|
||||
int spacing = (tag instanceof Integer) ? (Integer) tag : 0;
|
||||
LinearLayout.LayoutParams lp;
|
||||
Object existingLp = child.getLayoutParams();
|
||||
if (existingLp instanceof LinearLayout.LayoutParams) {
|
||||
lp = (LinearLayout.LayoutParams) existingLp;
|
||||
} else {
|
||||
lp = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
/* Apply spacing as margin on the leading/top edge (after first child). */
|
||||
if (ll.getChildCount() > 0 && spacing > 0) {
|
||||
int px = dpToPx(spacing);
|
||||
if (ll.getOrientation() == LinearLayout.VERTICAL) {
|
||||
lp.topMargin = px;
|
||||
} else {
|
||||
lp.leftMargin = px;
|
||||
}
|
||||
}
|
||||
child.setLayoutParams(lp);
|
||||
ll.addView(child);
|
||||
} else if (parent instanceof ScrollView) {
|
||||
ScrollView sv = (ScrollView) parent;
|
||||
sv.removeAllViews();
|
||||
sv.addView(child);
|
||||
} else if (parent instanceof ViewGroup) {
|
||||
((ViewGroup) parent).addView(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove child from its parent. */
|
||||
public static void removeChild(final int parentSlot, final int childSlot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View parent = getView(parentSlot);
|
||||
View child = getView(childSlot);
|
||||
if (parent instanceof ViewGroup && child != null) {
|
||||
((ViewGroup) parent).removeView(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove the view from its parent and release the slot. */
|
||||
public static void destroyView(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
if (v.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) v.getParent()).removeView(v);
|
||||
}
|
||||
sViews[slot] = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Property setters ────────────────────────────────────────────────── */
|
||||
|
||||
public static void setText(final int slot, final String text) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
String s = text != null ? text : "";
|
||||
if (v instanceof EditText) {
|
||||
((EditText) v).setText(s);
|
||||
} else if (v instanceof Button) {
|
||||
((Button) v).setText(s);
|
||||
} else if (v instanceof TextView) {
|
||||
((TextView) v).setText(s);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static String getText(final int slot) {
|
||||
final String[] result = { "" };
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v instanceof TextView) {
|
||||
CharSequence cs = ((TextView) v).getText();
|
||||
result[0] = cs != null ? cs.toString() : "";
|
||||
}
|
||||
}
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
/** Set foreground text color. Components in [0,1]. */
|
||||
public static void setTextColor(final int slot, final float r, final float g,
|
||||
final float b, final float a) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v instanceof TextView) {
|
||||
((TextView) v).setTextColor(floatToArgb(r, g, b, a));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set background color using a GradientDrawable so corner radius is preserved. */
|
||||
public static void setBackgroundColor(final int slot, final float r, final float g,
|
||||
final float b, final float a) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ensureGradientBackground(v);
|
||||
GradientDrawable gd = (GradientDrawable) v.getBackground();
|
||||
gd.setColor(floatToArgb(r, g, b, a));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set font family and size.
|
||||
* family: "system" or null → system default; otherwise tries to load by name.
|
||||
* bold: if true uses Typeface.BOLD.
|
||||
*/
|
||||
public static void setFont(final int slot, final String family,
|
||||
final int sizeSp, final boolean bold) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof TextView)) return;
|
||||
TextView tv = (TextView) v;
|
||||
Typeface tf;
|
||||
if (family != null && !family.isEmpty()
|
||||
&& !family.equals("system")) {
|
||||
Typeface base = Typeface.create(family,
|
||||
bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
tf = (base != null) ? base
|
||||
: Typeface.defaultFromStyle(bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
} else {
|
||||
tf = Typeface.defaultFromStyle(bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
}
|
||||
tv.setTypeface(tf);
|
||||
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, sizeSp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set padding in dp. */
|
||||
public static void setPadding(final int slot, final int top, final int right,
|
||||
final int bottom, final int left) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) {
|
||||
v.setPadding(dpToPx(left), dpToPx(top), dpToPx(right), dpToPx(bottom));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set explicit width in dp. Passes MATCH_PARENT for negative values. */
|
||||
public static void setWidth(final int slot, final int widthDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp == null) lp = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.width = widthDp < 0
|
||||
? ViewGroup.LayoutParams.MATCH_PARENT
|
||||
: dpToPx(widthDp);
|
||||
v.setLayoutParams(lp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set explicit height in dp. */
|
||||
public static void setHeight(final int slot, final int heightDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp == null) lp = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.height = heightDp < 0
|
||||
? ViewGroup.LayoutParams.MATCH_PARENT
|
||||
: dpToPx(heightDp);
|
||||
v.setLayoutParams(lp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flex weight on a child of a LinearLayout.
|
||||
* flex > 0 → weight = flex, width/height = 0dp (expand).
|
||||
* flex == 0 → weight = 0, wrap_content (shrink to content).
|
||||
*/
|
||||
public static void setFlex(final int slot, final int flex) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp instanceof LinearLayout.LayoutParams) {
|
||||
LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams) lp;
|
||||
if (flex > 0) {
|
||||
llp.weight = (float) flex;
|
||||
/* Determine orientation from parent to set 0dp on the right axis. */
|
||||
if (v.getParent() instanceof LinearLayout) {
|
||||
LinearLayout parent = (LinearLayout) v.getParent();
|
||||
if (parent.getOrientation() == LinearLayout.VERTICAL) {
|
||||
llp.height = 0;
|
||||
} else {
|
||||
llp.width = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
llp.weight = 0f;
|
||||
}
|
||||
v.setLayoutParams(llp);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set corner radius in dp using a GradientDrawable background. */
|
||||
public static void setCornerRadius(final int slot, final float radiusDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ensureGradientBackground(v);
|
||||
GradientDrawable gd = (GradientDrawable) v.getBackground();
|
||||
gd.setCornerRadius(dpToPxF(radiusDp));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setEnabled(final int slot, final boolean enabled) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) v.setEnabled(enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide a view.
|
||||
* @param visible true = VISIBLE, false = GONE (matches AppKit setHidden semantics)
|
||||
*/
|
||||
public static void setVisibility(final int slot, final boolean visible) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) v.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Event listener registration ─────────────────────────────────────── */
|
||||
|
||||
/** Register an OnClickListener that calls back into C nativeOnClick. */
|
||||
public static void setOnClickListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
final int capturedSlot = slot;
|
||||
v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override public void onClick(View view) {
|
||||
nativeOnClick(capturedSlot);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a TextWatcher on an EditText that calls back nativeOnChange
|
||||
* for every text change.
|
||||
*/
|
||||
public static void setOnChangeListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof EditText)) return;
|
||||
final int capturedSlot = slot;
|
||||
((EditText) v).addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start,
|
||||
int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start,
|
||||
int before, int count) {}
|
||||
@Override public void afterTextChanged(Editable s) {
|
||||
nativeOnChange(capturedSlot, s != null ? s.toString() : "");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an OnEditorActionListener on a single-line EditText that calls
|
||||
* nativeOnSubmit when the user presses the action/enter key.
|
||||
*/
|
||||
public static void setOnSubmitListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof EditText)) return;
|
||||
final int capturedSlot = slot;
|
||||
((EditText) v).setOnEditorActionListener(
|
||||
new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView tv, int actionId,
|
||||
android.view.KeyEvent event) {
|
||||
nativeOnSubmit(capturedSlot,
|
||||
tv.getText() != null ? tv.getText().toString() : "");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Internal helpers ─────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Tag key used to stash the spacing value on LinearLayouts so addChild
|
||||
* can apply the correct margin between children.
|
||||
* We use a stable integer resource-id-like value; because we do not have
|
||||
* a resources file here we use View.generateViewId() lazily.
|
||||
*/
|
||||
private static int sSpacingTagKey = 0;
|
||||
|
||||
private static int R_TAG_SPACING;
|
||||
static {
|
||||
R_TAG_SPACING = View.generateViewId();
|
||||
}
|
||||
|
||||
/** Convert dp to pixels using the Activity's display metrics. */
|
||||
private static int dpToPx(float dp) {
|
||||
if (sActivity == null) return (int) dp;
|
||||
float density = sActivity.getResources().getDisplayMetrics().density;
|
||||
return Math.round(dp * density);
|
||||
}
|
||||
|
||||
private static float dpToPxF(float dp) {
|
||||
if (sActivity == null) return dp;
|
||||
float density = sActivity.getResources().getDisplayMetrics().density;
|
||||
return dp * density;
|
||||
}
|
||||
|
||||
/** Convert RGBA float components (0–1) to an Android ARGB int. */
|
||||
private static int floatToArgb(float r, float g, float b, float a) {
|
||||
int ai = Math.round(a * 255f);
|
||||
int ri = Math.round(r * 255f);
|
||||
int gi = Math.round(g * 255f);
|
||||
int bi = Math.round(b * 255f);
|
||||
return Color.argb(ai, ri, gi, bi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the view has a GradientDrawable background so that both color
|
||||
* and corner radius can be set independently. If the current background
|
||||
* is already a GradientDrawable it is reused; otherwise a new transparent
|
||||
* one is installed.
|
||||
*/
|
||||
private static void ensureGradientBackground(View v) {
|
||||
if (!(v.getBackground() instanceof GradientDrawable)) {
|
||||
GradientDrawable gd = new GradientDrawable();
|
||||
gd.setColor(Color.TRANSPARENT);
|
||||
v.setBackground(gd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.neuron.el;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* MainActivity — entry point for native-hello-android.
|
||||
*
|
||||
* Loads the el native shared library, initialises ElBridge with the Activity
|
||||
* reference (required before any widget operations), then hands control to the
|
||||
* compiled el program via nativeMain().
|
||||
*
|
||||
* The el boot sequence (native_init → window_from_manifest → app_build →
|
||||
* window_show) runs inside nativeMain. __native_run_loop is a no-op on Android;
|
||||
* the Activity lifecycle owns the UI thread after onCreate returns.
|
||||
*/
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
static {
|
||||
System.loadLibrary("elnative");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Register this Activity with the bridge BEFORE nativeMain so that
|
||||
// el_android_init can look up ElBridge methods and __native_init works.
|
||||
ElBridge.init(this);
|
||||
// Run the compiled el program.
|
||||
nativeMain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented in el_android.c as Java_com_neuron_el_MainActivity_nativeMain.
|
||||
* Calls the el program's main(), which runs the full boot sequence.
|
||||
*/
|
||||
private native void nativeMain();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project("elnative")
|
||||
|
||||
#
|
||||
# CMakeLists.txt for native-hello-android
|
||||
#
|
||||
# Sources:
|
||||
# el_android.c — Android JNI widget bridge
|
||||
# el_seed.c — OS-boundary __-prefixed primitives
|
||||
# el_runtime.c — High-level el builtins (str_len, json_*, etc.)
|
||||
# el_native_vessel.c — el-native vessel (compiled from vessels/el-native/src/main.el)
|
||||
# native_hello.c — App entry point (compiled from examples/native-hello/src/main.el)
|
||||
#
|
||||
# el_runtime.c and el_seed.c both define __-prefixed symbols. On Android the
|
||||
# linker accepts duplicate weak symbols; the shared library loads one copy.
|
||||
# The EL_TARGET_ANDROID guard in el_android.c ensures only the Android widget
|
||||
# backend is compiled in.
|
||||
#
|
||||
|
||||
add_library(
|
||||
elnative
|
||||
SHARED
|
||||
|
||||
# native_hello.c listed first so its main() takes precedence over the
|
||||
# vessel's main() when --allow-multiple-definition is in effect.
|
||||
native_hello.c
|
||||
el_native_vessel.c
|
||||
el_android.c
|
||||
el_seed.c
|
||||
el_runtime.c
|
||||
)
|
||||
|
||||
# EL_TARGET_ANDROID activates the Android JNI widget backend in el_android.c
|
||||
# and the corresponding guards in el_seed.c / el_native_target.h.
|
||||
target_compile_definitions(elnative PRIVATE
|
||||
EL_TARGET_ANDROID
|
||||
)
|
||||
|
||||
target_include_directories(elnative PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# el_runtime.c and el_seed.c both define __-prefixed OS-boundary symbols.
|
||||
# el_runtime.c's copies are weaker definitions; allow-multiple-definition lets
|
||||
# the linker pick one copy silently (el_seed.c listed later = its copy wins
|
||||
# in the Android linker's right-to-left resolution order).
|
||||
target_link_options(elnative PRIVATE
|
||||
"-Wl,--allow-multiple-definition"
|
||||
)
|
||||
|
||||
find_library(log-lib log)
|
||||
find_library(android-lib android)
|
||||
|
||||
target_link_libraries(elnative
|
||||
${log-lib}
|
||||
${android-lib}
|
||||
)
|
||||
@@ -0,0 +1,964 @@
|
||||
/*
|
||||
* el_android.c — Android JNI backend for the el native widget system.
|
||||
*
|
||||
* This file implements the Android widget layer that el_seed.c calls through
|
||||
* to when EL_TARGET_ANDROID is defined. It is the exact Android counterpart
|
||||
* to el_appkit.m and presents the same C API surface.
|
||||
*
|
||||
* Architecture:
|
||||
* el program (el code)
|
||||
* → __widget_* C builtins in el_seed.c
|
||||
* → el_android_* C-callable functions declared here
|
||||
* → ElBridge static methods in Java via JNI
|
||||
* → android.view.View subclasses on the UI thread
|
||||
*
|
||||
* Widget handles: every widget (window root, view, control) is assigned an
|
||||
* int64_t slot index into view_slots[]. The el program holds these as opaque
|
||||
* Int values. Slot 0 is never valid (null handle = -1 convention).
|
||||
*
|
||||
* Threading: Android requires all UI operations to run on the main (UI) thread.
|
||||
* Every JNI call that mutates a View is dispatched through
|
||||
* Activity.runOnUiThread(Runnable) if the current thread is not the UI thread.
|
||||
* el_android_run_loop is a no-op — Android lifecycle is driven by the Activity.
|
||||
*
|
||||
* Callback mechanism: when a widget fires an event Java calls
|
||||
* nativeOnClick / nativeOnChange / nativeOnSubmit
|
||||
* The C side looks up the registered El function name for that slot, then:
|
||||
* dlsym(RTLD_DEFAULT, fn_name)(widget_handle, event_data_string)
|
||||
* This matches the __thread_create pattern in el_seed.c exactly.
|
||||
*
|
||||
* Compile / link (as part of libelruntime.so):
|
||||
* Compiled by the Android Gradle NDK build system with -DEL_TARGET_ANDROID.
|
||||
* Link flags: -landroid -llog -ldl
|
||||
*
|
||||
* Java companion: ElBridge.java in the same directory must be compiled into
|
||||
* the Android application's APK (package com.neuron.el).
|
||||
*/
|
||||
|
||||
#ifdef EL_TARGET_ANDROID
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
/* ── Logging ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_TAG "ElAndroid"
|
||||
#define EL_LOGI(...) __android_log_print(ANDROID_LOG_INFO, EL_TAG, __VA_ARGS__)
|
||||
#define EL_LOGW(...) __android_log_print(ANDROID_LOG_WARN, EL_TAG, __VA_ARGS__)
|
||||
#define EL_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, EL_TAG, __VA_ARGS__)
|
||||
|
||||
/* ── JNI global state ────────────────────────────────────────────────────── */
|
||||
|
||||
static JavaVM *g_jvm = NULL;
|
||||
static jobject g_activity = NULL; /* global ref to Activity */
|
||||
static jclass g_bridge_class = NULL; /* global ref to ElBridge class */
|
||||
|
||||
/* Cached method IDs on ElBridge — filled in el_android_init(). */
|
||||
static jmethodID g_mid_createLinearLayout = NULL;
|
||||
static jmethodID g_mid_createFrameLayout = NULL;
|
||||
static jmethodID g_mid_createScrollView = NULL;
|
||||
static jmethodID g_mid_createTextView = NULL;
|
||||
static jmethodID g_mid_createButton = NULL;
|
||||
static jmethodID g_mid_createEditText = NULL;
|
||||
static jmethodID g_mid_createImageView = NULL;
|
||||
static jmethodID g_mid_setContentView = NULL;
|
||||
static jmethodID g_mid_setTitle = NULL;
|
||||
static jmethodID g_mid_addChild = NULL;
|
||||
static jmethodID g_mid_removeChild = NULL;
|
||||
static jmethodID g_mid_destroyView = NULL;
|
||||
static jmethodID g_mid_setText = NULL;
|
||||
static jmethodID g_mid_getText = NULL;
|
||||
static jmethodID g_mid_setTextColor = NULL;
|
||||
static jmethodID g_mid_setBackgroundColor = NULL;
|
||||
static jmethodID g_mid_setFont = NULL;
|
||||
static jmethodID g_mid_setPadding = NULL;
|
||||
static jmethodID g_mid_setWidth = NULL;
|
||||
static jmethodID g_mid_setHeight = NULL;
|
||||
static jmethodID g_mid_setFlex = NULL;
|
||||
static jmethodID g_mid_setCornerRadius = NULL;
|
||||
static jmethodID g_mid_setEnabled = NULL;
|
||||
static jmethodID g_mid_setVisibility = NULL;
|
||||
static jmethodID g_mid_setOnClickListener = NULL;
|
||||
static jmethodID g_mid_setOnChangeListener = NULL;
|
||||
static jmethodID g_mid_setOnSubmitListener = NULL;
|
||||
static jmethodID g_mid_runOnUiThread = NULL; /* Activity.runOnUiThread */
|
||||
|
||||
/* ── Widget table ─────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_ANDROID_MAX_WIDGETS 4096
|
||||
|
||||
typedef enum {
|
||||
EL_WIDGET_FREE = 0,
|
||||
EL_WIDGET_WINDOW = 1,
|
||||
EL_WIDGET_VSTACK = 2,
|
||||
EL_WIDGET_HSTACK = 3,
|
||||
EL_WIDGET_ZSTACK = 4,
|
||||
EL_WIDGET_SCROLL = 5,
|
||||
EL_WIDGET_LABEL = 6,
|
||||
EL_WIDGET_BUTTON = 7,
|
||||
EL_WIDGET_TEXTFIELD = 8,
|
||||
EL_WIDGET_TEXTAREA = 9,
|
||||
EL_WIDGET_IMAGE = 10,
|
||||
} ElWidgetKind;
|
||||
|
||||
typedef struct {
|
||||
ElWidgetKind kind;
|
||||
jint slot; /* Java-side slot index (matches C index) */
|
||||
char *cb_click; /* El function name for click / submit events */
|
||||
char *cb_change; /* El function name for value-change events */
|
||||
} ElWidget;
|
||||
|
||||
static ElWidget _el_widgets[EL_ANDROID_MAX_WIDGETS];
|
||||
|
||||
static int64_t el_widget_alloc(ElWidgetKind kind, jint slot) {
|
||||
for (int i = 1; i < EL_ANDROID_MAX_WIDGETS; i++) {
|
||||
if (_el_widgets[i].kind == EL_WIDGET_FREE) {
|
||||
_el_widgets[i].kind = kind;
|
||||
_el_widgets[i].slot = slot;
|
||||
_el_widgets[i].cb_click = NULL;
|
||||
_el_widgets[i].cb_change = NULL;
|
||||
return (int64_t)i;
|
||||
}
|
||||
}
|
||||
EL_LOGE("el_widget_alloc: slot table full");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ElWidget *el_widget_get(int64_t handle) {
|
||||
if (handle <= 0 || handle >= EL_ANDROID_MAX_WIDGETS) return NULL;
|
||||
if (_el_widgets[handle].kind == EL_WIDGET_FREE) return NULL;
|
||||
return &_el_widgets[handle];
|
||||
}
|
||||
|
||||
static void el_widget_free(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->kind = EL_WIDGET_FREE;
|
||||
w->slot = -1;
|
||||
free(w->cb_click); w->cb_click = NULL;
|
||||
free(w->cb_change); w->cb_change = NULL;
|
||||
}
|
||||
|
||||
/* ── JNI environment helpers ─────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Obtain a JNIEnv for the calling thread. Attaches the thread to the JVM if
|
||||
* needed (detaches in el_jni_detach_if_attached — call in pairs).
|
||||
*/
|
||||
static int g_was_attached = 0; /* thread-local would be cleaner but this is
|
||||
safe for single-threaded el programs */
|
||||
|
||||
static JNIEnv *el_jni_env(void) {
|
||||
if (!g_jvm) return NULL;
|
||||
JNIEnv *env = NULL;
|
||||
jint rc = (*g_jvm)->GetEnv(g_jvm, (void **)&env, JNI_VERSION_1_6);
|
||||
if (rc == JNI_OK) { g_was_attached = 0; return env; }
|
||||
if (rc == JNI_EDETACHED) {
|
||||
if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) == JNI_OK) {
|
||||
g_was_attached = 1;
|
||||
return env;
|
||||
}
|
||||
}
|
||||
EL_LOGE("el_jni_env: failed to obtain JNIEnv");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void el_jni_detach_if_attached(void) {
|
||||
if (g_was_attached && g_jvm) {
|
||||
(*g_jvm)->DetachCurrentThread(g_jvm);
|
||||
g_was_attached = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── UI-thread dispatch ──────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Most ElBridge static methods already dispatch to the UI thread internally
|
||||
* (they call Activity.runOnUiThread). The helper below is available for
|
||||
* cases where the caller needs to be sure the call has completed before
|
||||
* returning (ElBridge methods marked "sync" use a CountDownLatch internally).
|
||||
*
|
||||
* For the current implementation we call ElBridge methods directly; ElBridge
|
||||
* itself marshals to the UI thread via Activity.runOnUiThread + latch.
|
||||
* This keeps the C side simple and mirrors the AppKit dispatch_sync pattern.
|
||||
*/
|
||||
|
||||
/* ── JNI_OnLoad ──────────────────────────────────────────────────────────── */
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
(void)reserved;
|
||||
g_jvm = vm;
|
||||
EL_LOGI("JNI_OnLoad: el Android bridge loaded");
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/* ── el_android_init ─────────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Called from __native_init(). The Activity must have already called
|
||||
* ElBridge.registerActivity(activity) from Java before this runs, which sets
|
||||
* g_activity via the nativeRegisterActivity JNI method below.
|
||||
*
|
||||
* Caches all method IDs used later so individual widget calls avoid repeated
|
||||
* FindClass / GetStaticMethodID lookups.
|
||||
*/
|
||||
void el_android_init(void) {
|
||||
static int done = 0;
|
||||
if (done) return;
|
||||
done = 1;
|
||||
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env) { EL_LOGE("el_android_init: no JNIEnv"); return; }
|
||||
|
||||
jclass cls = (*env)->FindClass(env, "com/neuron/el/ElBridge");
|
||||
if (!cls) { EL_LOGE("el_android_init: ElBridge class not found"); return; }
|
||||
g_bridge_class = (*env)->NewGlobalRef(env, cls);
|
||||
(*env)->DeleteLocalRef(env, cls);
|
||||
|
||||
#define CACHE_STATIC(var, name, sig) \
|
||||
var = (*env)->GetStaticMethodID(env, g_bridge_class, name, sig); \
|
||||
if (!var) EL_LOGW("el_android_init: method not found: %s %s", name, sig)
|
||||
|
||||
CACHE_STATIC(g_mid_createLinearLayout, "createLinearLayout", "(II)I");
|
||||
CACHE_STATIC(g_mid_createFrameLayout, "createFrameLayout", "()I");
|
||||
CACHE_STATIC(g_mid_createScrollView, "createScrollView", "()I");
|
||||
CACHE_STATIC(g_mid_createTextView, "createTextView", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_createButton, "createButton", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_createEditText, "createEditText", "(Ljava/lang/String;Z)I");
|
||||
CACHE_STATIC(g_mid_createImageView, "createImageView", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_setContentView, "setContentView", "(I)V");
|
||||
CACHE_STATIC(g_mid_setTitle, "setTitle", "(Ljava/lang/String;)V");
|
||||
CACHE_STATIC(g_mid_addChild, "addChild", "(II)V");
|
||||
CACHE_STATIC(g_mid_removeChild, "removeChild", "(II)V");
|
||||
CACHE_STATIC(g_mid_destroyView, "destroyView", "(I)V");
|
||||
CACHE_STATIC(g_mid_setText, "setText", "(ILjava/lang/String;)V");
|
||||
CACHE_STATIC(g_mid_getText, "getText", "(I)Ljava/lang/String;");
|
||||
CACHE_STATIC(g_mid_setTextColor, "setTextColor", "(IFFFF)V");
|
||||
CACHE_STATIC(g_mid_setBackgroundColor, "setBackgroundColor", "(IFFFF)V");
|
||||
CACHE_STATIC(g_mid_setFont, "setFont", "(ILjava/lang/String;IZ)V");
|
||||
CACHE_STATIC(g_mid_setPadding, "setPadding", "(IIIII)V");
|
||||
CACHE_STATIC(g_mid_setWidth, "setWidth", "(II)V");
|
||||
CACHE_STATIC(g_mid_setHeight, "setHeight", "(II)V");
|
||||
CACHE_STATIC(g_mid_setFlex, "setFlex", "(II)V");
|
||||
CACHE_STATIC(g_mid_setCornerRadius, "setCornerRadius", "(IF)V");
|
||||
CACHE_STATIC(g_mid_setEnabled, "setEnabled", "(IZ)V");
|
||||
CACHE_STATIC(g_mid_setVisibility, "setVisibility", "(IZ)V");
|
||||
CACHE_STATIC(g_mid_setOnClickListener, "setOnClickListener", "(I)V");
|
||||
CACHE_STATIC(g_mid_setOnChangeListener, "setOnChangeListener", "(I)V");
|
||||
CACHE_STATIC(g_mid_setOnSubmitListener, "setOnSubmitListener", "(I)V");
|
||||
#undef CACHE_STATIC
|
||||
|
||||
el_jni_detach_if_attached();
|
||||
EL_LOGI("el_android_init: complete");
|
||||
}
|
||||
|
||||
/* ── JNI: Activity registration ─────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Called from Java: ElBridge.registerActivity(activity) calls back here.
|
||||
* Stores a global reference to the Activity so C code can dispatch to it.
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeRegisterActivity(JNIEnv *env, jclass cls,
|
||||
jobject activity) {
|
||||
(void)cls;
|
||||
if (g_activity) {
|
||||
(*env)->DeleteGlobalRef(env, g_activity);
|
||||
g_activity = NULL;
|
||||
}
|
||||
if (activity) {
|
||||
g_activity = (*env)->NewGlobalRef(env, activity);
|
||||
EL_LOGI("nativeRegisterActivity: activity registered");
|
||||
}
|
||||
}
|
||||
|
||||
/* ── El callback invocation ──────────────────────────────────────────────── */
|
||||
/*
|
||||
* Invoke an El callback by symbol name.
|
||||
* Signature matches AppKit: fn(handle: Int, data: String) -> Void
|
||||
* compiled to: void fn(el_val_t handle, el_val_t data)
|
||||
*/
|
||||
typedef void (*ElCb2)(int64_t handle, int64_t data);
|
||||
|
||||
static void el_android_invoke_cb(const char *fn_name, int64_t handle,
|
||||
const char *data) {
|
||||
if (!fn_name || !*fn_name) return;
|
||||
void *sym = dlsym(RTLD_DEFAULT, fn_name);
|
||||
if (!sym) { EL_LOGW("invoke_cb: symbol not found: %s", fn_name); return; }
|
||||
ElCb2 fn = (ElCb2)sym;
|
||||
fn(handle, (int64_t)(uintptr_t)(data ? data : ""));
|
||||
}
|
||||
|
||||
/* ── JNI: callbacks from Java → C ───────────────────────────────────────── */
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnClick(JNIEnv *env, jclass cls, jint slot) {
|
||||
(void)env; (void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_click) {
|
||||
el_android_invoke_cb(w->cb_click, handle, "");
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnChange(JNIEnv *env, jclass cls,
|
||||
jint slot, jstring text) {
|
||||
(void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_change) {
|
||||
const char *ctext = text ? (*env)->GetStringUTFChars(env, text, NULL) : "";
|
||||
el_android_invoke_cb(w->cb_change, handle, ctext);
|
||||
if (text) (*env)->ReleaseStringUTFChars(env, text, ctext);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnSubmit(JNIEnv *env, jclass cls,
|
||||
jint slot, jstring text) {
|
||||
(void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_click) { /* submit stored in cb_click, same as AppKit */
|
||||
const char *ctext = text ? (*env)->GetStringUTFChars(env, text, NULL) : "";
|
||||
el_android_invoke_cb(w->cb_click, handle, ctext);
|
||||
if (text) (*env)->ReleaseStringUTFChars(env, text, ctext);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Helper: jstring from C string ──────────────────────────────────────── */
|
||||
|
||||
static jstring el_jstr(JNIEnv *env, const char *s) {
|
||||
return (*env)->NewStringUTF(env, s ? s : "");
|
||||
}
|
||||
|
||||
/* ── Window ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_android_window_create — on Android a "window" is the root LinearLayout
|
||||
* set as the Activity's content view. We create a vertical LinearLayout and
|
||||
* store it. el_android_window_show calls setContentView on the Activity.
|
||||
*/
|
||||
int64_t el_android_window_create(const char *title, int width, int height,
|
||||
int min_width, int min_height) {
|
||||
(void)width; (void)height; (void)min_width; (void)min_height;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
|
||||
/* VERTICAL LinearLayout with no spacing (spacing added via margins in Java) */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)1 /* VERTICAL */, (jint)0);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1;
|
||||
}
|
||||
|
||||
/* Set activity title */
|
||||
if (g_mid_setTitle && title) {
|
||||
jstring jtitle = el_jstr(env, title);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTitle, jtitle);
|
||||
(*env)->DeleteLocalRef(env, jtitle);
|
||||
}
|
||||
|
||||
int64_t handle = el_widget_alloc(EL_WIDGET_WINDOW, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return handle;
|
||||
}
|
||||
|
||||
void el_android_window_show(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w || w->kind != EL_WIDGET_WINDOW) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setContentView,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_window_set_title(int64_t handle, const char *title) {
|
||||
(void)handle;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jtitle = el_jstr(env, title);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTitle, jtitle);
|
||||
(*env)->DeleteLocalRef(env, jtitle);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Layout containers ───────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_android_vstack_create(int spacing) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)1 /* VERTICAL */, (jint)spacing);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_VSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_hstack_create(int spacing) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)0 /* HORIZONTAL */, (jint)spacing);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_HSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_zstack_create(void) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createFrameLayout);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_ZSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_scroll_create(void) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createScrollView);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_SCROLL, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
/* ── Widget factories ─────────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_android_label_create(const char *text) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jt = el_jstr(env, text);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createTextView, jt);
|
||||
(*env)->DeleteLocalRef(env, jt);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_LABEL, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_button_create(const char *label) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jl = el_jstr(env, label);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createButton, jl);
|
||||
(*env)->DeleteLocalRef(env, jl);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_BUTTON, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_text_field_create(const char *placeholder) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, placeholder);
|
||||
/* singleLine = true */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createEditText, jp, (jboolean)JNI_TRUE);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_TEXTFIELD, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_text_area_create(const char *placeholder) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, placeholder);
|
||||
/* singleLine = false → multiline EditText */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createEditText, jp, (jboolean)JNI_FALSE);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_TEXTAREA, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_image_create(const char *path) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, path);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createImageView, jp);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_IMAGE, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
/* ── Widget property setters ─────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_set_text(int64_t handle, const char *text) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jt = el_jstr(env, text);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setText,
|
||||
(jint)w->slot, jt);
|
||||
(*env)->DeleteLocalRef(env, jt);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
const char *el_android_widget_get_text(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return "";
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return "";
|
||||
jstring js = (jstring)(*env)->CallStaticObjectMethod(env, g_bridge_class,
|
||||
g_mid_getText,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return ""; }
|
||||
const char *result = "";
|
||||
if (js) {
|
||||
const char *cstr = (*env)->GetStringUTFChars(env, js, NULL);
|
||||
result = cstr ? strdup(cstr) : "";
|
||||
if (cstr) (*env)->ReleaseStringUTFChars(env, js, cstr);
|
||||
(*env)->DeleteLocalRef(env, js);
|
||||
}
|
||||
el_jni_detach_if_attached();
|
||||
return result;
|
||||
}
|
||||
|
||||
void el_android_widget_set_color(int64_t handle, float r, float g, float b, float a) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTextColor,
|
||||
(jint)w->slot, (jfloat)r, (jfloat)g,
|
||||
(jfloat)b, (jfloat)a);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_bg_color(int64_t handle, float r, float g, float b, float a) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setBackgroundColor,
|
||||
(jint)w->slot, (jfloat)r, (jfloat)g,
|
||||
(jfloat)b, (jfloat)a);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_font(int64_t handle, const char *family, int size, int bold) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jfam = el_jstr(env, family);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setFont,
|
||||
(jint)w->slot, jfam, (jint)size,
|
||||
(jboolean)(bold ? JNI_TRUE : JNI_FALSE));
|
||||
(*env)->DeleteLocalRef(env, jfam);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_padding(int64_t handle, int top, int right, int bottom, int left) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setPadding,
|
||||
(jint)w->slot, (jint)top, (jint)right,
|
||||
(jint)bottom, (jint)left);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_width(int64_t handle, int width) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setWidth,
|
||||
(jint)w->slot, (jint)width);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_height(int64_t handle, int height) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setHeight,
|
||||
(jint)w->slot, (jint)height);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_flex(int64_t handle, int flex) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setFlex,
|
||||
(jint)w->slot, (jint)flex);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_corner_radius(int64_t handle, int radius) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setCornerRadius,
|
||||
(jint)w->slot, (jfloat)radius);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_disabled(int64_t handle, int disabled) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setEnabled,
|
||||
(jint)w->slot,
|
||||
(jboolean)(disabled ? JNI_FALSE : JNI_TRUE));
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_hidden(int64_t handle, int hidden) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
/* visible=true means NOT hidden */
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setVisibility,
|
||||
(jint)w->slot,
|
||||
(jboolean)(hidden ? JNI_FALSE : JNI_TRUE));
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Child management ─────────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_add_child(int64_t parent, int64_t child) {
|
||||
ElWidget *pw = el_widget_get(parent);
|
||||
ElWidget *cw = el_widget_get(child);
|
||||
if (!pw || !cw) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_addChild,
|
||||
(jint)pw->slot, (jint)cw->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_remove_child(int64_t parent, int64_t child) {
|
||||
ElWidget *pw = el_widget_get(parent);
|
||||
ElWidget *cw = el_widget_get(child);
|
||||
if (!pw || !cw) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_removeChild,
|
||||
(jint)pw->slot, (jint)cw->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Event registration ───────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_on_click(int64_t handle, const char *fn_name) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_click);
|
||||
w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
if (!w->cb_click) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnClickListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_on_change(int64_t handle, const char *fn_name) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_change);
|
||||
w->cb_change = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
if (!w->cb_change) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnChangeListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_on_submit(int64_t handle, const char *fn_name) {
|
||||
/* Submit stored in cb_click, same as AppKit. */
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_click);
|
||||
w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
if (!w->cb_click) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnSubmitListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Widget destroy ───────────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_destroy(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (env && g_bridge_class) {
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_destroyView,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
}
|
||||
el_widget_free(handle);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Manifest reader ─────────────────────────────────────────────────────── */
|
||||
/*
|
||||
* __manifest_read: parse the app{} block from a manifest file.
|
||||
* Returns the raw file contents as an el_val_t (const char* cast).
|
||||
* The caller (el program) parses the returned string.
|
||||
* Reads from the filesystem; for APK assets use the AssetManager path instead.
|
||||
*/
|
||||
static char *el_read_file(const char *path) {
|
||||
if (!path || !*path) return NULL;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (len <= 0) { fclose(f); return NULL; }
|
||||
char *buf = (char *)malloc((size_t)len + 1);
|
||||
if (!buf) { fclose(f); return NULL; }
|
||||
fread(buf, 1, (size_t)len, f);
|
||||
buf[len] = '\0';
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
el_val_t el_android_manifest_read(const char *path) {
|
||||
char *contents = el_read_file(path);
|
||||
if (!contents) return (el_val_t)(uintptr_t)"";
|
||||
return (el_val_t)(uintptr_t)contents; /* caller owns allocation */
|
||||
}
|
||||
|
||||
/* ── __widget_* C API (called from el_seed.c) ────────────────────────────── */
|
||||
/*
|
||||
* These are the functions declared in el_native_target.h under EL_TARGET_ANDROID.
|
||||
* They forward to the el_android_* internal functions above.
|
||||
*
|
||||
* The el_val_t / int64_t ABI matches the AppKit functions exactly:
|
||||
* - Integer params passed as int64_t, extracted with (int)
|
||||
* - String params passed as int64_t, extracted with (const char*)(uintptr_t)
|
||||
* - Float params (r,g,b,a) passed as int64_t bit-cast from double; extracted
|
||||
* with el_to_float / bit-cast union
|
||||
*/
|
||||
|
||||
static inline float el_val_to_float(el_val_t v) {
|
||||
union { double d; int64_t i; } u;
|
||||
u.i = v;
|
||||
return (float)u.d;
|
||||
}
|
||||
|
||||
void __native_init(void) {
|
||||
el_android_init();
|
||||
}
|
||||
|
||||
void __native_run_loop(void) {
|
||||
/* No-op on Android — lifecycle is driven by the Activity. */
|
||||
}
|
||||
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height) {
|
||||
return (el_val_t)el_android_window_create(
|
||||
(const char *)(uintptr_t)title,
|
||||
(int)width, (int)height, (int)min_width, (int)min_height);
|
||||
}
|
||||
|
||||
void __window_show(el_val_t handle) {
|
||||
el_android_window_show((int64_t)handle);
|
||||
}
|
||||
|
||||
void __window_set_title(el_val_t handle, el_val_t title) {
|
||||
el_android_window_set_title((int64_t)handle,
|
||||
(const char *)(uintptr_t)title);
|
||||
}
|
||||
|
||||
el_val_t __vstack_create(el_val_t spacing) {
|
||||
return (el_val_t)el_android_vstack_create((int)spacing);
|
||||
}
|
||||
|
||||
el_val_t __hstack_create(el_val_t spacing) {
|
||||
return (el_val_t)el_android_hstack_create((int)spacing);
|
||||
}
|
||||
|
||||
el_val_t __zstack_create(void) {
|
||||
return (el_val_t)el_android_zstack_create();
|
||||
}
|
||||
|
||||
el_val_t __scroll_create(void) {
|
||||
return (el_val_t)el_android_scroll_create();
|
||||
}
|
||||
|
||||
el_val_t __label_create(el_val_t text) {
|
||||
return (el_val_t)el_android_label_create((const char *)(uintptr_t)text);
|
||||
}
|
||||
|
||||
el_val_t __button_create(el_val_t label) {
|
||||
return (el_val_t)el_android_button_create((const char *)(uintptr_t)label);
|
||||
}
|
||||
|
||||
el_val_t __text_field_create(el_val_t placeholder) {
|
||||
return (el_val_t)el_android_text_field_create((const char *)(uintptr_t)placeholder);
|
||||
}
|
||||
|
||||
el_val_t __text_area_create(el_val_t placeholder) {
|
||||
return (el_val_t)el_android_text_area_create((const char *)(uintptr_t)placeholder);
|
||||
}
|
||||
|
||||
el_val_t __image_create(el_val_t path_or_name) {
|
||||
return (el_val_t)el_android_image_create((const char *)(uintptr_t)path_or_name);
|
||||
}
|
||||
|
||||
void __widget_set_text(el_val_t handle, el_val_t text) {
|
||||
el_android_widget_set_text((int64_t)handle,
|
||||
(const char *)(uintptr_t)text);
|
||||
}
|
||||
|
||||
el_val_t __widget_get_text(el_val_t handle) {
|
||||
return (el_val_t)(uintptr_t)el_android_widget_get_text((int64_t)handle);
|
||||
}
|
||||
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a) {
|
||||
el_android_widget_set_color((int64_t)handle,
|
||||
el_val_to_float(r), el_val_to_float(g),
|
||||
el_val_to_float(b), el_val_to_float(a));
|
||||
}
|
||||
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a) {
|
||||
el_android_widget_set_bg_color((int64_t)handle,
|
||||
el_val_to_float(r), el_val_to_float(g),
|
||||
el_val_to_float(b), el_val_to_float(a));
|
||||
}
|
||||
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold) {
|
||||
el_android_widget_set_font((int64_t)handle,
|
||||
(const char *)(uintptr_t)family,
|
||||
(int)size, (int)bold);
|
||||
}
|
||||
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left) {
|
||||
el_android_widget_set_padding((int64_t)handle,
|
||||
(int)top, (int)right, (int)bottom, (int)left);
|
||||
}
|
||||
|
||||
void __widget_set_width(el_val_t handle, el_val_t width) {
|
||||
el_android_widget_set_width((int64_t)handle, (int)width);
|
||||
}
|
||||
|
||||
void __widget_set_height(el_val_t handle, el_val_t height) {
|
||||
el_android_widget_set_height((int64_t)handle, (int)height);
|
||||
}
|
||||
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
el_android_widget_set_flex((int64_t)handle, (int)flex);
|
||||
}
|
||||
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
el_android_widget_set_corner_radius((int64_t)handle, (int)radius);
|
||||
}
|
||||
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_android_widget_set_disabled((int64_t)handle, (int)disabled);
|
||||
}
|
||||
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_android_widget_set_hidden((int64_t)handle, (int)hidden);
|
||||
}
|
||||
|
||||
void __widget_add_child(el_val_t parent, el_val_t child) {
|
||||
el_android_widget_add_child((int64_t)parent, (int64_t)child);
|
||||
}
|
||||
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
el_android_widget_remove_child((int64_t)parent, (int64_t)child);
|
||||
}
|
||||
|
||||
void __widget_destroy(el_val_t handle) {
|
||||
el_android_widget_destroy((int64_t)handle);
|
||||
}
|
||||
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_click((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_change((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_submit((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
el_val_t __manifest_read(el_val_t path) {
|
||||
return el_android_manifest_read((const char *)(uintptr_t)path);
|
||||
}
|
||||
|
||||
/* ── MainActivity JNI entry point ─────────────────────────────────────────── */
|
||||
/*
|
||||
* Java_com_neuron_el_MainActivity_nativeMain — invoked from MainActivity.onCreate
|
||||
* after ElBridge.init(this). Calls the el program's compiled main() which runs
|
||||
* the boot sequence: native_init → window_from_manifest → app_build → window_show.
|
||||
* __native_run_loop is a no-op on Android; the Activity lifecycle drives the UI.
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_MainActivity_nativeMain(JNIEnv *env, jobject obj) {
|
||||
(void)env; (void)obj;
|
||||
extern int main(int argc, char **argv);
|
||||
char *argv[] = {"el-app", NULL};
|
||||
main(1, argv);
|
||||
}
|
||||
|
||||
#endif /* EL_TARGET_ANDROID */
|
||||
@@ -0,0 +1,573 @@
|
||||
/*
|
||||
* el_native_target.h — Native widget declarations for el programs targeting
|
||||
* native desktop UI (AppKit / GTK4 / Win32).
|
||||
*
|
||||
* This header is designed to be included AFTER el_runtime.h without conflict:
|
||||
* - It does NOT redefine el_to_float, el_from_float, or any el_runtime.h
|
||||
* static inlines.
|
||||
* - It does NOT redeclare __println, __print, or other functions whose
|
||||
* return types differ between el_seed.h and el_runtime.h.
|
||||
* - It adds: native widget builtins + float arithmetic helpers that the
|
||||
* current el_runtime.h omits but elc still emits calls to.
|
||||
*
|
||||
* Usage:
|
||||
* Inject via -include at compile time, OR #include it after el_runtime.h.
|
||||
*
|
||||
* clang -DEL_TARGET_MACOS -include el_native_target.h -c my_app.c ...
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* el_val_t must already be defined by el_runtime.h or el_seed.h. */
|
||||
#ifndef EL_VAL_T_DEFINED
|
||||
typedef int64_t el_val_t;
|
||||
#endif
|
||||
|
||||
/* ── Float arithmetic helpers ───────────────────────────────────────────────
|
||||
* elc emits calls to float_div / float_mul etc. for Float-typed expressions.
|
||||
* These were in el_runtime.c through v1.0.0-20260501 but are missing from the
|
||||
* current el_runtime.h. Redeclared here as static inline to avoid link deps.
|
||||
* Only defined if not already declared (old runtimes that still have them). */
|
||||
|
||||
#ifndef EL_FLOAT_OPS_DEFINED
|
||||
#define EL_FLOAT_OPS_DEFINED
|
||||
|
||||
/* el_to_float / el_from_float — bit-cast between el_val_t and double.
|
||||
* Defined as static inline in both el_runtime.h and el_seed.h; we do NOT
|
||||
* redefine them here. We rely on one of those headers being included first. */
|
||||
|
||||
static inline el_val_t float_div(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b;
|
||||
ur.d = (ub.d != 0.0) ? (ua.d / ub.d) : 0.0;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_mul(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d * ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_add(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d + ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_sub(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d - ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_lt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d < ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d > ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_lte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d <= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d >= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_eq(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d == ub.d);
|
||||
}
|
||||
|
||||
#endif /* EL_FLOAT_OPS_DEFINED */
|
||||
|
||||
/* ── Native widget system (macOS AppKit) ────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_MACOS and linked with el_appkit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid. */
|
||||
|
||||
#ifdef EL_TARGET_MACOS
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_MACOS */
|
||||
|
||||
/* ── Native widget system (Linux GTK4) ──────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_LINUX and linked with el_gtk4.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* All functions have the same signatures as EL_TARGET_MACOS above. */
|
||||
|
||||
#ifdef EL_TARGET_LINUX
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as EL_TARGET_MACOS */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LINUX */
|
||||
|
||||
/* ── Native widget system (Windows Win32) ───────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_WIN32 and linked with el_win32.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* Link: el_win32.obj comctl32.lib user32.lib gdi32.lib */
|
||||
|
||||
#ifdef EL_TARGET_WIN32
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_WIN32 */
|
||||
|
||||
/* ── Native widget system (iOS UIKit) ───────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_IOS and linked with el_uikit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* iOS lifecycle note: UIApplicationMain never returns. The el program must
|
||||
* store its UI-build logic in a void(*)(void) function pointer, assign it to
|
||||
* el_main_entry_fn, then call __native_run_loop. ElAppDelegate invokes
|
||||
* el_main_entry_fn inside didFinishLaunchingWithOptions.
|
||||
* Call el_uikit_set_args(argc, argv) from main() before __native_run_loop. */
|
||||
|
||||
#ifdef EL_TARGET_IOS
|
||||
|
||||
/* Lifecycle entry-function hook — set before calling __native_run_loop. */
|
||||
extern void (*el_main_entry_fn)(void);
|
||||
|
||||
/* Forward argc/argv from main() to UIApplicationMain. */
|
||||
void el_uikit_set_args(int argc, char** argv);
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_IOS */
|
||||
|
||||
/* ── Native widget system (Android JNI) ─────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_ANDROID and linked with
|
||||
* libelruntime.so (which includes el_android.c compiled by the NDK build).
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Java companion: ElBridge.java (package com.neuron.el) must be compiled into
|
||||
* the APK. The Activity must call ElBridge.init(this) before any widget ops.
|
||||
*
|
||||
* Link flags (in Android.mk or CMakeLists.txt):
|
||||
* -landroid -llog -ldl */
|
||||
|
||||
#ifdef EL_TARGET_ANDROID
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void); /* no-op on Android */
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_ANDROID */
|
||||
|
||||
/* ── Native widget system (LVGL v9 — embedded / microcontroller) ─────────────
|
||||
* Available when compiled with -DEL_TARGET_LVGL and linked with el_lvgl.c
|
||||
* and the LVGL library (lvgl.a or lvgl source tree).
|
||||
*
|
||||
* Target platforms: ESP32, STM32, industrial panels. Any system with 256KB+
|
||||
* RAM and an LVGL-compatible display driver. No OS required.
|
||||
*
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Bare-metal / no dynamic linker:
|
||||
* Compile with -DEL_LVGL_NO_DLSYM and provide:
|
||||
* el_val_t el_lvgl_dispatch(const char *fn, el_val_t a, el_val_t b);
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_LVGL -I./lvgl el_lvgl.c -c -o el_lvgl.o
|
||||
* # Then link with lvgl.a. */
|
||||
|
||||
#ifdef EL_TARGET_LVGL
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as all other native targets */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LVGL */
|
||||
|
||||
/* ── Native widget system (SDL2 — embedded / Pi) ────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_SDL2 and linked with el_sdl2.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Target: Raspberry Pi Zero, embedded Linux, any system with a framebuffer
|
||||
* and SDL2 available. No GTK, no desktop environment required.
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_SDL2 $(sdl2-config --cflags) -c el_sdl2.c -o el_sdl2.o
|
||||
* Link:
|
||||
* $(sdl2-config --libs) -lSDL2_ttf -lSDL2_image -ldl */
|
||||
|
||||
#ifdef EL_TARGET_SDL2
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_SDL2 */
|
||||
@@ -0,0 +1,459 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
el_val_t TOKEN_PRIMARY;
|
||||
el_val_t TOKEN_ON_PRIMARY;
|
||||
el_val_t TOKEN_BACKGROUND;
|
||||
el_val_t TOKEN_ON_BG;
|
||||
el_val_t TOKEN_SURFACE;
|
||||
el_val_t TOKEN_ON_SURFACE;
|
||||
el_val_t TOKEN_OUTLINE;
|
||||
el_val_t TOKEN_ERROR;
|
||||
|
||||
el_val_t native_init(void) {
|
||||
__native_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t native_run_loop(void) {
|
||||
__native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_read(el_val_t path) {
|
||||
return __manifest_read(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_title(el_val_t m) {
|
||||
return json_get_string(m, EL_STR("title"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height) {
|
||||
return __window_create(title, width, height, min_width, min_height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_from_manifest(el_val_t manifest_path) {
|
||||
el_val_t m = manifest_read(manifest_path);
|
||||
el_val_t title = manifest_title(m);
|
||||
el_val_t w = manifest_width(m);
|
||||
el_val_t h = manifest_height(m);
|
||||
el_val_t mw = manifest_min_width(m);
|
||||
el_val_t mh = manifest_min_height(m);
|
||||
el_val_t safe_w = ({ el_val_t _if_result_1 = 0; if ((w > 0)) { _if_result_1 = (w); } else { _if_result_1 = (1200); } _if_result_1; });
|
||||
el_val_t safe_h = ({ el_val_t _if_result_2 = 0; if ((h > 0)) { _if_result_2 = (h); } else { _if_result_2 = (800); } _if_result_2; });
|
||||
el_val_t safe_mw = ({ el_val_t _if_result_3 = 0; if ((mw > 0)) { _if_result_3 = (mw); } else { _if_result_3 = (600); } _if_result_3; });
|
||||
el_val_t safe_mh = ({ el_val_t _if_result_4 = 0; if ((mh > 0)) { _if_result_4 = (mh); } else { _if_result_4 = (400); } _if_result_4; });
|
||||
el_val_t safe_t = ({ el_val_t _if_result_5 = 0; if ((str_len(title) > 0)) { _if_result_5 = (title); } else { _if_result_5 = (EL_STR("App")); } _if_result_5; });
|
||||
return window_create(safe_t, safe_w, safe_h, safe_mw, safe_mh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_show(el_val_t handle) {
|
||||
__window_show(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title) {
|
||||
__window_set_title(handle, title);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack(el_val_t spacing) {
|
||||
return __vstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack_tight(void) {
|
||||
return __vstack_create(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hstack(el_val_t spacing) {
|
||||
return __hstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t zstack(void) {
|
||||
return __zstack_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t scroll(void) {
|
||||
return __scroll_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t label(el_val_t text) {
|
||||
return __label_create(text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t button(el_val_t label) {
|
||||
return __button_create(label);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_field(el_val_t placeholder) {
|
||||
return __text_field_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_area(el_val_t placeholder) {
|
||||
return __text_area_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t image(el_val_t path_or_name) {
|
||||
return __image_create(path_or_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text) {
|
||||
__widget_set_text(handle, text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_get_text(el_val_t handle) {
|
||||
return __widget_get_text(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_bg_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold) {
|
||||
el_val_t bold_int = ({ el_val_t _if_result_6 = 0; if (bold) { _if_result_6 = (1); } else { _if_result_6 = (0); } _if_result_6; });
|
||||
__widget_set_font(handle, family, size, bold_int);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left) {
|
||||
__widget_set_padding(handle, top, right, bottom, left);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p) {
|
||||
__widget_set_padding(handle, p, p, p, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py) {
|
||||
__widget_set_padding(handle, py, px, py, px);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width) {
|
||||
__widget_set_width(handle, width);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height) {
|
||||
__widget_set_height(handle, height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
__widget_set_flex(handle, flex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
__widget_set_corner_radius(handle, radius);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_val_t d = ({ el_val_t _if_result_7 = 0; if (disabled) { _if_result_7 = (1); } else { _if_result_7 = (0); } _if_result_7; });
|
||||
__widget_set_disabled(handle, d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_val_t h = ({ el_val_t _if_result_8 = 0; if (hidden) { _if_result_8 = (1); } else { _if_result_8 = (0); } _if_result_8; });
|
||||
__widget_set_hidden(handle, h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child) {
|
||||
__widget_add_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
__widget_remove_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_destroy(el_val_t handle) {
|
||||
__widget_destroy(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_click(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_change(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_submit(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset) {
|
||||
el_val_t hi = str_slice(s, offset, (offset + 1));
|
||||
el_val_t lo = str_slice(s, (offset + 1), (offset + 2));
|
||||
el_val_t h = hex_nibble(hi);
|
||||
el_val_t l = hex_nibble(lo);
|
||||
return ((h * 16) + l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_nibble(el_val_t c) {
|
||||
if (str_eq(c, EL_STR("0"))) {
|
||||
return 0;
|
||||
}
|
||||
if (str_eq(c, EL_STR("1"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(c, EL_STR("2"))) {
|
||||
return 2;
|
||||
}
|
||||
if (str_eq(c, EL_STR("3"))) {
|
||||
return 3;
|
||||
}
|
||||
if (str_eq(c, EL_STR("4"))) {
|
||||
return 4;
|
||||
}
|
||||
if (str_eq(c, EL_STR("5"))) {
|
||||
return 5;
|
||||
}
|
||||
if (str_eq(c, EL_STR("6"))) {
|
||||
return 6;
|
||||
}
|
||||
if (str_eq(c, EL_STR("7"))) {
|
||||
return 7;
|
||||
}
|
||||
if (str_eq(c, EL_STR("8"))) {
|
||||
return 8;
|
||||
}
|
||||
if (str_eq(c, EL_STR("9"))) {
|
||||
return 9;
|
||||
}
|
||||
if (str_eq(c, EL_STR("a"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("b"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("c"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("d"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("e"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("f"))) {
|
||||
return 15;
|
||||
}
|
||||
if (str_eq(c, EL_STR("A"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("B"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("C"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("D"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("E"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("F"))) {
|
||||
return 15;
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_r(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_9 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_9 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_9 = (hex); } _if_result_9; });
|
||||
el_val_t v = hex_channel(s, 0);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_g(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_10 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_10 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_10 = (hex); } _if_result_10; });
|
||||
el_val_t v = hex_channel(s, 2);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_b(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_11 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_11 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_11 = (hex); } _if_result_11; });
|
||||
el_val_t v = hex_channel(s, 4);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_a(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_12 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_12 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_12 = (hex); } _if_result_12; });
|
||||
if (str_len(s) < 8) {
|
||||
return el_from_float(1.0);
|
||||
}
|
||||
el_val_t v = hex_channel(s, 6);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_bg_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_surface(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_SURFACE);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_button_primary(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_PRIMARY);
|
||||
widget_set_color_hex(handle, TOKEN_ON_PRIMARY);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
widget_set_padding_xy(handle, 16, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_body(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 14, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_heading(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 20, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_muted(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_OUTLINE);
|
||||
widget_set_font(handle, EL_STR("system"), 12, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int el_vessel_main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
TOKEN_PRIMARY = EL_STR("#60a5fa");
|
||||
TOKEN_ON_PRIMARY = EL_STR("#0f172a");
|
||||
TOKEN_BACKGROUND = EL_STR("#0f172a");
|
||||
TOKEN_ON_BG = EL_STR("#f8fafc");
|
||||
TOKEN_SURFACE = EL_STR("#1e293b");
|
||||
TOKEN_ON_SURFACE = EL_STR("#f8fafc");
|
||||
TOKEN_OUTLINE = EL_STR("#475569");
|
||||
TOKEN_ERROR = EL_STR("#f87171");
|
||||
return 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,883 @@
|
||||
/*
|
||||
* el_runtime.h — El language C runtime header
|
||||
*
|
||||
* Declares all built-in functions available to compiled El programs.
|
||||
* Include this in every generated .c file.
|
||||
*
|
||||
* Value model:
|
||||
* All El values are represented as el_val_t (= int64_t).
|
||||
* On 64-bit systems a pointer fits in int64_t.
|
||||
* String values are cast: (el_val_t)(uintptr_t)"hello"
|
||||
* Integer values are stored directly.
|
||||
* This lets arithmetic work naturally while still passing strings around.
|
||||
*
|
||||
* Type conventions (El -> C):
|
||||
* String -> el_val_t (holds const char* via uintptr_t cast)
|
||||
* Int -> el_val_t
|
||||
* Bool -> el_val_t (0 = false, nonzero = true)
|
||||
* Any -> el_val_t
|
||||
* Void -> void
|
||||
*
|
||||
* Macros for convenience:
|
||||
* EL_STR(s) cast string literal to el_val_t
|
||||
* EL_CSTR(v) cast el_val_t back to const char*
|
||||
* EL_INT(v) identity — el_val_t is already int64_t
|
||||
* EL_NULL null / zero value
|
||||
* EL_FALSE boolean false (0)
|
||||
* EL_TRUE boolean true (1)
|
||||
*
|
||||
* Link requirements:
|
||||
* -lcurl — required for the HTTP client (http_get, http_post, llm_*).
|
||||
* -lpthread — required for the HTTP server (one detached thread per
|
||||
* connection, capped at 64 concurrent).
|
||||
* -loqs — optional; required only when liboqs is installed and the
|
||||
* pq_* / sha3_256_hex entry points are needed. Detected at
|
||||
* compile time via __has_include(<oqs/oqs.h>).
|
||||
* -lcrypto — optional; pulled in alongside -loqs. Used for X25519 in
|
||||
* pq_hybrid_* and HKDF-SHA256 derivation.
|
||||
*
|
||||
* Canonical compile command:
|
||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
||||
*
|
||||
* With liboqs (post-quantum stack):
|
||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread -loqs -lcrypto \
|
||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
||||
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
||||
#define EL_INT(v) (v)
|
||||
#define EL_NULL ((el_val_t)0)
|
||||
#define EL_FALSE ((el_val_t)0)
|
||||
#define EL_TRUE ((el_val_t)1)
|
||||
|
||||
/* Float values share the el_val_t (int64) slot via a bit-cast.
|
||||
* The codegen emits Float literals as `el_from_float(<dbl>)` so the
|
||||
* underlying bits represent the IEEE 754 double. Float-aware builtins
|
||||
* (math, format, json) round-trip via these helpers. */
|
||||
static inline double el_to_float(el_val_t v) {
|
||||
union { int64_t i; double f; } u;
|
||||
u.i = (int64_t)v;
|
||||
return u.f;
|
||||
}
|
||||
|
||||
static inline el_val_t el_from_float(double f) {
|
||||
union { double f; int64_t i; } u;
|
||||
u.f = f;
|
||||
return (el_val_t)u.i;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ── I/O ──────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t println(el_val_t s);
|
||||
el_val_t print(el_val_t s);
|
||||
el_val_t readline(void);
|
||||
|
||||
/* ── String builtins ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t str_eq(el_val_t a, el_val_t b);
|
||||
el_val_t str_starts_with(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_ends_with(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_len(el_val_t s);
|
||||
el_val_t str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t int_to_str(el_val_t n);
|
||||
el_val_t str_to_int(el_val_t s);
|
||||
el_val_t native_str_to_int(el_val_t s);
|
||||
el_val_t str_slice(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t str_contains(el_val_t s, el_val_t sub);
|
||||
el_val_t str_replace(el_val_t s, el_val_t from, el_val_t to);
|
||||
el_val_t str_to_upper(el_val_t s);
|
||||
el_val_t str_to_lower(el_val_t s);
|
||||
el_val_t str_trim(el_val_t s);
|
||||
|
||||
/* ── Math ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_abs(el_val_t n);
|
||||
el_val_t el_max(el_val_t a, el_val_t b);
|
||||
el_val_t el_min(el_val_t a, el_val_t b);
|
||||
|
||||
/* ── Refcount (ARC) ──────────────────────────────────────────────────────────
|
||||
* Lists and Maps carry a refcount. Strings and ints do not — el_retain and
|
||||
* el_release are safe no-ops on non-refcounted values (they sniff a magic
|
||||
* header at offset 0 and only act if the magic matches).
|
||||
*
|
||||
* Codegen emits these at let-binding shadowing, function entry (params), and
|
||||
* function exit (locals other than the returned value). The refcount lets
|
||||
* el_list_append and el_map_set mutate in place when uniquely owned (cheap)
|
||||
* and copy-on-write when shared (preserves persistent semantics across
|
||||
* accumulator patterns in the compiler itself). */
|
||||
|
||||
void el_retain(el_val_t v);
|
||||
void el_release(el_val_t v);
|
||||
|
||||
/* ── Scoped arena (CLI use) ───────────────────────────────────────────────── */
|
||||
el_val_t el_arena_push(void);
|
||||
el_val_t el_arena_pop(el_val_t mark);
|
||||
|
||||
/* ── List ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_list_new(el_val_t count, ...);
|
||||
el_val_t el_list_len(el_val_t list);
|
||||
el_val_t el_list_get(el_val_t list, el_val_t index);
|
||||
el_val_t el_list_append(el_val_t list, el_val_t elem);
|
||||
el_val_t el_list_empty(void);
|
||||
el_val_t el_list_clone(el_val_t list);
|
||||
|
||||
/* ── Map ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_map_new(el_val_t pair_count, ...);
|
||||
el_val_t el_get_field(el_val_t map, el_val_t key);
|
||||
el_val_t el_map_get(el_val_t map, el_val_t key);
|
||||
el_val_t el_map_set(el_val_t map, el_val_t key, el_val_t value);
|
||||
|
||||
/* ── HTTP ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t http_get(el_val_t url);
|
||||
el_val_t http_post(el_val_t url, el_val_t body);
|
||||
el_val_t http_post_json(el_val_t url, el_val_t json_body);
|
||||
el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map);
|
||||
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
|
||||
el_val_t http_post_json_with_headers(el_val_t url, el_val_t headers_map, el_val_t json_body);
|
||||
el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
|
||||
el_val_t http_delete(el_val_t url);
|
||||
el_val_t http_serve(el_val_t port, el_val_t handler);
|
||||
el_val_t http_set_handler(el_val_t name);
|
||||
|
||||
/* HTTP server v2 ─────────────────────────────────────────────────────────────
|
||||
* Same dispatch model as http_serve, but the handler signature is widened:
|
||||
*
|
||||
* el_val_t handler(method, path, headers_map, body)
|
||||
*
|
||||
* `headers_map` is an ElMap from lowercased header name → header value (both
|
||||
* Strings). Repeated headers are joined with ", " per RFC 7230.
|
||||
*
|
||||
* Response value: the handler may return either
|
||||
* (a) a plain body string — same auto-content-type / 200-OK behaviour as
|
||||
* http_serve (3-arg) — or
|
||||
* (b) a response envelope built with `http_response(status, headers_json,
|
||||
* body)`. The runtime detects the envelope discriminator
|
||||
* `"el_http_response":1` at the start of the returned string and
|
||||
* unpacks status / headers / body before sending.
|
||||
*
|
||||
* The 3-arg http_serve(port, handler) remains supported unchanged for
|
||||
* existing handlers (e.g. products/web/server.el): it dispatches with
|
||||
* (method, path, body), hardcodes 200 OK, and auto-detects content type. */
|
||||
el_val_t http_serve_v2(el_val_t port, el_val_t handler);
|
||||
el_val_t http_set_handler_v2(el_val_t name);
|
||||
|
||||
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
||||
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
||||
* returned string carries the discriminator `{"el_http_response":1,...}`
|
||||
* which the runtime's send-path detects and unpacks. Detection happens
|
||||
* uniformly inside http_send_response, so a 3-arg handler may also return
|
||||
* an envelope. The 3-arg variant remains documented as a fixed 200-OK
|
||||
* auto-content-type contract for legacy handlers that return plain bodies. */
|
||||
el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
|
||||
/* SSE connection fd — set by http_worker_v2 before calling the El handler,
|
||||
* cleared afterwards. Defined in el_seed.c; called from el_runtime.c.
|
||||
* The getter is exposed as __http_conn_fd() to El programs. */
|
||||
void el_seed_set_http_conn_fd(int fd);
|
||||
|
||||
/* HTTP timeout — every libcurl request honors EL_HTTP_TIMEOUT_MS (default
|
||||
* 60000ms). Read lazily on first use, so setting the env var any time before
|
||||
* the first http_* call is sufficient. */
|
||||
|
||||
/* Streaming variants — write the response body straight to a file via
|
||||
* libcurl's CURLOPT_WRITEFUNCTION = fwrite. These bypass the el_val_t string
|
||||
* wrapper entirely, so binary payloads (audio/mpeg, image/png, etc.) survive
|
||||
* embedded NUL bytes that would truncate a strlen()-based code path.
|
||||
*
|
||||
* Both honor EL_HTTP_TIMEOUT_MS, follow redirects, and accept the same
|
||||
* `headers_map` shape as http_post_with_headers (ElMap of String→String).
|
||||
*
|
||||
* Return value: 1 on success (file fully written), 0 on any failure
|
||||
* (network, file open, partial write). On failure the output file is removed
|
||||
* so callers cannot mistake a partially-written file for a valid one. */
|
||||
el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el_val_t output_path);
|
||||
el_val_t http_get_to_file(el_val_t url, el_val_t headers_map, el_val_t output_path);
|
||||
|
||||
/* ── URL encoding ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t url_encode(el_val_t s); /* RFC 3986 unreserved set */
|
||||
el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */
|
||||
|
||||
/* ── HTML allowlist sanitizer ────────────────────────────────────────────────
|
||||
* el_html_sanitize(input_html, allowlist_json) — strict allowlist HTML
|
||||
* cleaner. State-machine parser; tag/attribute names compared case-
|
||||
* insensitively against the allowlist; `<a href>` / `<… src>` URL schemes
|
||||
* validated (http, https, mailto, fragment-only, or relative); whole-
|
||||
* subtree drop for script / style / iframe / object / embed / form; HTML-
|
||||
* escapes free text outside dropped subtrees.
|
||||
*
|
||||
* The allowlist is JSON of the form
|
||||
* {"p":[],"a":["href","title"],"strong":[],...}
|
||||
* where each value is the array of attribute names allowed for that tag. */
|
||||
el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json);
|
||||
el_val_t html_raw(el_val_t s);
|
||||
el_val_t html_escape(el_val_t s);
|
||||
|
||||
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t fs_read(el_val_t path);
|
||||
el_val_t fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t fs_list(el_val_t path);
|
||||
el_val_t fs_list_json(el_val_t path);
|
||||
el_val_t fs_exists(el_val_t path);
|
||||
el_val_t fs_mkdir(el_val_t path); /* mkdir -p, mode 0755 */
|
||||
|
||||
/* Length-explicit binary write. `length` is an Int (el_val_t holding the
|
||||
* byte count). The caller knows the length from context — typically because
|
||||
* `bytes` came from base64_decode (which produces a magic-tagged binary
|
||||
* buffer with embedded NULs possible) and the caller already tracks the
|
||||
* decoded length, OR because the bytes came from a fixed-size source
|
||||
* (sha256_bytes = 32, hmac_sha256_bytes = 32). Bypasses strlen entirely.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure (invalid path, can't open, partial
|
||||
* write, negative length). On partial-write failure, the file is removed
|
||||
* so callers cannot read back a truncated artefact. */
|
||||
el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t length);
|
||||
|
||||
/* ── JSON ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t json_get(el_val_t json, el_val_t key);
|
||||
el_val_t json_parse(el_val_t s);
|
||||
el_val_t json_stringify(el_val_t v);
|
||||
el_val_t json_get_string(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_int(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_float(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_bool(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_raw(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value);
|
||||
el_val_t json_array_len(el_val_t json_str);
|
||||
el_val_t json_array_get(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_array_get_string(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_escape_string(el_val_t sv);
|
||||
el_val_t json_build_object(el_val_t kvs);
|
||||
el_val_t json_build_array(el_val_t items);
|
||||
|
||||
/* ── Time ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t time_now(void);
|
||||
el_val_t time_now_utc(void);
|
||||
el_val_t sleep_secs(el_val_t secs);
|
||||
el_val_t sleep_ms(el_val_t ms);
|
||||
el_val_t time_format(el_val_t ts, el_val_t fmt);
|
||||
el_val_t time_to_parts(el_val_t ts);
|
||||
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
|
||||
el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit);
|
||||
el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
|
||||
el_val_t now_ns(void);
|
||||
|
||||
/* ── Instant + Duration: first-class temporal types ──────────────────────────
|
||||
* Both types share the el_val_t (int64) slot. Instants are nanoseconds
|
||||
* since the Unix epoch; Durations are signed nanoseconds. Type discipline
|
||||
* is enforced at codegen-time: BinOps on names registered as Instant or
|
||||
* Duration route through the typed wrappers below; mismatches like
|
||||
* Instant+Instant become #error at the C compiler.
|
||||
*
|
||||
* Postfix literals — `30.seconds`, `1.hour`, `500.millis`, `30.nanos` — are
|
||||
* recognised by the parser as DurationLit AST nodes and lowered to literal
|
||||
* int64 nanoseconds at codegen time. The runtime never sees the units. */
|
||||
|
||||
el_val_t el_now_instant(void);
|
||||
el_val_t now(void);
|
||||
el_val_t unix_seconds(el_val_t n);
|
||||
el_val_t unix_millis(el_val_t n);
|
||||
el_val_t instant_from_iso8601(el_val_t s);
|
||||
|
||||
el_val_t el_duration_from_nanos(el_val_t ns);
|
||||
el_val_t duration_seconds(el_val_t n);
|
||||
el_val_t duration_millis(el_val_t n);
|
||||
el_val_t duration_nanos(el_val_t n);
|
||||
|
||||
el_val_t el_instant_add_dur(el_val_t inst, el_val_t dur);
|
||||
el_val_t el_instant_sub_dur(el_val_t inst, el_val_t dur);
|
||||
el_val_t el_instant_diff(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_add(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_sub(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_scale(el_val_t dur, el_val_t scalar);
|
||||
el_val_t el_duration_div(el_val_t dur, el_val_t scalar);
|
||||
|
||||
el_val_t el_instant_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_le(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_gt(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_ge(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_eq(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_ne(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_le(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_gt(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_ge(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_eq(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_ne(el_val_t a, el_val_t b);
|
||||
|
||||
el_val_t instant_to_unix_seconds(el_val_t i);
|
||||
el_val_t instant_to_unix_millis(el_val_t i);
|
||||
el_val_t instant_to_iso8601(el_val_t i);
|
||||
el_val_t duration_to_seconds(el_val_t d);
|
||||
el_val_t duration_to_millis(el_val_t d);
|
||||
el_val_t duration_to_nanos(el_val_t d);
|
||||
|
||||
el_val_t el_sleep_duration(el_val_t dur);
|
||||
el_val_t unix_timestamp(void);
|
||||
|
||||
el_val_t ttl_cache_set(el_val_t key, el_val_t value);
|
||||
el_val_t ttl_cache_get(el_val_t key, el_val_t max_age);
|
||||
el_val_t ttl_cache_age(el_val_t key);
|
||||
|
||||
/* ── Calendar + CalendarTime + Rhythm + LocalDate/Time/DateTime ─────────────
|
||||
* Phase 1.5 of the time system. Calendar is pluggable: EarthCalendar (IANA
|
||||
* zones, Gregorian, DST) is the user-facing default; MarsCalendar,
|
||||
* CycleCalendar(period), NoCycleCalendar, RelativeCalendar handle non-Earth
|
||||
* domains.
|
||||
*
|
||||
* A Calendar interprets an Instant under a particular cycle convention and
|
||||
* produces a CalendarTime. CalendarTime carries the underlying Instant and
|
||||
* a back-pointer to its Calendar; arithmetic and formatting consult the
|
||||
* Calendar to convert ns since epoch into year/month/day/hour/minute/second
|
||||
* (or sol/phase, or cycle/phase, depending on kind).
|
||||
*
|
||||
* Storage convention: Calendar / CalendarTime / Rhythm / LocalDate /
|
||||
* LocalDateTime are heap-allocated structs whose pointers are cast into
|
||||
* el_val_t. A 24-bit magic header at offset 0 lets the runtime identify
|
||||
* the kind safely. LocalTime is small enough to live in the int64 slot
|
||||
* directly (nanos since midnight, signed). */
|
||||
|
||||
/* Zone — opaque IANA zone or fixed offset, used by EarthCalendar.
|
||||
* `zone_id` is either an IANA name ("America/New_York", "UTC") or a fixed
|
||||
* offset string ("+05:30", "-08:00"). The runtime resolves it via tzset()
|
||||
* on first use of the owning EarthCalendar. */
|
||||
el_val_t zone(el_val_t id);
|
||||
el_val_t zone_utc(void);
|
||||
el_val_t zone_local(void);
|
||||
el_val_t zone_offset(el_val_t hours, el_val_t minutes);
|
||||
|
||||
/* Calendar constructors. Each returns an el_val_t pointer to a heap-
|
||||
* allocated, magic-tagged Calendar struct. Calendars are interned by
|
||||
* (kind, zone_id, period_ns, epoch_ns) so identical constructors return
|
||||
* the same pointer — equality is reference equality. */
|
||||
el_val_t earth_calendar(el_val_t z);
|
||||
el_val_t earth_calendar_default(void);
|
||||
el_val_t mars_calendar(void);
|
||||
el_val_t cycle_calendar(el_val_t period_dur);
|
||||
el_val_t no_cycle_calendar(void);
|
||||
el_val_t relative_calendar(el_val_t epoch_inst);
|
||||
|
||||
/* CalendarTime constructors and methods. Returns a heap-allocated struct
|
||||
* whose pointer fits in el_val_t. */
|
||||
el_val_t now_in(el_val_t cal);
|
||||
el_val_t in_calendar(el_val_t inst, el_val_t cal);
|
||||
el_val_t cal_format(el_val_t ct, el_val_t pattern);
|
||||
el_val_t cal_to_instant(el_val_t ct);
|
||||
el_val_t cal_cycle_phase(el_val_t ct);
|
||||
el_val_t cal_in(el_val_t ct, el_val_t cal);
|
||||
|
||||
/* LocalDate / LocalTime / LocalDateTime — calendar-agnostic value types.
|
||||
* LocalTime carries nanoseconds since midnight as a signed int64 directly
|
||||
* in the el_val_t slot (no allocation). LocalDate / LocalDateTime are
|
||||
* heap-allocated structs with magic headers. */
|
||||
el_val_t local_date(el_val_t y, el_val_t m, el_val_t d);
|
||||
el_val_t local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns);
|
||||
el_val_t local_datetime(el_val_t date, el_val_t time);
|
||||
el_val_t zoned(el_val_t date, el_val_t time, el_val_t cal);
|
||||
|
||||
el_val_t local_date_year(el_val_t ld);
|
||||
el_val_t local_date_month(el_val_t ld);
|
||||
el_val_t local_date_day(el_val_t ld);
|
||||
el_val_t local_time_hour(el_val_t lt);
|
||||
el_val_t local_time_minute(el_val_t lt);
|
||||
el_val_t local_time_second(el_val_t lt);
|
||||
el_val_t local_time_nanos(el_val_t lt);
|
||||
|
||||
el_val_t el_local_date_add_dur(el_val_t ld, el_val_t dur);
|
||||
el_val_t el_local_time_add_dur(el_val_t lt, el_val_t dur);
|
||||
el_val_t el_local_date_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_local_date_eq(el_val_t a, el_val_t b);
|
||||
|
||||
/* Rhythm — pluggable recurrence AST. Returns a heap-allocated struct
|
||||
* pointer in el_val_t; rhythms are immutable so callers may share them. */
|
||||
el_val_t rhythm_cycle_start(void);
|
||||
el_val_t rhythm_cycle_phase(el_val_t phase);
|
||||
el_val_t rhythm_duration(el_val_t d);
|
||||
el_val_t rhythm_session_start(void);
|
||||
el_val_t rhythm_event(el_val_t name);
|
||||
el_val_t rhythm_and(el_val_t a, el_val_t b);
|
||||
el_val_t rhythm_or(el_val_t a, el_val_t b);
|
||||
el_val_t rhythm_weekday(el_val_t day);
|
||||
el_val_t rhythm_weekly_at(el_val_t day, el_val_t hour, el_val_t minute);
|
||||
el_val_t rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal);
|
||||
el_val_t rhythm_matches(el_val_t r, el_val_t ct);
|
||||
|
||||
/* ── UUID ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t uuid_new(void);
|
||||
el_val_t uuid_v4(void);
|
||||
|
||||
/* ── Environment ─────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t env(el_val_t key);
|
||||
|
||||
/* ── In-process state K/V ────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t state_set(el_val_t key, el_val_t value);
|
||||
el_val_t state_get(el_val_t key);
|
||||
el_val_t state_del(el_val_t key);
|
||||
el_val_t state_keys(void);
|
||||
el_val_t state_has(el_val_t key);
|
||||
el_val_t state_get_or(el_val_t key, el_val_t default_val);
|
||||
|
||||
/* ── Float formatting ────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t float_to_str(el_val_t f);
|
||||
el_val_t int_to_float(el_val_t n);
|
||||
el_val_t float_to_int(el_val_t f);
|
||||
el_val_t format_float(el_val_t f, el_val_t decimals);
|
||||
el_val_t decimal_round(el_val_t f, el_val_t decimals);
|
||||
el_val_t str_to_float(el_val_t s);
|
||||
|
||||
/* ── Math (Float-aware) ──────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t math_sqrt(el_val_t f);
|
||||
el_val_t math_log(el_val_t f);
|
||||
el_val_t math_ln(el_val_t f);
|
||||
el_val_t math_sin(el_val_t f);
|
||||
el_val_t math_cos(el_val_t f);
|
||||
el_val_t math_pi(void);
|
||||
|
||||
/* ── String additions ────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t str_index_of(el_val_t s, el_val_t sub);
|
||||
el_val_t str_split(el_val_t s, el_val_t sep);
|
||||
el_val_t str_char_at(el_val_t s, el_val_t i);
|
||||
el_val_t str_char_code(el_val_t s, el_val_t i);
|
||||
el_val_t str_pad_left(el_val_t s, el_val_t width, el_val_t pad);
|
||||
el_val_t str_pad_right(el_val_t s, el_val_t width, el_val_t pad);
|
||||
el_val_t str_format(el_val_t fmt, el_val_t data);
|
||||
el_val_t str_lower(el_val_t s);
|
||||
el_val_t str_upper(el_val_t s);
|
||||
|
||||
/* ── Text-processing primitives (Phase 1: byte/codepoint, ASCII char classes)
|
||||
* Phase 2 (filed): Unicode-grapheme awareness, NFC/NFD normalization, regex.
|
||||
* is_* predicates: empty input returns false; multi-char requires ALL bytes
|
||||
* to match. ASCII ranges only in Phase 1. */
|
||||
|
||||
/* Counting */
|
||||
el_val_t str_count(el_val_t s, el_val_t sub); /* non-overlapping */
|
||||
el_val_t str_count_chars(el_val_t s); /* codepoint count */
|
||||
el_val_t str_count_bytes(el_val_t s); /* alias of str_len */
|
||||
el_val_t str_count_lines(el_val_t s);
|
||||
el_val_t str_count_words(el_val_t s);
|
||||
el_val_t str_count_letters(el_val_t s); /* ASCII [A-Za-z] */
|
||||
el_val_t str_count_digits(el_val_t s); /* ASCII [0-9] */
|
||||
|
||||
/* Find / position */
|
||||
el_val_t str_index_of_all(el_val_t s, el_val_t sub); /* [Int] of byte offsets */
|
||||
el_val_t str_last_index_of(el_val_t s, el_val_t sub);
|
||||
el_val_t str_find_chars(el_val_t s, el_val_t any_of); /* first idx of any ch */
|
||||
|
||||
/* Transform */
|
||||
el_val_t str_repeat(el_val_t s, el_val_t n);
|
||||
el_val_t str_reverse(el_val_t s); /* by codepoint */
|
||||
el_val_t str_strip_prefix(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_strip_suffix(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_strip_chars(el_val_t s, el_val_t chars);
|
||||
el_val_t str_lstrip(el_val_t s);
|
||||
el_val_t str_rstrip(el_val_t s);
|
||||
|
||||
/* Char classification (Bool) */
|
||||
el_val_t is_letter(el_val_t s);
|
||||
el_val_t is_digit(el_val_t s);
|
||||
el_val_t is_alphanumeric(el_val_t s);
|
||||
el_val_t is_whitespace(el_val_t s);
|
||||
el_val_t is_punctuation(el_val_t s);
|
||||
el_val_t is_uppercase(el_val_t s);
|
||||
el_val_t is_lowercase(el_val_t s);
|
||||
|
||||
/* Split / join */
|
||||
el_val_t str_split_lines(el_val_t s);
|
||||
el_val_t str_split_chars(el_val_t s); /* alias of native_string_chars */
|
||||
el_val_t str_split_n(el_val_t s, el_val_t sep, el_val_t n);
|
||||
el_val_t str_join(el_val_t list, el_val_t sep); /* alias of list_join */
|
||||
|
||||
/* ── List additions ──────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t list_push(el_val_t list, el_val_t elem);
|
||||
el_val_t list_push_front(el_val_t list, el_val_t elem);
|
||||
el_val_t list_join(el_val_t list, el_val_t sep);
|
||||
el_val_t list_range(el_val_t start, el_val_t end);
|
||||
|
||||
/* ── Bool helpers ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t bool_to_str(el_val_t b);
|
||||
|
||||
/* ── Numeric parsing ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t parse_int(el_val_t s, el_val_t default_val);
|
||||
|
||||
/* ── Process ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t exit_program(el_val_t code);
|
||||
el_val_t getpid_now(void);
|
||||
|
||||
/* Self-terminating memory guard. Reads ELC_MAX_MEM_MB (default 512) and
|
||||
* exits with code 1 if resident memory exceeds the limit. Call periodically
|
||||
* during long compilation loops (e.g. after each function is compiled).
|
||||
* Returns 0 when memory is within bounds. */
|
||||
el_val_t el_mem_check(void);
|
||||
|
||||
/* ── CGI identity ─────────────────────────────────────────────────────────────
|
||||
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
|
||||
* Records the program's DHARMA identity before any other code executes. */
|
||||
|
||||
void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
el_val_t network, el_val_t engram);
|
||||
|
||||
/* ── DHARMA network builtins ─────────────────────────────────────────────────
|
||||
* Available to CGI programs (declared with a `cgi {}` block).
|
||||
*
|
||||
* Peers are addressed by `dharma_id` of the form
|
||||
* "<registry-id>@<transport-url>" e.g. "ntn-genesis@http://localhost:7770"
|
||||
* If the @<url> portion is omitted, transport defaults to
|
||||
* "http://localhost:7770" (the local CGI daemon assumption).
|
||||
*
|
||||
* Wire protocol (all peers expose):
|
||||
* POST <url>/dharma/recv { channel, from, content } → response body
|
||||
* POST <url>/dharma/event { type, payload, source, timestamp }
|
||||
* POST <url>/api/activate { query } → list of nodes
|
||||
*
|
||||
* Hosting application's responsibility: an El program with a `cgi {}` block
|
||||
* runs http_serve() with its own request handler; that handler should route
|
||||
* "/dharma/event" requests by calling el_runtime_dharma_event_arrive() so
|
||||
* incoming events feed dharma_field() queues. The runtime itself does not
|
||||
* intercept any /dharma path. */
|
||||
|
||||
el_val_t dharma_connect(el_val_t cgi_id);
|
||||
el_val_t dharma_send(el_val_t channel, el_val_t content);
|
||||
el_val_t dharma_activate(el_val_t query);
|
||||
void dharma_emit(el_val_t event_type, el_val_t payload);
|
||||
el_val_t dharma_field(el_val_t event_type);
|
||||
void dharma_strengthen(el_val_t cgi_id, el_val_t weight);
|
||||
el_val_t dharma_relationship(el_val_t cgi_id);
|
||||
el_val_t dharma_peers(void);
|
||||
|
||||
/* Public C API: called by an El program's HTTP handler when a /dharma/event
|
||||
* request arrives. Pushes onto the per-event-type queue and signals any
|
||||
* pending dharma_field() blockers. All three arguments must be NUL-terminated
|
||||
* C strings (or NULL — then treated as empty). */
|
||||
void el_runtime_dharma_event_arrive(const char* event_type,
|
||||
const char* payload,
|
||||
const char* source);
|
||||
|
||||
/* ── Engram local graph primitives ───────────────────────────────────────────
|
||||
* Operate on the CGI's local Engram knowledge graph.
|
||||
* `engram_activate` queries the local graph only; `dharma_activate` is
|
||||
* network-wide across all connected CGI graphs. */
|
||||
|
||||
el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience);
|
||||
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t importance, el_val_t confidence,
|
||||
el_val_t tier, el_val_t tags);
|
||||
/* Layered consciousness — see el_runtime.c for the layered architecture
|
||||
* design notes (search "Layered consciousness architecture"). The five
|
||||
* canonical layers (safety / core-identity / domain-knowledge / imprint /
|
||||
* suit) are seeded automatically; engram_add_layer extends the registry
|
||||
* with imprint or suit overlays at runtime. Nodes default to layer 1
|
||||
* (core-identity) when created via engram_node / engram_node_full. */
|
||||
el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t certainty, el_val_t confidence,
|
||||
el_val_t status, el_val_t tags, el_val_t layer_id);
|
||||
el_val_t engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible,
|
||||
el_val_t transparent, el_val_t injectable);
|
||||
el_val_t engram_remove_layer(el_val_t layer_id);
|
||||
el_val_t engram_list_layers(void);
|
||||
el_val_t engram_get_node(el_val_t id);
|
||||
void engram_strengthen(el_val_t node_id);
|
||||
void engram_forget(el_val_t node_id);
|
||||
el_val_t engram_node_count(void);
|
||||
el_val_t engram_search(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||
void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation);
|
||||
el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id);
|
||||
el_val_t engram_neighbors(el_val_t node_id);
|
||||
el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t engram_edge_count(void);
|
||||
/* Three-pass activation: background fan-out → working-memory promotion →
|
||||
* Layer 0 override. See "Three-pass activation" in el_runtime.c. */
|
||||
el_val_t engram_activate(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_save(el_val_t path);
|
||||
el_val_t engram_load(el_val_t path);
|
||||
|
||||
/* JSON-string accessors — return pre-serialized JSON so HTTP handlers
|
||||
* can pass results straight through without round-tripping ElList/ElMap
|
||||
* through json_stringify. */
|
||||
el_val_t engram_get_node_json(el_val_t id);
|
||||
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_stats_json(void);
|
||||
el_val_t engram_list_layers_json(void);
|
||||
/* engram_compile_layered_json — produce a prompt-ready text block split
|
||||
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
||||
* and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
|
||||
* no nodes promoted to working memory. */
|
||||
el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
||||
|
||||
/* ── LLM (Anthropic API client) ─────────────────────────────────────────────
|
||||
* All functions call https://api.anthropic.com/v1/messages with the API key
|
||||
* from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */
|
||||
|
||||
el_val_t llm_call(el_val_t model, el_val_t prompt);
|
||||
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt);
|
||||
el_val_t llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools);
|
||||
el_val_t llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64);
|
||||
el_val_t llm_models(void);
|
||||
|
||||
/* Register a tool handler by name. The handler is looked up via dlsym
|
||||
* (mirroring http_set_handler), so any El `fn <name>(input)` compiles to
|
||||
* a global C symbol that this function can locate at runtime.
|
||||
* Handler signature: `el_val_t handler(el_val_t input_json)` — receives
|
||||
* the tool input as a JSON-string el_val_t and returns a JSON-string
|
||||
* el_val_t result. Used by llm_call_agentic. */
|
||||
void llm_register_tool(el_val_t name, el_val_t handler_fn_name);
|
||||
|
||||
/* ── args() ─────────────────────────────────────────────────────────────────
|
||||
* Provides access to command-line arguments passed to the program.
|
||||
* Populated by el_runtime_init_args() before main() runs. */
|
||||
|
||||
el_val_t args(void);
|
||||
void el_runtime_init_args(int argc, char** argv);
|
||||
|
||||
/* ── Crypto primitives ─────────────────────────────────────────────────────
|
||||
* SHA-256, HMAC-SHA-256, and base64 (standard + URL-safe).
|
||||
* Self-contained — no OpenSSL/libcrypto dependency. The implementations are
|
||||
* adapted from public-domain reference code (Brad Conte / RFC 4648).
|
||||
*
|
||||
* Bytes-returning variants (sha256_bytes, hmac_sha256_bytes) return a string
|
||||
* value whose contents are raw binary; callers usually feed these into
|
||||
* base64_encode. Note that el_val_t strings are NUL-terminated by convention,
|
||||
* so the binary payload may contain embedded NULs — pass it directly into
|
||||
* base64_encode (which uses an explicit length) rather than treating it as
|
||||
* a printable C string.
|
||||
*
|
||||
* The "base64" variants emit/accept RFC 4648 standard alphabet with padding.
|
||||
* The "base64url" variants use URL-safe alphabet (`-`/`_`) with no padding,
|
||||
* as used in JWTs. */
|
||||
|
||||
el_val_t sha256_hex(el_val_t input);
|
||||
el_val_t sha256_bytes(el_val_t input);
|
||||
el_val_t hmac_sha256_hex(el_val_t key, el_val_t message);
|
||||
el_val_t hmac_sha256_bytes(el_val_t key, el_val_t message);
|
||||
el_val_t base64_encode(el_val_t input);
|
||||
el_val_t base64_decode(el_val_t input);
|
||||
el_val_t base64url_encode(el_val_t input);
|
||||
el_val_t base64url_decode(el_val_t input);
|
||||
|
||||
/* Length-aware variants (internal — exposed for the rare caller that already
|
||||
* has a known-length binary buffer and doesn't want to round-trip through
|
||||
* a NUL-terminated el_val_t string). Sha256_bytes and hmac_sha256_bytes feed
|
||||
* these implicitly. */
|
||||
el_val_t el_sha256_bytes_n(const unsigned char* data, size_t len);
|
||||
el_val_t el_base64_encode_n(const unsigned char* data, size_t len, int url_safe);
|
||||
|
||||
/* ── Post-quantum primitives (liboqs-backed) ────────────────────────────────
|
||||
* All inputs/outputs hex-encoded. Algorithm choices:
|
||||
* Signature: CRYSTALS-Dilithium-3 (NIST level 3, balanced)
|
||||
* KEM: CRYSTALS-Kyber-768 (NIST level 3)
|
||||
* Hash: SHA3-256 (Keccak) (PQ-aware protocols favour SHA3 over SHA2)
|
||||
*
|
||||
* If liboqs is not linked (detected via __has_include(<oqs/oqs.h>) at compile
|
||||
* time), the pq_* entry points return a JSON-shaped error string so callers
|
||||
* fail loudly rather than silently fall back to classical schemes:
|
||||
* {"error":"liboqs not linked, post-quantum primitives unavailable"}
|
||||
*
|
||||
* The hybrid handshake pairs X25519 with Kyber-768 per NIST PQ guidance and
|
||||
* CNSA 2.0. Combined shared secret is HKDF-SHA256(x25519_ss || kyber_ss).
|
||||
* Even if Kyber falls, X25519 holds; if X25519 falls under quantum attack,
|
||||
* Kyber holds. SHA3-256 also remains usable independent of liboqs (the
|
||||
* Keccak permutation is PQ-OK as a primitive). */
|
||||
|
||||
el_val_t pq_keygen_signature(void);
|
||||
el_val_t pq_sign(el_val_t secret_key_hex, el_val_t message);
|
||||
el_val_t pq_verify(el_val_t public_key_hex, el_val_t message, el_val_t signature_hex);
|
||||
|
||||
el_val_t pq_kem_keygen(void);
|
||||
el_val_t pq_kem_encaps(el_val_t public_key_hex);
|
||||
el_val_t pq_kem_decaps(el_val_t secret_key_hex, el_val_t ciphertext_hex);
|
||||
|
||||
el_val_t pq_hybrid_keygen(void);
|
||||
el_val_t pq_hybrid_handshake(el_val_t remote_pub_combined);
|
||||
|
||||
el_val_t sha3_256_hex(el_val_t input);
|
||||
|
||||
/* ── AEAD: AES-256-GCM (libcrypto-backed) ───────────────────────────────────
|
||||
* Symmetric authenticated encryption used to wrap envelopes after a KEM
|
||||
* handshake. Caller MUST supply a 32-byte key (64 hex chars) — typically the
|
||||
* Kyber-768 / hybrid shared_secret, optionally normalized via SHA3-256.
|
||||
*
|
||||
* aead_encrypt returns a JSON map {"nonce":"...","ciphertext":"..."} where
|
||||
* ciphertext is the AES-256-GCM output with the 16-byte auth tag appended.
|
||||
* Nonce is a fresh 12-byte CSPRNG draw — callers never pick the nonce, which
|
||||
* structurally rules out the GCM nonce-reuse footgun.
|
||||
*
|
||||
* aead_decrypt returns the plaintext String, or "" on any failure (including
|
||||
* auth-tag mismatch). Callers MUST check for "" before trusting the result. */
|
||||
el_val_t aead_encrypt(el_val_t key_hex, el_val_t plaintext);
|
||||
el_val_t aead_decrypt(el_val_t key_hex, el_val_t nonce_hex, el_val_t ciphertext_hex);
|
||||
|
||||
/* ── Native VM builtin aliases (for compiled El source) ─────────────────────
|
||||
* These match the El VM's native_* builtins so that El source compiled
|
||||
* to C can call the same names without modification. */
|
||||
|
||||
el_val_t native_list_get(el_val_t list, el_val_t index);
|
||||
el_val_t native_list_len(el_val_t list);
|
||||
el_val_t native_list_append(el_val_t list, el_val_t elem);
|
||||
el_val_t native_list_empty(void);
|
||||
el_val_t native_list_clone(el_val_t list);
|
||||
el_val_t native_string_chars(el_val_t s);
|
||||
el_val_t native_int_to_str(el_val_t n);
|
||||
|
||||
/* ── Method-call shorthand aliases ──────────────────────────────────────────
|
||||
* The El method-call convention `obj.method(args)` compiles to
|
||||
* `method(obj, args)`. These aliases expose the runtime functions under
|
||||
* the short names that result from method calls in El source.
|
||||
*
|
||||
* Example: `myList.append(x)` → `append(myList, x)` (calls this alias)
|
||||
* `myList.len()` → `len(myList)` (calls this alias) */
|
||||
|
||||
el_val_t append(el_val_t list, el_val_t elem); /* el_list_append */
|
||||
el_val_t len(el_val_t list); /* el_list_len */
|
||||
el_val_t get(el_val_t list, el_val_t index); /* el_list_get */
|
||||
el_val_t map_get(el_val_t map, el_val_t key); /* el_map_get */
|
||||
el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */
|
||||
|
||||
/* ── OTLP/HTTP Observability ─────────────────────────────────────────────── */
|
||||
/* See bottom of el_runtime.c for the implementation.
|
||||
* Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
|
||||
* No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
|
||||
/* ── Subprocess execution ────────────────────────────────────────────────── */
|
||||
el_val_t exec_command(el_val_t cmd); /* run shell command, return exit code */
|
||||
el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
||||
el_val_t exec(el_val_t cmd); /* exec(cmd) → stdout String (30s timeout) */
|
||||
el_val_t exec_bg(el_val_t cmd); /* exec_bg(cmd) → PID String (non-blocking) */
|
||||
|
||||
/* ── Stdout redirection (used by compiler JS pipeline) ───────────────────── */
|
||||
el_val_t stdout_to_file(el_val_t path); /* redirect process stdout to a file */
|
||||
el_val_t stdout_restore(void); /* restore process stdout to terminal */
|
||||
|
||||
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
||||
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
||||
el_val_t trace_span_start(el_val_t name);
|
||||
el_val_t trace_span_end(el_val_t span_handle);
|
||||
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
|
||||
|
||||
el_val_t __thread_create(el_val_t fn_name_v, el_val_t arg_v);
|
||||
el_val_t __thread_join(el_val_t tid_v);
|
||||
|
||||
/* ── __ prefixed aliases (self-hosting compiler ABI) ─────────────────────────
|
||||
* The El self-hosting compiler emits calls to __-prefixed names. These are
|
||||
* forwarding wrappers around the existing el_runtime functions above. */
|
||||
|
||||
/* I/O */
|
||||
el_val_t __println(el_val_t s);
|
||||
el_val_t __print(el_val_t s);
|
||||
el_val_t __readline(void);
|
||||
|
||||
/* String */
|
||||
el_val_t __int_to_str(el_val_t n);
|
||||
el_val_t __str_to_int(el_val_t s);
|
||||
el_val_t __float_to_str(el_val_t f);
|
||||
el_val_t __str_to_float(el_val_t s);
|
||||
el_val_t __str_len(el_val_t s);
|
||||
el_val_t __str_char_at(el_val_t s, el_val_t i);
|
||||
el_val_t __str_cmp(el_val_t a, el_val_t b);
|
||||
el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n);
|
||||
el_val_t __str_concat_raw(el_val_t a, el_val_t b);
|
||||
el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t __str_alloc(el_val_t n);
|
||||
el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c);
|
||||
|
||||
/* URL encoding */
|
||||
el_val_t __url_encode(el_val_t s);
|
||||
el_val_t __url_decode(el_val_t s);
|
||||
|
||||
/* Environment */
|
||||
el_val_t __env_get(el_val_t key);
|
||||
|
||||
/* Subprocess */
|
||||
el_val_t __exec(el_val_t cmd);
|
||||
el_val_t __exec_bg(el_val_t cmd);
|
||||
|
||||
/* Process */
|
||||
el_val_t __exit_program(el_val_t code);
|
||||
|
||||
/* Filesystem */
|
||||
el_val_t __fs_exists(el_val_t path);
|
||||
el_val_t __fs_mkdir(el_val_t path);
|
||||
el_val_t __fs_read(el_val_t path);
|
||||
el_val_t __fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n);
|
||||
el_val_t __fs_list_raw(el_val_t path);
|
||||
|
||||
/* HTTP server */
|
||||
el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
el_val_t __http_serve(el_val_t port, el_val_t handler);
|
||||
el_val_t __http_serve_v2(el_val_t port, el_val_t handler);
|
||||
|
||||
/* HTTP conn fd / SSE (weak; overridden by el_seed.c when linked together) */
|
||||
el_val_t __http_conn_fd(void);
|
||||
el_val_t __http_sse_open(el_val_t conn_id);
|
||||
el_val_t __http_sse_send(el_val_t conn_id, el_val_t data);
|
||||
el_val_t __http_sse_close(el_val_t conn_id);
|
||||
|
||||
/* HTTP client (requires HAVE_CURL; stubs provided for no-curl builds) */
|
||||
el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_map, el_val_t timeout_ms);
|
||||
el_val_t __http_do_map(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t timeout_ms);
|
||||
el_val_t __http_do_map_to_file(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t output_path);
|
||||
|
||||
/* JSON */
|
||||
el_val_t __json_array_get(el_val_t json, el_val_t index);
|
||||
el_val_t __json_array_get_string(el_val_t json, el_val_t index);
|
||||
el_val_t __json_array_len(el_val_t json);
|
||||
el_val_t __json_get(el_val_t json, el_val_t key);
|
||||
el_val_t __json_get_raw(el_val_t json, el_val_t key);
|
||||
el_val_t __json_set(el_val_t json, el_val_t key, el_val_t value);
|
||||
el_val_t __json_parse_map(el_val_t json_str);
|
||||
el_val_t __json_stringify_val(el_val_t val);
|
||||
|
||||
/* Hashing */
|
||||
el_val_t __sha256_hex(el_val_t s);
|
||||
|
||||
/* State K/V */
|
||||
el_val_t __state_del(el_val_t key);
|
||||
el_val_t __state_get(el_val_t key);
|
||||
el_val_t __state_keys(void);
|
||||
el_val_t __state_set(el_val_t key, el_val_t val);
|
||||
|
||||
/* UUID */
|
||||
el_val_t __uuid_v4(void);
|
||||
|
||||
/* Args */
|
||||
el_val_t __args_json(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,123 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data);
|
||||
el_val_t app_build(el_val_t window);
|
||||
|
||||
el_val_t g_window;
|
||||
el_val_t g_label;
|
||||
el_val_t g_input;
|
||||
el_val_t g_button;
|
||||
el_val_t g_counter;
|
||||
el_val_t g_counter_lbl;
|
||||
el_val_t manifest_env;
|
||||
el_val_t manifest_path;
|
||||
el_val_t win;
|
||||
el_val_t g_window;
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t name = widget_get_text(g_input);
|
||||
el_val_t greeting = ({ el_val_t _if_result_1 = 0; if ((str_len(name) > 0)) { _if_result_1 = (el_str_concat(el_str_concat(EL_STR("Hello, "), name), EL_STR("!"))); } else { _if_result_1 = (EL_STR("Hello, World!")); } _if_result_1; });
|
||||
widget_set_text(g_label, greeting);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t g_counter = (g_counter + 1);
|
||||
widget_set_text(g_counter_lbl, el_str_concat(EL_STR("Clicks: "), int_to_str(g_counter)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data) {
|
||||
el_val_t has_text = (str_len(data) > 0);
|
||||
widget_set_disabled(g_button, !has_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t app_build(el_val_t window) {
|
||||
el_val_t root = vstack(0);
|
||||
widget_set_padding_all(root, 24);
|
||||
widget_set_bg_color_hex(root, EL_STR("#0f172a"));
|
||||
widget_set_flex(root, 1);
|
||||
el_val_t title = label(EL_STR("el-native \xe2\x80\x94 native widget demo"));
|
||||
style_label_heading(title);
|
||||
widget_add_child(root, title);
|
||||
el_val_t gap1 = label(EL_STR(""));
|
||||
widget_set_height(gap1, 16);
|
||||
widget_add_child(root, gap1);
|
||||
el_val_t subtitle = label(EL_STR("AppKit controls from el code, no ObjC in the app layer."));
|
||||
style_label_muted(subtitle);
|
||||
widget_add_child(root, subtitle);
|
||||
el_val_t gap2 = label(EL_STR(""));
|
||||
widget_set_height(gap2, 24);
|
||||
widget_add_child(root, gap2);
|
||||
el_val_t input_row = hstack(8);
|
||||
el_val_t name_label = label(EL_STR("Name:"));
|
||||
style_label_body(name_label);
|
||||
widget_set_width(name_label, 60);
|
||||
el_val_t input = text_field(EL_STR("Enter your name\xe2\x80\xa6"));
|
||||
style_label_body(input);
|
||||
widget_set_flex(input, 1);
|
||||
widget_on_change(input, EL_STR("on_name_change"));
|
||||
el_val_t g_input = input;
|
||||
el_val_t greet_btn = button(EL_STR("Greet"));
|
||||
style_button_primary(greet_btn);
|
||||
widget_set_disabled(greet_btn, 1);
|
||||
widget_on_click(greet_btn, EL_STR("on_greet_click"));
|
||||
el_val_t g_button = greet_btn;
|
||||
widget_add_child(input_row, name_label);
|
||||
widget_add_child(input_row, input);
|
||||
widget_add_child(input_row, greet_btn);
|
||||
widget_add_child(root, input_row);
|
||||
el_val_t gap3 = label(EL_STR(""));
|
||||
widget_set_height(gap3, 12);
|
||||
widget_add_child(root, gap3);
|
||||
el_val_t greeting = label(EL_STR("Waiting for name\xe2\x80\xa6"));
|
||||
style_label_body(greeting);
|
||||
widget_set_color_hex(greeting, EL_STR("#60a5fa"));
|
||||
widget_set_font(greeting, EL_STR("system"), 16, 1);
|
||||
el_val_t g_label = greeting;
|
||||
widget_add_child(root, greeting);
|
||||
el_val_t gap4 = label(EL_STR(""));
|
||||
widget_set_height(gap4, 24);
|
||||
widget_add_child(root, gap4);
|
||||
el_val_t counter_row = hstack(12);
|
||||
el_val_t counter_lbl = label(EL_STR("Clicks: 0"));
|
||||
style_label_body(counter_lbl);
|
||||
el_val_t g_counter_lbl = counter_lbl;
|
||||
el_val_t counter_btn = button(EL_STR("Click me"));
|
||||
style_button_primary(counter_btn);
|
||||
widget_on_click(counter_btn, EL_STR("on_counter_click"));
|
||||
widget_add_child(counter_row, counter_lbl);
|
||||
widget_add_child(counter_row, counter_btn);
|
||||
widget_add_child(root, counter_row);
|
||||
widget_add_child(window, root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
g_window = (-1);
|
||||
g_label = (-1);
|
||||
g_input = (-1);
|
||||
g_button = (-1);
|
||||
g_counter = 0;
|
||||
g_counter_lbl = (-1);
|
||||
native_init();
|
||||
manifest_env = env(EL_STR("EL_MANIFEST"));
|
||||
manifest_path = ({ el_val_t _if_result_2 = 0; if ((str_len(manifest_env) > 0)) { _if_result_2 = (manifest_env); } else { _if_result_2 = (EL_STR("manifest.el")); } _if_result_2; });
|
||||
win = window_from_manifest(manifest_path);
|
||||
g_window = win;
|
||||
if (win < 0) {
|
||||
println(EL_STR("Error: failed to create window. Is EL_TARGET_MACOS defined?"));
|
||||
exit_program(1);
|
||||
}
|
||||
app_build(win);
|
||||
window_show(win);
|
||||
native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
activity_main.xml — placeholder layout.
|
||||
|
||||
The el program sets the content view programmatically via ElBridge.setContentView
|
||||
(called from __window_show → el_android_window_show). This layout is not used
|
||||
at runtime but satisfies Gradle's res/ validation checks.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">el-native</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
// Top-level build file — configuration for all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.2.2' apply false
|
||||
}
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
# build.sh — Build native-hello-android.
|
||||
#
|
||||
# Usage:
|
||||
# ./build.sh # regenerate C sources + ./gradlew assembleDebug
|
||||
# ./build.sh gen # regenerate C sources only
|
||||
# ./build.sh gradle # run ./gradlew assembleDebug (skip elc step)
|
||||
# ./build.sh clean # clean Gradle build outputs
|
||||
#
|
||||
# Requirements:
|
||||
# - Android SDK with NDK and CMake installed
|
||||
# - local.properties present (copy from local.properties.template and edit sdk.dir)
|
||||
# - Java 11+ on PATH (for Gradle)
|
||||
# - elc at ../../../lang/dist/platform/elc
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||
EL_APP_ENTRY="${EL_UI_ROOT}/examples/native-hello/src/main.el"
|
||||
EL_MANIFEST="${EL_UI_ROOT}/examples/native-hello/manifest.el"
|
||||
JNI_DIR="${SCRIPT_DIR}/app/src/main/jni"
|
||||
|
||||
# Locate elc.
|
||||
if command -v elc &>/dev/null; then
|
||||
ELC="elc"
|
||||
elif [ -x "${EL_LANG_ROOT}/dist/platform/elc" ]; then
|
||||
ELC="${EL_LANG_ROOT}/dist/platform/elc"
|
||||
else
|
||||
echo "Error: elc not found. Add it to PATH or place it at:"
|
||||
echo " ${EL_LANG_ROOT}/dist/platform/elc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gen() {
|
||||
echo "==> Generating native_hello.c..."
|
||||
EL_MANIFEST="${EL_MANIFEST}" \
|
||||
"${ELC}" "${EL_APP_ENTRY}" \
|
||||
> "${JNI_DIR}/native_hello.c"
|
||||
|
||||
echo "==> Generating el_native_vessel.c..."
|
||||
"${ELC}" "${EL_NATIVE_VESSEL}" \
|
||||
> "${JNI_DIR}/el_native_vessel.c"
|
||||
# Rename the vessel's main() to avoid symbol collision with native_hello.c's main().
|
||||
# The Android JNI entry point (nativeMain) calls native_hello.c's main() explicitly.
|
||||
sed -i '' 's/^int main(int _argc, char\*\* _argv) {/int el_vessel_main(int _argc, char** _argv) {/' \
|
||||
"${JNI_DIR}/el_native_vessel.c" 2>/dev/null || \
|
||||
sed -i 's/^int main(int _argc, char\*\* _argv) {/int el_vessel_main(int _argc, char** _argv) {/' \
|
||||
"${JNI_DIR}/el_native_vessel.c"
|
||||
|
||||
echo "==> C source generation complete."
|
||||
}
|
||||
|
||||
gradle_build() {
|
||||
echo "==> Running ./gradlew assembleDebug..."
|
||||
cd "${SCRIPT_DIR}"
|
||||
./gradlew assembleDebug
|
||||
|
||||
APK_PATH="${SCRIPT_DIR}/app/build/outputs/apk/debug/app-debug.apk"
|
||||
if [ -f "${APK_PATH}" ]; then
|
||||
echo "==> Build complete."
|
||||
echo " APK: ${APK_PATH}"
|
||||
else
|
||||
echo "==> Build complete (APK path may differ — check app/build/outputs/apk/)."
|
||||
fi
|
||||
}
|
||||
|
||||
clean() {
|
||||
echo "==> Cleaning..."
|
||||
cd "${SCRIPT_DIR}"
|
||||
./gradlew clean
|
||||
echo "==> Clean complete."
|
||||
}
|
||||
|
||||
case "${1:-all}" in
|
||||
gen) gen ;;
|
||||
gradle) gradle_build ;;
|
||||
clean) clean ;;
|
||||
all|*) gen && gradle_build ;;
|
||||
esac
|
||||
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=false
|
||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Gradle start up script for POSIX generated by "gradle init".
|
||||
#
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
# Make relative path absolute, based on the path of the symlink:
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
;;
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,7 @@
|
||||
# local.properties — machine-local SDK/NDK paths. DO NOT commit. Copy to local.properties.
|
||||
#
|
||||
# Generate: Android Studio creates this automatically, or set manually:
|
||||
# sdk.dir=/Users/<you>/Library/Android/sdk
|
||||
# ndk.dir=/Users/<you>/Library/Android/sdk/ndk/<version>
|
||||
|
||||
sdk.dir=/Users/will/Library/Android/sdk
|
||||
@@ -0,0 +1,17 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "native-hello-android"
|
||||
include ':app'
|
||||
@@ -0,0 +1,4 @@
|
||||
build/
|
||||
*.xcuserstate
|
||||
xcuserdata/
|
||||
NativeHello.xcodeproj/xcuserdata/
|
||||
@@ -0,0 +1,377 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4C6D9832BE495DD69A8573D9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FFDE02477855A55BC5F02CE /* main.m */; };
|
||||
396FD073065B58F39358D798 /* el_uikit.m in Sources */ = {isa = PBXBuildFile; fileRef = 606F96EC1EDC54CD99E7D722 /* el_uikit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
|
||||
92BBC3E2AA055FCE840BF779 /* el_seed.c in Sources */ = {isa = PBXBuildFile; fileRef = 618FBE18EAA850D9B493A20F /* el_seed.c */; };
|
||||
136C7763096C5037A69077AD /* el_runtime.c in Sources */ = {isa = PBXBuildFile; fileRef = A8CCF8316D83597CBB332064 /* el_runtime.c */; };
|
||||
25136DF2A63F507EA5C7EF99 /* el_native_vessel.c in Sources */ = {isa = PBXBuildFile; fileRef = 3373ABFFE7FB5EFA9343AD02 /* el_native_vessel.c */; };
|
||||
C05D581DBD4E52F6B7F7303E /* native_hello.c in Sources */ = {isa = PBXBuildFile; fileRef = EDA408C749EB5F3CA1E5EEC2 /* native_hello.c */; };
|
||||
C3D9A9987F0050EB859D9218 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FF42D2E0FA0655149E573F7C /* UIKit.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
8FFDE02477855A55BC5F02CE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
606F96EC1EDC54CD99E7D722 /* el_uikit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = el_uikit.m; sourceTree = "<group>"; };
|
||||
618FBE18EAA850D9B493A20F /* el_seed.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = el_seed.c; sourceTree = "<group>"; };
|
||||
A8CCF8316D83597CBB332064 /* el_runtime.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = el_runtime.c; sourceTree = "<group>"; };
|
||||
530AA788858357FBA923217E /* el_runtime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = el_runtime.h; sourceTree = "<group>"; };
|
||||
E1A7B02609E95D1686523B80 /* el_native_target.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = el_native_target.h; sourceTree = "<group>"; };
|
||||
3373ABFFE7FB5EFA9343AD02 /* el_native_vessel.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = el_native_vessel.c; sourceTree = "<group>"; };
|
||||
EDA408C749EB5F3CA1E5EEC2 /* native_hello.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = native_hello.c; sourceTree = "<group>"; };
|
||||
08B6475D8C7952F48EF46B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
FF42D2E0FA0655149E573F7C /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||
BDC23A5F3FCD5D1098144064 /* NativeHello.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NativeHello.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
5673C72A848C5FFF94E7CF8F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C3D9A9987F0050EB859D9218 /* UIKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
05039DD2316A5B41A35E094E /* root */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EBC5644CBC315AFDA6A6A59E /* NativeHello */,
|
||||
D1E4BC2FC3925E6FAFCD38F6 /* Frameworks */,
|
||||
177F2C27230B527E80CEAED5 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
177F2C27230B527E80CEAED5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BDC23A5F3FCD5D1098144064 /* NativeHello.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EBC5644CBC315AFDA6A6A59E /* NativeHello */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8FFDE02477855A55BC5F02CE /* main.m */,
|
||||
606F96EC1EDC54CD99E7D722 /* el_uikit.m */,
|
||||
618FBE18EAA850D9B493A20F /* el_seed.c */,
|
||||
A8CCF8316D83597CBB332064 /* el_runtime.c */,
|
||||
530AA788858357FBA923217E /* el_runtime.h */,
|
||||
E1A7B02609E95D1686523B80 /* el_native_target.h */,
|
||||
3373ABFFE7FB5EFA9343AD02 /* el_native_vessel.c */,
|
||||
EDA408C749EB5F3CA1E5EEC2 /* native_hello.c */,
|
||||
08B6475D8C7952F48EF46B10 /* Info.plist */,
|
||||
);
|
||||
path = NativeHello;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D1E4BC2FC3925E6FAFCD38F6 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FF42D2E0FA0655149E573F7C /* UIKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
F1275C1CDFD05A40B9D6C717 /* NativeHello */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 07AFC970D6B05512B39EDE6A /* Build configuration list for PBXNativeTarget "NativeHello" */;
|
||||
buildPhases = (
|
||||
DF29C8DEE5CB5F1C83967F1F /* Sources */,
|
||||
5673C72A848C5FFF94E7CF8F /* Frameworks */,
|
||||
D6D57F9604C45AAEA747645E /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = NativeHello;
|
||||
productName = NativeHello;
|
||||
productReference = BDC23A5F3FCD5D1098144064 /* NativeHello.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
D8A823993D3254C7B6541F91 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastUpgradeCheck = 1500;
|
||||
TargetAttributes = {
|
||||
F1275C1CDFD05A40B9D6C717 = {
|
||||
CreatedOnToolsVersion = 15.0;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 3DAB235BBB945E0CBB3EF554 /* Build configuration list for PBXProject "NativeHello" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 05039DD2316A5B41A35E094E;
|
||||
productRefGroup = 177F2C27230B527E80CEAED5 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
F1275C1CDFD05A40B9D6C717 /* NativeHello */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
D6D57F9604C45AAEA747645E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
DF29C8DEE5CB5F1C83967F1F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4C6D9832BE495DD69A8573D9 /* main.m in Sources */,
|
||||
396FD073065B58F39358D798 /* el_uikit.m in Sources */,
|
||||
92BBC3E2AA055FCE840BF779 /* el_seed.c in Sources */,
|
||||
136C7763096C5037A69077AD /* el_runtime.c in Sources */,
|
||||
25136DF2A63F507EA5C7EF99 /* el_native_vessel.c in Sources */,
|
||||
C05D581DBD4E52F6B7F7303E /* native_hello.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
071FC6E313815E66BB76FC93 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = NO;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_EXCEPTION = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
64E467FC48E757C0BDE1DFF6 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = NO;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_EXCEPTION = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
5DAFA921569F54D081B65492 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"EL_TARGET_IOS=1",
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(SRCROOT)/NativeHello",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = NativeHello/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CFLAGS = (
|
||||
"-DEL_TARGET_IOS",
|
||||
"$(inherited)",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ldl",
|
||||
"$(inherited)",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = ai.neurontechnologies.el.nativehello;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
81898B3044815511B8C31D64 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"EL_TARGET_IOS=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(SRCROOT)/NativeHello",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = NativeHello/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_CFLAGS = (
|
||||
"-DEL_TARGET_IOS",
|
||||
"$(inherited)",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ldl",
|
||||
"$(inherited)",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = ai.neurontechnologies.el.nativehello;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
3DAB235BBB945E0CBB3EF554 /* Build configuration list for PBXProject "NativeHello" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
071FC6E313815E66BB76FC93 /* Debug */,
|
||||
64E467FC48E757C0BDE1DFF6 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
07AFC970D6B05512B39EDE6A /* Build configuration list for PBXNativeTarget "NativeHello" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5DAFA921569F54D081B65492 /* Debug */,
|
||||
81898B3044815511B8C31D64 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
};
|
||||
rootObject = D8A823993D3254C7B6541F91 /* Project object */;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F1275C1CDFD05A40B9D6C717"
|
||||
BuildableName = "NativeHello.app"
|
||||
BlueprintName = "NativeHello"
|
||||
ReferencedContainer = "container:NativeHello.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F1275C1CDFD05A40B9D6C717"
|
||||
BuildableName = "NativeHello.app"
|
||||
BlueprintName = "NativeHello"
|
||||
ReferencedContainer = "container:NativeHello.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F1275C1CDFD05A40B9D6C717"
|
||||
BuildableName = "NativeHello.app"
|
||||
BlueprintName = "NativeHello"
|
||||
ReferencedContainer = "container:NativeHello.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict/>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict/>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,573 @@
|
||||
/*
|
||||
* el_native_target.h — Native widget declarations for el programs targeting
|
||||
* native desktop UI (AppKit / GTK4 / Win32).
|
||||
*
|
||||
* This header is designed to be included AFTER el_runtime.h without conflict:
|
||||
* - It does NOT redefine el_to_float, el_from_float, or any el_runtime.h
|
||||
* static inlines.
|
||||
* - It does NOT redeclare __println, __print, or other functions whose
|
||||
* return types differ between el_seed.h and el_runtime.h.
|
||||
* - It adds: native widget builtins + float arithmetic helpers that the
|
||||
* current el_runtime.h omits but elc still emits calls to.
|
||||
*
|
||||
* Usage:
|
||||
* Inject via -include at compile time, OR #include it after el_runtime.h.
|
||||
*
|
||||
* clang -DEL_TARGET_MACOS -include el_native_target.h -c my_app.c ...
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* el_val_t must already be defined by el_runtime.h or el_seed.h. */
|
||||
#ifndef EL_VAL_T_DEFINED
|
||||
typedef int64_t el_val_t;
|
||||
#endif
|
||||
|
||||
/* ── Float arithmetic helpers ───────────────────────────────────────────────
|
||||
* elc emits calls to float_div / float_mul etc. for Float-typed expressions.
|
||||
* These were in el_runtime.c through v1.0.0-20260501 but are missing from the
|
||||
* current el_runtime.h. Redeclared here as static inline to avoid link deps.
|
||||
* Only defined if not already declared (old runtimes that still have them). */
|
||||
|
||||
#ifndef EL_FLOAT_OPS_DEFINED
|
||||
#define EL_FLOAT_OPS_DEFINED
|
||||
|
||||
/* el_to_float / el_from_float — bit-cast between el_val_t and double.
|
||||
* Defined as static inline in both el_runtime.h and el_seed.h; we do NOT
|
||||
* redefine them here. We rely on one of those headers being included first. */
|
||||
|
||||
static inline el_val_t float_div(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b;
|
||||
ur.d = (ub.d != 0.0) ? (ua.d / ub.d) : 0.0;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_mul(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d * ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_add(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d + ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_sub(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d - ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_lt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d < ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d > ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_lte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d <= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d >= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_eq(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d == ub.d);
|
||||
}
|
||||
|
||||
#endif /* EL_FLOAT_OPS_DEFINED */
|
||||
|
||||
/* ── Native widget system (macOS AppKit) ────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_MACOS and linked with el_appkit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid. */
|
||||
|
||||
#ifdef EL_TARGET_MACOS
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_MACOS */
|
||||
|
||||
/* ── Native widget system (Linux GTK4) ──────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_LINUX and linked with el_gtk4.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* All functions have the same signatures as EL_TARGET_MACOS above. */
|
||||
|
||||
#ifdef EL_TARGET_LINUX
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as EL_TARGET_MACOS */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LINUX */
|
||||
|
||||
/* ── Native widget system (Windows Win32) ───────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_WIN32 and linked with el_win32.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* Link: el_win32.obj comctl32.lib user32.lib gdi32.lib */
|
||||
|
||||
#ifdef EL_TARGET_WIN32
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_WIN32 */
|
||||
|
||||
/* ── Native widget system (iOS UIKit) ───────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_IOS and linked with el_uikit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* iOS lifecycle note: UIApplicationMain never returns. The el program must
|
||||
* store its UI-build logic in a void(*)(void) function pointer, assign it to
|
||||
* el_main_entry_fn, then call __native_run_loop. ElAppDelegate invokes
|
||||
* el_main_entry_fn inside didFinishLaunchingWithOptions.
|
||||
* Call el_uikit_set_args(argc, argv) from main() before __native_run_loop. */
|
||||
|
||||
#ifdef EL_TARGET_IOS
|
||||
|
||||
/* Lifecycle entry-function hook — set before calling __native_run_loop. */
|
||||
extern void (*el_main_entry_fn)(void);
|
||||
|
||||
/* Forward argc/argv from main() to UIApplicationMain. */
|
||||
void el_uikit_set_args(int argc, char** argv);
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_IOS */
|
||||
|
||||
/* ── Native widget system (Android JNI) ─────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_ANDROID and linked with
|
||||
* libelruntime.so (which includes el_android.c compiled by the NDK build).
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Java companion: ElBridge.java (package com.neuron.el) must be compiled into
|
||||
* the APK. The Activity must call ElBridge.init(this) before any widget ops.
|
||||
*
|
||||
* Link flags (in Android.mk or CMakeLists.txt):
|
||||
* -landroid -llog -ldl */
|
||||
|
||||
#ifdef EL_TARGET_ANDROID
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void); /* no-op on Android */
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_ANDROID */
|
||||
|
||||
/* ── Native widget system (LVGL v9 — embedded / microcontroller) ─────────────
|
||||
* Available when compiled with -DEL_TARGET_LVGL and linked with el_lvgl.c
|
||||
* and the LVGL library (lvgl.a or lvgl source tree).
|
||||
*
|
||||
* Target platforms: ESP32, STM32, industrial panels. Any system with 256KB+
|
||||
* RAM and an LVGL-compatible display driver. No OS required.
|
||||
*
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Bare-metal / no dynamic linker:
|
||||
* Compile with -DEL_LVGL_NO_DLSYM and provide:
|
||||
* el_val_t el_lvgl_dispatch(const char *fn, el_val_t a, el_val_t b);
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_LVGL -I./lvgl el_lvgl.c -c -o el_lvgl.o
|
||||
* # Then link with lvgl.a. */
|
||||
|
||||
#ifdef EL_TARGET_LVGL
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as all other native targets */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LVGL */
|
||||
|
||||
/* ── Native widget system (SDL2 — embedded / Pi) ────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_SDL2 and linked with el_sdl2.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Target: Raspberry Pi Zero, embedded Linux, any system with a framebuffer
|
||||
* and SDL2 available. No GTK, no desktop environment required.
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_SDL2 $(sdl2-config --cflags) -c el_sdl2.c -o el_sdl2.o
|
||||
* Link:
|
||||
* $(sdl2-config --libs) -lSDL2_ttf -lSDL2_image -ldl */
|
||||
|
||||
#ifdef EL_TARGET_SDL2
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_SDL2 */
|
||||
@@ -0,0 +1,459 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
el_val_t TOKEN_PRIMARY;
|
||||
el_val_t TOKEN_ON_PRIMARY;
|
||||
el_val_t TOKEN_BACKGROUND;
|
||||
el_val_t TOKEN_ON_BG;
|
||||
el_val_t TOKEN_SURFACE;
|
||||
el_val_t TOKEN_ON_SURFACE;
|
||||
el_val_t TOKEN_OUTLINE;
|
||||
el_val_t TOKEN_ERROR;
|
||||
|
||||
el_val_t native_init(void) {
|
||||
__native_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t native_run_loop(void) {
|
||||
__native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_read(el_val_t path) {
|
||||
return __manifest_read(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_title(el_val_t m) {
|
||||
return json_get_string(m, EL_STR("title"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height) {
|
||||
return __window_create(title, width, height, min_width, min_height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_from_manifest(el_val_t manifest_path) {
|
||||
el_val_t m = manifest_read(manifest_path);
|
||||
el_val_t title = manifest_title(m);
|
||||
el_val_t w = manifest_width(m);
|
||||
el_val_t h = manifest_height(m);
|
||||
el_val_t mw = manifest_min_width(m);
|
||||
el_val_t mh = manifest_min_height(m);
|
||||
el_val_t safe_w = ({ el_val_t _if_result_1 = 0; if ((w > 0)) { _if_result_1 = (w); } else { _if_result_1 = (1200); } _if_result_1; });
|
||||
el_val_t safe_h = ({ el_val_t _if_result_2 = 0; if ((h > 0)) { _if_result_2 = (h); } else { _if_result_2 = (800); } _if_result_2; });
|
||||
el_val_t safe_mw = ({ el_val_t _if_result_3 = 0; if ((mw > 0)) { _if_result_3 = (mw); } else { _if_result_3 = (600); } _if_result_3; });
|
||||
el_val_t safe_mh = ({ el_val_t _if_result_4 = 0; if ((mh > 0)) { _if_result_4 = (mh); } else { _if_result_4 = (400); } _if_result_4; });
|
||||
el_val_t safe_t = ({ el_val_t _if_result_5 = 0; if ((str_len(title) > 0)) { _if_result_5 = (title); } else { _if_result_5 = (EL_STR("App")); } _if_result_5; });
|
||||
return window_create(safe_t, safe_w, safe_h, safe_mw, safe_mh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_show(el_val_t handle) {
|
||||
__window_show(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title) {
|
||||
__window_set_title(handle, title);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack(el_val_t spacing) {
|
||||
return __vstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack_tight(void) {
|
||||
return __vstack_create(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hstack(el_val_t spacing) {
|
||||
return __hstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t zstack(void) {
|
||||
return __zstack_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t scroll(void) {
|
||||
return __scroll_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t label(el_val_t text) {
|
||||
return __label_create(text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t button(el_val_t label) {
|
||||
return __button_create(label);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_field(el_val_t placeholder) {
|
||||
return __text_field_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_area(el_val_t placeholder) {
|
||||
return __text_area_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t image(el_val_t path_or_name) {
|
||||
return __image_create(path_or_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text) {
|
||||
__widget_set_text(handle, text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_get_text(el_val_t handle) {
|
||||
return __widget_get_text(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_bg_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold) {
|
||||
el_val_t bold_int = ({ el_val_t _if_result_6 = 0; if (bold) { _if_result_6 = (1); } else { _if_result_6 = (0); } _if_result_6; });
|
||||
__widget_set_font(handle, family, size, bold_int);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left) {
|
||||
__widget_set_padding(handle, top, right, bottom, left);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p) {
|
||||
__widget_set_padding(handle, p, p, p, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py) {
|
||||
__widget_set_padding(handle, py, px, py, px);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width) {
|
||||
__widget_set_width(handle, width);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height) {
|
||||
__widget_set_height(handle, height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
__widget_set_flex(handle, flex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
__widget_set_corner_radius(handle, radius);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_val_t d = ({ el_val_t _if_result_7 = 0; if (disabled) { _if_result_7 = (1); } else { _if_result_7 = (0); } _if_result_7; });
|
||||
__widget_set_disabled(handle, d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_val_t h = ({ el_val_t _if_result_8 = 0; if (hidden) { _if_result_8 = (1); } else { _if_result_8 = (0); } _if_result_8; });
|
||||
__widget_set_hidden(handle, h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child) {
|
||||
__widget_add_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
__widget_remove_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_destroy(el_val_t handle) {
|
||||
__widget_destroy(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_click(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_change(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_submit(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset) {
|
||||
el_val_t hi = str_slice(s, offset, (offset + 1));
|
||||
el_val_t lo = str_slice(s, (offset + 1), (offset + 2));
|
||||
el_val_t h = hex_nibble(hi);
|
||||
el_val_t l = hex_nibble(lo);
|
||||
return ((h * 16) + l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_nibble(el_val_t c) {
|
||||
if (str_eq(c, EL_STR("0"))) {
|
||||
return 0;
|
||||
}
|
||||
if (str_eq(c, EL_STR("1"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(c, EL_STR("2"))) {
|
||||
return 2;
|
||||
}
|
||||
if (str_eq(c, EL_STR("3"))) {
|
||||
return 3;
|
||||
}
|
||||
if (str_eq(c, EL_STR("4"))) {
|
||||
return 4;
|
||||
}
|
||||
if (str_eq(c, EL_STR("5"))) {
|
||||
return 5;
|
||||
}
|
||||
if (str_eq(c, EL_STR("6"))) {
|
||||
return 6;
|
||||
}
|
||||
if (str_eq(c, EL_STR("7"))) {
|
||||
return 7;
|
||||
}
|
||||
if (str_eq(c, EL_STR("8"))) {
|
||||
return 8;
|
||||
}
|
||||
if (str_eq(c, EL_STR("9"))) {
|
||||
return 9;
|
||||
}
|
||||
if (str_eq(c, EL_STR("a"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("b"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("c"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("d"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("e"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("f"))) {
|
||||
return 15;
|
||||
}
|
||||
if (str_eq(c, EL_STR("A"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("B"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("C"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("D"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("E"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("F"))) {
|
||||
return 15;
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_r(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_9 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_9 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_9 = (hex); } _if_result_9; });
|
||||
el_val_t v = hex_channel(s, 0);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_g(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_10 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_10 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_10 = (hex); } _if_result_10; });
|
||||
el_val_t v = hex_channel(s, 2);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_b(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_11 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_11 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_11 = (hex); } _if_result_11; });
|
||||
el_val_t v = hex_channel(s, 4);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_a(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_12 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_12 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_12 = (hex); } _if_result_12; });
|
||||
if (str_len(s) < 8) {
|
||||
return el_from_float(1.0);
|
||||
}
|
||||
el_val_t v = hex_channel(s, 6);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_bg_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_surface(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_SURFACE);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_button_primary(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_PRIMARY);
|
||||
widget_set_color_hex(handle, TOKEN_ON_PRIMARY);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
widget_set_padding_xy(handle, 16, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_body(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 14, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_heading(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 20, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_muted(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_OUTLINE);
|
||||
widget_set_font(handle, EL_STR("system"), 12, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int el_vessel_main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
TOKEN_PRIMARY = EL_STR("#60a5fa");
|
||||
TOKEN_ON_PRIMARY = EL_STR("#0f172a");
|
||||
TOKEN_BACKGROUND = EL_STR("#0f172a");
|
||||
TOKEN_ON_BG = EL_STR("#f8fafc");
|
||||
TOKEN_SURFACE = EL_STR("#1e293b");
|
||||
TOKEN_ON_SURFACE = EL_STR("#f8fafc");
|
||||
TOKEN_OUTLINE = EL_STR("#475569");
|
||||
TOKEN_ERROR = EL_STR("#f87171");
|
||||
return 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,883 @@
|
||||
/*
|
||||
* el_runtime.h — El language C runtime header
|
||||
*
|
||||
* Declares all built-in functions available to compiled El programs.
|
||||
* Include this in every generated .c file.
|
||||
*
|
||||
* Value model:
|
||||
* All El values are represented as el_val_t (= int64_t).
|
||||
* On 64-bit systems a pointer fits in int64_t.
|
||||
* String values are cast: (el_val_t)(uintptr_t)"hello"
|
||||
* Integer values are stored directly.
|
||||
* This lets arithmetic work naturally while still passing strings around.
|
||||
*
|
||||
* Type conventions (El -> C):
|
||||
* String -> el_val_t (holds const char* via uintptr_t cast)
|
||||
* Int -> el_val_t
|
||||
* Bool -> el_val_t (0 = false, nonzero = true)
|
||||
* Any -> el_val_t
|
||||
* Void -> void
|
||||
*
|
||||
* Macros for convenience:
|
||||
* EL_STR(s) cast string literal to el_val_t
|
||||
* EL_CSTR(v) cast el_val_t back to const char*
|
||||
* EL_INT(v) identity — el_val_t is already int64_t
|
||||
* EL_NULL null / zero value
|
||||
* EL_FALSE boolean false (0)
|
||||
* EL_TRUE boolean true (1)
|
||||
*
|
||||
* Link requirements:
|
||||
* -lcurl — required for the HTTP client (http_get, http_post, llm_*).
|
||||
* -lpthread — required for the HTTP server (one detached thread per
|
||||
* connection, capped at 64 concurrent).
|
||||
* -loqs — optional; required only when liboqs is installed and the
|
||||
* pq_* / sha3_256_hex entry points are needed. Detected at
|
||||
* compile time via __has_include(<oqs/oqs.h>).
|
||||
* -lcrypto — optional; pulled in alongside -loqs. Used for X25519 in
|
||||
* pq_hybrid_* and HKDF-SHA256 derivation.
|
||||
*
|
||||
* Canonical compile command:
|
||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
||||
*
|
||||
* With liboqs (post-quantum stack):
|
||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread -loqs -lcrypto \
|
||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
||||
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
||||
#define EL_INT(v) (v)
|
||||
#define EL_NULL ((el_val_t)0)
|
||||
#define EL_FALSE ((el_val_t)0)
|
||||
#define EL_TRUE ((el_val_t)1)
|
||||
|
||||
/* Float values share the el_val_t (int64) slot via a bit-cast.
|
||||
* The codegen emits Float literals as `el_from_float(<dbl>)` so the
|
||||
* underlying bits represent the IEEE 754 double. Float-aware builtins
|
||||
* (math, format, json) round-trip via these helpers. */
|
||||
static inline double el_to_float(el_val_t v) {
|
||||
union { int64_t i; double f; } u;
|
||||
u.i = (int64_t)v;
|
||||
return u.f;
|
||||
}
|
||||
|
||||
static inline el_val_t el_from_float(double f) {
|
||||
union { double f; int64_t i; } u;
|
||||
u.f = f;
|
||||
return (el_val_t)u.i;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ── I/O ──────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t println(el_val_t s);
|
||||
el_val_t print(el_val_t s);
|
||||
el_val_t readline(void);
|
||||
|
||||
/* ── String builtins ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t str_eq(el_val_t a, el_val_t b);
|
||||
el_val_t str_starts_with(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_ends_with(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_len(el_val_t s);
|
||||
el_val_t str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t int_to_str(el_val_t n);
|
||||
el_val_t str_to_int(el_val_t s);
|
||||
el_val_t native_str_to_int(el_val_t s);
|
||||
el_val_t str_slice(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t str_contains(el_val_t s, el_val_t sub);
|
||||
el_val_t str_replace(el_val_t s, el_val_t from, el_val_t to);
|
||||
el_val_t str_to_upper(el_val_t s);
|
||||
el_val_t str_to_lower(el_val_t s);
|
||||
el_val_t str_trim(el_val_t s);
|
||||
|
||||
/* ── Math ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_abs(el_val_t n);
|
||||
el_val_t el_max(el_val_t a, el_val_t b);
|
||||
el_val_t el_min(el_val_t a, el_val_t b);
|
||||
|
||||
/* ── Refcount (ARC) ──────────────────────────────────────────────────────────
|
||||
* Lists and Maps carry a refcount. Strings and ints do not — el_retain and
|
||||
* el_release are safe no-ops on non-refcounted values (they sniff a magic
|
||||
* header at offset 0 and only act if the magic matches).
|
||||
*
|
||||
* Codegen emits these at let-binding shadowing, function entry (params), and
|
||||
* function exit (locals other than the returned value). The refcount lets
|
||||
* el_list_append and el_map_set mutate in place when uniquely owned (cheap)
|
||||
* and copy-on-write when shared (preserves persistent semantics across
|
||||
* accumulator patterns in the compiler itself). */
|
||||
|
||||
void el_retain(el_val_t v);
|
||||
void el_release(el_val_t v);
|
||||
|
||||
/* ── Scoped arena (CLI use) ───────────────────────────────────────────────── */
|
||||
el_val_t el_arena_push(void);
|
||||
el_val_t el_arena_pop(el_val_t mark);
|
||||
|
||||
/* ── List ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_list_new(el_val_t count, ...);
|
||||
el_val_t el_list_len(el_val_t list);
|
||||
el_val_t el_list_get(el_val_t list, el_val_t index);
|
||||
el_val_t el_list_append(el_val_t list, el_val_t elem);
|
||||
el_val_t el_list_empty(void);
|
||||
el_val_t el_list_clone(el_val_t list);
|
||||
|
||||
/* ── Map ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_map_new(el_val_t pair_count, ...);
|
||||
el_val_t el_get_field(el_val_t map, el_val_t key);
|
||||
el_val_t el_map_get(el_val_t map, el_val_t key);
|
||||
el_val_t el_map_set(el_val_t map, el_val_t key, el_val_t value);
|
||||
|
||||
/* ── HTTP ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t http_get(el_val_t url);
|
||||
el_val_t http_post(el_val_t url, el_val_t body);
|
||||
el_val_t http_post_json(el_val_t url, el_val_t json_body);
|
||||
el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map);
|
||||
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
|
||||
el_val_t http_post_json_with_headers(el_val_t url, el_val_t headers_map, el_val_t json_body);
|
||||
el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
|
||||
el_val_t http_delete(el_val_t url);
|
||||
el_val_t http_serve(el_val_t port, el_val_t handler);
|
||||
el_val_t http_set_handler(el_val_t name);
|
||||
|
||||
/* HTTP server v2 ─────────────────────────────────────────────────────────────
|
||||
* Same dispatch model as http_serve, but the handler signature is widened:
|
||||
*
|
||||
* el_val_t handler(method, path, headers_map, body)
|
||||
*
|
||||
* `headers_map` is an ElMap from lowercased header name → header value (both
|
||||
* Strings). Repeated headers are joined with ", " per RFC 7230.
|
||||
*
|
||||
* Response value: the handler may return either
|
||||
* (a) a plain body string — same auto-content-type / 200-OK behaviour as
|
||||
* http_serve (3-arg) — or
|
||||
* (b) a response envelope built with `http_response(status, headers_json,
|
||||
* body)`. The runtime detects the envelope discriminator
|
||||
* `"el_http_response":1` at the start of the returned string and
|
||||
* unpacks status / headers / body before sending.
|
||||
*
|
||||
* The 3-arg http_serve(port, handler) remains supported unchanged for
|
||||
* existing handlers (e.g. products/web/server.el): it dispatches with
|
||||
* (method, path, body), hardcodes 200 OK, and auto-detects content type. */
|
||||
el_val_t http_serve_v2(el_val_t port, el_val_t handler);
|
||||
el_val_t http_set_handler_v2(el_val_t name);
|
||||
|
||||
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
||||
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
||||
* returned string carries the discriminator `{"el_http_response":1,...}`
|
||||
* which the runtime's send-path detects and unpacks. Detection happens
|
||||
* uniformly inside http_send_response, so a 3-arg handler may also return
|
||||
* an envelope. The 3-arg variant remains documented as a fixed 200-OK
|
||||
* auto-content-type contract for legacy handlers that return plain bodies. */
|
||||
el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
|
||||
/* SSE connection fd — set by http_worker_v2 before calling the El handler,
|
||||
* cleared afterwards. Defined in el_seed.c; called from el_runtime.c.
|
||||
* The getter is exposed as __http_conn_fd() to El programs. */
|
||||
void el_seed_set_http_conn_fd(int fd);
|
||||
|
||||
/* HTTP timeout — every libcurl request honors EL_HTTP_TIMEOUT_MS (default
|
||||
* 60000ms). Read lazily on first use, so setting the env var any time before
|
||||
* the first http_* call is sufficient. */
|
||||
|
||||
/* Streaming variants — write the response body straight to a file via
|
||||
* libcurl's CURLOPT_WRITEFUNCTION = fwrite. These bypass the el_val_t string
|
||||
* wrapper entirely, so binary payloads (audio/mpeg, image/png, etc.) survive
|
||||
* embedded NUL bytes that would truncate a strlen()-based code path.
|
||||
*
|
||||
* Both honor EL_HTTP_TIMEOUT_MS, follow redirects, and accept the same
|
||||
* `headers_map` shape as http_post_with_headers (ElMap of String→String).
|
||||
*
|
||||
* Return value: 1 on success (file fully written), 0 on any failure
|
||||
* (network, file open, partial write). On failure the output file is removed
|
||||
* so callers cannot mistake a partially-written file for a valid one. */
|
||||
el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el_val_t output_path);
|
||||
el_val_t http_get_to_file(el_val_t url, el_val_t headers_map, el_val_t output_path);
|
||||
|
||||
/* ── URL encoding ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t url_encode(el_val_t s); /* RFC 3986 unreserved set */
|
||||
el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */
|
||||
|
||||
/* ── HTML allowlist sanitizer ────────────────────────────────────────────────
|
||||
* el_html_sanitize(input_html, allowlist_json) — strict allowlist HTML
|
||||
* cleaner. State-machine parser; tag/attribute names compared case-
|
||||
* insensitively against the allowlist; `<a href>` / `<… src>` URL schemes
|
||||
* validated (http, https, mailto, fragment-only, or relative); whole-
|
||||
* subtree drop for script / style / iframe / object / embed / form; HTML-
|
||||
* escapes free text outside dropped subtrees.
|
||||
*
|
||||
* The allowlist is JSON of the form
|
||||
* {"p":[],"a":["href","title"],"strong":[],...}
|
||||
* where each value is the array of attribute names allowed for that tag. */
|
||||
el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json);
|
||||
el_val_t html_raw(el_val_t s);
|
||||
el_val_t html_escape(el_val_t s);
|
||||
|
||||
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t fs_read(el_val_t path);
|
||||
el_val_t fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t fs_list(el_val_t path);
|
||||
el_val_t fs_list_json(el_val_t path);
|
||||
el_val_t fs_exists(el_val_t path);
|
||||
el_val_t fs_mkdir(el_val_t path); /* mkdir -p, mode 0755 */
|
||||
|
||||
/* Length-explicit binary write. `length` is an Int (el_val_t holding the
|
||||
* byte count). The caller knows the length from context — typically because
|
||||
* `bytes` came from base64_decode (which produces a magic-tagged binary
|
||||
* buffer with embedded NULs possible) and the caller already tracks the
|
||||
* decoded length, OR because the bytes came from a fixed-size source
|
||||
* (sha256_bytes = 32, hmac_sha256_bytes = 32). Bypasses strlen entirely.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure (invalid path, can't open, partial
|
||||
* write, negative length). On partial-write failure, the file is removed
|
||||
* so callers cannot read back a truncated artefact. */
|
||||
el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t length);
|
||||
|
||||
/* ── JSON ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t json_get(el_val_t json, el_val_t key);
|
||||
el_val_t json_parse(el_val_t s);
|
||||
el_val_t json_stringify(el_val_t v);
|
||||
el_val_t json_get_string(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_int(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_float(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_bool(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_raw(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value);
|
||||
el_val_t json_array_len(el_val_t json_str);
|
||||
el_val_t json_array_get(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_array_get_string(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_escape_string(el_val_t sv);
|
||||
el_val_t json_build_object(el_val_t kvs);
|
||||
el_val_t json_build_array(el_val_t items);
|
||||
|
||||
/* ── Time ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t time_now(void);
|
||||
el_val_t time_now_utc(void);
|
||||
el_val_t sleep_secs(el_val_t secs);
|
||||
el_val_t sleep_ms(el_val_t ms);
|
||||
el_val_t time_format(el_val_t ts, el_val_t fmt);
|
||||
el_val_t time_to_parts(el_val_t ts);
|
||||
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
|
||||
el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit);
|
||||
el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
|
||||
el_val_t now_ns(void);
|
||||
|
||||
/* ── Instant + Duration: first-class temporal types ──────────────────────────
|
||||
* Both types share the el_val_t (int64) slot. Instants are nanoseconds
|
||||
* since the Unix epoch; Durations are signed nanoseconds. Type discipline
|
||||
* is enforced at codegen-time: BinOps on names registered as Instant or
|
||||
* Duration route through the typed wrappers below; mismatches like
|
||||
* Instant+Instant become #error at the C compiler.
|
||||
*
|
||||
* Postfix literals — `30.seconds`, `1.hour`, `500.millis`, `30.nanos` — are
|
||||
* recognised by the parser as DurationLit AST nodes and lowered to literal
|
||||
* int64 nanoseconds at codegen time. The runtime never sees the units. */
|
||||
|
||||
el_val_t el_now_instant(void);
|
||||
el_val_t now(void);
|
||||
el_val_t unix_seconds(el_val_t n);
|
||||
el_val_t unix_millis(el_val_t n);
|
||||
el_val_t instant_from_iso8601(el_val_t s);
|
||||
|
||||
el_val_t el_duration_from_nanos(el_val_t ns);
|
||||
el_val_t duration_seconds(el_val_t n);
|
||||
el_val_t duration_millis(el_val_t n);
|
||||
el_val_t duration_nanos(el_val_t n);
|
||||
|
||||
el_val_t el_instant_add_dur(el_val_t inst, el_val_t dur);
|
||||
el_val_t el_instant_sub_dur(el_val_t inst, el_val_t dur);
|
||||
el_val_t el_instant_diff(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_add(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_sub(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_scale(el_val_t dur, el_val_t scalar);
|
||||
el_val_t el_duration_div(el_val_t dur, el_val_t scalar);
|
||||
|
||||
el_val_t el_instant_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_le(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_gt(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_ge(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_eq(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_ne(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_le(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_gt(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_ge(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_eq(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_ne(el_val_t a, el_val_t b);
|
||||
|
||||
el_val_t instant_to_unix_seconds(el_val_t i);
|
||||
el_val_t instant_to_unix_millis(el_val_t i);
|
||||
el_val_t instant_to_iso8601(el_val_t i);
|
||||
el_val_t duration_to_seconds(el_val_t d);
|
||||
el_val_t duration_to_millis(el_val_t d);
|
||||
el_val_t duration_to_nanos(el_val_t d);
|
||||
|
||||
el_val_t el_sleep_duration(el_val_t dur);
|
||||
el_val_t unix_timestamp(void);
|
||||
|
||||
el_val_t ttl_cache_set(el_val_t key, el_val_t value);
|
||||
el_val_t ttl_cache_get(el_val_t key, el_val_t max_age);
|
||||
el_val_t ttl_cache_age(el_val_t key);
|
||||
|
||||
/* ── Calendar + CalendarTime + Rhythm + LocalDate/Time/DateTime ─────────────
|
||||
* Phase 1.5 of the time system. Calendar is pluggable: EarthCalendar (IANA
|
||||
* zones, Gregorian, DST) is the user-facing default; MarsCalendar,
|
||||
* CycleCalendar(period), NoCycleCalendar, RelativeCalendar handle non-Earth
|
||||
* domains.
|
||||
*
|
||||
* A Calendar interprets an Instant under a particular cycle convention and
|
||||
* produces a CalendarTime. CalendarTime carries the underlying Instant and
|
||||
* a back-pointer to its Calendar; arithmetic and formatting consult the
|
||||
* Calendar to convert ns since epoch into year/month/day/hour/minute/second
|
||||
* (or sol/phase, or cycle/phase, depending on kind).
|
||||
*
|
||||
* Storage convention: Calendar / CalendarTime / Rhythm / LocalDate /
|
||||
* LocalDateTime are heap-allocated structs whose pointers are cast into
|
||||
* el_val_t. A 24-bit magic header at offset 0 lets the runtime identify
|
||||
* the kind safely. LocalTime is small enough to live in the int64 slot
|
||||
* directly (nanos since midnight, signed). */
|
||||
|
||||
/* Zone — opaque IANA zone or fixed offset, used by EarthCalendar.
|
||||
* `zone_id` is either an IANA name ("America/New_York", "UTC") or a fixed
|
||||
* offset string ("+05:30", "-08:00"). The runtime resolves it via tzset()
|
||||
* on first use of the owning EarthCalendar. */
|
||||
el_val_t zone(el_val_t id);
|
||||
el_val_t zone_utc(void);
|
||||
el_val_t zone_local(void);
|
||||
el_val_t zone_offset(el_val_t hours, el_val_t minutes);
|
||||
|
||||
/* Calendar constructors. Each returns an el_val_t pointer to a heap-
|
||||
* allocated, magic-tagged Calendar struct. Calendars are interned by
|
||||
* (kind, zone_id, period_ns, epoch_ns) so identical constructors return
|
||||
* the same pointer — equality is reference equality. */
|
||||
el_val_t earth_calendar(el_val_t z);
|
||||
el_val_t earth_calendar_default(void);
|
||||
el_val_t mars_calendar(void);
|
||||
el_val_t cycle_calendar(el_val_t period_dur);
|
||||
el_val_t no_cycle_calendar(void);
|
||||
el_val_t relative_calendar(el_val_t epoch_inst);
|
||||
|
||||
/* CalendarTime constructors and methods. Returns a heap-allocated struct
|
||||
* whose pointer fits in el_val_t. */
|
||||
el_val_t now_in(el_val_t cal);
|
||||
el_val_t in_calendar(el_val_t inst, el_val_t cal);
|
||||
el_val_t cal_format(el_val_t ct, el_val_t pattern);
|
||||
el_val_t cal_to_instant(el_val_t ct);
|
||||
el_val_t cal_cycle_phase(el_val_t ct);
|
||||
el_val_t cal_in(el_val_t ct, el_val_t cal);
|
||||
|
||||
/* LocalDate / LocalTime / LocalDateTime — calendar-agnostic value types.
|
||||
* LocalTime carries nanoseconds since midnight as a signed int64 directly
|
||||
* in the el_val_t slot (no allocation). LocalDate / LocalDateTime are
|
||||
* heap-allocated structs with magic headers. */
|
||||
el_val_t local_date(el_val_t y, el_val_t m, el_val_t d);
|
||||
el_val_t local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns);
|
||||
el_val_t local_datetime(el_val_t date, el_val_t time);
|
||||
el_val_t zoned(el_val_t date, el_val_t time, el_val_t cal);
|
||||
|
||||
el_val_t local_date_year(el_val_t ld);
|
||||
el_val_t local_date_month(el_val_t ld);
|
||||
el_val_t local_date_day(el_val_t ld);
|
||||
el_val_t local_time_hour(el_val_t lt);
|
||||
el_val_t local_time_minute(el_val_t lt);
|
||||
el_val_t local_time_second(el_val_t lt);
|
||||
el_val_t local_time_nanos(el_val_t lt);
|
||||
|
||||
el_val_t el_local_date_add_dur(el_val_t ld, el_val_t dur);
|
||||
el_val_t el_local_time_add_dur(el_val_t lt, el_val_t dur);
|
||||
el_val_t el_local_date_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_local_date_eq(el_val_t a, el_val_t b);
|
||||
|
||||
/* Rhythm — pluggable recurrence AST. Returns a heap-allocated struct
|
||||
* pointer in el_val_t; rhythms are immutable so callers may share them. */
|
||||
el_val_t rhythm_cycle_start(void);
|
||||
el_val_t rhythm_cycle_phase(el_val_t phase);
|
||||
el_val_t rhythm_duration(el_val_t d);
|
||||
el_val_t rhythm_session_start(void);
|
||||
el_val_t rhythm_event(el_val_t name);
|
||||
el_val_t rhythm_and(el_val_t a, el_val_t b);
|
||||
el_val_t rhythm_or(el_val_t a, el_val_t b);
|
||||
el_val_t rhythm_weekday(el_val_t day);
|
||||
el_val_t rhythm_weekly_at(el_val_t day, el_val_t hour, el_val_t minute);
|
||||
el_val_t rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal);
|
||||
el_val_t rhythm_matches(el_val_t r, el_val_t ct);
|
||||
|
||||
/* ── UUID ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t uuid_new(void);
|
||||
el_val_t uuid_v4(void);
|
||||
|
||||
/* ── Environment ─────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t env(el_val_t key);
|
||||
|
||||
/* ── In-process state K/V ────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t state_set(el_val_t key, el_val_t value);
|
||||
el_val_t state_get(el_val_t key);
|
||||
el_val_t state_del(el_val_t key);
|
||||
el_val_t state_keys(void);
|
||||
el_val_t state_has(el_val_t key);
|
||||
el_val_t state_get_or(el_val_t key, el_val_t default_val);
|
||||
|
||||
/* ── Float formatting ────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t float_to_str(el_val_t f);
|
||||
el_val_t int_to_float(el_val_t n);
|
||||
el_val_t float_to_int(el_val_t f);
|
||||
el_val_t format_float(el_val_t f, el_val_t decimals);
|
||||
el_val_t decimal_round(el_val_t f, el_val_t decimals);
|
||||
el_val_t str_to_float(el_val_t s);
|
||||
|
||||
/* ── Math (Float-aware) ──────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t math_sqrt(el_val_t f);
|
||||
el_val_t math_log(el_val_t f);
|
||||
el_val_t math_ln(el_val_t f);
|
||||
el_val_t math_sin(el_val_t f);
|
||||
el_val_t math_cos(el_val_t f);
|
||||
el_val_t math_pi(void);
|
||||
|
||||
/* ── String additions ────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t str_index_of(el_val_t s, el_val_t sub);
|
||||
el_val_t str_split(el_val_t s, el_val_t sep);
|
||||
el_val_t str_char_at(el_val_t s, el_val_t i);
|
||||
el_val_t str_char_code(el_val_t s, el_val_t i);
|
||||
el_val_t str_pad_left(el_val_t s, el_val_t width, el_val_t pad);
|
||||
el_val_t str_pad_right(el_val_t s, el_val_t width, el_val_t pad);
|
||||
el_val_t str_format(el_val_t fmt, el_val_t data);
|
||||
el_val_t str_lower(el_val_t s);
|
||||
el_val_t str_upper(el_val_t s);
|
||||
|
||||
/* ── Text-processing primitives (Phase 1: byte/codepoint, ASCII char classes)
|
||||
* Phase 2 (filed): Unicode-grapheme awareness, NFC/NFD normalization, regex.
|
||||
* is_* predicates: empty input returns false; multi-char requires ALL bytes
|
||||
* to match. ASCII ranges only in Phase 1. */
|
||||
|
||||
/* Counting */
|
||||
el_val_t str_count(el_val_t s, el_val_t sub); /* non-overlapping */
|
||||
el_val_t str_count_chars(el_val_t s); /* codepoint count */
|
||||
el_val_t str_count_bytes(el_val_t s); /* alias of str_len */
|
||||
el_val_t str_count_lines(el_val_t s);
|
||||
el_val_t str_count_words(el_val_t s);
|
||||
el_val_t str_count_letters(el_val_t s); /* ASCII [A-Za-z] */
|
||||
el_val_t str_count_digits(el_val_t s); /* ASCII [0-9] */
|
||||
|
||||
/* Find / position */
|
||||
el_val_t str_index_of_all(el_val_t s, el_val_t sub); /* [Int] of byte offsets */
|
||||
el_val_t str_last_index_of(el_val_t s, el_val_t sub);
|
||||
el_val_t str_find_chars(el_val_t s, el_val_t any_of); /* first idx of any ch */
|
||||
|
||||
/* Transform */
|
||||
el_val_t str_repeat(el_val_t s, el_val_t n);
|
||||
el_val_t str_reverse(el_val_t s); /* by codepoint */
|
||||
el_val_t str_strip_prefix(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_strip_suffix(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_strip_chars(el_val_t s, el_val_t chars);
|
||||
el_val_t str_lstrip(el_val_t s);
|
||||
el_val_t str_rstrip(el_val_t s);
|
||||
|
||||
/* Char classification (Bool) */
|
||||
el_val_t is_letter(el_val_t s);
|
||||
el_val_t is_digit(el_val_t s);
|
||||
el_val_t is_alphanumeric(el_val_t s);
|
||||
el_val_t is_whitespace(el_val_t s);
|
||||
el_val_t is_punctuation(el_val_t s);
|
||||
el_val_t is_uppercase(el_val_t s);
|
||||
el_val_t is_lowercase(el_val_t s);
|
||||
|
||||
/* Split / join */
|
||||
el_val_t str_split_lines(el_val_t s);
|
||||
el_val_t str_split_chars(el_val_t s); /* alias of native_string_chars */
|
||||
el_val_t str_split_n(el_val_t s, el_val_t sep, el_val_t n);
|
||||
el_val_t str_join(el_val_t list, el_val_t sep); /* alias of list_join */
|
||||
|
||||
/* ── List additions ──────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t list_push(el_val_t list, el_val_t elem);
|
||||
el_val_t list_push_front(el_val_t list, el_val_t elem);
|
||||
el_val_t list_join(el_val_t list, el_val_t sep);
|
||||
el_val_t list_range(el_val_t start, el_val_t end);
|
||||
|
||||
/* ── Bool helpers ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t bool_to_str(el_val_t b);
|
||||
|
||||
/* ── Numeric parsing ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t parse_int(el_val_t s, el_val_t default_val);
|
||||
|
||||
/* ── Process ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t exit_program(el_val_t code);
|
||||
el_val_t getpid_now(void);
|
||||
|
||||
/* Self-terminating memory guard. Reads ELC_MAX_MEM_MB (default 512) and
|
||||
* exits with code 1 if resident memory exceeds the limit. Call periodically
|
||||
* during long compilation loops (e.g. after each function is compiled).
|
||||
* Returns 0 when memory is within bounds. */
|
||||
el_val_t el_mem_check(void);
|
||||
|
||||
/* ── CGI identity ─────────────────────────────────────────────────────────────
|
||||
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
|
||||
* Records the program's DHARMA identity before any other code executes. */
|
||||
|
||||
void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
el_val_t network, el_val_t engram);
|
||||
|
||||
/* ── DHARMA network builtins ─────────────────────────────────────────────────
|
||||
* Available to CGI programs (declared with a `cgi {}` block).
|
||||
*
|
||||
* Peers are addressed by `dharma_id` of the form
|
||||
* "<registry-id>@<transport-url>" e.g. "ntn-genesis@http://localhost:7770"
|
||||
* If the @<url> portion is omitted, transport defaults to
|
||||
* "http://localhost:7770" (the local CGI daemon assumption).
|
||||
*
|
||||
* Wire protocol (all peers expose):
|
||||
* POST <url>/dharma/recv { channel, from, content } → response body
|
||||
* POST <url>/dharma/event { type, payload, source, timestamp }
|
||||
* POST <url>/api/activate { query } → list of nodes
|
||||
*
|
||||
* Hosting application's responsibility: an El program with a `cgi {}` block
|
||||
* runs http_serve() with its own request handler; that handler should route
|
||||
* "/dharma/event" requests by calling el_runtime_dharma_event_arrive() so
|
||||
* incoming events feed dharma_field() queues. The runtime itself does not
|
||||
* intercept any /dharma path. */
|
||||
|
||||
el_val_t dharma_connect(el_val_t cgi_id);
|
||||
el_val_t dharma_send(el_val_t channel, el_val_t content);
|
||||
el_val_t dharma_activate(el_val_t query);
|
||||
void dharma_emit(el_val_t event_type, el_val_t payload);
|
||||
el_val_t dharma_field(el_val_t event_type);
|
||||
void dharma_strengthen(el_val_t cgi_id, el_val_t weight);
|
||||
el_val_t dharma_relationship(el_val_t cgi_id);
|
||||
el_val_t dharma_peers(void);
|
||||
|
||||
/* Public C API: called by an El program's HTTP handler when a /dharma/event
|
||||
* request arrives. Pushes onto the per-event-type queue and signals any
|
||||
* pending dharma_field() blockers. All three arguments must be NUL-terminated
|
||||
* C strings (or NULL — then treated as empty). */
|
||||
void el_runtime_dharma_event_arrive(const char* event_type,
|
||||
const char* payload,
|
||||
const char* source);
|
||||
|
||||
/* ── Engram local graph primitives ───────────────────────────────────────────
|
||||
* Operate on the CGI's local Engram knowledge graph.
|
||||
* `engram_activate` queries the local graph only; `dharma_activate` is
|
||||
* network-wide across all connected CGI graphs. */
|
||||
|
||||
el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience);
|
||||
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t importance, el_val_t confidence,
|
||||
el_val_t tier, el_val_t tags);
|
||||
/* Layered consciousness — see el_runtime.c for the layered architecture
|
||||
* design notes (search "Layered consciousness architecture"). The five
|
||||
* canonical layers (safety / core-identity / domain-knowledge / imprint /
|
||||
* suit) are seeded automatically; engram_add_layer extends the registry
|
||||
* with imprint or suit overlays at runtime. Nodes default to layer 1
|
||||
* (core-identity) when created via engram_node / engram_node_full. */
|
||||
el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t certainty, el_val_t confidence,
|
||||
el_val_t status, el_val_t tags, el_val_t layer_id);
|
||||
el_val_t engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible,
|
||||
el_val_t transparent, el_val_t injectable);
|
||||
el_val_t engram_remove_layer(el_val_t layer_id);
|
||||
el_val_t engram_list_layers(void);
|
||||
el_val_t engram_get_node(el_val_t id);
|
||||
void engram_strengthen(el_val_t node_id);
|
||||
void engram_forget(el_val_t node_id);
|
||||
el_val_t engram_node_count(void);
|
||||
el_val_t engram_search(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||
void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation);
|
||||
el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id);
|
||||
el_val_t engram_neighbors(el_val_t node_id);
|
||||
el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t engram_edge_count(void);
|
||||
/* Three-pass activation: background fan-out → working-memory promotion →
|
||||
* Layer 0 override. See "Three-pass activation" in el_runtime.c. */
|
||||
el_val_t engram_activate(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_save(el_val_t path);
|
||||
el_val_t engram_load(el_val_t path);
|
||||
|
||||
/* JSON-string accessors — return pre-serialized JSON so HTTP handlers
|
||||
* can pass results straight through without round-tripping ElList/ElMap
|
||||
* through json_stringify. */
|
||||
el_val_t engram_get_node_json(el_val_t id);
|
||||
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_stats_json(void);
|
||||
el_val_t engram_list_layers_json(void);
|
||||
/* engram_compile_layered_json — produce a prompt-ready text block split
|
||||
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
||||
* and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
|
||||
* no nodes promoted to working memory. */
|
||||
el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
||||
|
||||
/* ── LLM (Anthropic API client) ─────────────────────────────────────────────
|
||||
* All functions call https://api.anthropic.com/v1/messages with the API key
|
||||
* from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */
|
||||
|
||||
el_val_t llm_call(el_val_t model, el_val_t prompt);
|
||||
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt);
|
||||
el_val_t llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools);
|
||||
el_val_t llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64);
|
||||
el_val_t llm_models(void);
|
||||
|
||||
/* Register a tool handler by name. The handler is looked up via dlsym
|
||||
* (mirroring http_set_handler), so any El `fn <name>(input)` compiles to
|
||||
* a global C symbol that this function can locate at runtime.
|
||||
* Handler signature: `el_val_t handler(el_val_t input_json)` — receives
|
||||
* the tool input as a JSON-string el_val_t and returns a JSON-string
|
||||
* el_val_t result. Used by llm_call_agentic. */
|
||||
void llm_register_tool(el_val_t name, el_val_t handler_fn_name);
|
||||
|
||||
/* ── args() ─────────────────────────────────────────────────────────────────
|
||||
* Provides access to command-line arguments passed to the program.
|
||||
* Populated by el_runtime_init_args() before main() runs. */
|
||||
|
||||
el_val_t args(void);
|
||||
void el_runtime_init_args(int argc, char** argv);
|
||||
|
||||
/* ── Crypto primitives ─────────────────────────────────────────────────────
|
||||
* SHA-256, HMAC-SHA-256, and base64 (standard + URL-safe).
|
||||
* Self-contained — no OpenSSL/libcrypto dependency. The implementations are
|
||||
* adapted from public-domain reference code (Brad Conte / RFC 4648).
|
||||
*
|
||||
* Bytes-returning variants (sha256_bytes, hmac_sha256_bytes) return a string
|
||||
* value whose contents are raw binary; callers usually feed these into
|
||||
* base64_encode. Note that el_val_t strings are NUL-terminated by convention,
|
||||
* so the binary payload may contain embedded NULs — pass it directly into
|
||||
* base64_encode (which uses an explicit length) rather than treating it as
|
||||
* a printable C string.
|
||||
*
|
||||
* The "base64" variants emit/accept RFC 4648 standard alphabet with padding.
|
||||
* The "base64url" variants use URL-safe alphabet (`-`/`_`) with no padding,
|
||||
* as used in JWTs. */
|
||||
|
||||
el_val_t sha256_hex(el_val_t input);
|
||||
el_val_t sha256_bytes(el_val_t input);
|
||||
el_val_t hmac_sha256_hex(el_val_t key, el_val_t message);
|
||||
el_val_t hmac_sha256_bytes(el_val_t key, el_val_t message);
|
||||
el_val_t base64_encode(el_val_t input);
|
||||
el_val_t base64_decode(el_val_t input);
|
||||
el_val_t base64url_encode(el_val_t input);
|
||||
el_val_t base64url_decode(el_val_t input);
|
||||
|
||||
/* Length-aware variants (internal — exposed for the rare caller that already
|
||||
* has a known-length binary buffer and doesn't want to round-trip through
|
||||
* a NUL-terminated el_val_t string). Sha256_bytes and hmac_sha256_bytes feed
|
||||
* these implicitly. */
|
||||
el_val_t el_sha256_bytes_n(const unsigned char* data, size_t len);
|
||||
el_val_t el_base64_encode_n(const unsigned char* data, size_t len, int url_safe);
|
||||
|
||||
/* ── Post-quantum primitives (liboqs-backed) ────────────────────────────────
|
||||
* All inputs/outputs hex-encoded. Algorithm choices:
|
||||
* Signature: CRYSTALS-Dilithium-3 (NIST level 3, balanced)
|
||||
* KEM: CRYSTALS-Kyber-768 (NIST level 3)
|
||||
* Hash: SHA3-256 (Keccak) (PQ-aware protocols favour SHA3 over SHA2)
|
||||
*
|
||||
* If liboqs is not linked (detected via __has_include(<oqs/oqs.h>) at compile
|
||||
* time), the pq_* entry points return a JSON-shaped error string so callers
|
||||
* fail loudly rather than silently fall back to classical schemes:
|
||||
* {"error":"liboqs not linked, post-quantum primitives unavailable"}
|
||||
*
|
||||
* The hybrid handshake pairs X25519 with Kyber-768 per NIST PQ guidance and
|
||||
* CNSA 2.0. Combined shared secret is HKDF-SHA256(x25519_ss || kyber_ss).
|
||||
* Even if Kyber falls, X25519 holds; if X25519 falls under quantum attack,
|
||||
* Kyber holds. SHA3-256 also remains usable independent of liboqs (the
|
||||
* Keccak permutation is PQ-OK as a primitive). */
|
||||
|
||||
el_val_t pq_keygen_signature(void);
|
||||
el_val_t pq_sign(el_val_t secret_key_hex, el_val_t message);
|
||||
el_val_t pq_verify(el_val_t public_key_hex, el_val_t message, el_val_t signature_hex);
|
||||
|
||||
el_val_t pq_kem_keygen(void);
|
||||
el_val_t pq_kem_encaps(el_val_t public_key_hex);
|
||||
el_val_t pq_kem_decaps(el_val_t secret_key_hex, el_val_t ciphertext_hex);
|
||||
|
||||
el_val_t pq_hybrid_keygen(void);
|
||||
el_val_t pq_hybrid_handshake(el_val_t remote_pub_combined);
|
||||
|
||||
el_val_t sha3_256_hex(el_val_t input);
|
||||
|
||||
/* ── AEAD: AES-256-GCM (libcrypto-backed) ───────────────────────────────────
|
||||
* Symmetric authenticated encryption used to wrap envelopes after a KEM
|
||||
* handshake. Caller MUST supply a 32-byte key (64 hex chars) — typically the
|
||||
* Kyber-768 / hybrid shared_secret, optionally normalized via SHA3-256.
|
||||
*
|
||||
* aead_encrypt returns a JSON map {"nonce":"...","ciphertext":"..."} where
|
||||
* ciphertext is the AES-256-GCM output with the 16-byte auth tag appended.
|
||||
* Nonce is a fresh 12-byte CSPRNG draw — callers never pick the nonce, which
|
||||
* structurally rules out the GCM nonce-reuse footgun.
|
||||
*
|
||||
* aead_decrypt returns the plaintext String, or "" on any failure (including
|
||||
* auth-tag mismatch). Callers MUST check for "" before trusting the result. */
|
||||
el_val_t aead_encrypt(el_val_t key_hex, el_val_t plaintext);
|
||||
el_val_t aead_decrypt(el_val_t key_hex, el_val_t nonce_hex, el_val_t ciphertext_hex);
|
||||
|
||||
/* ── Native VM builtin aliases (for compiled El source) ─────────────────────
|
||||
* These match the El VM's native_* builtins so that El source compiled
|
||||
* to C can call the same names without modification. */
|
||||
|
||||
el_val_t native_list_get(el_val_t list, el_val_t index);
|
||||
el_val_t native_list_len(el_val_t list);
|
||||
el_val_t native_list_append(el_val_t list, el_val_t elem);
|
||||
el_val_t native_list_empty(void);
|
||||
el_val_t native_list_clone(el_val_t list);
|
||||
el_val_t native_string_chars(el_val_t s);
|
||||
el_val_t native_int_to_str(el_val_t n);
|
||||
|
||||
/* ── Method-call shorthand aliases ──────────────────────────────────────────
|
||||
* The El method-call convention `obj.method(args)` compiles to
|
||||
* `method(obj, args)`. These aliases expose the runtime functions under
|
||||
* the short names that result from method calls in El source.
|
||||
*
|
||||
* Example: `myList.append(x)` → `append(myList, x)` (calls this alias)
|
||||
* `myList.len()` → `len(myList)` (calls this alias) */
|
||||
|
||||
el_val_t append(el_val_t list, el_val_t elem); /* el_list_append */
|
||||
el_val_t len(el_val_t list); /* el_list_len */
|
||||
el_val_t get(el_val_t list, el_val_t index); /* el_list_get */
|
||||
el_val_t map_get(el_val_t map, el_val_t key); /* el_map_get */
|
||||
el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */
|
||||
|
||||
/* ── OTLP/HTTP Observability ─────────────────────────────────────────────── */
|
||||
/* See bottom of el_runtime.c for the implementation.
|
||||
* Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
|
||||
* No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
|
||||
/* ── Subprocess execution ────────────────────────────────────────────────── */
|
||||
el_val_t exec_command(el_val_t cmd); /* run shell command, return exit code */
|
||||
el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
||||
el_val_t exec(el_val_t cmd); /* exec(cmd) → stdout String (30s timeout) */
|
||||
el_val_t exec_bg(el_val_t cmd); /* exec_bg(cmd) → PID String (non-blocking) */
|
||||
|
||||
/* ── Stdout redirection (used by compiler JS pipeline) ───────────────────── */
|
||||
el_val_t stdout_to_file(el_val_t path); /* redirect process stdout to a file */
|
||||
el_val_t stdout_restore(void); /* restore process stdout to terminal */
|
||||
|
||||
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
||||
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
||||
el_val_t trace_span_start(el_val_t name);
|
||||
el_val_t trace_span_end(el_val_t span_handle);
|
||||
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
|
||||
|
||||
el_val_t __thread_create(el_val_t fn_name_v, el_val_t arg_v);
|
||||
el_val_t __thread_join(el_val_t tid_v);
|
||||
|
||||
/* ── __ prefixed aliases (self-hosting compiler ABI) ─────────────────────────
|
||||
* The El self-hosting compiler emits calls to __-prefixed names. These are
|
||||
* forwarding wrappers around the existing el_runtime functions above. */
|
||||
|
||||
/* I/O */
|
||||
el_val_t __println(el_val_t s);
|
||||
el_val_t __print(el_val_t s);
|
||||
el_val_t __readline(void);
|
||||
|
||||
/* String */
|
||||
el_val_t __int_to_str(el_val_t n);
|
||||
el_val_t __str_to_int(el_val_t s);
|
||||
el_val_t __float_to_str(el_val_t f);
|
||||
el_val_t __str_to_float(el_val_t s);
|
||||
el_val_t __str_len(el_val_t s);
|
||||
el_val_t __str_char_at(el_val_t s, el_val_t i);
|
||||
el_val_t __str_cmp(el_val_t a, el_val_t b);
|
||||
el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n);
|
||||
el_val_t __str_concat_raw(el_val_t a, el_val_t b);
|
||||
el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t __str_alloc(el_val_t n);
|
||||
el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c);
|
||||
|
||||
/* URL encoding */
|
||||
el_val_t __url_encode(el_val_t s);
|
||||
el_val_t __url_decode(el_val_t s);
|
||||
|
||||
/* Environment */
|
||||
el_val_t __env_get(el_val_t key);
|
||||
|
||||
/* Subprocess */
|
||||
el_val_t __exec(el_val_t cmd);
|
||||
el_val_t __exec_bg(el_val_t cmd);
|
||||
|
||||
/* Process */
|
||||
el_val_t __exit_program(el_val_t code);
|
||||
|
||||
/* Filesystem */
|
||||
el_val_t __fs_exists(el_val_t path);
|
||||
el_val_t __fs_mkdir(el_val_t path);
|
||||
el_val_t __fs_read(el_val_t path);
|
||||
el_val_t __fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n);
|
||||
el_val_t __fs_list_raw(el_val_t path);
|
||||
|
||||
/* HTTP server */
|
||||
el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
el_val_t __http_serve(el_val_t port, el_val_t handler);
|
||||
el_val_t __http_serve_v2(el_val_t port, el_val_t handler);
|
||||
|
||||
/* HTTP conn fd / SSE (weak; overridden by el_seed.c when linked together) */
|
||||
el_val_t __http_conn_fd(void);
|
||||
el_val_t __http_sse_open(el_val_t conn_id);
|
||||
el_val_t __http_sse_send(el_val_t conn_id, el_val_t data);
|
||||
el_val_t __http_sse_close(el_val_t conn_id);
|
||||
|
||||
/* HTTP client (requires HAVE_CURL; stubs provided for no-curl builds) */
|
||||
el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_map, el_val_t timeout_ms);
|
||||
el_val_t __http_do_map(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t timeout_ms);
|
||||
el_val_t __http_do_map_to_file(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t output_path);
|
||||
|
||||
/* JSON */
|
||||
el_val_t __json_array_get(el_val_t json, el_val_t index);
|
||||
el_val_t __json_array_get_string(el_val_t json, el_val_t index);
|
||||
el_val_t __json_array_len(el_val_t json);
|
||||
el_val_t __json_get(el_val_t json, el_val_t key);
|
||||
el_val_t __json_get_raw(el_val_t json, el_val_t key);
|
||||
el_val_t __json_set(el_val_t json, el_val_t key, el_val_t value);
|
||||
el_val_t __json_parse_map(el_val_t json_str);
|
||||
el_val_t __json_stringify_val(el_val_t val);
|
||||
|
||||
/* Hashing */
|
||||
el_val_t __sha256_hex(el_val_t s);
|
||||
|
||||
/* State K/V */
|
||||
el_val_t __state_del(el_val_t key);
|
||||
el_val_t __state_get(el_val_t key);
|
||||
el_val_t __state_keys(void);
|
||||
el_val_t __state_set(el_val_t key, el_val_t val);
|
||||
|
||||
/* UUID */
|
||||
el_val_t __uuid_v4(void);
|
||||
|
||||
/* Args */
|
||||
el_val_t __args_json(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* main.m — entry point for native-hello-ios.
|
||||
*
|
||||
* iOS UIKit lifecycle:
|
||||
* UIApplicationMain never returns. The compiled el program's boot body
|
||||
* (native_init → window_from_manifest → app_build → window_show) must
|
||||
* run inside applicationDidFinishLaunchingWithOptions on the main thread.
|
||||
*
|
||||
* How it works:
|
||||
* 1. el_uikit_set_args() forwards argc/argv to UIApplicationMain.
|
||||
* 2. el_main_entry_fn is set to el_app_boot (defined below) before
|
||||
* UIApplicationMain is entered.
|
||||
* 3. ElAppDelegate.didFinishLaunchingWithOptions calls el_main_entry_fn(),
|
||||
* which runs the el program's widget setup at the correct lifecycle point.
|
||||
* 4. __native_run_loop() inside the compiled el main is a no-op on iOS —
|
||||
* UIApplicationMain already owns the run loop.
|
||||
*
|
||||
* The generated native_hello.c exports el_app_main() (renamed from main by
|
||||
* the gen step in build.sh). We call it from el_app_boot so it runs after
|
||||
* UIApplicationMain sets up the application object.
|
||||
*
|
||||
* Compile with -fno-objc-arc (required by el_uikit.m).
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/* From el_uikit.m */
|
||||
void el_uikit_set_args(int argc, char **argv);
|
||||
extern void (*el_main_entry_fn)(void);
|
||||
|
||||
/* Forward declaration of the renamed compiled el main. */
|
||||
int el_app_main(int argc, char **argv);
|
||||
|
||||
/* Boot function registered as el_main_entry_fn. Called by ElAppDelegate
|
||||
* inside didFinishLaunchingWithOptions on the main thread. */
|
||||
static void el_app_boot(void) {
|
||||
char *argv[] = {"el-app", NULL};
|
||||
el_app_main(1, argv);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
el_uikit_set_args(argc, argv);
|
||||
/* Register the boot function so ElAppDelegate can call it. */
|
||||
el_main_entry_fn = el_app_boot;
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, @"ElAppDelegate");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data);
|
||||
el_val_t app_build(el_val_t window);
|
||||
|
||||
el_val_t g_window;
|
||||
el_val_t g_label;
|
||||
el_val_t g_input;
|
||||
el_val_t g_button;
|
||||
el_val_t g_counter;
|
||||
el_val_t g_counter_lbl;
|
||||
el_val_t manifest_env;
|
||||
el_val_t manifest_path;
|
||||
el_val_t win;
|
||||
el_val_t g_window;
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t name = widget_get_text(g_input);
|
||||
el_val_t greeting = ({ el_val_t _if_result_1 = 0; if ((str_len(name) > 0)) { _if_result_1 = (el_str_concat(el_str_concat(EL_STR("Hello, "), name), EL_STR("!"))); } else { _if_result_1 = (EL_STR("Hello, World!")); } _if_result_1; });
|
||||
widget_set_text(g_label, greeting);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t g_counter = (g_counter + 1);
|
||||
widget_set_text(g_counter_lbl, el_str_concat(EL_STR("Clicks: "), int_to_str(g_counter)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data) {
|
||||
el_val_t has_text = (str_len(data) > 0);
|
||||
widget_set_disabled(g_button, !has_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t app_build(el_val_t window) {
|
||||
el_val_t root = vstack(0);
|
||||
widget_set_padding_all(root, 24);
|
||||
widget_set_bg_color_hex(root, EL_STR("#0f172a"));
|
||||
widget_set_flex(root, 1);
|
||||
el_val_t title = label(EL_STR("el-native \xe2\x80\x94 native widget demo"));
|
||||
style_label_heading(title);
|
||||
widget_add_child(root, title);
|
||||
el_val_t gap1 = label(EL_STR(""));
|
||||
widget_set_height(gap1, 16);
|
||||
widget_add_child(root, gap1);
|
||||
el_val_t subtitle = label(EL_STR("AppKit controls from el code, no ObjC in the app layer."));
|
||||
style_label_muted(subtitle);
|
||||
widget_add_child(root, subtitle);
|
||||
el_val_t gap2 = label(EL_STR(""));
|
||||
widget_set_height(gap2, 24);
|
||||
widget_add_child(root, gap2);
|
||||
el_val_t input_row = hstack(8);
|
||||
el_val_t name_label = label(EL_STR("Name:"));
|
||||
style_label_body(name_label);
|
||||
widget_set_width(name_label, 60);
|
||||
el_val_t input = text_field(EL_STR("Enter your name\xe2\x80\xa6"));
|
||||
style_label_body(input);
|
||||
widget_set_flex(input, 1);
|
||||
widget_on_change(input, EL_STR("on_name_change"));
|
||||
el_val_t g_input = input;
|
||||
el_val_t greet_btn = button(EL_STR("Greet"));
|
||||
style_button_primary(greet_btn);
|
||||
widget_set_disabled(greet_btn, 1);
|
||||
widget_on_click(greet_btn, EL_STR("on_greet_click"));
|
||||
el_val_t g_button = greet_btn;
|
||||
widget_add_child(input_row, name_label);
|
||||
widget_add_child(input_row, input);
|
||||
widget_add_child(input_row, greet_btn);
|
||||
widget_add_child(root, input_row);
|
||||
el_val_t gap3 = label(EL_STR(""));
|
||||
widget_set_height(gap3, 12);
|
||||
widget_add_child(root, gap3);
|
||||
el_val_t greeting = label(EL_STR("Waiting for name\xe2\x80\xa6"));
|
||||
style_label_body(greeting);
|
||||
widget_set_color_hex(greeting, EL_STR("#60a5fa"));
|
||||
widget_set_font(greeting, EL_STR("system"), 16, 1);
|
||||
el_val_t g_label = greeting;
|
||||
widget_add_child(root, greeting);
|
||||
el_val_t gap4 = label(EL_STR(""));
|
||||
widget_set_height(gap4, 24);
|
||||
widget_add_child(root, gap4);
|
||||
el_val_t counter_row = hstack(12);
|
||||
el_val_t counter_lbl = label(EL_STR("Clicks: 0"));
|
||||
style_label_body(counter_lbl);
|
||||
el_val_t g_counter_lbl = counter_lbl;
|
||||
el_val_t counter_btn = button(EL_STR("Click me"));
|
||||
style_button_primary(counter_btn);
|
||||
widget_on_click(counter_btn, EL_STR("on_counter_click"));
|
||||
widget_add_child(counter_row, counter_lbl);
|
||||
widget_add_child(counter_row, counter_btn);
|
||||
widget_add_child(root, counter_row);
|
||||
widget_add_child(window, root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int el_app_main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
g_window = (-1);
|
||||
g_label = (-1);
|
||||
g_input = (-1);
|
||||
g_button = (-1);
|
||||
g_counter = 0;
|
||||
g_counter_lbl = (-1);
|
||||
native_init();
|
||||
manifest_env = env(EL_STR("EL_MANIFEST"));
|
||||
manifest_path = ({ el_val_t _if_result_2 = 0; if ((str_len(manifest_env) > 0)) { _if_result_2 = (manifest_env); } else { _if_result_2 = (EL_STR("manifest.el")); } _if_result_2; });
|
||||
win = window_from_manifest(manifest_path);
|
||||
g_window = win;
|
||||
if (win < 0) {
|
||||
println(EL_STR("Error: failed to create window. Is EL_TARGET_MACOS defined?"));
|
||||
exit_program(1);
|
||||
}
|
||||
app_build(win);
|
||||
window_show(win);
|
||||
native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
# build.sh — Build and install native-hello-ios to the iOS Simulator.
|
||||
#
|
||||
# Usage:
|
||||
# ./build.sh # gen C sources + xcodebuild for simulator
|
||||
# ./build.sh gen # regenerate C sources only
|
||||
# ./build.sh build # xcodebuild only (skip elc step)
|
||||
# ./build.sh install # gen + build + boot simulator + install + launch
|
||||
# ./build.sh clean # remove build/ directory
|
||||
#
|
||||
# Requirements:
|
||||
# - Xcode with iOS Simulator SDK installed
|
||||
# - elc at ../../../lang/dist/platform/elc (or in PATH)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||
EL_APP_ENTRY="${EL_UI_ROOT}/examples/native-hello/src/main.el"
|
||||
EL_MANIFEST="${EL_UI_ROOT}/examples/native-hello/manifest.el"
|
||||
SOURCES_DIR="${SCRIPT_DIR}/NativeHello"
|
||||
BUILD_DIR="${SCRIPT_DIR}/build"
|
||||
|
||||
# Locate elc.
|
||||
if command -v elc &>/dev/null; then
|
||||
ELC="elc"
|
||||
elif [ -x "${EL_LANG_ROOT}/dist/platform/elc" ]; then
|
||||
ELC="${EL_LANG_ROOT}/dist/platform/elc"
|
||||
else
|
||||
echo "Error: elc not found. Add it to PATH or place it at:"
|
||||
echo " ${EL_LANG_ROOT}/dist/platform/elc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gen() {
|
||||
echo "==> Generating native_hello.c..."
|
||||
EL_MANIFEST="${EL_MANIFEST}" \
|
||||
"${ELC}" "${EL_APP_ENTRY}" \
|
||||
> "${SOURCES_DIR}/native_hello.c"
|
||||
# Rename the compiled el main() to el_app_main so it doesn't conflict with
|
||||
# main.m's main(). main.m's main() is the real entry point for UIApplicationMain.
|
||||
# Use a simple sed substitution on the exact signature elc emits.
|
||||
sed -i '' 's/^int main(int _argc, char\*\* _argv) {/int el_app_main(int _argc, char** _argv) {/' \
|
||||
"${SOURCES_DIR}/native_hello.c"
|
||||
|
||||
echo "==> Generating el_native_vessel.c..."
|
||||
"${ELC}" "${EL_NATIVE_VESSEL}" \
|
||||
> "${SOURCES_DIR}/el_native_vessel.c"
|
||||
# Rename vessel main() similarly.
|
||||
sed -i '' 's/^int main(int _argc, char\*\* _argv) {/int el_vessel_main(int _argc, char** _argv) {/' \
|
||||
"${SOURCES_DIR}/el_native_vessel.c"
|
||||
|
||||
echo "==> Copying runtime files..."
|
||||
cp "${EL_RUNTIME}/el_uikit.m" "${SOURCES_DIR}/"
|
||||
cp "${EL_RUNTIME}/el_seed.c" "${SOURCES_DIR}/"
|
||||
cp "${EL_RUNTIME}/el_runtime.c" "${SOURCES_DIR}/"
|
||||
cp "${EL_RUNTIME}/el_runtime.h" "${SOURCES_DIR}/"
|
||||
cp "${EL_RUNTIME}/el_native_target.h" "${SOURCES_DIR}/"
|
||||
|
||||
echo "==> Source generation complete."
|
||||
}
|
||||
|
||||
xcode_build() {
|
||||
echo "==> Building with xcodebuild (iphonesimulator)..."
|
||||
xcodebuild \
|
||||
-project "${SCRIPT_DIR}/NativeHello.xcodeproj" \
|
||||
-scheme NativeHello \
|
||||
-sdk iphonesimulator \
|
||||
-configuration Debug \
|
||||
-derivedDataPath "${BUILD_DIR}" \
|
||||
CLANG_ENABLE_OBJC_ARC=NO \
|
||||
OTHER_CFLAGS="-DEL_TARGET_IOS" \
|
||||
OTHER_LDFLAGS="-ldl" \
|
||||
build
|
||||
|
||||
APP_PATH=$(find "${BUILD_DIR}" -name "NativeHello.app" -maxdepth 6 | head -1)
|
||||
if [ -n "${APP_PATH}" ]; then
|
||||
echo "==> Build complete."
|
||||
echo " App bundle: ${APP_PATH}"
|
||||
else
|
||||
echo "==> Build complete (app bundle in ${BUILD_DIR})."
|
||||
fi
|
||||
}
|
||||
|
||||
install_and_launch() {
|
||||
gen
|
||||
xcode_build
|
||||
|
||||
APP_PATH=$(find "${BUILD_DIR}" -name "NativeHello.app" -maxdepth 6 | head -1)
|
||||
if [ -z "${APP_PATH}" ]; then
|
||||
echo "Error: could not find NativeHello.app in ${BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Boot the default simulator if not already booted.
|
||||
SIM_UDID=$(xcrun simctl list devices available --json \
|
||||
| python3 -c "
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)['devices']
|
||||
for runtime,devs in d.items():
|
||||
for dev in devs:
|
||||
if 'iPhone' in dev['name'] and dev['isAvailable']:
|
||||
print(dev['udid'])
|
||||
exit()
|
||||
")
|
||||
if [ -z "${SIM_UDID}" ]; then
|
||||
echo "Error: no available iPhone simulator found."
|
||||
exit 1
|
||||
fi
|
||||
echo "==> Using simulator: ${SIM_UDID}"
|
||||
xcrun simctl boot "${SIM_UDID}" 2>/dev/null || true
|
||||
open -a Simulator
|
||||
|
||||
echo "==> Installing app..."
|
||||
xcrun simctl install "${SIM_UDID}" "${APP_PATH}"
|
||||
|
||||
echo "==> Launching app..."
|
||||
xcrun simctl launch "${SIM_UDID}" "ai.neurontechnologies.el.nativehello"
|
||||
}
|
||||
|
||||
clean() {
|
||||
echo "==> Cleaning..."
|
||||
rm -rf "${BUILD_DIR}"
|
||||
echo "==> Clean complete."
|
||||
}
|
||||
|
||||
case "${1:-all}" in
|
||||
gen) gen ;;
|
||||
build) xcode_build ;;
|
||||
install) install_and_launch ;;
|
||||
clean) clean ;;
|
||||
all|*) gen && xcode_build ;;
|
||||
esac
|
||||
@@ -0,0 +1,88 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
libgtk-4-dev \
|
||||
pkg-config \
|
||||
libcurl4-openssl-dev \
|
||||
binutils \
|
||||
file \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# All sources arrive in /build — runtime/ subdirectory contains the el runtime,
|
||||
# top-level contains elc-generated app/vessel C files.
|
||||
COPY runtime/ runtime/
|
||||
COPY el_native_vessel.c el_native_vessel.h native_hello.c ./
|
||||
|
||||
# Compile el_gtk4.c
|
||||
RUN gcc -DEL_TARGET_LINUX $(pkg-config --cflags gtk4) \
|
||||
-std=c11 -I/build/runtime \
|
||||
-c /build/runtime/el_gtk4.c -o /build/el_gtk4.o && \
|
||||
echo "el_gtk4.c compiled OK"
|
||||
|
||||
# Compile el_seed.c
|
||||
RUN gcc -DEL_TARGET_LINUX $(pkg-config --cflags gtk4) \
|
||||
-std=c11 -I/build/runtime \
|
||||
-c /build/runtime/el_seed.c -o /build/el_seed.o && \
|
||||
echo "el_seed.c compiled OK"
|
||||
|
||||
# Compile el_runtime.c
|
||||
RUN gcc -std=c11 -I/build/runtime \
|
||||
-c /build/runtime/el_runtime.c -o /build/el_runtime.o && \
|
||||
echo "el_runtime.c compiled OK"
|
||||
|
||||
# Patch el_runtime.o: hide __-prefixed symbols that el_seed.o also defines.
|
||||
# This is the Linux objcopy equivalent of macOS nmedit -s.
|
||||
# el_seed.o is canonical for all __ symbols; el_runtime.o provides high-level
|
||||
# builtins (str_len, json_get_string, etc.) that el_seed.o does NOT export.
|
||||
RUN nm /build/el_seed.o | awk '/[0-9a-f]+ T /{print $3}' | sort > /build/.seed_T.txt && \
|
||||
nm /build/el_runtime.o | awk '/[0-9a-f]+ T /{print $3}' | sort > /build/.rt_T.txt && \
|
||||
comm -12 /build/.seed_T.txt /build/.rt_T.txt > /build/.dups.txt && \
|
||||
if [ -s /build/.dups.txt ]; then \
|
||||
ARGS=$(awk '{printf "--localize-symbol=%s ", $1}' /build/.dups.txt); \
|
||||
eval "objcopy $ARGS /build/el_runtime.o"; \
|
||||
echo "Patched $(wc -l < /build/.dups.txt) duplicate symbols in el_runtime.o"; \
|
||||
else \
|
||||
echo "No duplicate symbols found"; \
|
||||
fi
|
||||
|
||||
# Compile vessel
|
||||
RUN gcc -DEL_TARGET_LINUX -std=c11 -I/build/runtime \
|
||||
-include /build/runtime/el_native_target.h \
|
||||
-c /build/el_native_vessel.c -o /build/el_native_vessel.o && \
|
||||
echo "vessel compiled OK"
|
||||
|
||||
# Compile app (before patching vessel, so we know which symbols to localise)
|
||||
RUN gcc -DEL_TARGET_LINUX -std=c11 -I/build/runtime \
|
||||
-include /build/runtime/el_native_target.h \
|
||||
-include /build/el_native_vessel.h \
|
||||
-c /build/native_hello.c -o /build/native_hello.o && \
|
||||
echo "app compiled OK"
|
||||
|
||||
# Patch el_native_vessel.o:
|
||||
# 1. Localise main() so it doesn't conflict with the app's main.
|
||||
# 2. Localise any T/B symbols the app also defines (elc emits shared globals
|
||||
# like TOKEN_PRIMARY in every translation unit; GNU ld rejects duplicates
|
||||
# unlike macOS ld which picks the first one silently).
|
||||
RUN nm /build/native_hello.o | awk '/[0-9a-f]+ [TBb] /{print $3}' | sort > /build/.app_syms.txt && \
|
||||
nm /build/el_native_vessel.o | awk '/[0-9a-f]+ [TBb] /{print $3}' | sort > /build/.vessel_syms.txt && \
|
||||
comm -12 /build/.app_syms.txt /build/.vessel_syms.txt > /build/.vessel_dups.txt && \
|
||||
echo "main" >> /build/.vessel_dups.txt && \
|
||||
sort -u /build/.vessel_dups.txt -o /build/.vessel_dups.txt && \
|
||||
ARGS=$(awk '{printf "--localize-symbol=%s ", $1}' /build/.vessel_dups.txt) && \
|
||||
eval "objcopy $ARGS /build/el_native_vessel.o" && \
|
||||
echo "Patched $(wc -l < /build/.vessel_dups.txt) symbols in el_native_vessel.o"
|
||||
|
||||
# Link — add -lm for math functions (exp, log, etc.) used by el_runtime.c
|
||||
RUN gcc \
|
||||
/build/native_hello.o \
|
||||
/build/el_native_vessel.o \
|
||||
/build/el_gtk4.o \
|
||||
/build/el_runtime.o \
|
||||
/build/el_seed.o \
|
||||
$(pkg-config --libs gtk4) -ldl -lpthread -lcurl -lm \
|
||||
-o /build/native-hello-linux && \
|
||||
echo "==> Linked: native-hello-linux" && \
|
||||
file /build/native-hello-linux
|
||||
@@ -0,0 +1,99 @@
|
||||
FROM --platform=linux/arm64 ubuntu:22.04
|
||||
|
||||
# GTK4 + SDL2 for Raspberry Pi (ARM64 / aarch64)
|
||||
# Targets Pi 3 / 4 / 5 running 64-bit Raspberry Pi OS or Ubuntu 22.04.
|
||||
#
|
||||
# GTK4 requires a desktop environment / Wayland compositor at runtime.
|
||||
# SDL2 works on bare framebuffer or KMS/DRM without a full desktop.
|
||||
#
|
||||
# Usage:
|
||||
# docker build --platform linux/arm64 \
|
||||
# -f Dockerfile.pi --tag el-native-pi-arm64 ./build-docker/
|
||||
# docker create --name pi-extract el-native-pi-arm64
|
||||
# docker cp pi-extract:/build/native-hello-linux-pi ./build/native-hello-pi-arm64
|
||||
# docker rm pi-extract
|
||||
#
|
||||
# The GTK4 build is the default. SDL2 is available as an alternative
|
||||
# for headless / bare-metal Pi deployments.
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
libgtk-4-dev \
|
||||
libsdl2-dev \
|
||||
libsdl2-ttf-dev \
|
||||
libsdl2-image-dev \
|
||||
pkg-config \
|
||||
libcurl4-openssl-dev \
|
||||
binutils \
|
||||
file \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# All sources arrive in /build
|
||||
COPY runtime/ runtime/
|
||||
COPY el_native_vessel.c el_native_vessel.h native_hello.c ./
|
||||
|
||||
# ── GTK4 build ────────────────────────────────────────────────────────────────
|
||||
|
||||
# Compile el_gtk4.c
|
||||
RUN gcc -DEL_TARGET_LINUX $(pkg-config --cflags gtk4) \
|
||||
-std=c11 -I/build/runtime \
|
||||
-c /build/runtime/el_gtk4.c -o /build/el_gtk4.o && \
|
||||
echo "el_gtk4.c compiled OK"
|
||||
|
||||
# Compile el_seed.c
|
||||
RUN gcc -DEL_TARGET_LINUX $(pkg-config --cflags gtk4) \
|
||||
-std=c11 -I/build/runtime \
|
||||
-c /build/runtime/el_seed.c -o /build/el_seed.o && \
|
||||
echo "el_seed.c compiled OK"
|
||||
|
||||
# Compile el_runtime.c
|
||||
RUN gcc -std=c11 -I/build/runtime \
|
||||
-c /build/runtime/el_runtime.c -o /build/el_runtime.o && \
|
||||
echo "el_runtime.c compiled OK"
|
||||
|
||||
# Patch el_runtime.o: localise __-prefixed duplicate symbols (el_seed.o is canonical)
|
||||
RUN nm /build/el_seed.o | awk '/[0-9a-f]+ T /{print $3}' | sort > /build/.seed_T.txt && \
|
||||
nm /build/el_runtime.o | awk '/[0-9a-f]+ T /{print $3}' | sort > /build/.rt_T.txt && \
|
||||
comm -12 /build/.seed_T.txt /build/.rt_T.txt > /build/.dups.txt && \
|
||||
if [ -s /build/.dups.txt ]; then \
|
||||
ARGS=$(awk '{printf "--localize-symbol=%s ", $1}' /build/.dups.txt); \
|
||||
eval "objcopy $ARGS /build/el_runtime.o"; \
|
||||
echo "Patched $(wc -l < /build/.dups.txt) duplicate symbols in el_runtime.o"; \
|
||||
fi
|
||||
|
||||
# Compile vessel
|
||||
RUN gcc -DEL_TARGET_LINUX -std=c11 -I/build/runtime \
|
||||
-include /build/runtime/el_native_target.h \
|
||||
-c /build/el_native_vessel.c -o /build/el_native_vessel.o && \
|
||||
echo "vessel compiled OK"
|
||||
|
||||
# Compile app
|
||||
RUN gcc -DEL_TARGET_LINUX -std=c11 -I/build/runtime \
|
||||
-include /build/runtime/el_native_target.h \
|
||||
-include /build/el_native_vessel.h \
|
||||
-c /build/native_hello.c -o /build/native_hello.o && \
|
||||
echo "app compiled OK"
|
||||
|
||||
# Patch el_native_vessel.o: localise shared globals (main + TOKEN_* etc.)
|
||||
RUN nm /build/native_hello.o | awk '/[0-9a-f]+ [TBb] /{print $3}' | sort > /build/.app_syms.txt && \
|
||||
nm /build/el_native_vessel.o | awk '/[0-9a-f]+ [TBb] /{print $3}' | sort > /build/.vessel_syms.txt && \
|
||||
comm -12 /build/.app_syms.txt /build/.vessel_syms.txt > /build/.vessel_dups.txt && \
|
||||
echo "main" >> /build/.vessel_dups.txt && \
|
||||
sort -u /build/.vessel_dups.txt -o /build/.vessel_dups.txt && \
|
||||
ARGS=$(awk '{printf "--localize-symbol=%s ", $1}' /build/.vessel_dups.txt) && \
|
||||
eval "objcopy $ARGS /build/el_native_vessel.o" && \
|
||||
echo "Patched $(wc -l < /build/.vessel_dups.txt) symbols in el_native_vessel.o"
|
||||
|
||||
# Link (GTK4)
|
||||
RUN gcc \
|
||||
/build/native_hello.o \
|
||||
/build/el_native_vessel.o \
|
||||
/build/el_gtk4.o \
|
||||
/build/el_runtime.o \
|
||||
/build/el_seed.o \
|
||||
$(pkg-config --libs gtk4) -ldl -lpthread -lcurl -lm \
|
||||
-o /build/native-hello-linux-pi && \
|
||||
echo "==> Linked: native-hello-linux-pi (GTK4)" && \
|
||||
file /build/native-hello-linux-pi
|
||||
@@ -0,0 +1,459 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
el_val_t TOKEN_PRIMARY;
|
||||
el_val_t TOKEN_ON_PRIMARY;
|
||||
el_val_t TOKEN_BACKGROUND;
|
||||
el_val_t TOKEN_ON_BG;
|
||||
el_val_t TOKEN_SURFACE;
|
||||
el_val_t TOKEN_ON_SURFACE;
|
||||
el_val_t TOKEN_OUTLINE;
|
||||
el_val_t TOKEN_ERROR;
|
||||
|
||||
el_val_t native_init(void) {
|
||||
__native_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t native_run_loop(void) {
|
||||
__native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_read(el_val_t path) {
|
||||
return __manifest_read(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_title(el_val_t m) {
|
||||
return json_get_string(m, EL_STR("title"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height) {
|
||||
return __window_create(title, width, height, min_width, min_height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_from_manifest(el_val_t manifest_path) {
|
||||
el_val_t m = manifest_read(manifest_path);
|
||||
el_val_t title = manifest_title(m);
|
||||
el_val_t w = manifest_width(m);
|
||||
el_val_t h = manifest_height(m);
|
||||
el_val_t mw = manifest_min_width(m);
|
||||
el_val_t mh = manifest_min_height(m);
|
||||
el_val_t safe_w = ({ el_val_t _if_result_1 = 0; if ((w > 0)) { _if_result_1 = (w); } else { _if_result_1 = (1200); } _if_result_1; });
|
||||
el_val_t safe_h = ({ el_val_t _if_result_2 = 0; if ((h > 0)) { _if_result_2 = (h); } else { _if_result_2 = (800); } _if_result_2; });
|
||||
el_val_t safe_mw = ({ el_val_t _if_result_3 = 0; if ((mw > 0)) { _if_result_3 = (mw); } else { _if_result_3 = (600); } _if_result_3; });
|
||||
el_val_t safe_mh = ({ el_val_t _if_result_4 = 0; if ((mh > 0)) { _if_result_4 = (mh); } else { _if_result_4 = (400); } _if_result_4; });
|
||||
el_val_t safe_t = ({ el_val_t _if_result_5 = 0; if ((str_len(title) > 0)) { _if_result_5 = (title); } else { _if_result_5 = (EL_STR("App")); } _if_result_5; });
|
||||
return window_create(safe_t, safe_w, safe_h, safe_mw, safe_mh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_show(el_val_t handle) {
|
||||
__window_show(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title) {
|
||||
__window_set_title(handle, title);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack(el_val_t spacing) {
|
||||
return __vstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack_tight(void) {
|
||||
return __vstack_create(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hstack(el_val_t spacing) {
|
||||
return __hstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t zstack(void) {
|
||||
return __zstack_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t scroll(void) {
|
||||
return __scroll_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t label(el_val_t text) {
|
||||
return __label_create(text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t button(el_val_t label) {
|
||||
return __button_create(label);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_field(el_val_t placeholder) {
|
||||
return __text_field_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_area(el_val_t placeholder) {
|
||||
return __text_area_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t image(el_val_t path_or_name) {
|
||||
return __image_create(path_or_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text) {
|
||||
__widget_set_text(handle, text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_get_text(el_val_t handle) {
|
||||
return __widget_get_text(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_bg_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold) {
|
||||
el_val_t bold_int = ({ el_val_t _if_result_6 = 0; if (bold) { _if_result_6 = (1); } else { _if_result_6 = (0); } _if_result_6; });
|
||||
__widget_set_font(handle, family, size, bold_int);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left) {
|
||||
__widget_set_padding(handle, top, right, bottom, left);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p) {
|
||||
__widget_set_padding(handle, p, p, p, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py) {
|
||||
__widget_set_padding(handle, py, px, py, px);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width) {
|
||||
__widget_set_width(handle, width);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height) {
|
||||
__widget_set_height(handle, height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
__widget_set_flex(handle, flex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
__widget_set_corner_radius(handle, radius);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_val_t d = ({ el_val_t _if_result_7 = 0; if (disabled) { _if_result_7 = (1); } else { _if_result_7 = (0); } _if_result_7; });
|
||||
__widget_set_disabled(handle, d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_val_t h = ({ el_val_t _if_result_8 = 0; if (hidden) { _if_result_8 = (1); } else { _if_result_8 = (0); } _if_result_8; });
|
||||
__widget_set_hidden(handle, h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child) {
|
||||
__widget_add_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
__widget_remove_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_destroy(el_val_t handle) {
|
||||
__widget_destroy(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_click(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_change(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_submit(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset) {
|
||||
el_val_t hi = str_slice(s, offset, (offset + 1));
|
||||
el_val_t lo = str_slice(s, (offset + 1), (offset + 2));
|
||||
el_val_t h = hex_nibble(hi);
|
||||
el_val_t l = hex_nibble(lo);
|
||||
return ((h * 16) + l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_nibble(el_val_t c) {
|
||||
if (str_eq(c, EL_STR("0"))) {
|
||||
return 0;
|
||||
}
|
||||
if (str_eq(c, EL_STR("1"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(c, EL_STR("2"))) {
|
||||
return 2;
|
||||
}
|
||||
if (str_eq(c, EL_STR("3"))) {
|
||||
return 3;
|
||||
}
|
||||
if (str_eq(c, EL_STR("4"))) {
|
||||
return 4;
|
||||
}
|
||||
if (str_eq(c, EL_STR("5"))) {
|
||||
return 5;
|
||||
}
|
||||
if (str_eq(c, EL_STR("6"))) {
|
||||
return 6;
|
||||
}
|
||||
if (str_eq(c, EL_STR("7"))) {
|
||||
return 7;
|
||||
}
|
||||
if (str_eq(c, EL_STR("8"))) {
|
||||
return 8;
|
||||
}
|
||||
if (str_eq(c, EL_STR("9"))) {
|
||||
return 9;
|
||||
}
|
||||
if (str_eq(c, EL_STR("a"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("b"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("c"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("d"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("e"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("f"))) {
|
||||
return 15;
|
||||
}
|
||||
if (str_eq(c, EL_STR("A"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("B"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("C"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("D"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("E"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("F"))) {
|
||||
return 15;
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_r(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_9 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_9 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_9 = (hex); } _if_result_9; });
|
||||
el_val_t v = hex_channel(s, 0);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_g(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_10 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_10 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_10 = (hex); } _if_result_10; });
|
||||
el_val_t v = hex_channel(s, 2);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_b(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_11 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_11 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_11 = (hex); } _if_result_11; });
|
||||
el_val_t v = hex_channel(s, 4);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_a(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_12 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_12 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_12 = (hex); } _if_result_12; });
|
||||
if (str_len(s) < 8) {
|
||||
return el_from_float(1.0);
|
||||
}
|
||||
el_val_t v = hex_channel(s, 6);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_bg_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_surface(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_SURFACE);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_button_primary(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_PRIMARY);
|
||||
widget_set_color_hex(handle, TOKEN_ON_PRIMARY);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
widget_set_padding_xy(handle, 16, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_body(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 14, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_heading(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 20, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_muted(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_OUTLINE);
|
||||
widget_set_font(handle, EL_STR("system"), 12, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
TOKEN_PRIMARY = EL_STR("#60a5fa");
|
||||
TOKEN_ON_PRIMARY = EL_STR("#0f172a");
|
||||
TOKEN_BACKGROUND = EL_STR("#0f172a");
|
||||
TOKEN_ON_BG = EL_STR("#f8fafc");
|
||||
TOKEN_SURFACE = EL_STR("#1e293b");
|
||||
TOKEN_ON_SURFACE = EL_STR("#f8fafc");
|
||||
TOKEN_OUTLINE = EL_STR("#475569");
|
||||
TOKEN_ERROR = EL_STR("#f87171");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
el_val_t TOKEN_PRIMARY;
|
||||
el_val_t TOKEN_ON_PRIMARY;
|
||||
el_val_t TOKEN_BACKGROUND;
|
||||
el_val_t TOKEN_ON_BG;
|
||||
el_val_t TOKEN_SURFACE;
|
||||
el_val_t TOKEN_ON_SURFACE;
|
||||
el_val_t TOKEN_OUTLINE;
|
||||
el_val_t TOKEN_ERROR;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data);
|
||||
el_val_t app_build(el_val_t window);
|
||||
|
||||
el_val_t g_window;
|
||||
el_val_t g_label;
|
||||
el_val_t g_input;
|
||||
el_val_t g_button;
|
||||
el_val_t g_counter;
|
||||
el_val_t g_counter_lbl;
|
||||
el_val_t manifest_env;
|
||||
el_val_t manifest_path;
|
||||
el_val_t win;
|
||||
el_val_t g_window;
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t name = widget_get_text(g_input);
|
||||
el_val_t greeting = ({ el_val_t _if_result_1 = 0; if ((str_len(name) > 0)) { _if_result_1 = (el_str_concat(el_str_concat(EL_STR("Hello, "), name), EL_STR("!"))); } else { _if_result_1 = (EL_STR("Hello, World!")); } _if_result_1; });
|
||||
widget_set_text(g_label, greeting);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t g_counter = (g_counter + 1);
|
||||
widget_set_text(g_counter_lbl, el_str_concat(EL_STR("Clicks: "), int_to_str(g_counter)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data) {
|
||||
el_val_t has_text = (str_len(data) > 0);
|
||||
widget_set_disabled(g_button, !has_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t app_build(el_val_t window) {
|
||||
el_val_t root = vstack(0);
|
||||
widget_set_padding_all(root, 24);
|
||||
widget_set_bg_color_hex(root, EL_STR("#0f172a"));
|
||||
widget_set_flex(root, 1);
|
||||
el_val_t title = label(EL_STR("el-native \xe2\x80\x94 native widget demo"));
|
||||
style_label_heading(title);
|
||||
widget_add_child(root, title);
|
||||
el_val_t gap1 = label(EL_STR(""));
|
||||
widget_set_height(gap1, 16);
|
||||
widget_add_child(root, gap1);
|
||||
el_val_t subtitle = label(EL_STR("AppKit controls from el code, no ObjC in the app layer."));
|
||||
style_label_muted(subtitle);
|
||||
widget_add_child(root, subtitle);
|
||||
el_val_t gap2 = label(EL_STR(""));
|
||||
widget_set_height(gap2, 24);
|
||||
widget_add_child(root, gap2);
|
||||
el_val_t input_row = hstack(8);
|
||||
el_val_t name_label = label(EL_STR("Name:"));
|
||||
style_label_body(name_label);
|
||||
widget_set_width(name_label, 60);
|
||||
el_val_t input = text_field(EL_STR("Enter your name\xe2\x80\xa6"));
|
||||
style_label_body(input);
|
||||
widget_set_flex(input, 1);
|
||||
widget_on_change(input, EL_STR("on_name_change"));
|
||||
el_val_t g_input = input;
|
||||
el_val_t greet_btn = button(EL_STR("Greet"));
|
||||
style_button_primary(greet_btn);
|
||||
widget_set_disabled(greet_btn, 1);
|
||||
widget_on_click(greet_btn, EL_STR("on_greet_click"));
|
||||
el_val_t g_button = greet_btn;
|
||||
widget_add_child(input_row, name_label);
|
||||
widget_add_child(input_row, input);
|
||||
widget_add_child(input_row, greet_btn);
|
||||
widget_add_child(root, input_row);
|
||||
el_val_t gap3 = label(EL_STR(""));
|
||||
widget_set_height(gap3, 12);
|
||||
widget_add_child(root, gap3);
|
||||
el_val_t greeting = label(EL_STR("Waiting for name\xe2\x80\xa6"));
|
||||
style_label_body(greeting);
|
||||
widget_set_color_hex(greeting, EL_STR("#60a5fa"));
|
||||
widget_set_font(greeting, EL_STR("system"), 16, 1);
|
||||
el_val_t g_label = greeting;
|
||||
widget_add_child(root, greeting);
|
||||
el_val_t gap4 = label(EL_STR(""));
|
||||
widget_set_height(gap4, 24);
|
||||
widget_add_child(root, gap4);
|
||||
el_val_t counter_row = hstack(12);
|
||||
el_val_t counter_lbl = label(EL_STR("Clicks: 0"));
|
||||
style_label_body(counter_lbl);
|
||||
el_val_t g_counter_lbl = counter_lbl;
|
||||
el_val_t counter_btn = button(EL_STR("Click me"));
|
||||
style_button_primary(counter_btn);
|
||||
widget_on_click(counter_btn, EL_STR("on_counter_click"));
|
||||
widget_add_child(counter_row, counter_lbl);
|
||||
widget_add_child(counter_row, counter_btn);
|
||||
widget_add_child(root, counter_row);
|
||||
widget_add_child(window, root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
g_window = (-1);
|
||||
g_label = (-1);
|
||||
g_input = (-1);
|
||||
g_button = (-1);
|
||||
g_counter = 0;
|
||||
g_counter_lbl = (-1);
|
||||
native_init();
|
||||
manifest_env = env(EL_STR("EL_MANIFEST"));
|
||||
manifest_path = ({ el_val_t _if_result_2 = 0; if ((str_len(manifest_env) > 0)) { _if_result_2 = (manifest_env); } else { _if_result_2 = (EL_STR("manifest.el")); } _if_result_2; });
|
||||
win = window_from_manifest(manifest_path);
|
||||
g_window = win;
|
||||
if (win < 0) {
|
||||
println(EL_STR("Error: failed to create window. Is EL_TARGET_MACOS defined?"));
|
||||
exit_program(1);
|
||||
}
|
||||
app_build(win);
|
||||
window_show(win);
|
||||
native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,573 @@
|
||||
/*
|
||||
* el_native_target.h — Native widget declarations for el programs targeting
|
||||
* native desktop UI (AppKit / GTK4 / Win32).
|
||||
*
|
||||
* This header is designed to be included AFTER el_runtime.h without conflict:
|
||||
* - It does NOT redefine el_to_float, el_from_float, or any el_runtime.h
|
||||
* static inlines.
|
||||
* - It does NOT redeclare __println, __print, or other functions whose
|
||||
* return types differ between el_seed.h and el_runtime.h.
|
||||
* - It adds: native widget builtins + float arithmetic helpers that the
|
||||
* current el_runtime.h omits but elc still emits calls to.
|
||||
*
|
||||
* Usage:
|
||||
* Inject via -include at compile time, OR #include it after el_runtime.h.
|
||||
*
|
||||
* clang -DEL_TARGET_MACOS -include el_native_target.h -c my_app.c ...
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* el_val_t must already be defined by el_runtime.h or el_seed.h. */
|
||||
#ifndef EL_VAL_T_DEFINED
|
||||
typedef int64_t el_val_t;
|
||||
#endif
|
||||
|
||||
/* ── Float arithmetic helpers ───────────────────────────────────────────────
|
||||
* elc emits calls to float_div / float_mul etc. for Float-typed expressions.
|
||||
* These were in el_runtime.c through v1.0.0-20260501 but are missing from the
|
||||
* current el_runtime.h. Redeclared here as static inline to avoid link deps.
|
||||
* Only defined if not already declared (old runtimes that still have them). */
|
||||
|
||||
#ifndef EL_FLOAT_OPS_DEFINED
|
||||
#define EL_FLOAT_OPS_DEFINED
|
||||
|
||||
/* el_to_float / el_from_float — bit-cast between el_val_t and double.
|
||||
* Defined as static inline in both el_runtime.h and el_seed.h; we do NOT
|
||||
* redefine them here. We rely on one of those headers being included first. */
|
||||
|
||||
static inline el_val_t float_div(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b;
|
||||
ur.d = (ub.d != 0.0) ? (ua.d / ub.d) : 0.0;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_mul(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d * ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_add(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d + ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_sub(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d - ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_lt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d < ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d > ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_lte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d <= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d >= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_eq(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d == ub.d);
|
||||
}
|
||||
|
||||
#endif /* EL_FLOAT_OPS_DEFINED */
|
||||
|
||||
/* ── Native widget system (macOS AppKit) ────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_MACOS and linked with el_appkit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid. */
|
||||
|
||||
#ifdef EL_TARGET_MACOS
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_MACOS */
|
||||
|
||||
/* ── Native widget system (Linux GTK4) ──────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_LINUX and linked with el_gtk4.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* All functions have the same signatures as EL_TARGET_MACOS above. */
|
||||
|
||||
#ifdef EL_TARGET_LINUX
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as EL_TARGET_MACOS */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LINUX */
|
||||
|
||||
/* ── Native widget system (Windows Win32) ───────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_WIN32 and linked with el_win32.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* Link: el_win32.obj comctl32.lib user32.lib gdi32.lib */
|
||||
|
||||
#ifdef EL_TARGET_WIN32
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_WIN32 */
|
||||
|
||||
/* ── Native widget system (iOS UIKit) ───────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_IOS and linked with el_uikit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* iOS lifecycle note: UIApplicationMain never returns. The el program must
|
||||
* store its UI-build logic in a void(*)(void) function pointer, assign it to
|
||||
* el_main_entry_fn, then call __native_run_loop. ElAppDelegate invokes
|
||||
* el_main_entry_fn inside didFinishLaunchingWithOptions.
|
||||
* Call el_uikit_set_args(argc, argv) from main() before __native_run_loop. */
|
||||
|
||||
#ifdef EL_TARGET_IOS
|
||||
|
||||
/* Lifecycle entry-function hook — set before calling __native_run_loop. */
|
||||
extern void (*el_main_entry_fn)(void);
|
||||
|
||||
/* Forward argc/argv from main() to UIApplicationMain. */
|
||||
void el_uikit_set_args(int argc, char** argv);
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_IOS */
|
||||
|
||||
/* ── Native widget system (Android JNI) ─────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_ANDROID and linked with
|
||||
* libelruntime.so (which includes el_android.c compiled by the NDK build).
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Java companion: ElBridge.java (package com.neuron.el) must be compiled into
|
||||
* the APK. The Activity must call ElBridge.init(this) before any widget ops.
|
||||
*
|
||||
* Link flags (in Android.mk or CMakeLists.txt):
|
||||
* -landroid -llog -ldl */
|
||||
|
||||
#ifdef EL_TARGET_ANDROID
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void); /* no-op on Android */
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_ANDROID */
|
||||
|
||||
/* ── Native widget system (LVGL v9 — embedded / microcontroller) ─────────────
|
||||
* Available when compiled with -DEL_TARGET_LVGL and linked with el_lvgl.c
|
||||
* and the LVGL library (lvgl.a or lvgl source tree).
|
||||
*
|
||||
* Target platforms: ESP32, STM32, industrial panels. Any system with 256KB+
|
||||
* RAM and an LVGL-compatible display driver. No OS required.
|
||||
*
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Bare-metal / no dynamic linker:
|
||||
* Compile with -DEL_LVGL_NO_DLSYM and provide:
|
||||
* el_val_t el_lvgl_dispatch(const char *fn, el_val_t a, el_val_t b);
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_LVGL -I./lvgl el_lvgl.c -c -o el_lvgl.o
|
||||
* # Then link with lvgl.a. */
|
||||
|
||||
#ifdef EL_TARGET_LVGL
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as all other native targets */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LVGL */
|
||||
|
||||
/* ── Native widget system (SDL2 — embedded / Pi) ────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_SDL2 and linked with el_sdl2.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Target: Raspberry Pi Zero, embedded Linux, any system with a framebuffer
|
||||
* and SDL2 available. No GTK, no desktop environment required.
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_SDL2 $(sdl2-config --cflags) -c el_sdl2.c -o el_sdl2.o
|
||||
* Link:
|
||||
* $(sdl2-config --libs) -lSDL2_ttf -lSDL2_image -ldl */
|
||||
|
||||
#ifdef EL_TARGET_SDL2
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_SDL2 */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,883 @@
|
||||
/*
|
||||
* el_runtime.h — El language C runtime header
|
||||
*
|
||||
* Declares all built-in functions available to compiled El programs.
|
||||
* Include this in every generated .c file.
|
||||
*
|
||||
* Value model:
|
||||
* All El values are represented as el_val_t (= int64_t).
|
||||
* On 64-bit systems a pointer fits in int64_t.
|
||||
* String values are cast: (el_val_t)(uintptr_t)"hello"
|
||||
* Integer values are stored directly.
|
||||
* This lets arithmetic work naturally while still passing strings around.
|
||||
*
|
||||
* Type conventions (El -> C):
|
||||
* String -> el_val_t (holds const char* via uintptr_t cast)
|
||||
* Int -> el_val_t
|
||||
* Bool -> el_val_t (0 = false, nonzero = true)
|
||||
* Any -> el_val_t
|
||||
* Void -> void
|
||||
*
|
||||
* Macros for convenience:
|
||||
* EL_STR(s) cast string literal to el_val_t
|
||||
* EL_CSTR(v) cast el_val_t back to const char*
|
||||
* EL_INT(v) identity — el_val_t is already int64_t
|
||||
* EL_NULL null / zero value
|
||||
* EL_FALSE boolean false (0)
|
||||
* EL_TRUE boolean true (1)
|
||||
*
|
||||
* Link requirements:
|
||||
* -lcurl — required for the HTTP client (http_get, http_post, llm_*).
|
||||
* -lpthread — required for the HTTP server (one detached thread per
|
||||
* connection, capped at 64 concurrent).
|
||||
* -loqs — optional; required only when liboqs is installed and the
|
||||
* pq_* / sha3_256_hex entry points are needed. Detected at
|
||||
* compile time via __has_include(<oqs/oqs.h>).
|
||||
* -lcrypto — optional; pulled in alongside -loqs. Used for X25519 in
|
||||
* pq_hybrid_* and HKDF-SHA256 derivation.
|
||||
*
|
||||
* Canonical compile command:
|
||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
||||
*
|
||||
* With liboqs (post-quantum stack):
|
||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread -loqs -lcrypto \
|
||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
||||
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
||||
#define EL_INT(v) (v)
|
||||
#define EL_NULL ((el_val_t)0)
|
||||
#define EL_FALSE ((el_val_t)0)
|
||||
#define EL_TRUE ((el_val_t)1)
|
||||
|
||||
/* Float values share the el_val_t (int64) slot via a bit-cast.
|
||||
* The codegen emits Float literals as `el_from_float(<dbl>)` so the
|
||||
* underlying bits represent the IEEE 754 double. Float-aware builtins
|
||||
* (math, format, json) round-trip via these helpers. */
|
||||
static inline double el_to_float(el_val_t v) {
|
||||
union { int64_t i; double f; } u;
|
||||
u.i = (int64_t)v;
|
||||
return u.f;
|
||||
}
|
||||
|
||||
static inline el_val_t el_from_float(double f) {
|
||||
union { double f; int64_t i; } u;
|
||||
u.f = f;
|
||||
return (el_val_t)u.i;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ── I/O ──────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t println(el_val_t s);
|
||||
el_val_t print(el_val_t s);
|
||||
el_val_t readline(void);
|
||||
|
||||
/* ── String builtins ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t str_eq(el_val_t a, el_val_t b);
|
||||
el_val_t str_starts_with(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_ends_with(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_len(el_val_t s);
|
||||
el_val_t str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t int_to_str(el_val_t n);
|
||||
el_val_t str_to_int(el_val_t s);
|
||||
el_val_t native_str_to_int(el_val_t s);
|
||||
el_val_t str_slice(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t str_contains(el_val_t s, el_val_t sub);
|
||||
el_val_t str_replace(el_val_t s, el_val_t from, el_val_t to);
|
||||
el_val_t str_to_upper(el_val_t s);
|
||||
el_val_t str_to_lower(el_val_t s);
|
||||
el_val_t str_trim(el_val_t s);
|
||||
|
||||
/* ── Math ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_abs(el_val_t n);
|
||||
el_val_t el_max(el_val_t a, el_val_t b);
|
||||
el_val_t el_min(el_val_t a, el_val_t b);
|
||||
|
||||
/* ── Refcount (ARC) ──────────────────────────────────────────────────────────
|
||||
* Lists and Maps carry a refcount. Strings and ints do not — el_retain and
|
||||
* el_release are safe no-ops on non-refcounted values (they sniff a magic
|
||||
* header at offset 0 and only act if the magic matches).
|
||||
*
|
||||
* Codegen emits these at let-binding shadowing, function entry (params), and
|
||||
* function exit (locals other than the returned value). The refcount lets
|
||||
* el_list_append and el_map_set mutate in place when uniquely owned (cheap)
|
||||
* and copy-on-write when shared (preserves persistent semantics across
|
||||
* accumulator patterns in the compiler itself). */
|
||||
|
||||
void el_retain(el_val_t v);
|
||||
void el_release(el_val_t v);
|
||||
|
||||
/* ── Scoped arena (CLI use) ───────────────────────────────────────────────── */
|
||||
el_val_t el_arena_push(void);
|
||||
el_val_t el_arena_pop(el_val_t mark);
|
||||
|
||||
/* ── List ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_list_new(el_val_t count, ...);
|
||||
el_val_t el_list_len(el_val_t list);
|
||||
el_val_t el_list_get(el_val_t list, el_val_t index);
|
||||
el_val_t el_list_append(el_val_t list, el_val_t elem);
|
||||
el_val_t el_list_empty(void);
|
||||
el_val_t el_list_clone(el_val_t list);
|
||||
|
||||
/* ── Map ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_map_new(el_val_t pair_count, ...);
|
||||
el_val_t el_get_field(el_val_t map, el_val_t key);
|
||||
el_val_t el_map_get(el_val_t map, el_val_t key);
|
||||
el_val_t el_map_set(el_val_t map, el_val_t key, el_val_t value);
|
||||
|
||||
/* ── HTTP ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t http_get(el_val_t url);
|
||||
el_val_t http_post(el_val_t url, el_val_t body);
|
||||
el_val_t http_post_json(el_val_t url, el_val_t json_body);
|
||||
el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map);
|
||||
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
|
||||
el_val_t http_post_json_with_headers(el_val_t url, el_val_t headers_map, el_val_t json_body);
|
||||
el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
|
||||
el_val_t http_delete(el_val_t url);
|
||||
el_val_t http_serve(el_val_t port, el_val_t handler);
|
||||
el_val_t http_set_handler(el_val_t name);
|
||||
|
||||
/* HTTP server v2 ─────────────────────────────────────────────────────────────
|
||||
* Same dispatch model as http_serve, but the handler signature is widened:
|
||||
*
|
||||
* el_val_t handler(method, path, headers_map, body)
|
||||
*
|
||||
* `headers_map` is an ElMap from lowercased header name → header value (both
|
||||
* Strings). Repeated headers are joined with ", " per RFC 7230.
|
||||
*
|
||||
* Response value: the handler may return either
|
||||
* (a) a plain body string — same auto-content-type / 200-OK behaviour as
|
||||
* http_serve (3-arg) — or
|
||||
* (b) a response envelope built with `http_response(status, headers_json,
|
||||
* body)`. The runtime detects the envelope discriminator
|
||||
* `"el_http_response":1` at the start of the returned string and
|
||||
* unpacks status / headers / body before sending.
|
||||
*
|
||||
* The 3-arg http_serve(port, handler) remains supported unchanged for
|
||||
* existing handlers (e.g. products/web/server.el): it dispatches with
|
||||
* (method, path, body), hardcodes 200 OK, and auto-detects content type. */
|
||||
el_val_t http_serve_v2(el_val_t port, el_val_t handler);
|
||||
el_val_t http_set_handler_v2(el_val_t name);
|
||||
|
||||
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
||||
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
||||
* returned string carries the discriminator `{"el_http_response":1,...}`
|
||||
* which the runtime's send-path detects and unpacks. Detection happens
|
||||
* uniformly inside http_send_response, so a 3-arg handler may also return
|
||||
* an envelope. The 3-arg variant remains documented as a fixed 200-OK
|
||||
* auto-content-type contract for legacy handlers that return plain bodies. */
|
||||
el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
|
||||
/* SSE connection fd — set by http_worker_v2 before calling the El handler,
|
||||
* cleared afterwards. Defined in el_seed.c; called from el_runtime.c.
|
||||
* The getter is exposed as __http_conn_fd() to El programs. */
|
||||
void el_seed_set_http_conn_fd(int fd);
|
||||
|
||||
/* HTTP timeout — every libcurl request honors EL_HTTP_TIMEOUT_MS (default
|
||||
* 60000ms). Read lazily on first use, so setting the env var any time before
|
||||
* the first http_* call is sufficient. */
|
||||
|
||||
/* Streaming variants — write the response body straight to a file via
|
||||
* libcurl's CURLOPT_WRITEFUNCTION = fwrite. These bypass the el_val_t string
|
||||
* wrapper entirely, so binary payloads (audio/mpeg, image/png, etc.) survive
|
||||
* embedded NUL bytes that would truncate a strlen()-based code path.
|
||||
*
|
||||
* Both honor EL_HTTP_TIMEOUT_MS, follow redirects, and accept the same
|
||||
* `headers_map` shape as http_post_with_headers (ElMap of String→String).
|
||||
*
|
||||
* Return value: 1 on success (file fully written), 0 on any failure
|
||||
* (network, file open, partial write). On failure the output file is removed
|
||||
* so callers cannot mistake a partially-written file for a valid one. */
|
||||
el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el_val_t output_path);
|
||||
el_val_t http_get_to_file(el_val_t url, el_val_t headers_map, el_val_t output_path);
|
||||
|
||||
/* ── URL encoding ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t url_encode(el_val_t s); /* RFC 3986 unreserved set */
|
||||
el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */
|
||||
|
||||
/* ── HTML allowlist sanitizer ────────────────────────────────────────────────
|
||||
* el_html_sanitize(input_html, allowlist_json) — strict allowlist HTML
|
||||
* cleaner. State-machine parser; tag/attribute names compared case-
|
||||
* insensitively against the allowlist; `<a href>` / `<… src>` URL schemes
|
||||
* validated (http, https, mailto, fragment-only, or relative); whole-
|
||||
* subtree drop for script / style / iframe / object / embed / form; HTML-
|
||||
* escapes free text outside dropped subtrees.
|
||||
*
|
||||
* The allowlist is JSON of the form
|
||||
* {"p":[],"a":["href","title"],"strong":[],...}
|
||||
* where each value is the array of attribute names allowed for that tag. */
|
||||
el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json);
|
||||
el_val_t html_raw(el_val_t s);
|
||||
el_val_t html_escape(el_val_t s);
|
||||
|
||||
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t fs_read(el_val_t path);
|
||||
el_val_t fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t fs_list(el_val_t path);
|
||||
el_val_t fs_list_json(el_val_t path);
|
||||
el_val_t fs_exists(el_val_t path);
|
||||
el_val_t fs_mkdir(el_val_t path); /* mkdir -p, mode 0755 */
|
||||
|
||||
/* Length-explicit binary write. `length` is an Int (el_val_t holding the
|
||||
* byte count). The caller knows the length from context — typically because
|
||||
* `bytes` came from base64_decode (which produces a magic-tagged binary
|
||||
* buffer with embedded NULs possible) and the caller already tracks the
|
||||
* decoded length, OR because the bytes came from a fixed-size source
|
||||
* (sha256_bytes = 32, hmac_sha256_bytes = 32). Bypasses strlen entirely.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure (invalid path, can't open, partial
|
||||
* write, negative length). On partial-write failure, the file is removed
|
||||
* so callers cannot read back a truncated artefact. */
|
||||
el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t length);
|
||||
|
||||
/* ── JSON ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t json_get(el_val_t json, el_val_t key);
|
||||
el_val_t json_parse(el_val_t s);
|
||||
el_val_t json_stringify(el_val_t v);
|
||||
el_val_t json_get_string(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_int(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_float(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_bool(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_raw(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value);
|
||||
el_val_t json_array_len(el_val_t json_str);
|
||||
el_val_t json_array_get(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_array_get_string(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_escape_string(el_val_t sv);
|
||||
el_val_t json_build_object(el_val_t kvs);
|
||||
el_val_t json_build_array(el_val_t items);
|
||||
|
||||
/* ── Time ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t time_now(void);
|
||||
el_val_t time_now_utc(void);
|
||||
el_val_t sleep_secs(el_val_t secs);
|
||||
el_val_t sleep_ms(el_val_t ms);
|
||||
el_val_t time_format(el_val_t ts, el_val_t fmt);
|
||||
el_val_t time_to_parts(el_val_t ts);
|
||||
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
|
||||
el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit);
|
||||
el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
|
||||
el_val_t now_ns(void);
|
||||
|
||||
/* ── Instant + Duration: first-class temporal types ──────────────────────────
|
||||
* Both types share the el_val_t (int64) slot. Instants are nanoseconds
|
||||
* since the Unix epoch; Durations are signed nanoseconds. Type discipline
|
||||
* is enforced at codegen-time: BinOps on names registered as Instant or
|
||||
* Duration route through the typed wrappers below; mismatches like
|
||||
* Instant+Instant become #error at the C compiler.
|
||||
*
|
||||
* Postfix literals — `30.seconds`, `1.hour`, `500.millis`, `30.nanos` — are
|
||||
* recognised by the parser as DurationLit AST nodes and lowered to literal
|
||||
* int64 nanoseconds at codegen time. The runtime never sees the units. */
|
||||
|
||||
el_val_t el_now_instant(void);
|
||||
el_val_t now(void);
|
||||
el_val_t unix_seconds(el_val_t n);
|
||||
el_val_t unix_millis(el_val_t n);
|
||||
el_val_t instant_from_iso8601(el_val_t s);
|
||||
|
||||
el_val_t el_duration_from_nanos(el_val_t ns);
|
||||
el_val_t duration_seconds(el_val_t n);
|
||||
el_val_t duration_millis(el_val_t n);
|
||||
el_val_t duration_nanos(el_val_t n);
|
||||
|
||||
el_val_t el_instant_add_dur(el_val_t inst, el_val_t dur);
|
||||
el_val_t el_instant_sub_dur(el_val_t inst, el_val_t dur);
|
||||
el_val_t el_instant_diff(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_add(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_sub(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_scale(el_val_t dur, el_val_t scalar);
|
||||
el_val_t el_duration_div(el_val_t dur, el_val_t scalar);
|
||||
|
||||
el_val_t el_instant_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_le(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_gt(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_ge(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_eq(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_ne(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_le(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_gt(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_ge(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_eq(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_ne(el_val_t a, el_val_t b);
|
||||
|
||||
el_val_t instant_to_unix_seconds(el_val_t i);
|
||||
el_val_t instant_to_unix_millis(el_val_t i);
|
||||
el_val_t instant_to_iso8601(el_val_t i);
|
||||
el_val_t duration_to_seconds(el_val_t d);
|
||||
el_val_t duration_to_millis(el_val_t d);
|
||||
el_val_t duration_to_nanos(el_val_t d);
|
||||
|
||||
el_val_t el_sleep_duration(el_val_t dur);
|
||||
el_val_t unix_timestamp(void);
|
||||
|
||||
el_val_t ttl_cache_set(el_val_t key, el_val_t value);
|
||||
el_val_t ttl_cache_get(el_val_t key, el_val_t max_age);
|
||||
el_val_t ttl_cache_age(el_val_t key);
|
||||
|
||||
/* ── Calendar + CalendarTime + Rhythm + LocalDate/Time/DateTime ─────────────
|
||||
* Phase 1.5 of the time system. Calendar is pluggable: EarthCalendar (IANA
|
||||
* zones, Gregorian, DST) is the user-facing default; MarsCalendar,
|
||||
* CycleCalendar(period), NoCycleCalendar, RelativeCalendar handle non-Earth
|
||||
* domains.
|
||||
*
|
||||
* A Calendar interprets an Instant under a particular cycle convention and
|
||||
* produces a CalendarTime. CalendarTime carries the underlying Instant and
|
||||
* a back-pointer to its Calendar; arithmetic and formatting consult the
|
||||
* Calendar to convert ns since epoch into year/month/day/hour/minute/second
|
||||
* (or sol/phase, or cycle/phase, depending on kind).
|
||||
*
|
||||
* Storage convention: Calendar / CalendarTime / Rhythm / LocalDate /
|
||||
* LocalDateTime are heap-allocated structs whose pointers are cast into
|
||||
* el_val_t. A 24-bit magic header at offset 0 lets the runtime identify
|
||||
* the kind safely. LocalTime is small enough to live in the int64 slot
|
||||
* directly (nanos since midnight, signed). */
|
||||
|
||||
/* Zone — opaque IANA zone or fixed offset, used by EarthCalendar.
|
||||
* `zone_id` is either an IANA name ("America/New_York", "UTC") or a fixed
|
||||
* offset string ("+05:30", "-08:00"). The runtime resolves it via tzset()
|
||||
* on first use of the owning EarthCalendar. */
|
||||
el_val_t zone(el_val_t id);
|
||||
el_val_t zone_utc(void);
|
||||
el_val_t zone_local(void);
|
||||
el_val_t zone_offset(el_val_t hours, el_val_t minutes);
|
||||
|
||||
/* Calendar constructors. Each returns an el_val_t pointer to a heap-
|
||||
* allocated, magic-tagged Calendar struct. Calendars are interned by
|
||||
* (kind, zone_id, period_ns, epoch_ns) so identical constructors return
|
||||
* the same pointer — equality is reference equality. */
|
||||
el_val_t earth_calendar(el_val_t z);
|
||||
el_val_t earth_calendar_default(void);
|
||||
el_val_t mars_calendar(void);
|
||||
el_val_t cycle_calendar(el_val_t period_dur);
|
||||
el_val_t no_cycle_calendar(void);
|
||||
el_val_t relative_calendar(el_val_t epoch_inst);
|
||||
|
||||
/* CalendarTime constructors and methods. Returns a heap-allocated struct
|
||||
* whose pointer fits in el_val_t. */
|
||||
el_val_t now_in(el_val_t cal);
|
||||
el_val_t in_calendar(el_val_t inst, el_val_t cal);
|
||||
el_val_t cal_format(el_val_t ct, el_val_t pattern);
|
||||
el_val_t cal_to_instant(el_val_t ct);
|
||||
el_val_t cal_cycle_phase(el_val_t ct);
|
||||
el_val_t cal_in(el_val_t ct, el_val_t cal);
|
||||
|
||||
/* LocalDate / LocalTime / LocalDateTime — calendar-agnostic value types.
|
||||
* LocalTime carries nanoseconds since midnight as a signed int64 directly
|
||||
* in the el_val_t slot (no allocation). LocalDate / LocalDateTime are
|
||||
* heap-allocated structs with magic headers. */
|
||||
el_val_t local_date(el_val_t y, el_val_t m, el_val_t d);
|
||||
el_val_t local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns);
|
||||
el_val_t local_datetime(el_val_t date, el_val_t time);
|
||||
el_val_t zoned(el_val_t date, el_val_t time, el_val_t cal);
|
||||
|
||||
el_val_t local_date_year(el_val_t ld);
|
||||
el_val_t local_date_month(el_val_t ld);
|
||||
el_val_t local_date_day(el_val_t ld);
|
||||
el_val_t local_time_hour(el_val_t lt);
|
||||
el_val_t local_time_minute(el_val_t lt);
|
||||
el_val_t local_time_second(el_val_t lt);
|
||||
el_val_t local_time_nanos(el_val_t lt);
|
||||
|
||||
el_val_t el_local_date_add_dur(el_val_t ld, el_val_t dur);
|
||||
el_val_t el_local_time_add_dur(el_val_t lt, el_val_t dur);
|
||||
el_val_t el_local_date_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_local_date_eq(el_val_t a, el_val_t b);
|
||||
|
||||
/* Rhythm — pluggable recurrence AST. Returns a heap-allocated struct
|
||||
* pointer in el_val_t; rhythms are immutable so callers may share them. */
|
||||
el_val_t rhythm_cycle_start(void);
|
||||
el_val_t rhythm_cycle_phase(el_val_t phase);
|
||||
el_val_t rhythm_duration(el_val_t d);
|
||||
el_val_t rhythm_session_start(void);
|
||||
el_val_t rhythm_event(el_val_t name);
|
||||
el_val_t rhythm_and(el_val_t a, el_val_t b);
|
||||
el_val_t rhythm_or(el_val_t a, el_val_t b);
|
||||
el_val_t rhythm_weekday(el_val_t day);
|
||||
el_val_t rhythm_weekly_at(el_val_t day, el_val_t hour, el_val_t minute);
|
||||
el_val_t rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal);
|
||||
el_val_t rhythm_matches(el_val_t r, el_val_t ct);
|
||||
|
||||
/* ── UUID ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t uuid_new(void);
|
||||
el_val_t uuid_v4(void);
|
||||
|
||||
/* ── Environment ─────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t env(el_val_t key);
|
||||
|
||||
/* ── In-process state K/V ────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t state_set(el_val_t key, el_val_t value);
|
||||
el_val_t state_get(el_val_t key);
|
||||
el_val_t state_del(el_val_t key);
|
||||
el_val_t state_keys(void);
|
||||
el_val_t state_has(el_val_t key);
|
||||
el_val_t state_get_or(el_val_t key, el_val_t default_val);
|
||||
|
||||
/* ── Float formatting ────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t float_to_str(el_val_t f);
|
||||
el_val_t int_to_float(el_val_t n);
|
||||
el_val_t float_to_int(el_val_t f);
|
||||
el_val_t format_float(el_val_t f, el_val_t decimals);
|
||||
el_val_t decimal_round(el_val_t f, el_val_t decimals);
|
||||
el_val_t str_to_float(el_val_t s);
|
||||
|
||||
/* ── Math (Float-aware) ──────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t math_sqrt(el_val_t f);
|
||||
el_val_t math_log(el_val_t f);
|
||||
el_val_t math_ln(el_val_t f);
|
||||
el_val_t math_sin(el_val_t f);
|
||||
el_val_t math_cos(el_val_t f);
|
||||
el_val_t math_pi(void);
|
||||
|
||||
/* ── String additions ────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t str_index_of(el_val_t s, el_val_t sub);
|
||||
el_val_t str_split(el_val_t s, el_val_t sep);
|
||||
el_val_t str_char_at(el_val_t s, el_val_t i);
|
||||
el_val_t str_char_code(el_val_t s, el_val_t i);
|
||||
el_val_t str_pad_left(el_val_t s, el_val_t width, el_val_t pad);
|
||||
el_val_t str_pad_right(el_val_t s, el_val_t width, el_val_t pad);
|
||||
el_val_t str_format(el_val_t fmt, el_val_t data);
|
||||
el_val_t str_lower(el_val_t s);
|
||||
el_val_t str_upper(el_val_t s);
|
||||
|
||||
/* ── Text-processing primitives (Phase 1: byte/codepoint, ASCII char classes)
|
||||
* Phase 2 (filed): Unicode-grapheme awareness, NFC/NFD normalization, regex.
|
||||
* is_* predicates: empty input returns false; multi-char requires ALL bytes
|
||||
* to match. ASCII ranges only in Phase 1. */
|
||||
|
||||
/* Counting */
|
||||
el_val_t str_count(el_val_t s, el_val_t sub); /* non-overlapping */
|
||||
el_val_t str_count_chars(el_val_t s); /* codepoint count */
|
||||
el_val_t str_count_bytes(el_val_t s); /* alias of str_len */
|
||||
el_val_t str_count_lines(el_val_t s);
|
||||
el_val_t str_count_words(el_val_t s);
|
||||
el_val_t str_count_letters(el_val_t s); /* ASCII [A-Za-z] */
|
||||
el_val_t str_count_digits(el_val_t s); /* ASCII [0-9] */
|
||||
|
||||
/* Find / position */
|
||||
el_val_t str_index_of_all(el_val_t s, el_val_t sub); /* [Int] of byte offsets */
|
||||
el_val_t str_last_index_of(el_val_t s, el_val_t sub);
|
||||
el_val_t str_find_chars(el_val_t s, el_val_t any_of); /* first idx of any ch */
|
||||
|
||||
/* Transform */
|
||||
el_val_t str_repeat(el_val_t s, el_val_t n);
|
||||
el_val_t str_reverse(el_val_t s); /* by codepoint */
|
||||
el_val_t str_strip_prefix(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_strip_suffix(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_strip_chars(el_val_t s, el_val_t chars);
|
||||
el_val_t str_lstrip(el_val_t s);
|
||||
el_val_t str_rstrip(el_val_t s);
|
||||
|
||||
/* Char classification (Bool) */
|
||||
el_val_t is_letter(el_val_t s);
|
||||
el_val_t is_digit(el_val_t s);
|
||||
el_val_t is_alphanumeric(el_val_t s);
|
||||
el_val_t is_whitespace(el_val_t s);
|
||||
el_val_t is_punctuation(el_val_t s);
|
||||
el_val_t is_uppercase(el_val_t s);
|
||||
el_val_t is_lowercase(el_val_t s);
|
||||
|
||||
/* Split / join */
|
||||
el_val_t str_split_lines(el_val_t s);
|
||||
el_val_t str_split_chars(el_val_t s); /* alias of native_string_chars */
|
||||
el_val_t str_split_n(el_val_t s, el_val_t sep, el_val_t n);
|
||||
el_val_t str_join(el_val_t list, el_val_t sep); /* alias of list_join */
|
||||
|
||||
/* ── List additions ──────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t list_push(el_val_t list, el_val_t elem);
|
||||
el_val_t list_push_front(el_val_t list, el_val_t elem);
|
||||
el_val_t list_join(el_val_t list, el_val_t sep);
|
||||
el_val_t list_range(el_val_t start, el_val_t end);
|
||||
|
||||
/* ── Bool helpers ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t bool_to_str(el_val_t b);
|
||||
|
||||
/* ── Numeric parsing ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t parse_int(el_val_t s, el_val_t default_val);
|
||||
|
||||
/* ── Process ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t exit_program(el_val_t code);
|
||||
el_val_t getpid_now(void);
|
||||
|
||||
/* Self-terminating memory guard. Reads ELC_MAX_MEM_MB (default 512) and
|
||||
* exits with code 1 if resident memory exceeds the limit. Call periodically
|
||||
* during long compilation loops (e.g. after each function is compiled).
|
||||
* Returns 0 when memory is within bounds. */
|
||||
el_val_t el_mem_check(void);
|
||||
|
||||
/* ── CGI identity ─────────────────────────────────────────────────────────────
|
||||
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
|
||||
* Records the program's DHARMA identity before any other code executes. */
|
||||
|
||||
void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
el_val_t network, el_val_t engram);
|
||||
|
||||
/* ── DHARMA network builtins ─────────────────────────────────────────────────
|
||||
* Available to CGI programs (declared with a `cgi {}` block).
|
||||
*
|
||||
* Peers are addressed by `dharma_id` of the form
|
||||
* "<registry-id>@<transport-url>" e.g. "ntn-genesis@http://localhost:7770"
|
||||
* If the @<url> portion is omitted, transport defaults to
|
||||
* "http://localhost:7770" (the local CGI daemon assumption).
|
||||
*
|
||||
* Wire protocol (all peers expose):
|
||||
* POST <url>/dharma/recv { channel, from, content } → response body
|
||||
* POST <url>/dharma/event { type, payload, source, timestamp }
|
||||
* POST <url>/api/activate { query } → list of nodes
|
||||
*
|
||||
* Hosting application's responsibility: an El program with a `cgi {}` block
|
||||
* runs http_serve() with its own request handler; that handler should route
|
||||
* "/dharma/event" requests by calling el_runtime_dharma_event_arrive() so
|
||||
* incoming events feed dharma_field() queues. The runtime itself does not
|
||||
* intercept any /dharma path. */
|
||||
|
||||
el_val_t dharma_connect(el_val_t cgi_id);
|
||||
el_val_t dharma_send(el_val_t channel, el_val_t content);
|
||||
el_val_t dharma_activate(el_val_t query);
|
||||
void dharma_emit(el_val_t event_type, el_val_t payload);
|
||||
el_val_t dharma_field(el_val_t event_type);
|
||||
void dharma_strengthen(el_val_t cgi_id, el_val_t weight);
|
||||
el_val_t dharma_relationship(el_val_t cgi_id);
|
||||
el_val_t dharma_peers(void);
|
||||
|
||||
/* Public C API: called by an El program's HTTP handler when a /dharma/event
|
||||
* request arrives. Pushes onto the per-event-type queue and signals any
|
||||
* pending dharma_field() blockers. All three arguments must be NUL-terminated
|
||||
* C strings (or NULL — then treated as empty). */
|
||||
void el_runtime_dharma_event_arrive(const char* event_type,
|
||||
const char* payload,
|
||||
const char* source);
|
||||
|
||||
/* ── Engram local graph primitives ───────────────────────────────────────────
|
||||
* Operate on the CGI's local Engram knowledge graph.
|
||||
* `engram_activate` queries the local graph only; `dharma_activate` is
|
||||
* network-wide across all connected CGI graphs. */
|
||||
|
||||
el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience);
|
||||
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t importance, el_val_t confidence,
|
||||
el_val_t tier, el_val_t tags);
|
||||
/* Layered consciousness — see el_runtime.c for the layered architecture
|
||||
* design notes (search "Layered consciousness architecture"). The five
|
||||
* canonical layers (safety / core-identity / domain-knowledge / imprint /
|
||||
* suit) are seeded automatically; engram_add_layer extends the registry
|
||||
* with imprint or suit overlays at runtime. Nodes default to layer 1
|
||||
* (core-identity) when created via engram_node / engram_node_full. */
|
||||
el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t certainty, el_val_t confidence,
|
||||
el_val_t status, el_val_t tags, el_val_t layer_id);
|
||||
el_val_t engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible,
|
||||
el_val_t transparent, el_val_t injectable);
|
||||
el_val_t engram_remove_layer(el_val_t layer_id);
|
||||
el_val_t engram_list_layers(void);
|
||||
el_val_t engram_get_node(el_val_t id);
|
||||
void engram_strengthen(el_val_t node_id);
|
||||
void engram_forget(el_val_t node_id);
|
||||
el_val_t engram_node_count(void);
|
||||
el_val_t engram_search(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||
void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation);
|
||||
el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id);
|
||||
el_val_t engram_neighbors(el_val_t node_id);
|
||||
el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t engram_edge_count(void);
|
||||
/* Three-pass activation: background fan-out → working-memory promotion →
|
||||
* Layer 0 override. See "Three-pass activation" in el_runtime.c. */
|
||||
el_val_t engram_activate(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_save(el_val_t path);
|
||||
el_val_t engram_load(el_val_t path);
|
||||
|
||||
/* JSON-string accessors — return pre-serialized JSON so HTTP handlers
|
||||
* can pass results straight through without round-tripping ElList/ElMap
|
||||
* through json_stringify. */
|
||||
el_val_t engram_get_node_json(el_val_t id);
|
||||
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_stats_json(void);
|
||||
el_val_t engram_list_layers_json(void);
|
||||
/* engram_compile_layered_json — produce a prompt-ready text block split
|
||||
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
||||
* and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
|
||||
* no nodes promoted to working memory. */
|
||||
el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
||||
|
||||
/* ── LLM (Anthropic API client) ─────────────────────────────────────────────
|
||||
* All functions call https://api.anthropic.com/v1/messages with the API key
|
||||
* from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */
|
||||
|
||||
el_val_t llm_call(el_val_t model, el_val_t prompt);
|
||||
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt);
|
||||
el_val_t llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools);
|
||||
el_val_t llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64);
|
||||
el_val_t llm_models(void);
|
||||
|
||||
/* Register a tool handler by name. The handler is looked up via dlsym
|
||||
* (mirroring http_set_handler), so any El `fn <name>(input)` compiles to
|
||||
* a global C symbol that this function can locate at runtime.
|
||||
* Handler signature: `el_val_t handler(el_val_t input_json)` — receives
|
||||
* the tool input as a JSON-string el_val_t and returns a JSON-string
|
||||
* el_val_t result. Used by llm_call_agentic. */
|
||||
void llm_register_tool(el_val_t name, el_val_t handler_fn_name);
|
||||
|
||||
/* ── args() ─────────────────────────────────────────────────────────────────
|
||||
* Provides access to command-line arguments passed to the program.
|
||||
* Populated by el_runtime_init_args() before main() runs. */
|
||||
|
||||
el_val_t args(void);
|
||||
void el_runtime_init_args(int argc, char** argv);
|
||||
|
||||
/* ── Crypto primitives ─────────────────────────────────────────────────────
|
||||
* SHA-256, HMAC-SHA-256, and base64 (standard + URL-safe).
|
||||
* Self-contained — no OpenSSL/libcrypto dependency. The implementations are
|
||||
* adapted from public-domain reference code (Brad Conte / RFC 4648).
|
||||
*
|
||||
* Bytes-returning variants (sha256_bytes, hmac_sha256_bytes) return a string
|
||||
* value whose contents are raw binary; callers usually feed these into
|
||||
* base64_encode. Note that el_val_t strings are NUL-terminated by convention,
|
||||
* so the binary payload may contain embedded NULs — pass it directly into
|
||||
* base64_encode (which uses an explicit length) rather than treating it as
|
||||
* a printable C string.
|
||||
*
|
||||
* The "base64" variants emit/accept RFC 4648 standard alphabet with padding.
|
||||
* The "base64url" variants use URL-safe alphabet (`-`/`_`) with no padding,
|
||||
* as used in JWTs. */
|
||||
|
||||
el_val_t sha256_hex(el_val_t input);
|
||||
el_val_t sha256_bytes(el_val_t input);
|
||||
el_val_t hmac_sha256_hex(el_val_t key, el_val_t message);
|
||||
el_val_t hmac_sha256_bytes(el_val_t key, el_val_t message);
|
||||
el_val_t base64_encode(el_val_t input);
|
||||
el_val_t base64_decode(el_val_t input);
|
||||
el_val_t base64url_encode(el_val_t input);
|
||||
el_val_t base64url_decode(el_val_t input);
|
||||
|
||||
/* Length-aware variants (internal — exposed for the rare caller that already
|
||||
* has a known-length binary buffer and doesn't want to round-trip through
|
||||
* a NUL-terminated el_val_t string). Sha256_bytes and hmac_sha256_bytes feed
|
||||
* these implicitly. */
|
||||
el_val_t el_sha256_bytes_n(const unsigned char* data, size_t len);
|
||||
el_val_t el_base64_encode_n(const unsigned char* data, size_t len, int url_safe);
|
||||
|
||||
/* ── Post-quantum primitives (liboqs-backed) ────────────────────────────────
|
||||
* All inputs/outputs hex-encoded. Algorithm choices:
|
||||
* Signature: CRYSTALS-Dilithium-3 (NIST level 3, balanced)
|
||||
* KEM: CRYSTALS-Kyber-768 (NIST level 3)
|
||||
* Hash: SHA3-256 (Keccak) (PQ-aware protocols favour SHA3 over SHA2)
|
||||
*
|
||||
* If liboqs is not linked (detected via __has_include(<oqs/oqs.h>) at compile
|
||||
* time), the pq_* entry points return a JSON-shaped error string so callers
|
||||
* fail loudly rather than silently fall back to classical schemes:
|
||||
* {"error":"liboqs not linked, post-quantum primitives unavailable"}
|
||||
*
|
||||
* The hybrid handshake pairs X25519 with Kyber-768 per NIST PQ guidance and
|
||||
* CNSA 2.0. Combined shared secret is HKDF-SHA256(x25519_ss || kyber_ss).
|
||||
* Even if Kyber falls, X25519 holds; if X25519 falls under quantum attack,
|
||||
* Kyber holds. SHA3-256 also remains usable independent of liboqs (the
|
||||
* Keccak permutation is PQ-OK as a primitive). */
|
||||
|
||||
el_val_t pq_keygen_signature(void);
|
||||
el_val_t pq_sign(el_val_t secret_key_hex, el_val_t message);
|
||||
el_val_t pq_verify(el_val_t public_key_hex, el_val_t message, el_val_t signature_hex);
|
||||
|
||||
el_val_t pq_kem_keygen(void);
|
||||
el_val_t pq_kem_encaps(el_val_t public_key_hex);
|
||||
el_val_t pq_kem_decaps(el_val_t secret_key_hex, el_val_t ciphertext_hex);
|
||||
|
||||
el_val_t pq_hybrid_keygen(void);
|
||||
el_val_t pq_hybrid_handshake(el_val_t remote_pub_combined);
|
||||
|
||||
el_val_t sha3_256_hex(el_val_t input);
|
||||
|
||||
/* ── AEAD: AES-256-GCM (libcrypto-backed) ───────────────────────────────────
|
||||
* Symmetric authenticated encryption used to wrap envelopes after a KEM
|
||||
* handshake. Caller MUST supply a 32-byte key (64 hex chars) — typically the
|
||||
* Kyber-768 / hybrid shared_secret, optionally normalized via SHA3-256.
|
||||
*
|
||||
* aead_encrypt returns a JSON map {"nonce":"...","ciphertext":"..."} where
|
||||
* ciphertext is the AES-256-GCM output with the 16-byte auth tag appended.
|
||||
* Nonce is a fresh 12-byte CSPRNG draw — callers never pick the nonce, which
|
||||
* structurally rules out the GCM nonce-reuse footgun.
|
||||
*
|
||||
* aead_decrypt returns the plaintext String, or "" on any failure (including
|
||||
* auth-tag mismatch). Callers MUST check for "" before trusting the result. */
|
||||
el_val_t aead_encrypt(el_val_t key_hex, el_val_t plaintext);
|
||||
el_val_t aead_decrypt(el_val_t key_hex, el_val_t nonce_hex, el_val_t ciphertext_hex);
|
||||
|
||||
/* ── Native VM builtin aliases (for compiled El source) ─────────────────────
|
||||
* These match the El VM's native_* builtins so that El source compiled
|
||||
* to C can call the same names without modification. */
|
||||
|
||||
el_val_t native_list_get(el_val_t list, el_val_t index);
|
||||
el_val_t native_list_len(el_val_t list);
|
||||
el_val_t native_list_append(el_val_t list, el_val_t elem);
|
||||
el_val_t native_list_empty(void);
|
||||
el_val_t native_list_clone(el_val_t list);
|
||||
el_val_t native_string_chars(el_val_t s);
|
||||
el_val_t native_int_to_str(el_val_t n);
|
||||
|
||||
/* ── Method-call shorthand aliases ──────────────────────────────────────────
|
||||
* The El method-call convention `obj.method(args)` compiles to
|
||||
* `method(obj, args)`. These aliases expose the runtime functions under
|
||||
* the short names that result from method calls in El source.
|
||||
*
|
||||
* Example: `myList.append(x)` → `append(myList, x)` (calls this alias)
|
||||
* `myList.len()` → `len(myList)` (calls this alias) */
|
||||
|
||||
el_val_t append(el_val_t list, el_val_t elem); /* el_list_append */
|
||||
el_val_t len(el_val_t list); /* el_list_len */
|
||||
el_val_t get(el_val_t list, el_val_t index); /* el_list_get */
|
||||
el_val_t map_get(el_val_t map, el_val_t key); /* el_map_get */
|
||||
el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */
|
||||
|
||||
/* ── OTLP/HTTP Observability ─────────────────────────────────────────────── */
|
||||
/* See bottom of el_runtime.c for the implementation.
|
||||
* Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
|
||||
* No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
|
||||
/* ── Subprocess execution ────────────────────────────────────────────────── */
|
||||
el_val_t exec_command(el_val_t cmd); /* run shell command, return exit code */
|
||||
el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
||||
el_val_t exec(el_val_t cmd); /* exec(cmd) → stdout String (30s timeout) */
|
||||
el_val_t exec_bg(el_val_t cmd); /* exec_bg(cmd) → PID String (non-blocking) */
|
||||
|
||||
/* ── Stdout redirection (used by compiler JS pipeline) ───────────────────── */
|
||||
el_val_t stdout_to_file(el_val_t path); /* redirect process stdout to a file */
|
||||
el_val_t stdout_restore(void); /* restore process stdout to terminal */
|
||||
|
||||
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
||||
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
||||
el_val_t trace_span_start(el_val_t name);
|
||||
el_val_t trace_span_end(el_val_t span_handle);
|
||||
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
|
||||
|
||||
el_val_t __thread_create(el_val_t fn_name_v, el_val_t arg_v);
|
||||
el_val_t __thread_join(el_val_t tid_v);
|
||||
|
||||
/* ── __ prefixed aliases (self-hosting compiler ABI) ─────────────────────────
|
||||
* The El self-hosting compiler emits calls to __-prefixed names. These are
|
||||
* forwarding wrappers around the existing el_runtime functions above. */
|
||||
|
||||
/* I/O */
|
||||
el_val_t __println(el_val_t s);
|
||||
el_val_t __print(el_val_t s);
|
||||
el_val_t __readline(void);
|
||||
|
||||
/* String */
|
||||
el_val_t __int_to_str(el_val_t n);
|
||||
el_val_t __str_to_int(el_val_t s);
|
||||
el_val_t __float_to_str(el_val_t f);
|
||||
el_val_t __str_to_float(el_val_t s);
|
||||
el_val_t __str_len(el_val_t s);
|
||||
el_val_t __str_char_at(el_val_t s, el_val_t i);
|
||||
el_val_t __str_cmp(el_val_t a, el_val_t b);
|
||||
el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n);
|
||||
el_val_t __str_concat_raw(el_val_t a, el_val_t b);
|
||||
el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t __str_alloc(el_val_t n);
|
||||
el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c);
|
||||
|
||||
/* URL encoding */
|
||||
el_val_t __url_encode(el_val_t s);
|
||||
el_val_t __url_decode(el_val_t s);
|
||||
|
||||
/* Environment */
|
||||
el_val_t __env_get(el_val_t key);
|
||||
|
||||
/* Subprocess */
|
||||
el_val_t __exec(el_val_t cmd);
|
||||
el_val_t __exec_bg(el_val_t cmd);
|
||||
|
||||
/* Process */
|
||||
el_val_t __exit_program(el_val_t code);
|
||||
|
||||
/* Filesystem */
|
||||
el_val_t __fs_exists(el_val_t path);
|
||||
el_val_t __fs_mkdir(el_val_t path);
|
||||
el_val_t __fs_read(el_val_t path);
|
||||
el_val_t __fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n);
|
||||
el_val_t __fs_list_raw(el_val_t path);
|
||||
|
||||
/* HTTP server */
|
||||
el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
el_val_t __http_serve(el_val_t port, el_val_t handler);
|
||||
el_val_t __http_serve_v2(el_val_t port, el_val_t handler);
|
||||
|
||||
/* HTTP conn fd / SSE (weak; overridden by el_seed.c when linked together) */
|
||||
el_val_t __http_conn_fd(void);
|
||||
el_val_t __http_sse_open(el_val_t conn_id);
|
||||
el_val_t __http_sse_send(el_val_t conn_id, el_val_t data);
|
||||
el_val_t __http_sse_close(el_val_t conn_id);
|
||||
|
||||
/* HTTP client (requires HAVE_CURL; stubs provided for no-curl builds) */
|
||||
el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_map, el_val_t timeout_ms);
|
||||
el_val_t __http_do_map(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t timeout_ms);
|
||||
el_val_t __http_do_map_to_file(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t output_path);
|
||||
|
||||
/* JSON */
|
||||
el_val_t __json_array_get(el_val_t json, el_val_t index);
|
||||
el_val_t __json_array_get_string(el_val_t json, el_val_t index);
|
||||
el_val_t __json_array_len(el_val_t json);
|
||||
el_val_t __json_get(el_val_t json, el_val_t key);
|
||||
el_val_t __json_get_raw(el_val_t json, el_val_t key);
|
||||
el_val_t __json_set(el_val_t json, el_val_t key, el_val_t value);
|
||||
el_val_t __json_parse_map(el_val_t json_str);
|
||||
el_val_t __json_stringify_val(el_val_t val);
|
||||
|
||||
/* Hashing */
|
||||
el_val_t __sha256_hex(el_val_t s);
|
||||
|
||||
/* State K/V */
|
||||
el_val_t __state_del(el_val_t key);
|
||||
el_val_t __state_get(el_val_t key);
|
||||
el_val_t __state_keys(void);
|
||||
el_val_t __state_set(el_val_t key, el_val_t val);
|
||||
|
||||
/* UUID */
|
||||
el_val_t __uuid_v4(void);
|
||||
|
||||
/* Args */
|
||||
el_val_t __args_json(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* el_seed.h — El language seed runtime header
|
||||
*
|
||||
* Declares all OS-boundary primitives available to compiled El programs.
|
||||
* All functions use the __ prefix convention. Signatures use el_val_t (= int64_t)
|
||||
* as the universal value type.
|
||||
*
|
||||
* el_seed.c is the complete C boundary for the El runtime. The heavy runtime
|
||||
* (el_runtime.c) has been retired — everything lives in el_seed.c plus the
|
||||
* native El runtime (runtime/ *.el files).
|
||||
*
|
||||
* Link requirements:
|
||||
* -lcurl — HTTP client (__http_do, __http_do_to_file)
|
||||
* -lpthread — threading (__thread_create, __thread_join, __mutex_new, ...)
|
||||
*
|
||||
* Canonical compile (via elb):
|
||||
* elb builds and links el_seed.c automatically.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ── Value model ─────────────────────────────────────────────────────────────
|
||||
* All El values are el_val_t (int64_t). On 64-bit systems a pointer fits.
|
||||
* String -> el_val_t (holds const char* via uintptr_t cast)
|
||||
* Int -> el_val_t (stored directly)
|
||||
* Bool -> el_val_t (0 = false, nonzero = true)
|
||||
* Void -> void
|
||||
*/
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
||||
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
||||
#define EL_INT(v) (v)
|
||||
#define EL_NULL ((el_val_t)0)
|
||||
|
||||
/* Float values share the el_val_t slot via bit-cast. */
|
||||
static inline double el_to_float(el_val_t v) {
|
||||
union { int64_t i; double f; } u; u.i = (int64_t)v; return u.f;
|
||||
}
|
||||
static inline el_val_t el_from_float(double f) {
|
||||
union { double f; int64_t i; } u; u.f = f; return (el_val_t)u.i;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ── String primitives ───────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __str_len(el_val_t s);
|
||||
el_val_t __str_char_at(el_val_t s, el_val_t i); /* returns Int (byte value) */
|
||||
el_val_t __str_alloc(el_val_t n); /* malloc(n+1), zero-init, return String */
|
||||
el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c); /* s[i]=c, return s */
|
||||
el_val_t __str_cmp(el_val_t a, el_val_t b); /* strcmp result as Int */
|
||||
el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n); /* strncmp */
|
||||
el_val_t __str_concat_raw(el_val_t a, el_val_t b); /* malloc+strcpy concat */
|
||||
el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end); /* substring copy */
|
||||
el_val_t __int_to_str(el_val_t n);
|
||||
el_val_t __str_to_int(el_val_t s);
|
||||
el_val_t __float_to_str(el_val_t f); /* f is bit-cast double */
|
||||
el_val_t __str_to_float(el_val_t s); /* strtod, bit-cast result */
|
||||
|
||||
/* ── I/O ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
void __println(el_val_t s);
|
||||
void __print(el_val_t s);
|
||||
el_val_t __readline(void);
|
||||
|
||||
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __fs_read(el_val_t path);
|
||||
el_val_t __fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t __fs_exists(el_val_t path);
|
||||
el_val_t __fs_list_raw(el_val_t path); /* newline-separated filenames */
|
||||
el_val_t __fs_mkdir(el_val_t path);
|
||||
el_val_t __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n);
|
||||
|
||||
/* ── HTTP client ─────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Unified HTTP call. headers_json is a JSON object of header name->value pairs
|
||||
* (e.g. {"Authorization":"Bearer ...","Content-Type":"application/json"}).
|
||||
* Use "" or "{}" for no extra headers. timeout_ms <= 0 uses the default. */
|
||||
el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t timeout_ms);
|
||||
|
||||
/* Stream response body directly to a file. Returns 1 on success, 0 on failure. */
|
||||
el_val_t __http_do_to_file(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t out_path);
|
||||
|
||||
/* ── HTTP server ─────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Blocking HTTP server. handler_name is the El function name to dispatch to.
|
||||
* v1 handler: (method, path, body) -> String
|
||||
* v2 handler: (method, path, headers_map, body) -> String or envelope */
|
||||
void __http_serve(el_val_t port, el_val_t handler_name);
|
||||
void __http_serve_v2(el_val_t port, el_val_t handler_name);
|
||||
|
||||
/* Build a structured HTTP response envelope.
|
||||
* headers_json: JSON object literal like {"Content-Type":"text/plain"} or "{}" */
|
||||
el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
|
||||
/* ── HTTP SSE — Server-Sent Events streaming ─────────────────────────────── */
|
||||
|
||||
/* Returns the raw file descriptor for the current HTTP connection.
|
||||
* Valid only inside an http_serve_v2 handler before it returns.
|
||||
* Returns -1 if called outside a handler context. */
|
||||
el_val_t __http_conn_fd(void);
|
||||
|
||||
/* Sends SSE response headers on conn_id (the fd from __http_conn_fd),
|
||||
* keeping the connection open for streaming. Returns 1 on success, 0 on
|
||||
* write failure. Call once at the start of an SSE handler. */
|
||||
el_val_t __http_sse_open(el_val_t conn_id);
|
||||
|
||||
/* Writes one SSE event frame: "data: <data>\n\n". data must not contain
|
||||
* newlines. Returns 1 on success, 0 if the client disconnected. */
|
||||
el_val_t __http_sse_send(el_val_t conn_id, el_val_t data);
|
||||
|
||||
/* Closes the SSE connection. The handler must return http_sse_sentinel()
|
||||
* so the HTTP worker does not double-close the fd. */
|
||||
el_val_t __http_sse_close(el_val_t conn_id);
|
||||
|
||||
/* ── Threading ───────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Create a thread that calls the named El function with a String argument.
|
||||
* fn_name is resolved via dlsym(RTLD_DEFAULT, fn_name). Returns a thread
|
||||
* handle Int that can be passed to __thread_join. Returns -1 on failure. */
|
||||
el_val_t __thread_create(el_val_t fn_name, el_val_t arg);
|
||||
|
||||
/* Wait for thread tid (returned by __thread_create) to finish.
|
||||
* Returns the thread's return value as a String. */
|
||||
el_val_t __thread_join(el_val_t tid);
|
||||
|
||||
/* Allocate a new mutex. Returns a handle Int (index into internal table). */
|
||||
el_val_t __mutex_new(void);
|
||||
|
||||
void __mutex_lock(el_val_t m);
|
||||
void __mutex_unlock(el_val_t m);
|
||||
|
||||
/* ── Subprocess ──────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __exec(el_val_t cmd); /* popen, capture all stdout, return String */
|
||||
void __exec_bg(el_val_t cmd); /* fire and forget */
|
||||
|
||||
/* ── Environment and process ─────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __env_get(el_val_t key); /* getenv, return "" if not set */
|
||||
void __exit_program(el_val_t code);
|
||||
el_val_t __args_json(void); /* CLI args as JSON array string */
|
||||
|
||||
/* ── Time ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __time_now_ns(void); /* clock_gettime REALTIME, nanoseconds */
|
||||
void __sleep_ms(el_val_t ms);
|
||||
|
||||
/* ── UUID ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __uuid_v4(void);
|
||||
|
||||
/* ── Math ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __sqrt_f(el_val_t f);
|
||||
el_val_t __log_f(el_val_t f);
|
||||
el_val_t __ln_f(el_val_t f);
|
||||
el_val_t __sin_f(el_val_t f);
|
||||
el_val_t __cos_f(el_val_t f);
|
||||
el_val_t __pi_f(void);
|
||||
|
||||
/* ── JSON ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __json_get(el_val_t json, el_val_t key);
|
||||
el_val_t __json_get_raw(el_val_t json_str, el_val_t key);
|
||||
el_val_t __json_parse(el_val_t s);
|
||||
el_val_t __json_stringify(el_val_t v);
|
||||
el_val_t __json_parse_map(el_val_t json_str); /* alias for __json_parse */
|
||||
el_val_t __json_stringify_val(el_val_t val); /* alias for __json_stringify */
|
||||
el_val_t __json_array_len(el_val_t json_str);
|
||||
el_val_t __json_array_get(el_val_t json_str, el_val_t index);
|
||||
el_val_t __json_array_get_string(el_val_t json_str, el_val_t index);
|
||||
el_val_t __json_get_string(el_val_t json_str, el_val_t key);
|
||||
el_val_t __json_get_int(el_val_t json_str, el_val_t key);
|
||||
el_val_t __json_get_float(el_val_t json_str, el_val_t key);
|
||||
el_val_t __json_get_bool(el_val_t json_str, el_val_t key);
|
||||
el_val_t __json_set(el_val_t json_str, el_val_t key, el_val_t value);
|
||||
|
||||
/* ── State K/V ───────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __state_set(el_val_t key, el_val_t value);
|
||||
el_val_t __state_get(el_val_t key);
|
||||
el_val_t __state_del(el_val_t key);
|
||||
el_val_t __state_keys(void);
|
||||
|
||||
/* ── HTML/URL ────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __html_sanitize(el_val_t input_html, el_val_t allowlist_json);
|
||||
el_val_t __url_encode(el_val_t s);
|
||||
el_val_t __url_decode(el_val_t s);
|
||||
|
||||
/* ── Engram ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t __engram_node(el_val_t content, el_val_t node_type, el_val_t salience);
|
||||
el_val_t __engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t importance, el_val_t confidence,
|
||||
el_val_t tier, el_val_t tags);
|
||||
el_val_t __engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t certainty, el_val_t confidence,
|
||||
el_val_t status, el_val_t tags, el_val_t layer_id);
|
||||
el_val_t __engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible,
|
||||
el_val_t transparent, el_val_t injectable);
|
||||
el_val_t __engram_remove_layer(el_val_t layer_id);
|
||||
el_val_t __engram_list_layers(void);
|
||||
el_val_t __engram_get_node(el_val_t id);
|
||||
void __engram_strengthen(el_val_t node_id);
|
||||
void __engram_forget(el_val_t node_id);
|
||||
el_val_t __engram_node_count(void);
|
||||
el_val_t __engram_search(el_val_t query, el_val_t limit);
|
||||
el_val_t __engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||
void __engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation);
|
||||
el_val_t __engram_edge_between(el_val_t from_id, el_val_t to_id);
|
||||
el_val_t __engram_neighbors(el_val_t node_id);
|
||||
el_val_t __engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t __engram_edge_count(void);
|
||||
el_val_t __engram_activate(el_val_t query, el_val_t depth);
|
||||
el_val_t __engram_save(el_val_t path);
|
||||
el_val_t __engram_load(el_val_t path);
|
||||
el_val_t __engram_get_node_json(el_val_t id);
|
||||
el_val_t __engram_search_json(el_val_t query, el_val_t limit);
|
||||
el_val_t __engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
||||
el_val_t __engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||
el_val_t __engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t __engram_activate_json(el_val_t query, el_val_t depth);
|
||||
el_val_t __engram_stats_json(void);
|
||||
el_val_t __engram_list_layers_json(void);
|
||||
el_val_t __engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
||||
|
||||
/* ── Cryptographic hashing ────────────────────────────────────────────────── */
|
||||
|
||||
/* __sha256_hex — return the SHA-256 hex digest of a string.
|
||||
* The returned string is 64 hex characters (lowercase). */
|
||||
el_val_t __sha256_hex(el_val_t s);
|
||||
|
||||
/* ── args init (called from main) ────────────────────────────────────────── */
|
||||
/* Store argc/argv for __args_json. Call once at the start of main(). */
|
||||
void el_seed_init_args(int argc, char** argv);
|
||||
|
||||
/* ── Native widget system (macOS AppKit) ─────────────────────────────────── */
|
||||
/*
|
||||
* Available when compiled with -DEL_TARGET_MACOS and linked with el_appkit.m
|
||||
* (-framework Cocoa). Widget handles are opaque Int values (int64_t slot
|
||||
* indices). The null/invalid handle value is -1.
|
||||
*
|
||||
* Lifecycle:
|
||||
* 1. Call __native_init() once at program start.
|
||||
* 2. Build widget tree with __window_create / __vstack_create / etc.
|
||||
* 3. Attach children with __widget_add_child(parent, child).
|
||||
* 4. Register callbacks with __widget_on_click / __widget_on_change.
|
||||
* 5. Call __window_show(win) to make the window visible.
|
||||
* 6. Call __native_run_loop() — never returns. AppKit owns the thread.
|
||||
*
|
||||
* Callback El function signature:
|
||||
* fn my_handler(widget: Int, data: String) -> Void
|
||||
*/
|
||||
#ifdef EL_TARGET_MACOS
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
|
||||
el_val_t min_width, el_val_t min_height);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
/* Parse the app{} block from manifest.el at path. Returns a JSON string:
|
||||
* {"title":"...","width":N,"height":N,"min_width":N,"min_height":N} */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_MACOS */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Executable
+182
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env bash
|
||||
# build-gtk4.sh — Compile and run native-hello using the GTK4 bridge on macOS.
|
||||
#
|
||||
# Usage:
|
||||
# ./build-gtk4.sh # compile and run
|
||||
# ./build-gtk4.sh compile # compile only
|
||||
# ./build-gtk4.sh clean # remove build artefacts
|
||||
#
|
||||
# Requirements:
|
||||
# - elc in PATH (or ../../../lang/dist/platform/elc)
|
||||
# - clang (Xcode Command Line Tools)
|
||||
# - GTK4 via Homebrew: brew install gtk4
|
||||
# - macOS 12+
|
||||
#
|
||||
# This is the same pipeline as build.sh but targeting the GTK4 backend:
|
||||
# - Links with $(pkg-config --libs gtk4) instead of -framework Cocoa
|
||||
# - Compiles el_gtk4.c (plain C) instead of el_appkit.m (ObjC)
|
||||
# - Defines EL_TARGET_LINUX (the GTK4 target flag)
|
||||
#
|
||||
# Multi-file compilation strategy: see build.sh comments — identical rationale.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||
BUILD_DIR="${SCRIPT_DIR}/build-gtk4"
|
||||
|
||||
# Locate elc
|
||||
if command -v elc &>/dev/null; then
|
||||
ELC="elc"
|
||||
elif [ -x "${EL_LANG_ROOT}/dist/platform/elc" ]; then
|
||||
ELC="${EL_LANG_ROOT}/dist/platform/elc"
|
||||
else
|
||||
echo "Error: elc not found. Add it to PATH or place it at:"
|
||||
echo " ${EL_LANG_ROOT}/dist/platform/elc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# GTK4 pkg-config flags
|
||||
GTK4_CFLAGS=$(pkg-config --cflags gtk4)
|
||||
GTK4_LIBS=$(pkg-config --libs gtk4)
|
||||
|
||||
CLANG_FLAGS_COMMON=(
|
||||
-std=c11
|
||||
-DEL_TARGET_LINUX
|
||||
-I "${EL_RUNTIME}"
|
||||
)
|
||||
|
||||
clean() {
|
||||
rm -rf "${BUILD_DIR}"
|
||||
echo "Cleaned."
|
||||
}
|
||||
|
||||
compile() {
|
||||
mkdir -p "${BUILD_DIR}"
|
||||
|
||||
# ── 1. Vessel forward-declarations header ─────────────────────────────────
|
||||
# elc --emit-header emits full function bodies, not just prototypes.
|
||||
# Extract only the forward-declaration lines: stop at the first function
|
||||
# definition (a line that ends with ') {' opening a body block).
|
||||
# macOS awk does not support \s — use POSIX [[:space:]] instead.
|
||||
echo "==> Generating el-native vessel header (declarations only)..."
|
||||
"${ELC}" --emit-header "${EL_NATIVE_VESSEL}" \
|
||||
| awk 'BEGIN{in_body=0} /^[a-zA-Z_].+\)[[:space:]]*\{/ {in_body=1} !in_body{print}' \
|
||||
> "${BUILD_DIR}/el_native_vessel.h"
|
||||
|
||||
# ── 2. Compile el-native vessel to C ──────────────────────────────────────
|
||||
echo "==> Compiling el-native vessel to C..."
|
||||
"${ELC}" "${EL_NATIVE_VESSEL}" \
|
||||
> "${BUILD_DIR}/el_native_vessel.c"
|
||||
|
||||
# ── 3. Compile app entry to C ─────────────────────────────────────────────
|
||||
echo "==> Compiling app entry to C..."
|
||||
EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
|
||||
"${ELC}" "${SCRIPT_DIR}/src/main.el" \
|
||||
> "${BUILD_DIR}/native_hello.c"
|
||||
|
||||
# ── 4. Compile el_gtk4.c (plain C, GTK4 backend) ─────────────────────────
|
||||
echo "==> Compiling el_gtk4.c (GTK4 bridge)..."
|
||||
# shellcheck disable=SC2086
|
||||
clang \
|
||||
-std=c11 \
|
||||
-DEL_TARGET_LINUX \
|
||||
-I "${EL_RUNTIME}" \
|
||||
${GTK4_CFLAGS} \
|
||||
-c "${EL_RUNTIME}/el_gtk4.c" \
|
||||
-o "${BUILD_DIR}/el_gtk4.o"
|
||||
|
||||
# ── 5a. Compile el_seed.c ─────────────────────────────────────────────────
|
||||
# el_seed.c provides the OS-boundary __-prefixed primitives + thin wrappers.
|
||||
# With -DEL_TARGET_LINUX, the EL_TARGET_LINUX section in el_seed.c is
|
||||
# compiled in (GTK4 forwarders) and the EL_TARGET_MACOS section is excluded.
|
||||
echo "==> Compiling el_seed.c (EL_TARGET_LINUX)..."
|
||||
# shellcheck disable=SC2086
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
${GTK4_CFLAGS} \
|
||||
-c "${EL_RUNTIME}/el_seed.c" \
|
||||
-o "${BUILD_DIR}/el_seed.o"
|
||||
|
||||
# ── 5b. Compile el_runtime.c and patch out duplicate __-prefixed symbols ──
|
||||
# Same strategy as build.sh: el_runtime.c has duplicates of the __-prefixed
|
||||
# OS wrappers that el_seed.c defines. Hide el_runtime.o's copies with nmedit.
|
||||
echo "==> Compiling el_runtime.c..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-c "${EL_RUNTIME}/el_runtime.c" \
|
||||
-o "${BUILD_DIR}/el_runtime.o"
|
||||
|
||||
echo "==> Patching el_runtime.o (hiding __-prefixed symbols duplicated in el_seed.o)..."
|
||||
nm "${BUILD_DIR}/el_seed.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.seed_T.txt"
|
||||
nm "${BUILD_DIR}/el_runtime.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.rt_T.txt"
|
||||
comm -23 "${BUILD_DIR}/.rt_T.txt" "${BUILD_DIR}/.seed_T.txt" \
|
||||
> "${BUILD_DIR}/.rt_keep.txt"
|
||||
nmedit -s "${BUILD_DIR}/.rt_keep.txt" "${BUILD_DIR}/el_runtime.o"
|
||||
|
||||
# ── 6. Compile el-native vessel object ────────────────────────────────────
|
||||
# Vessel code calls __-prefixed builtins. el_native_target.h provides the
|
||||
# EL_TARGET_LINUX block of declarations (same function signatures as MACOS).
|
||||
echo "==> Compiling el-native vessel object..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-include "${EL_RUNTIME}/el_native_target.h" \
|
||||
-c "${BUILD_DIR}/el_native_vessel.c" \
|
||||
-o "${BUILD_DIR}/el_native_vessel.o"
|
||||
|
||||
echo "==> Patching el_native_vessel.o (hiding vessel main)..."
|
||||
nm "${BUILD_DIR}/el_native_vessel.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' \
|
||||
| grep -v '^_main$' \
|
||||
> "${BUILD_DIR}/.vessel_keep.txt"
|
||||
nmedit -s "${BUILD_DIR}/.vessel_keep.txt" "${BUILD_DIR}/el_native_vessel.o"
|
||||
|
||||
# ── 7. Compile app object ─────────────────────────────────────────────────
|
||||
# Inject el_native_target.h (float ops + EL_TARGET_LINUX native decls) and
|
||||
# the vessel's forward-declaration header.
|
||||
echo "==> Compiling app object..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-include "${EL_RUNTIME}/el_native_target.h" \
|
||||
-include "${BUILD_DIR}/el_native_vessel.h" \
|
||||
-c "${BUILD_DIR}/native_hello.c" \
|
||||
-o "${BUILD_DIR}/native_hello.o"
|
||||
|
||||
# ── 8. Link ───────────────────────────────────────────────────────────────
|
||||
# el_seed.o = OS-boundary primitives (__-prefixed) + GTK4 forwarders
|
||||
# el_runtime.o = high-level builtins (str_len, json_*, etc.), patched
|
||||
# el_native_vessel.o = el-level widget API, patched
|
||||
# el_gtk4.o = GTK4 C bridge
|
||||
# GTK4_LIBS = $(pkg-config --libs gtk4)
|
||||
# -ldl = dlsym for callback dispatch
|
||||
# -lpthread = threading
|
||||
echo "==> Linking native-hello-gtk4..."
|
||||
# shellcheck disable=SC2086
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
"${BUILD_DIR}/native_hello.o" \
|
||||
"${BUILD_DIR}/el_native_vessel.o" \
|
||||
"${BUILD_DIR}/el_gtk4.o" \
|
||||
"${BUILD_DIR}/el_runtime.o" \
|
||||
"${BUILD_DIR}/el_seed.o" \
|
||||
${GTK4_LIBS} \
|
||||
-ldl \
|
||||
-lpthread \
|
||||
-lcurl \
|
||||
-o "${BUILD_DIR}/native-hello-gtk4"
|
||||
|
||||
echo "==> Build complete: ${BUILD_DIR}/native-hello-gtk4"
|
||||
}
|
||||
|
||||
run() {
|
||||
compile
|
||||
echo "==> Launching native-hello-gtk4..."
|
||||
EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
|
||||
"${BUILD_DIR}/native-hello-gtk4"
|
||||
}
|
||||
|
||||
case "${1:-run}" in
|
||||
clean) clean ;;
|
||||
compile) compile ;;
|
||||
run|*) run ;;
|
||||
esac
|
||||
@@ -0,0 +1,397 @@
|
||||
___args_json
|
||||
___channel_close
|
||||
___channel_new
|
||||
___channel_recv
|
||||
___channel_send
|
||||
___channel_try_recv
|
||||
___env_get
|
||||
___exec
|
||||
___exec_bg
|
||||
___exit_program
|
||||
___float_to_str
|
||||
___fs_exists
|
||||
___fs_list_raw
|
||||
___fs_mkdir
|
||||
___fs_read
|
||||
___fs_write
|
||||
___fs_write_bytes
|
||||
___http_conn_fd
|
||||
___http_do
|
||||
___http_do_map
|
||||
___http_do_map_to_file
|
||||
___http_response
|
||||
___http_serve
|
||||
___http_serve_v2
|
||||
___http_sse_close
|
||||
___http_sse_open
|
||||
___http_sse_send
|
||||
___int_to_str
|
||||
___json_array_get
|
||||
___json_array_get_string
|
||||
___json_array_len
|
||||
___json_get
|
||||
___json_get_raw
|
||||
___json_parse_map
|
||||
___json_set
|
||||
___json_stringify_val
|
||||
___mutex_lock
|
||||
___mutex_new
|
||||
___mutex_unlock
|
||||
___print
|
||||
___print_raw
|
||||
___println
|
||||
___read_n
|
||||
___readline
|
||||
___sha256_hex
|
||||
___state_del
|
||||
___state_get
|
||||
___state_keys
|
||||
___state_set
|
||||
___str_alloc
|
||||
___str_char_at
|
||||
___str_cmp
|
||||
___str_concat_raw
|
||||
___str_len
|
||||
___str_ncmp
|
||||
___str_set_char
|
||||
___str_slice_raw
|
||||
___str_to_float
|
||||
___str_to_int
|
||||
___thread_create
|
||||
___thread_join
|
||||
___url_decode
|
||||
___url_encode
|
||||
___uuid_v4
|
||||
_aead_decrypt
|
||||
_aead_encrypt
|
||||
_append
|
||||
_args
|
||||
_base64_decode
|
||||
_base64_encode
|
||||
_base64url_decode
|
||||
_base64url_encode
|
||||
_bool_to_str
|
||||
_bytes_to_str
|
||||
_cal_cycle_phase
|
||||
_cal_format
|
||||
_cal_in
|
||||
_cal_to_instant
|
||||
_config
|
||||
_cycle_calendar
|
||||
_decimal_round
|
||||
_duration_millis
|
||||
_duration_nanos
|
||||
_duration_seconds
|
||||
_duration_to_millis
|
||||
_duration_to_nanos
|
||||
_duration_to_seconds
|
||||
_earth_calendar
|
||||
_earth_calendar_default
|
||||
_el_abs
|
||||
_el_arena_pop
|
||||
_el_arena_push
|
||||
_el_base64_encode_n
|
||||
_el_cgi_init
|
||||
_el_duration_add
|
||||
_el_duration_div
|
||||
_el_duration_eq
|
||||
_el_duration_from_nanos
|
||||
_el_duration_ge
|
||||
_el_duration_gt
|
||||
_el_duration_le
|
||||
_el_duration_lt
|
||||
_el_duration_ne
|
||||
_el_duration_scale
|
||||
_el_duration_sub
|
||||
_el_get_field
|
||||
_el_html_sanitize
|
||||
_el_instant_add_dur
|
||||
_el_instant_diff
|
||||
_el_instant_eq
|
||||
_el_instant_ge
|
||||
_el_instant_gt
|
||||
_el_instant_le
|
||||
_el_instant_lt
|
||||
_el_instant_ne
|
||||
_el_instant_sub_dur
|
||||
_el_list_append
|
||||
_el_list_clone
|
||||
_el_list_empty
|
||||
_el_list_get
|
||||
_el_list_len
|
||||
_el_list_new
|
||||
_el_local_date_add_dur
|
||||
_el_local_date_eq
|
||||
_el_local_date_lt
|
||||
_el_local_time_add_dur
|
||||
_el_map_get
|
||||
_el_map_new
|
||||
_el_map_set
|
||||
_el_max
|
||||
_el_mem_check
|
||||
_el_min
|
||||
_el_now_instant
|
||||
_el_release
|
||||
_el_request_end
|
||||
_el_request_start
|
||||
_el_retain
|
||||
_el_runtime_init_args
|
||||
_el_runtime_register_handler
|
||||
_el_runtime_register_handler_v2
|
||||
_el_sha256_bytes_n
|
||||
_el_sleep_duration
|
||||
_el_str_concat
|
||||
_engram_activate
|
||||
_engram_activate_json
|
||||
_engram_add_layer
|
||||
_engram_apply_decay_json
|
||||
_engram_compile_layered_json
|
||||
_engram_connect
|
||||
_engram_edge_between
|
||||
_engram_edge_count
|
||||
_engram_forget
|
||||
_engram_get_node
|
||||
_engram_get_node_json
|
||||
_engram_list_layers
|
||||
_engram_list_layers_json
|
||||
_engram_load
|
||||
_engram_neighbors
|
||||
_engram_neighbors_filtered
|
||||
_engram_neighbors_json
|
||||
_engram_node
|
||||
_engram_node_count
|
||||
_engram_node_full
|
||||
_engram_node_layered
|
||||
_engram_query_range
|
||||
_engram_remove_layer
|
||||
_engram_save
|
||||
_engram_scan_nodes
|
||||
_engram_scan_nodes_by_type_json
|
||||
_engram_scan_nodes_json
|
||||
_engram_search
|
||||
_engram_search_json
|
||||
_engram_stats_json
|
||||
_engram_strengthen
|
||||
_env
|
||||
_exec
|
||||
_exec_bg
|
||||
_exec_capture
|
||||
_exec_command
|
||||
_exit_program
|
||||
_float_to_int
|
||||
_float_to_str
|
||||
_format_float
|
||||
_fs_exists
|
||||
_fs_list
|
||||
_fs_list_json
|
||||
_fs_mkdir
|
||||
_fs_read
|
||||
_fs_write
|
||||
_fs_write_bytes
|
||||
_get
|
||||
_getpid_now
|
||||
_hash_sha256
|
||||
_hmac_sha256_bytes
|
||||
_hmac_sha256_hex
|
||||
_html_escape
|
||||
_html_raw
|
||||
_http_delete
|
||||
_http_get
|
||||
_http_get_engram
|
||||
_http_get_to_file
|
||||
_http_get_with_headers
|
||||
_http_patch
|
||||
_http_post
|
||||
_http_post_engram
|
||||
_http_post_form_auth
|
||||
_http_post_json
|
||||
_http_post_json_with_headers
|
||||
_http_post_to_file
|
||||
_http_post_with_headers
|
||||
_http_response
|
||||
_http_serve
|
||||
_http_serve_v2
|
||||
_http_set_handler
|
||||
_http_set_handler_v2
|
||||
_in_calendar
|
||||
_instant_from_iso8601
|
||||
_instant_to_iso8601
|
||||
_instant_to_unix_millis
|
||||
_instant_to_unix_seconds
|
||||
_int_to_float
|
||||
_int_to_str
|
||||
_is_alphanumeric
|
||||
_is_digit
|
||||
_is_letter
|
||||
_is_lowercase
|
||||
_is_punctuation
|
||||
_is_uppercase
|
||||
_is_whitespace
|
||||
_json_array_get
|
||||
_json_array_get_string
|
||||
_json_array_len
|
||||
_json_array_push
|
||||
_json_build_array
|
||||
_json_build_object
|
||||
_json_escape_string
|
||||
_json_get
|
||||
_json_get_bool
|
||||
_json_get_float
|
||||
_json_get_int
|
||||
_json_get_raw
|
||||
_json_get_string
|
||||
_json_parse
|
||||
_json_set
|
||||
_json_stringify
|
||||
_len
|
||||
_list_get
|
||||
_list_join
|
||||
_list_len
|
||||
_list_push
|
||||
_list_push_front
|
||||
_list_range
|
||||
_llm_call
|
||||
_llm_call_agentic
|
||||
_llm_call_system
|
||||
_llm_models
|
||||
_llm_register_tool
|
||||
_llm_vision
|
||||
_local_date
|
||||
_local_date_day
|
||||
_local_date_month
|
||||
_local_date_year
|
||||
_local_datetime
|
||||
_local_time
|
||||
_local_time_hour
|
||||
_local_time_minute
|
||||
_local_time_nanos
|
||||
_local_time_second
|
||||
_log_info
|
||||
_log_warn
|
||||
_map_get
|
||||
_map_set
|
||||
_mars_calendar
|
||||
_math_cos
|
||||
_math_ln
|
||||
_math_log
|
||||
_math_pi
|
||||
_math_sin
|
||||
_math_sqrt
|
||||
_native_int_to_str
|
||||
_native_list_append
|
||||
_native_list_clone
|
||||
_native_list_empty
|
||||
_native_list_get
|
||||
_native_list_len
|
||||
_native_str_to_int
|
||||
_native_string_chars
|
||||
_no_cycle_calendar
|
||||
_now
|
||||
_now_in
|
||||
_now_millis
|
||||
_now_ns
|
||||
_parse_int
|
||||
_pq_hybrid_handshake
|
||||
_pq_hybrid_keygen
|
||||
_pq_kem_decaps
|
||||
_pq_kem_encaps
|
||||
_pq_kem_keygen
|
||||
_pq_keygen_signature
|
||||
_pq_sign
|
||||
_pq_verify
|
||||
_print
|
||||
_println
|
||||
_readline
|
||||
_relative_calendar
|
||||
_rhythm_and
|
||||
_rhythm_cycle_phase
|
||||
_rhythm_cycle_start
|
||||
_rhythm_duration
|
||||
_rhythm_event
|
||||
_rhythm_matches
|
||||
_rhythm_next_after
|
||||
_rhythm_or
|
||||
_rhythm_session_start
|
||||
_rhythm_weekday
|
||||
_rhythm_weekly_at
|
||||
_sha256_bytes
|
||||
_sha256_hex
|
||||
_sha3_256_hex
|
||||
_sleep_ms
|
||||
_sleep_secs
|
||||
_state_del
|
||||
_state_get
|
||||
_state_get_or
|
||||
_state_has
|
||||
_state_keys
|
||||
_state_set
|
||||
_stdout_restore
|
||||
_stdout_to_file
|
||||
_str_char_at
|
||||
_str_char_code
|
||||
_str_concat
|
||||
_str_contains
|
||||
_str_count
|
||||
_str_count_bytes
|
||||
_str_count_chars
|
||||
_str_count_digits
|
||||
_str_count_letters
|
||||
_str_count_lines
|
||||
_str_count_words
|
||||
_str_ends_with
|
||||
_str_eq
|
||||
_str_find_chars
|
||||
_str_format
|
||||
_str_index_of
|
||||
_str_index_of_all
|
||||
_str_join
|
||||
_str_last_index_of
|
||||
_str_len
|
||||
_str_lower
|
||||
_str_lstrip
|
||||
_str_pad_left
|
||||
_str_pad_right
|
||||
_str_repeat
|
||||
_str_replace
|
||||
_str_reverse
|
||||
_str_rstrip
|
||||
_str_slice
|
||||
_str_split
|
||||
_str_split_chars
|
||||
_str_split_lines
|
||||
_str_split_n
|
||||
_str_starts_with
|
||||
_str_strip_chars
|
||||
_str_strip_prefix
|
||||
_str_strip_suffix
|
||||
_str_to_bytes
|
||||
_str_to_float
|
||||
_str_to_int
|
||||
_str_to_lower
|
||||
_str_to_upper
|
||||
_str_trim
|
||||
_str_upper
|
||||
_time_add
|
||||
_time_diff
|
||||
_time_format
|
||||
_time_from_parts
|
||||
_time_now
|
||||
_time_now_ms
|
||||
_time_now_utc
|
||||
_time_to_parts
|
||||
_ttl_cache_age
|
||||
_ttl_cache_get
|
||||
_ttl_cache_set
|
||||
_unix_millis
|
||||
_unix_seconds
|
||||
_unix_timestamp
|
||||
_unix_timestamp_ms
|
||||
_url_decode
|
||||
_url_encode
|
||||
_uuid_new
|
||||
_uuid_v4
|
||||
_zone
|
||||
_zone_local
|
||||
_zone_offset
|
||||
_zone_utc
|
||||
_zoned
|
||||
@@ -0,0 +1,340 @@
|
||||
___channel_close
|
||||
___channel_new
|
||||
___channel_recv
|
||||
___channel_send
|
||||
___channel_try_recv
|
||||
___http_do_map
|
||||
___http_do_map_to_file
|
||||
___print_raw
|
||||
___read_n
|
||||
_aead_decrypt
|
||||
_aead_encrypt
|
||||
_append
|
||||
_args
|
||||
_base64_decode
|
||||
_base64_encode
|
||||
_base64url_decode
|
||||
_base64url_encode
|
||||
_bool_to_str
|
||||
_bytes_to_str
|
||||
_cal_cycle_phase
|
||||
_cal_format
|
||||
_cal_in
|
||||
_cal_to_instant
|
||||
_config
|
||||
_cycle_calendar
|
||||
_decimal_round
|
||||
_duration_millis
|
||||
_duration_nanos
|
||||
_duration_seconds
|
||||
_duration_to_millis
|
||||
_duration_to_nanos
|
||||
_duration_to_seconds
|
||||
_earth_calendar
|
||||
_earth_calendar_default
|
||||
_el_abs
|
||||
_el_arena_pop
|
||||
_el_arena_push
|
||||
_el_base64_encode_n
|
||||
_el_cgi_init
|
||||
_el_duration_add
|
||||
_el_duration_div
|
||||
_el_duration_eq
|
||||
_el_duration_from_nanos
|
||||
_el_duration_ge
|
||||
_el_duration_gt
|
||||
_el_duration_le
|
||||
_el_duration_lt
|
||||
_el_duration_ne
|
||||
_el_duration_scale
|
||||
_el_duration_sub
|
||||
_el_get_field
|
||||
_el_html_sanitize
|
||||
_el_instant_add_dur
|
||||
_el_instant_diff
|
||||
_el_instant_eq
|
||||
_el_instant_ge
|
||||
_el_instant_gt
|
||||
_el_instant_le
|
||||
_el_instant_lt
|
||||
_el_instant_ne
|
||||
_el_instant_sub_dur
|
||||
_el_list_append
|
||||
_el_list_clone
|
||||
_el_list_empty
|
||||
_el_list_get
|
||||
_el_list_len
|
||||
_el_list_new
|
||||
_el_local_date_add_dur
|
||||
_el_local_date_eq
|
||||
_el_local_date_lt
|
||||
_el_local_time_add_dur
|
||||
_el_map_get
|
||||
_el_map_new
|
||||
_el_map_set
|
||||
_el_max
|
||||
_el_mem_check
|
||||
_el_min
|
||||
_el_now_instant
|
||||
_el_release
|
||||
_el_retain
|
||||
_el_runtime_init_args
|
||||
_el_runtime_register_handler
|
||||
_el_runtime_register_handler_v2
|
||||
_el_sha256_bytes_n
|
||||
_el_sleep_duration
|
||||
_el_str_concat
|
||||
_engram_activate
|
||||
_engram_activate_json
|
||||
_engram_add_layer
|
||||
_engram_apply_decay_json
|
||||
_engram_compile_layered_json
|
||||
_engram_connect
|
||||
_engram_edge_between
|
||||
_engram_edge_count
|
||||
_engram_forget
|
||||
_engram_get_node
|
||||
_engram_get_node_json
|
||||
_engram_list_layers
|
||||
_engram_list_layers_json
|
||||
_engram_load
|
||||
_engram_neighbors
|
||||
_engram_neighbors_filtered
|
||||
_engram_neighbors_json
|
||||
_engram_node
|
||||
_engram_node_count
|
||||
_engram_node_full
|
||||
_engram_node_layered
|
||||
_engram_query_range
|
||||
_engram_remove_layer
|
||||
_engram_save
|
||||
_engram_scan_nodes
|
||||
_engram_scan_nodes_by_type_json
|
||||
_engram_scan_nodes_json
|
||||
_engram_search
|
||||
_engram_search_json
|
||||
_engram_stats_json
|
||||
_engram_strengthen
|
||||
_env
|
||||
_exec
|
||||
_exec_bg
|
||||
_exec_capture
|
||||
_exec_command
|
||||
_exit_program
|
||||
_float_to_int
|
||||
_float_to_str
|
||||
_format_float
|
||||
_fs_exists
|
||||
_fs_list
|
||||
_fs_list_json
|
||||
_fs_mkdir
|
||||
_fs_read
|
||||
_fs_write
|
||||
_fs_write_bytes
|
||||
_get
|
||||
_getpid_now
|
||||
_hash_sha256
|
||||
_hmac_sha256_bytes
|
||||
_hmac_sha256_hex
|
||||
_html_escape
|
||||
_html_raw
|
||||
_http_delete
|
||||
_http_get
|
||||
_http_get_engram
|
||||
_http_get_to_file
|
||||
_http_get_with_headers
|
||||
_http_patch
|
||||
_http_post
|
||||
_http_post_engram
|
||||
_http_post_form_auth
|
||||
_http_post_json
|
||||
_http_post_json_with_headers
|
||||
_http_post_to_file
|
||||
_http_post_with_headers
|
||||
_http_response
|
||||
_http_serve
|
||||
_http_serve_v2
|
||||
_http_set_handler
|
||||
_http_set_handler_v2
|
||||
_in_calendar
|
||||
_instant_from_iso8601
|
||||
_instant_to_iso8601
|
||||
_instant_to_unix_millis
|
||||
_instant_to_unix_seconds
|
||||
_int_to_float
|
||||
_int_to_str
|
||||
_is_alphanumeric
|
||||
_is_digit
|
||||
_is_letter
|
||||
_is_lowercase
|
||||
_is_punctuation
|
||||
_is_uppercase
|
||||
_is_whitespace
|
||||
_json_array_get
|
||||
_json_array_get_string
|
||||
_json_array_len
|
||||
_json_array_push
|
||||
_json_build_array
|
||||
_json_build_object
|
||||
_json_escape_string
|
||||
_json_get
|
||||
_json_get_bool
|
||||
_json_get_float
|
||||
_json_get_int
|
||||
_json_get_raw
|
||||
_json_get_string
|
||||
_json_parse
|
||||
_json_set
|
||||
_json_stringify
|
||||
_len
|
||||
_list_get
|
||||
_list_join
|
||||
_list_len
|
||||
_list_push
|
||||
_list_push_front
|
||||
_list_range
|
||||
_llm_call
|
||||
_llm_call_agentic
|
||||
_llm_call_system
|
||||
_llm_models
|
||||
_llm_register_tool
|
||||
_llm_vision
|
||||
_local_date
|
||||
_local_date_day
|
||||
_local_date_month
|
||||
_local_date_year
|
||||
_local_datetime
|
||||
_local_time
|
||||
_local_time_hour
|
||||
_local_time_minute
|
||||
_local_time_nanos
|
||||
_local_time_second
|
||||
_log_info
|
||||
_log_warn
|
||||
_map_get
|
||||
_map_set
|
||||
_mars_calendar
|
||||
_math_cos
|
||||
_math_ln
|
||||
_math_log
|
||||
_math_pi
|
||||
_math_sin
|
||||
_math_sqrt
|
||||
_native_int_to_str
|
||||
_native_list_append
|
||||
_native_list_clone
|
||||
_native_list_empty
|
||||
_native_list_get
|
||||
_native_list_len
|
||||
_native_str_to_int
|
||||
_native_string_chars
|
||||
_no_cycle_calendar
|
||||
_now
|
||||
_now_in
|
||||
_now_millis
|
||||
_now_ns
|
||||
_parse_int
|
||||
_pq_hybrid_handshake
|
||||
_pq_hybrid_keygen
|
||||
_pq_kem_decaps
|
||||
_pq_kem_encaps
|
||||
_pq_kem_keygen
|
||||
_pq_keygen_signature
|
||||
_pq_sign
|
||||
_pq_verify
|
||||
_print
|
||||
_println
|
||||
_readline
|
||||
_relative_calendar
|
||||
_rhythm_and
|
||||
_rhythm_cycle_phase
|
||||
_rhythm_cycle_start
|
||||
_rhythm_duration
|
||||
_rhythm_event
|
||||
_rhythm_matches
|
||||
_rhythm_next_after
|
||||
_rhythm_or
|
||||
_rhythm_session_start
|
||||
_rhythm_weekday
|
||||
_rhythm_weekly_at
|
||||
_sha256_bytes
|
||||
_sha256_hex
|
||||
_sha3_256_hex
|
||||
_sleep_ms
|
||||
_sleep_secs
|
||||
_state_del
|
||||
_state_get
|
||||
_state_get_or
|
||||
_state_has
|
||||
_state_keys
|
||||
_state_set
|
||||
_stdout_restore
|
||||
_stdout_to_file
|
||||
_str_char_at
|
||||
_str_char_code
|
||||
_str_concat
|
||||
_str_contains
|
||||
_str_count
|
||||
_str_count_bytes
|
||||
_str_count_chars
|
||||
_str_count_digits
|
||||
_str_count_letters
|
||||
_str_count_lines
|
||||
_str_count_words
|
||||
_str_ends_with
|
||||
_str_eq
|
||||
_str_find_chars
|
||||
_str_format
|
||||
_str_index_of
|
||||
_str_index_of_all
|
||||
_str_join
|
||||
_str_last_index_of
|
||||
_str_len
|
||||
_str_lower
|
||||
_str_lstrip
|
||||
_str_pad_left
|
||||
_str_pad_right
|
||||
_str_repeat
|
||||
_str_replace
|
||||
_str_reverse
|
||||
_str_rstrip
|
||||
_str_slice
|
||||
_str_split
|
||||
_str_split_chars
|
||||
_str_split_lines
|
||||
_str_split_n
|
||||
_str_starts_with
|
||||
_str_strip_chars
|
||||
_str_strip_prefix
|
||||
_str_strip_suffix
|
||||
_str_to_bytes
|
||||
_str_to_float
|
||||
_str_to_int
|
||||
_str_to_lower
|
||||
_str_to_upper
|
||||
_str_trim
|
||||
_str_upper
|
||||
_time_add
|
||||
_time_diff
|
||||
_time_format
|
||||
_time_from_parts
|
||||
_time_now
|
||||
_time_now_ms
|
||||
_time_now_utc
|
||||
_time_to_parts
|
||||
_ttl_cache_age
|
||||
_ttl_cache_get
|
||||
_ttl_cache_set
|
||||
_unix_millis
|
||||
_unix_seconds
|
||||
_unix_timestamp
|
||||
_unix_timestamp_ms
|
||||
_url_decode
|
||||
_url_encode
|
||||
_uuid_new
|
||||
_uuid_v4
|
||||
_zone
|
||||
_zone_local
|
||||
_zone_offset
|
||||
_zone_utc
|
||||
_zoned
|
||||
@@ -0,0 +1,137 @@
|
||||
___args_json
|
||||
___button_create
|
||||
___cos_f
|
||||
___engram_activate
|
||||
___engram_activate_json
|
||||
___engram_add_layer
|
||||
___engram_compile_layered_json
|
||||
___engram_connect
|
||||
___engram_edge_between
|
||||
___engram_edge_count
|
||||
___engram_forget
|
||||
___engram_get_node
|
||||
___engram_get_node_json
|
||||
___engram_list_layers
|
||||
___engram_list_layers_json
|
||||
___engram_load
|
||||
___engram_neighbors
|
||||
___engram_neighbors_filtered
|
||||
___engram_neighbors_json
|
||||
___engram_node
|
||||
___engram_node_count
|
||||
___engram_node_full
|
||||
___engram_node_layered
|
||||
___engram_remove_layer
|
||||
___engram_save
|
||||
___engram_scan_nodes
|
||||
___engram_scan_nodes_by_type_json
|
||||
___engram_scan_nodes_json
|
||||
___engram_search
|
||||
___engram_search_json
|
||||
___engram_stats_json
|
||||
___engram_strengthen
|
||||
___env_get
|
||||
___exec
|
||||
___exec_bg
|
||||
___exit_program
|
||||
___float_to_str
|
||||
___fs_exists
|
||||
___fs_list_raw
|
||||
___fs_mkdir
|
||||
___fs_read
|
||||
___fs_write
|
||||
___fs_write_bytes
|
||||
___hstack_create
|
||||
___html_sanitize
|
||||
___http_conn_fd
|
||||
___http_do
|
||||
___http_do_to_file
|
||||
___http_response
|
||||
___http_serve
|
||||
___http_serve_v2
|
||||
___http_sse_close
|
||||
___http_sse_open
|
||||
___http_sse_send
|
||||
___image_create
|
||||
___int_to_str
|
||||
___json_array_get
|
||||
___json_array_get_string
|
||||
___json_array_len
|
||||
___json_get
|
||||
___json_get_bool
|
||||
___json_get_float
|
||||
___json_get_int
|
||||
___json_get_raw
|
||||
___json_get_string
|
||||
___json_parse
|
||||
___json_parse_map
|
||||
___json_set
|
||||
___json_stringify
|
||||
___json_stringify_val
|
||||
___label_create
|
||||
___ln_f
|
||||
___log_f
|
||||
___manifest_read
|
||||
___mutex_lock
|
||||
___mutex_new
|
||||
___mutex_unlock
|
||||
___native_init
|
||||
___native_run_loop
|
||||
___pi_f
|
||||
___print
|
||||
___println
|
||||
___readline
|
||||
___scroll_create
|
||||
___sha256_hex
|
||||
___sin_f
|
||||
___sleep_ms
|
||||
___sqrt_f
|
||||
___state_del
|
||||
___state_get
|
||||
___state_keys
|
||||
___state_set
|
||||
___str_alloc
|
||||
___str_char_at
|
||||
___str_cmp
|
||||
___str_concat_raw
|
||||
___str_len
|
||||
___str_ncmp
|
||||
___str_set_char
|
||||
___str_slice_raw
|
||||
___str_to_float
|
||||
___str_to_int
|
||||
___text_area_create
|
||||
___text_field_create
|
||||
___thread_create
|
||||
___thread_join
|
||||
___time_now_ns
|
||||
___url_decode
|
||||
___url_encode
|
||||
___uuid_v4
|
||||
___vstack_create
|
||||
___widget_add_child
|
||||
___widget_destroy
|
||||
___widget_get_text
|
||||
___widget_on_change
|
||||
___widget_on_click
|
||||
___widget_on_submit
|
||||
___widget_remove_child
|
||||
___widget_set_bg_color
|
||||
___widget_set_color
|
||||
___widget_set_corner_radius
|
||||
___widget_set_disabled
|
||||
___widget_set_flex
|
||||
___widget_set_font
|
||||
___widget_set_height
|
||||
___widget_set_hidden
|
||||
___widget_set_padding
|
||||
___widget_set_text
|
||||
___widget_set_width
|
||||
___window_create
|
||||
___window_set_title
|
||||
___window_show
|
||||
___zstack_create
|
||||
_el_request_end
|
||||
_el_request_start
|
||||
_el_seed_init_args
|
||||
_el_seed_set_http_conn_fd
|
||||
@@ -0,0 +1,55 @@
|
||||
_button
|
||||
_color_hex_a
|
||||
_color_hex_b
|
||||
_color_hex_g
|
||||
_color_hex_r
|
||||
_hex_channel
|
||||
_hex_nibble
|
||||
_hstack
|
||||
_image
|
||||
_label
|
||||
_manifest_height
|
||||
_manifest_min_height
|
||||
_manifest_min_width
|
||||
_manifest_read
|
||||
_manifest_title
|
||||
_manifest_width
|
||||
_native_init
|
||||
_native_run_loop
|
||||
_scroll
|
||||
_style_button_primary
|
||||
_style_label_body
|
||||
_style_label_heading
|
||||
_style_label_muted
|
||||
_style_surface
|
||||
_text_area
|
||||
_text_field
|
||||
_vstack
|
||||
_vstack_tight
|
||||
_widget_add_child
|
||||
_widget_destroy
|
||||
_widget_get_text
|
||||
_widget_on_change
|
||||
_widget_on_click
|
||||
_widget_on_submit
|
||||
_widget_remove_child
|
||||
_widget_set_bg_color
|
||||
_widget_set_bg_color_hex
|
||||
_widget_set_color
|
||||
_widget_set_color_hex
|
||||
_widget_set_corner_radius
|
||||
_widget_set_disabled
|
||||
_widget_set_flex
|
||||
_widget_set_font
|
||||
_widget_set_height
|
||||
_widget_set_hidden
|
||||
_widget_set_padding
|
||||
_widget_set_padding_all
|
||||
_widget_set_padding_xy
|
||||
_widget_set_text
|
||||
_widget_set_width
|
||||
_window_create
|
||||
_window_from_manifest
|
||||
_window_set_title
|
||||
_window_show
|
||||
_zstack
|
||||
Binary file not shown.
@@ -0,0 +1,459 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
el_val_t TOKEN_PRIMARY;
|
||||
el_val_t TOKEN_ON_PRIMARY;
|
||||
el_val_t TOKEN_BACKGROUND;
|
||||
el_val_t TOKEN_ON_BG;
|
||||
el_val_t TOKEN_SURFACE;
|
||||
el_val_t TOKEN_ON_SURFACE;
|
||||
el_val_t TOKEN_OUTLINE;
|
||||
el_val_t TOKEN_ERROR;
|
||||
|
||||
el_val_t native_init(void) {
|
||||
__native_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t native_run_loop(void) {
|
||||
__native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_read(el_val_t path) {
|
||||
return __manifest_read(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_title(el_val_t m) {
|
||||
return json_get_string(m, EL_STR("title"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height) {
|
||||
return __window_create(title, width, height, min_width, min_height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_from_manifest(el_val_t manifest_path) {
|
||||
el_val_t m = manifest_read(manifest_path);
|
||||
el_val_t title = manifest_title(m);
|
||||
el_val_t w = manifest_width(m);
|
||||
el_val_t h = manifest_height(m);
|
||||
el_val_t mw = manifest_min_width(m);
|
||||
el_val_t mh = manifest_min_height(m);
|
||||
el_val_t safe_w = ({ el_val_t _if_result_1 = 0; if ((w > 0)) { _if_result_1 = (w); } else { _if_result_1 = (1200); } _if_result_1; });
|
||||
el_val_t safe_h = ({ el_val_t _if_result_2 = 0; if ((h > 0)) { _if_result_2 = (h); } else { _if_result_2 = (800); } _if_result_2; });
|
||||
el_val_t safe_mw = ({ el_val_t _if_result_3 = 0; if ((mw > 0)) { _if_result_3 = (mw); } else { _if_result_3 = (600); } _if_result_3; });
|
||||
el_val_t safe_mh = ({ el_val_t _if_result_4 = 0; if ((mh > 0)) { _if_result_4 = (mh); } else { _if_result_4 = (400); } _if_result_4; });
|
||||
el_val_t safe_t = ({ el_val_t _if_result_5 = 0; if ((str_len(title) > 0)) { _if_result_5 = (title); } else { _if_result_5 = (EL_STR("App")); } _if_result_5; });
|
||||
return window_create(safe_t, safe_w, safe_h, safe_mw, safe_mh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_show(el_val_t handle) {
|
||||
__window_show(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title) {
|
||||
__window_set_title(handle, title);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack(el_val_t spacing) {
|
||||
return __vstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack_tight(void) {
|
||||
return __vstack_create(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hstack(el_val_t spacing) {
|
||||
return __hstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t zstack(void) {
|
||||
return __zstack_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t scroll(void) {
|
||||
return __scroll_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t label(el_val_t text) {
|
||||
return __label_create(text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t button(el_val_t label) {
|
||||
return __button_create(label);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_field(el_val_t placeholder) {
|
||||
return __text_field_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_area(el_val_t placeholder) {
|
||||
return __text_area_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t image(el_val_t path_or_name) {
|
||||
return __image_create(path_or_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text) {
|
||||
__widget_set_text(handle, text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_get_text(el_val_t handle) {
|
||||
return __widget_get_text(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_bg_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold) {
|
||||
el_val_t bold_int = ({ el_val_t _if_result_6 = 0; if (bold) { _if_result_6 = (1); } else { _if_result_6 = (0); } _if_result_6; });
|
||||
__widget_set_font(handle, family, size, bold_int);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left) {
|
||||
__widget_set_padding(handle, top, right, bottom, left);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p) {
|
||||
__widget_set_padding(handle, p, p, p, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py) {
|
||||
__widget_set_padding(handle, py, px, py, px);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width) {
|
||||
__widget_set_width(handle, width);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height) {
|
||||
__widget_set_height(handle, height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
__widget_set_flex(handle, flex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
__widget_set_corner_radius(handle, radius);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_val_t d = ({ el_val_t _if_result_7 = 0; if (disabled) { _if_result_7 = (1); } else { _if_result_7 = (0); } _if_result_7; });
|
||||
__widget_set_disabled(handle, d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_val_t h = ({ el_val_t _if_result_8 = 0; if (hidden) { _if_result_8 = (1); } else { _if_result_8 = (0); } _if_result_8; });
|
||||
__widget_set_hidden(handle, h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child) {
|
||||
__widget_add_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
__widget_remove_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_destroy(el_val_t handle) {
|
||||
__widget_destroy(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_click(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_change(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_submit(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset) {
|
||||
el_val_t hi = str_slice(s, offset, (offset + 1));
|
||||
el_val_t lo = str_slice(s, (offset + 1), (offset + 2));
|
||||
el_val_t h = hex_nibble(hi);
|
||||
el_val_t l = hex_nibble(lo);
|
||||
return ((h * 16) + l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_nibble(el_val_t c) {
|
||||
if (str_eq(c, EL_STR("0"))) {
|
||||
return 0;
|
||||
}
|
||||
if (str_eq(c, EL_STR("1"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(c, EL_STR("2"))) {
|
||||
return 2;
|
||||
}
|
||||
if (str_eq(c, EL_STR("3"))) {
|
||||
return 3;
|
||||
}
|
||||
if (str_eq(c, EL_STR("4"))) {
|
||||
return 4;
|
||||
}
|
||||
if (str_eq(c, EL_STR("5"))) {
|
||||
return 5;
|
||||
}
|
||||
if (str_eq(c, EL_STR("6"))) {
|
||||
return 6;
|
||||
}
|
||||
if (str_eq(c, EL_STR("7"))) {
|
||||
return 7;
|
||||
}
|
||||
if (str_eq(c, EL_STR("8"))) {
|
||||
return 8;
|
||||
}
|
||||
if (str_eq(c, EL_STR("9"))) {
|
||||
return 9;
|
||||
}
|
||||
if (str_eq(c, EL_STR("a"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("b"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("c"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("d"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("e"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("f"))) {
|
||||
return 15;
|
||||
}
|
||||
if (str_eq(c, EL_STR("A"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("B"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("C"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("D"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("E"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("F"))) {
|
||||
return 15;
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_r(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_9 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_9 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_9 = (hex); } _if_result_9; });
|
||||
el_val_t v = hex_channel(s, 0);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_g(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_10 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_10 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_10 = (hex); } _if_result_10; });
|
||||
el_val_t v = hex_channel(s, 2);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_b(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_11 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_11 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_11 = (hex); } _if_result_11; });
|
||||
el_val_t v = hex_channel(s, 4);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_a(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_12 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_12 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_12 = (hex); } _if_result_12; });
|
||||
if (str_len(s) < 8) {
|
||||
return el_from_float(1.0);
|
||||
}
|
||||
el_val_t v = hex_channel(s, 6);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_bg_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_surface(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_SURFACE);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_button_primary(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_PRIMARY);
|
||||
widget_set_color_hex(handle, TOKEN_ON_PRIMARY);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
widget_set_padding_xy(handle, 16, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_body(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 14, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_heading(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 20, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_muted(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_OUTLINE);
|
||||
widget_set_font(handle, EL_STR("system"), 12, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
TOKEN_PRIMARY = EL_STR("#60a5fa");
|
||||
TOKEN_ON_PRIMARY = EL_STR("#0f172a");
|
||||
TOKEN_BACKGROUND = EL_STR("#0f172a");
|
||||
TOKEN_ON_BG = EL_STR("#f8fafc");
|
||||
TOKEN_SURFACE = EL_STR("#1e293b");
|
||||
TOKEN_ON_SURFACE = EL_STR("#f8fafc");
|
||||
TOKEN_OUTLINE = EL_STR("#475569");
|
||||
TOKEN_ERROR = EL_STR("#f87171");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
el_val_t TOKEN_PRIMARY;
|
||||
el_val_t TOKEN_ON_PRIMARY;
|
||||
el_val_t TOKEN_BACKGROUND;
|
||||
el_val_t TOKEN_ON_BG;
|
||||
el_val_t TOKEN_SURFACE;
|
||||
el_val_t TOKEN_ON_SURFACE;
|
||||
el_val_t TOKEN_OUTLINE;
|
||||
el_val_t TOKEN_ERROR;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data);
|
||||
el_val_t app_build(el_val_t window);
|
||||
|
||||
el_val_t g_window;
|
||||
el_val_t g_label;
|
||||
el_val_t g_input;
|
||||
el_val_t g_button;
|
||||
el_val_t g_counter;
|
||||
el_val_t g_counter_lbl;
|
||||
el_val_t manifest_env;
|
||||
el_val_t manifest_path;
|
||||
el_val_t win;
|
||||
el_val_t g_window;
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t name = widget_get_text(g_input);
|
||||
el_val_t greeting = ({ el_val_t _if_result_1 = 0; if ((str_len(name) > 0)) { _if_result_1 = (el_str_concat(el_str_concat(EL_STR("Hello, "), name), EL_STR("!"))); } else { _if_result_1 = (EL_STR("Hello, World!")); } _if_result_1; });
|
||||
widget_set_text(g_label, greeting);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t g_counter = (g_counter + 1);
|
||||
widget_set_text(g_counter_lbl, el_str_concat(EL_STR("Clicks: "), int_to_str(g_counter)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data) {
|
||||
el_val_t has_text = (str_len(data) > 0);
|
||||
widget_set_disabled(g_button, !has_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t app_build(el_val_t window) {
|
||||
el_val_t root = vstack(0);
|
||||
widget_set_padding_all(root, 24);
|
||||
widget_set_bg_color_hex(root, EL_STR("#0f172a"));
|
||||
widget_set_flex(root, 1);
|
||||
el_val_t title = label(EL_STR("el-native \xe2\x80\x94 native widget demo"));
|
||||
style_label_heading(title);
|
||||
widget_add_child(root, title);
|
||||
el_val_t gap1 = label(EL_STR(""));
|
||||
widget_set_height(gap1, 16);
|
||||
widget_add_child(root, gap1);
|
||||
el_val_t subtitle = label(EL_STR("AppKit controls from el code, no ObjC in the app layer."));
|
||||
style_label_muted(subtitle);
|
||||
widget_add_child(root, subtitle);
|
||||
el_val_t gap2 = label(EL_STR(""));
|
||||
widget_set_height(gap2, 24);
|
||||
widget_add_child(root, gap2);
|
||||
el_val_t input_row = hstack(8);
|
||||
el_val_t name_label = label(EL_STR("Name:"));
|
||||
style_label_body(name_label);
|
||||
widget_set_width(name_label, 60);
|
||||
el_val_t input = text_field(EL_STR("Enter your name\xe2\x80\xa6"));
|
||||
style_label_body(input);
|
||||
widget_set_flex(input, 1);
|
||||
widget_on_change(input, EL_STR("on_name_change"));
|
||||
el_val_t g_input = input;
|
||||
el_val_t greet_btn = button(EL_STR("Greet"));
|
||||
style_button_primary(greet_btn);
|
||||
widget_set_disabled(greet_btn, 1);
|
||||
widget_on_click(greet_btn, EL_STR("on_greet_click"));
|
||||
el_val_t g_button = greet_btn;
|
||||
widget_add_child(input_row, name_label);
|
||||
widget_add_child(input_row, input);
|
||||
widget_add_child(input_row, greet_btn);
|
||||
widget_add_child(root, input_row);
|
||||
el_val_t gap3 = label(EL_STR(""));
|
||||
widget_set_height(gap3, 12);
|
||||
widget_add_child(root, gap3);
|
||||
el_val_t greeting = label(EL_STR("Waiting for name\xe2\x80\xa6"));
|
||||
style_label_body(greeting);
|
||||
widget_set_color_hex(greeting, EL_STR("#60a5fa"));
|
||||
widget_set_font(greeting, EL_STR("system"), 16, 1);
|
||||
el_val_t g_label = greeting;
|
||||
widget_add_child(root, greeting);
|
||||
el_val_t gap4 = label(EL_STR(""));
|
||||
widget_set_height(gap4, 24);
|
||||
widget_add_child(root, gap4);
|
||||
el_val_t counter_row = hstack(12);
|
||||
el_val_t counter_lbl = label(EL_STR("Clicks: 0"));
|
||||
style_label_body(counter_lbl);
|
||||
el_val_t g_counter_lbl = counter_lbl;
|
||||
el_val_t counter_btn = button(EL_STR("Click me"));
|
||||
style_button_primary(counter_btn);
|
||||
widget_on_click(counter_btn, EL_STR("on_counter_click"));
|
||||
widget_add_child(counter_row, counter_lbl);
|
||||
widget_add_child(counter_row, counter_btn);
|
||||
widget_add_child(root, counter_row);
|
||||
widget_add_child(window, root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
g_window = (-1);
|
||||
g_label = (-1);
|
||||
g_input = (-1);
|
||||
g_button = (-1);
|
||||
g_counter = 0;
|
||||
g_counter_lbl = (-1);
|
||||
native_init();
|
||||
manifest_env = env(EL_STR("EL_MANIFEST"));
|
||||
manifest_path = ({ el_val_t _if_result_2 = 0; if ((str_len(manifest_env) > 0)) { _if_result_2 = (manifest_env); } else { _if_result_2 = (EL_STR("manifest.el")); } _if_result_2; });
|
||||
win = window_from_manifest(manifest_path);
|
||||
g_window = win;
|
||||
if (win < 0) {
|
||||
println(EL_STR("Error: failed to create window. Is EL_TARGET_MACOS defined?"));
|
||||
exit_program(1);
|
||||
}
|
||||
app_build(win);
|
||||
window_show(win);
|
||||
native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Binary file not shown.
Executable
+169
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bash
|
||||
# build-sdl2.sh — Compile and run native-hello on macOS using the SDL2 backend.
|
||||
#
|
||||
# Usage:
|
||||
# ./build-sdl2.sh # compile and run
|
||||
# ./build-sdl2.sh compile # compile only
|
||||
# ./build-sdl2.sh clean # remove build artefacts
|
||||
#
|
||||
# Requirements:
|
||||
# - elc in PATH (or ../../../lang/dist/platform/elc)
|
||||
# - SDL2, SDL2_ttf, SDL2_image via Homebrew (brew install sdl2 sdl2_ttf sdl2_image)
|
||||
# - clang (Xcode Command Line Tools)
|
||||
# - macOS 12+
|
||||
#
|
||||
# This is the SDL2 counterpart to build.sh (AppKit). The pipeline is identical;
|
||||
# only the platform bridge object and compile flags differ:
|
||||
# - el_sdl2.c instead of el_appkit.m
|
||||
# - -DEL_TARGET_SDL2 instead of -DEL_TARGET_MACOS
|
||||
# - sdl2-config --libs -lSDL2_ttf -lSDL2_image -ldl instead of -framework Cocoa
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||
BUILD_DIR="${SCRIPT_DIR}/build"
|
||||
|
||||
# Locate elc
|
||||
if command -v elc &>/dev/null; then
|
||||
ELC="elc"
|
||||
elif [ -x "${EL_LANG_ROOT}/dist/platform/elc" ]; then
|
||||
ELC="${EL_LANG_ROOT}/dist/platform/elc"
|
||||
else
|
||||
echo "Error: elc not found. Add it to PATH or place it at:"
|
||||
echo " ${EL_LANG_ROOT}/dist/platform/elc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify SDL2 is available
|
||||
if ! command -v sdl2-config &>/dev/null; then
|
||||
echo "Error: sdl2-config not found. Install SDL2 via:"
|
||||
echo " brew install sdl2 sdl2_ttf sdl2_image"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SDL2_CFLAGS=$(sdl2-config --cflags)
|
||||
SDL2_LIBS=$(sdl2-config --libs)
|
||||
|
||||
CLANG_FLAGS_COMMON=(
|
||||
-std=c11
|
||||
-DEL_TARGET_SDL2
|
||||
-I "${EL_RUNTIME}"
|
||||
)
|
||||
|
||||
clean() {
|
||||
rm -rf "${BUILD_DIR}"
|
||||
echo "Cleaned."
|
||||
}
|
||||
|
||||
compile() {
|
||||
mkdir -p "${BUILD_DIR}"
|
||||
|
||||
# ── 1. Vessel forward-declarations header ─────────────────────────────────
|
||||
echo "==> Generating el-native vessel header (declarations only)..."
|
||||
"${ELC}" --emit-header "${EL_NATIVE_VESSEL}" \
|
||||
| awk 'BEGIN{in_body=0} /^[a-zA-Z_].+\)[[:space:]]*\{/ {in_body=1} !in_body{print}' \
|
||||
> "${BUILD_DIR}/el_native_vessel.h"
|
||||
|
||||
# ── 2. Compile el-native vessel to C ──────────────────────────────────────
|
||||
echo "==> Compiling el-native vessel to C..."
|
||||
"${ELC}" "${EL_NATIVE_VESSEL}" \
|
||||
> "${BUILD_DIR}/el_native_vessel.c"
|
||||
|
||||
# ── 3. Compile app entry to C ─────────────────────────────────────────────
|
||||
echo "==> Compiling app entry to C..."
|
||||
EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
|
||||
"${ELC}" "${SCRIPT_DIR}/src/main.el" \
|
||||
> "${BUILD_DIR}/native_hello.c"
|
||||
|
||||
# ── 4. Compile el_sdl2.c (SDL2 bridge — pure C) ───────────────────────────
|
||||
# sdl2-config --cflags gives -I/opt/homebrew/include/SDL2 which handles
|
||||
# #include <SDL.h> but el_sdl2.c uses #include <SDL2/SDL.h> — also add
|
||||
# the parent directory so both forms resolve.
|
||||
echo "==> Compiling el_sdl2.c (SDL2 bridge)..."
|
||||
clang \
|
||||
-std=c11 \
|
||||
-DEL_TARGET_SDL2 \
|
||||
${SDL2_CFLAGS} \
|
||||
-I/opt/homebrew/include \
|
||||
-I "${EL_RUNTIME}" \
|
||||
-c "${EL_RUNTIME}/el_sdl2.c" \
|
||||
-o "${BUILD_DIR}/el_sdl2.o"
|
||||
|
||||
# ── 5a. Compile el_seed.c ─────────────────────────────────────────────────
|
||||
echo "==> Compiling el_seed.c..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-c "${EL_RUNTIME}/el_seed.c" \
|
||||
-o "${BUILD_DIR}/el_seed.o"
|
||||
|
||||
# ── 5b. Compile el_runtime.c and patch out duplicate __-prefixed symbols ──
|
||||
echo "==> Compiling el_runtime.c..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-c "${EL_RUNTIME}/el_runtime.c" \
|
||||
-o "${BUILD_DIR}/el_runtime.o"
|
||||
|
||||
echo "==> Patching el_runtime.o (hiding __-prefixed symbols duplicated in el_seed.o)..."
|
||||
nm "${BUILD_DIR}/el_seed.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.seed_T.txt"
|
||||
nm "${BUILD_DIR}/el_runtime.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.rt_T.txt"
|
||||
comm -23 "${BUILD_DIR}/.rt_T.txt" "${BUILD_DIR}/.seed_T.txt" \
|
||||
> "${BUILD_DIR}/.rt_keep.txt"
|
||||
nmedit -s "${BUILD_DIR}/.rt_keep.txt" "${BUILD_DIR}/el_runtime.o"
|
||||
|
||||
# ── 6. Compile el-native vessel object ────────────────────────────────────
|
||||
echo "==> Compiling el-native vessel object..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-include "${EL_RUNTIME}/el_native_target.h" \
|
||||
-c "${BUILD_DIR}/el_native_vessel.c" \
|
||||
-o "${BUILD_DIR}/el_native_vessel.o"
|
||||
|
||||
echo "==> Patching el_native_vessel.o (hiding vessel main)..."
|
||||
nm "${BUILD_DIR}/el_native_vessel.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' \
|
||||
| grep -v '^_main$' \
|
||||
> "${BUILD_DIR}/.vessel_keep.txt"
|
||||
nmedit -s "${BUILD_DIR}/.vessel_keep.txt" "${BUILD_DIR}/el_native_vessel.o"
|
||||
|
||||
# ── 7. Compile app object ─────────────────────────────────────────────────
|
||||
echo "==> Compiling app object..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-include "${EL_RUNTIME}/el_native_target.h" \
|
||||
-include "${BUILD_DIR}/el_native_vessel.h" \
|
||||
-c "${BUILD_DIR}/native_hello.c" \
|
||||
-o "${BUILD_DIR}/native_hello.o"
|
||||
|
||||
# ── 8. Link ───────────────────────────────────────────────────────────────
|
||||
echo "==> Linking native-hello-sdl2..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
${SDL2_LIBS} \
|
||||
-lSDL2_ttf \
|
||||
-lSDL2_image \
|
||||
-lcurl \
|
||||
-ldl \
|
||||
-lpthread \
|
||||
"${BUILD_DIR}/native_hello.o" \
|
||||
"${BUILD_DIR}/el_native_vessel.o" \
|
||||
"${BUILD_DIR}/el_sdl2.o" \
|
||||
"${BUILD_DIR}/el_runtime.o" \
|
||||
"${BUILD_DIR}/el_seed.o" \
|
||||
-o "${BUILD_DIR}/native-hello-sdl2"
|
||||
|
||||
echo "==> Build complete: ${BUILD_DIR}/native-hello-sdl2"
|
||||
}
|
||||
|
||||
run() {
|
||||
compile
|
||||
echo "==> Launching native-hello-sdl2..."
|
||||
EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
|
||||
"${BUILD_DIR}/native-hello-sdl2"
|
||||
}
|
||||
|
||||
case "${1:-run}" in
|
||||
clean) clean ;;
|
||||
compile) compile ;;
|
||||
run|*) run ;;
|
||||
esac
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
# build-win32.sh — Cross-compile native-hello for Windows using mingw64.
|
||||
#
|
||||
# Requirements:
|
||||
# - x86_64-w64-mingw32-gcc (brew install mingw-w64)
|
||||
# - elc in PATH or at ../../../lang/dist/platform/elc
|
||||
#
|
||||
# Output: build-win32/native-hello.exe
|
||||
#
|
||||
# Note: We use el_runtime_win32.c instead of el_runtime.c / el_seed.c because
|
||||
# those files have deep POSIX dependencies (pthread, curl, dlfcn, sys/socket,
|
||||
# etc.) that cannot be cross-compiled to Windows. el_runtime_win32.c provides
|
||||
# all the runtime symbols that native-hello and the el-native vessel actually
|
||||
# call, using only standard C11 + Win32 APIs.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||
BUILD_DIR="${SCRIPT_DIR}/build-win32"
|
||||
|
||||
# ── Locate elc ───────────────────────────────────────────────────────────────
|
||||
if command -v elc &>/dev/null; then
|
||||
ELC="elc"
|
||||
elif [ -x "${EL_LANG_ROOT}/dist/platform/elc" ]; then
|
||||
ELC="${EL_LANG_ROOT}/dist/platform/elc"
|
||||
else
|
||||
echo "Error: elc not found. Add it to PATH or place it at:"
|
||||
echo " ${EL_LANG_ROOT}/dist/platform/elc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CC="x86_64-w64-mingw32-gcc"
|
||||
|
||||
# -D_WIN32_IE for SetWindowSubclass (comctl32 >= IE 0x0600)
|
||||
CFLAGS=(
|
||||
-std=c11
|
||||
-DEL_TARGET_WIN32
|
||||
-D_WIN32_IE=0x0600
|
||||
-I"${EL_RUNTIME}"
|
||||
-Wall
|
||||
-Wno-unused-function
|
||||
)
|
||||
|
||||
mkdir -p "${BUILD_DIR}"
|
||||
|
||||
# ── 1. Compile el-native vessel to C ─────────────────────────────────────────
|
||||
echo "==> Compiling el-native vessel to C..."
|
||||
"${ELC}" "${EL_NATIVE_VESSEL}" \
|
||||
> "${BUILD_DIR}/el_native_vessel.c"
|
||||
|
||||
# ── 2. Generate vessel header (declarations only) ────────────────────────────
|
||||
# elc --emit-header includes function bodies AND bare global variable definitions.
|
||||
# We extract only forward declarations with two awk passes:
|
||||
# Pass 1: stop before function bodies (same as macOS build.sh).
|
||||
# Pass 2: convert bare global variable definitions (^el_val_t NAME;) to
|
||||
# `extern el_val_t NAME;` so the app TU sees them as declarations,
|
||||
# not definitions — avoiding duplicate symbol errors at link time.
|
||||
echo "==> Generating el-native vessel header (declarations only)..."
|
||||
"${ELC}" --emit-header "${EL_NATIVE_VESSEL}" \
|
||||
| awk 'BEGIN{in_body=0} /^[a-zA-Z_].+\)[[:space:]]*\{/ {in_body=1} !in_body{print}' \
|
||||
| awk '/^el_val_t [A-Za-z_][A-Za-z0-9_]*;/{print "extern " $0; next} {print}' \
|
||||
> "${BUILD_DIR}/el_native_vessel.h"
|
||||
|
||||
# ── 3. Compile app entry to C ─────────────────────────────────────────────────
|
||||
echo "==> Compiling app entry to C..."
|
||||
EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
|
||||
"${ELC}" "${SCRIPT_DIR}/src/main.el" \
|
||||
> "${BUILD_DIR}/native_hello.c"
|
||||
|
||||
# ── 4. Compile el_win32.c ─────────────────────────────────────────────────────
|
||||
echo "==> Compiling el_win32.c (Win32 widget bridge)..."
|
||||
${CC} "${CFLAGS[@]}" \
|
||||
-c "${EL_RUNTIME}/el_win32.c" \
|
||||
-o "${BUILD_DIR}/el_win32.o"
|
||||
|
||||
# ── 5. Compile el_runtime_win32.c ─────────────────────────────────────────────
|
||||
# This replaces el_seed.c + el_runtime.c for Win32 targets.
|
||||
echo "==> Compiling el_runtime_win32.c (Win32 runtime stub)..."
|
||||
${CC} "${CFLAGS[@]}" \
|
||||
-c "${EL_RUNTIME}/el_runtime_win32.c" \
|
||||
-o "${BUILD_DIR}/el_runtime_win32.o"
|
||||
|
||||
# ── 6. Compile el-native vessel object ───────────────────────────────────────
|
||||
echo "==> Compiling el-native vessel object..."
|
||||
${CC} "${CFLAGS[@]}" \
|
||||
-include "${EL_RUNTIME}/el_native_target.h" \
|
||||
-c "${BUILD_DIR}/el_native_vessel.c" \
|
||||
-o "${BUILD_DIR}/el_native_vessel.o"
|
||||
|
||||
echo "==> Patching el_native_vessel.o (localising vessel main + shared globals)..."
|
||||
# The vessel's compiled C has its own main(). We localize it so the linker sees
|
||||
# only the app's main(). We also localize any globals (TOKEN_*, etc.) that the
|
||||
# vessel defines but the app also declares — matching the nmedit step in
|
||||
# build.sh (macOS), using objcopy for mingw.
|
||||
VESSEL_LOCAL_SYMS=()
|
||||
# Collect all defined (T and D/B/C) symbols in the vessel object
|
||||
while IFS= read -r sym; do
|
||||
VESSEL_LOCAL_SYMS+=("--localize-symbol=${sym}")
|
||||
done < <(x86_64-w64-mingw32-nm "${BUILD_DIR}/el_native_vessel.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ [TDBCt] /{print $3}' \
|
||||
| grep -E '^(main|TOKEN_)' || true)
|
||||
if [ ${#VESSEL_LOCAL_SYMS[@]} -gt 0 ]; then
|
||||
x86_64-w64-mingw32-objcopy "${VESSEL_LOCAL_SYMS[@]}" \
|
||||
"${BUILD_DIR}/el_native_vessel.o"
|
||||
fi
|
||||
|
||||
# ── 7. Compile app object ─────────────────────────────────────────────────────
|
||||
echo "==> Compiling app object..."
|
||||
${CC} "${CFLAGS[@]}" \
|
||||
-include "${EL_RUNTIME}/el_native_target.h" \
|
||||
-include "${BUILD_DIR}/el_native_vessel.h" \
|
||||
-c "${BUILD_DIR}/native_hello.c" \
|
||||
-o "${BUILD_DIR}/native_hello.o"
|
||||
|
||||
# ── 8. Link ───────────────────────────────────────────────────────────────────
|
||||
# -Wl,--export-all-symbols: make el callback functions (on_greet_click etc.)
|
||||
# visible to GetProcAddress(GetModuleHandle(NULL), fn_name) — the Win32
|
||||
# equivalent of POSIX dlsym(RTLD_DEFAULT, fn_name).
|
||||
echo "==> Linking native-hello.exe..."
|
||||
${CC} "${CFLAGS[@]}" \
|
||||
-Wl,--export-all-symbols \
|
||||
"${BUILD_DIR}/native_hello.o" \
|
||||
"${BUILD_DIR}/el_native_vessel.o" \
|
||||
"${BUILD_DIR}/el_win32.o" \
|
||||
"${BUILD_DIR}/el_runtime_win32.o" \
|
||||
-lcomctl32 -luser32 -lgdi32 -lkernel32 \
|
||||
-o "${BUILD_DIR}/native-hello.exe"
|
||||
|
||||
echo ""
|
||||
echo "==> Built: ${BUILD_DIR}/native-hello.exe"
|
||||
ls -lh "${BUILD_DIR}/native-hello.exe"
|
||||
@@ -0,0 +1,459 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
el_val_t TOKEN_PRIMARY;
|
||||
el_val_t TOKEN_ON_PRIMARY;
|
||||
el_val_t TOKEN_BACKGROUND;
|
||||
el_val_t TOKEN_ON_BG;
|
||||
el_val_t TOKEN_SURFACE;
|
||||
el_val_t TOKEN_ON_SURFACE;
|
||||
el_val_t TOKEN_OUTLINE;
|
||||
el_val_t TOKEN_ERROR;
|
||||
|
||||
el_val_t native_init(void) {
|
||||
__native_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t native_run_loop(void) {
|
||||
__native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_read(el_val_t path) {
|
||||
return __manifest_read(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_title(el_val_t m) {
|
||||
return json_get_string(m, EL_STR("title"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height) {
|
||||
return __window_create(title, width, height, min_width, min_height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_from_manifest(el_val_t manifest_path) {
|
||||
el_val_t m = manifest_read(manifest_path);
|
||||
el_val_t title = manifest_title(m);
|
||||
el_val_t w = manifest_width(m);
|
||||
el_val_t h = manifest_height(m);
|
||||
el_val_t mw = manifest_min_width(m);
|
||||
el_val_t mh = manifest_min_height(m);
|
||||
el_val_t safe_w = ({ el_val_t _if_result_1 = 0; if ((w > 0)) { _if_result_1 = (w); } else { _if_result_1 = (1200); } _if_result_1; });
|
||||
el_val_t safe_h = ({ el_val_t _if_result_2 = 0; if ((h > 0)) { _if_result_2 = (h); } else { _if_result_2 = (800); } _if_result_2; });
|
||||
el_val_t safe_mw = ({ el_val_t _if_result_3 = 0; if ((mw > 0)) { _if_result_3 = (mw); } else { _if_result_3 = (600); } _if_result_3; });
|
||||
el_val_t safe_mh = ({ el_val_t _if_result_4 = 0; if ((mh > 0)) { _if_result_4 = (mh); } else { _if_result_4 = (400); } _if_result_4; });
|
||||
el_val_t safe_t = ({ el_val_t _if_result_5 = 0; if ((str_len(title) > 0)) { _if_result_5 = (title); } else { _if_result_5 = (EL_STR("App")); } _if_result_5; });
|
||||
return window_create(safe_t, safe_w, safe_h, safe_mw, safe_mh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_show(el_val_t handle) {
|
||||
__window_show(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title) {
|
||||
__window_set_title(handle, title);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack(el_val_t spacing) {
|
||||
return __vstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack_tight(void) {
|
||||
return __vstack_create(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hstack(el_val_t spacing) {
|
||||
return __hstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t zstack(void) {
|
||||
return __zstack_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t scroll(void) {
|
||||
return __scroll_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t label(el_val_t text) {
|
||||
return __label_create(text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t button(el_val_t label) {
|
||||
return __button_create(label);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_field(el_val_t placeholder) {
|
||||
return __text_field_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_area(el_val_t placeholder) {
|
||||
return __text_area_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t image(el_val_t path_or_name) {
|
||||
return __image_create(path_or_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text) {
|
||||
__widget_set_text(handle, text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_get_text(el_val_t handle) {
|
||||
return __widget_get_text(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_bg_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold) {
|
||||
el_val_t bold_int = ({ el_val_t _if_result_6 = 0; if (bold) { _if_result_6 = (1); } else { _if_result_6 = (0); } _if_result_6; });
|
||||
__widget_set_font(handle, family, size, bold_int);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left) {
|
||||
__widget_set_padding(handle, top, right, bottom, left);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p) {
|
||||
__widget_set_padding(handle, p, p, p, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py) {
|
||||
__widget_set_padding(handle, py, px, py, px);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width) {
|
||||
__widget_set_width(handle, width);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height) {
|
||||
__widget_set_height(handle, height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
__widget_set_flex(handle, flex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
__widget_set_corner_radius(handle, radius);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_val_t d = ({ el_val_t _if_result_7 = 0; if (disabled) { _if_result_7 = (1); } else { _if_result_7 = (0); } _if_result_7; });
|
||||
__widget_set_disabled(handle, d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_val_t h = ({ el_val_t _if_result_8 = 0; if (hidden) { _if_result_8 = (1); } else { _if_result_8 = (0); } _if_result_8; });
|
||||
__widget_set_hidden(handle, h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child) {
|
||||
__widget_add_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
__widget_remove_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_destroy(el_val_t handle) {
|
||||
__widget_destroy(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_click(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_change(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_submit(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset) {
|
||||
el_val_t hi = str_slice(s, offset, (offset + 1));
|
||||
el_val_t lo = str_slice(s, (offset + 1), (offset + 2));
|
||||
el_val_t h = hex_nibble(hi);
|
||||
el_val_t l = hex_nibble(lo);
|
||||
return ((h * 16) + l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_nibble(el_val_t c) {
|
||||
if (str_eq(c, EL_STR("0"))) {
|
||||
return 0;
|
||||
}
|
||||
if (str_eq(c, EL_STR("1"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(c, EL_STR("2"))) {
|
||||
return 2;
|
||||
}
|
||||
if (str_eq(c, EL_STR("3"))) {
|
||||
return 3;
|
||||
}
|
||||
if (str_eq(c, EL_STR("4"))) {
|
||||
return 4;
|
||||
}
|
||||
if (str_eq(c, EL_STR("5"))) {
|
||||
return 5;
|
||||
}
|
||||
if (str_eq(c, EL_STR("6"))) {
|
||||
return 6;
|
||||
}
|
||||
if (str_eq(c, EL_STR("7"))) {
|
||||
return 7;
|
||||
}
|
||||
if (str_eq(c, EL_STR("8"))) {
|
||||
return 8;
|
||||
}
|
||||
if (str_eq(c, EL_STR("9"))) {
|
||||
return 9;
|
||||
}
|
||||
if (str_eq(c, EL_STR("a"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("b"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("c"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("d"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("e"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("f"))) {
|
||||
return 15;
|
||||
}
|
||||
if (str_eq(c, EL_STR("A"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("B"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("C"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("D"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("E"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("F"))) {
|
||||
return 15;
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_r(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_9 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_9 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_9 = (hex); } _if_result_9; });
|
||||
el_val_t v = hex_channel(s, 0);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_g(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_10 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_10 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_10 = (hex); } _if_result_10; });
|
||||
el_val_t v = hex_channel(s, 2);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_b(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_11 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_11 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_11 = (hex); } _if_result_11; });
|
||||
el_val_t v = hex_channel(s, 4);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_a(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_12 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_12 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_12 = (hex); } _if_result_12; });
|
||||
if (str_len(s) < 8) {
|
||||
return el_from_float(1.0);
|
||||
}
|
||||
el_val_t v = hex_channel(s, 6);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_bg_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_surface(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_SURFACE);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_button_primary(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_PRIMARY);
|
||||
widget_set_color_hex(handle, TOKEN_ON_PRIMARY);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
widget_set_padding_xy(handle, 16, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_body(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 14, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_heading(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 20, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_muted(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_OUTLINE);
|
||||
widget_set_font(handle, EL_STR("system"), 12, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
TOKEN_PRIMARY = EL_STR("#60a5fa");
|
||||
TOKEN_ON_PRIMARY = EL_STR("#0f172a");
|
||||
TOKEN_BACKGROUND = EL_STR("#0f172a");
|
||||
TOKEN_ON_BG = EL_STR("#f8fafc");
|
||||
TOKEN_SURFACE = EL_STR("#1e293b");
|
||||
TOKEN_ON_SURFACE = EL_STR("#f8fafc");
|
||||
TOKEN_OUTLINE = EL_STR("#475569");
|
||||
TOKEN_ERROR = EL_STR("#f87171");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
el_val_t window_create(el_val_t title, el_val_t width, el_val_t height, el_val_t min_width, el_val_t min_height);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
extern el_val_t TOKEN_PRIMARY;
|
||||
extern el_val_t TOKEN_ON_PRIMARY;
|
||||
extern el_val_t TOKEN_BACKGROUND;
|
||||
extern el_val_t TOKEN_ON_BG;
|
||||
extern el_val_t TOKEN_SURFACE;
|
||||
extern el_val_t TOKEN_ON_SURFACE;
|
||||
extern el_val_t TOKEN_OUTLINE;
|
||||
extern el_val_t TOKEN_ERROR;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data);
|
||||
el_val_t app_build(el_val_t window);
|
||||
|
||||
el_val_t g_window;
|
||||
el_val_t g_label;
|
||||
el_val_t g_input;
|
||||
el_val_t g_button;
|
||||
el_val_t g_counter;
|
||||
el_val_t g_counter_lbl;
|
||||
el_val_t manifest_env;
|
||||
el_val_t manifest_path;
|
||||
el_val_t win;
|
||||
el_val_t g_window;
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t name = widget_get_text(g_input);
|
||||
el_val_t greeting = ({ el_val_t _if_result_1 = 0; if ((str_len(name) > 0)) { _if_result_1 = (el_str_concat(el_str_concat(EL_STR("Hello, "), name), EL_STR("!"))); } else { _if_result_1 = (EL_STR("Hello, World!")); } _if_result_1; });
|
||||
widget_set_text(g_label, greeting);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t g_counter = (g_counter + 1);
|
||||
widget_set_text(g_counter_lbl, el_str_concat(EL_STR("Clicks: "), int_to_str(g_counter)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data) {
|
||||
el_val_t has_text = (str_len(data) > 0);
|
||||
widget_set_disabled(g_button, !has_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t app_build(el_val_t window) {
|
||||
el_val_t root = vstack(0);
|
||||
widget_set_padding_all(root, 24);
|
||||
widget_set_bg_color_hex(root, EL_STR("#0f172a"));
|
||||
widget_set_flex(root, 1);
|
||||
el_val_t title = label(EL_STR("el-native \xe2\x80\x94 native widget demo"));
|
||||
style_label_heading(title);
|
||||
widget_add_child(root, title);
|
||||
el_val_t gap1 = label(EL_STR(""));
|
||||
widget_set_height(gap1, 16);
|
||||
widget_add_child(root, gap1);
|
||||
el_val_t subtitle = label(EL_STR("AppKit controls from el code, no ObjC in the app layer."));
|
||||
style_label_muted(subtitle);
|
||||
widget_add_child(root, subtitle);
|
||||
el_val_t gap2 = label(EL_STR(""));
|
||||
widget_set_height(gap2, 24);
|
||||
widget_add_child(root, gap2);
|
||||
el_val_t input_row = hstack(8);
|
||||
el_val_t name_label = label(EL_STR("Name:"));
|
||||
style_label_body(name_label);
|
||||
widget_set_width(name_label, 60);
|
||||
el_val_t input = text_field(EL_STR("Enter your name\xe2\x80\xa6"));
|
||||
style_label_body(input);
|
||||
widget_set_flex(input, 1);
|
||||
widget_on_change(input, EL_STR("on_name_change"));
|
||||
el_val_t g_input = input;
|
||||
el_val_t greet_btn = button(EL_STR("Greet"));
|
||||
style_button_primary(greet_btn);
|
||||
widget_set_disabled(greet_btn, 1);
|
||||
widget_on_click(greet_btn, EL_STR("on_greet_click"));
|
||||
el_val_t g_button = greet_btn;
|
||||
widget_add_child(input_row, name_label);
|
||||
widget_add_child(input_row, input);
|
||||
widget_add_child(input_row, greet_btn);
|
||||
widget_add_child(root, input_row);
|
||||
el_val_t gap3 = label(EL_STR(""));
|
||||
widget_set_height(gap3, 12);
|
||||
widget_add_child(root, gap3);
|
||||
el_val_t greeting = label(EL_STR("Waiting for name\xe2\x80\xa6"));
|
||||
style_label_body(greeting);
|
||||
widget_set_color_hex(greeting, EL_STR("#60a5fa"));
|
||||
widget_set_font(greeting, EL_STR("system"), 16, 1);
|
||||
el_val_t g_label = greeting;
|
||||
widget_add_child(root, greeting);
|
||||
el_val_t gap4 = label(EL_STR(""));
|
||||
widget_set_height(gap4, 24);
|
||||
widget_add_child(root, gap4);
|
||||
el_val_t counter_row = hstack(12);
|
||||
el_val_t counter_lbl = label(EL_STR("Clicks: 0"));
|
||||
style_label_body(counter_lbl);
|
||||
el_val_t g_counter_lbl = counter_lbl;
|
||||
el_val_t counter_btn = button(EL_STR("Click me"));
|
||||
style_button_primary(counter_btn);
|
||||
widget_on_click(counter_btn, EL_STR("on_counter_click"));
|
||||
widget_add_child(counter_row, counter_lbl);
|
||||
widget_add_child(counter_row, counter_btn);
|
||||
widget_add_child(root, counter_row);
|
||||
widget_add_child(window, root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
g_window = (-1);
|
||||
g_label = (-1);
|
||||
g_input = (-1);
|
||||
g_button = (-1);
|
||||
g_counter = 0;
|
||||
g_counter_lbl = (-1);
|
||||
native_init();
|
||||
manifest_env = env(EL_STR("EL_MANIFEST"));
|
||||
manifest_path = ({ el_val_t _if_result_2 = 0; if ((str_len(manifest_env) > 0)) { _if_result_2 = (manifest_env); } else { _if_result_2 = (EL_STR("manifest.el")); } _if_result_2; });
|
||||
win = window_from_manifest(manifest_path);
|
||||
g_window = win;
|
||||
if (win < 0) {
|
||||
println(EL_STR("Error: failed to create window. Is EL_TARGET_MACOS defined?"));
|
||||
exit_program(1);
|
||||
}
|
||||
app_build(win);
|
||||
window_show(win);
|
||||
native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Binary file not shown.
Executable
+198
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env bash
|
||||
# build.sh — Compile and run native-hello on macOS.
|
||||
#
|
||||
# Usage:
|
||||
# ./build.sh # compile and run
|
||||
# ./build.sh compile # compile only
|
||||
# ./build.sh clean # remove build artefacts
|
||||
# ./build.sh platforms # detect available platform bridges on this machine
|
||||
#
|
||||
# Requirements:
|
||||
# - elc in PATH (or ../../../lang/dist/platform/elc)
|
||||
# - clang with Xcode Command Line Tools (for Cocoa framework)
|
||||
# - macOS 12+
|
||||
#
|
||||
# Multi-file compilation strategy:
|
||||
# elc does not resolve cross-directory imports (../../vessels/...) at bundle
|
||||
# time. The el-native vessel is compiled as a separate translation unit.
|
||||
#
|
||||
# el_seed.c and el_runtime.c MUST both be linked:
|
||||
# - el_seed.c = OS-boundary layer (__-prefixed primitives) + thin wrappers
|
||||
# - el_runtime.c = high-level el builtins (str_len, json_get_string, etc.)
|
||||
# They share many __-prefixed symbols. The macOS linker rejects duplicate
|
||||
# definitions, so we use nmedit to hide the el_runtime.c copies of the __
|
||||
# symbols (keeping el_seed.c's versions canonical).
|
||||
#
|
||||
# el_native_target.h is a conflict-free header that adds:
|
||||
# - float_div / float_mul etc. (missing from current el_runtime.h)
|
||||
# - Native widget builtin declarations (#ifdef EL_TARGET_MACOS)
|
||||
# without redefining el_to_float / el_from_float from el_runtime.h.
|
||||
#
|
||||
# Vessel header generation: elc --emit-header includes function BODIES.
|
||||
# We extract only the forward declarations using awk (stop at first body).
|
||||
# NOTE: macOS awk does not support \s — use [[:space:]] instead.
|
||||
#
|
||||
# Vessel main(): the vessel's compiled .c file has its own main(). We use
|
||||
# nmedit to hide it from the vessel object before linking.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||
BUILD_DIR="${SCRIPT_DIR}/build"
|
||||
|
||||
# Locate elc
|
||||
if command -v elc &>/dev/null; then
|
||||
ELC="elc"
|
||||
elif [ -x "${EL_LANG_ROOT}/dist/platform/elc" ]; then
|
||||
ELC="${EL_LANG_ROOT}/dist/platform/elc"
|
||||
else
|
||||
echo "Error: elc not found. Add it to PATH or place it at:"
|
||||
echo " ${EL_LANG_ROOT}/dist/platform/elc"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CLANG_FLAGS_COMMON=(
|
||||
-std=c11
|
||||
-DEL_TARGET_MACOS
|
||||
-I "${EL_RUNTIME}"
|
||||
)
|
||||
|
||||
clean() {
|
||||
rm -rf "${BUILD_DIR}"
|
||||
echo "Cleaned."
|
||||
}
|
||||
|
||||
compile() {
|
||||
mkdir -p "${BUILD_DIR}"
|
||||
|
||||
# ── 1. Vessel forward-declarations header ─────────────────────────────────
|
||||
# elc --emit-header emits full function bodies, not just prototypes.
|
||||
# Extract only the forward-declaration lines: stop at the first function
|
||||
# definition (a line that ends with ') {' opening a body block).
|
||||
# macOS awk does not support \s — use POSIX [[:space:]] instead.
|
||||
echo "==> Generating el-native vessel header (declarations only)..."
|
||||
"${ELC}" --emit-header "${EL_NATIVE_VESSEL}" \
|
||||
| awk 'BEGIN{in_body=0} /^[a-zA-Z_].+\)[[:space:]]*\{/ {in_body=1} !in_body{print}' \
|
||||
> "${BUILD_DIR}/el_native_vessel.h"
|
||||
|
||||
# ── 2. Compile el-native vessel to C ──────────────────────────────────────
|
||||
echo "==> Compiling el-native vessel to C..."
|
||||
"${ELC}" "${EL_NATIVE_VESSEL}" \
|
||||
> "${BUILD_DIR}/el_native_vessel.c"
|
||||
|
||||
# ── 3. Compile app entry to C ─────────────────────────────────────────────
|
||||
echo "==> Compiling app entry to C..."
|
||||
EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
|
||||
"${ELC}" "${SCRIPT_DIR}/src/main.el" \
|
||||
> "${BUILD_DIR}/native_hello.c"
|
||||
|
||||
# ── 4. Compile el_appkit.m (ObjC, MRC — not ARC) ─────────────────────────
|
||||
echo "==> Compiling el_appkit.m (AppKit bridge, -fno-objc-arc)..."
|
||||
clang \
|
||||
-std=gnu11 \
|
||||
-ObjC \
|
||||
-fno-objc-arc \
|
||||
-DEL_TARGET_MACOS \
|
||||
-I "${EL_RUNTIME}" \
|
||||
-c "${EL_RUNTIME}/el_appkit.m" \
|
||||
-o "${BUILD_DIR}/el_appkit.o"
|
||||
|
||||
# ── 5a. Compile el_seed.c ─────────────────────────────────────────────────
|
||||
# el_seed.c provides the OS-boundary __-prefixed primitives + thin wrappers
|
||||
# around el_runtime.c's higher-level functions.
|
||||
echo "==> Compiling el_seed.c..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-c "${EL_RUNTIME}/el_seed.c" \
|
||||
-o "${BUILD_DIR}/el_seed.o"
|
||||
|
||||
# ── 5b. Compile el_runtime.c and patch out duplicate __-prefixed symbols ──
|
||||
# el_runtime.c provides the high-level builtins (str_len, json_get_string,
|
||||
# int_to_str, println, env, exit_program, el_runtime_init_args, etc.) that
|
||||
# el_seed.c does NOT provide (they are single-name, no __ prefix).
|
||||
#
|
||||
# el_runtime.c also defines the same __-prefixed OS wrappers as el_seed.c.
|
||||
# We hide those duplicates using nmedit so the linker sees only one copy.
|
||||
echo "==> Compiling el_runtime.c..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-c "${EL_RUNTIME}/el_runtime.c" \
|
||||
-o "${BUILD_DIR}/el_runtime.o"
|
||||
|
||||
echo "==> Patching el_runtime.o (hiding __-prefixed symbols duplicated in el_seed.o)..."
|
||||
# Build the set of symbols that el_seed.o defines and el_runtime.o also
|
||||
# defines. We will hide el_runtime.o's copies, keeping el_seed.o canonical.
|
||||
nm "${BUILD_DIR}/el_seed.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.seed_T.txt"
|
||||
nm "${BUILD_DIR}/el_runtime.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.rt_T.txt"
|
||||
# Symbols to keep public in el_runtime.o = all T symbols MINUS the
|
||||
# duplicates (those will be supplied by el_seed.o).
|
||||
comm -23 "${BUILD_DIR}/.rt_T.txt" "${BUILD_DIR}/.seed_T.txt" \
|
||||
> "${BUILD_DIR}/.rt_keep.txt"
|
||||
nmedit -s "${BUILD_DIR}/.rt_keep.txt" "${BUILD_DIR}/el_runtime.o"
|
||||
|
||||
# ── 6. Compile el-native vessel object ────────────────────────────────────
|
||||
# el_native_target.h adds float_div/mul/etc. + native widget declarations
|
||||
# without conflicting with el_runtime.h.
|
||||
echo "==> Compiling el-native vessel object..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-include "${EL_RUNTIME}/el_native_target.h" \
|
||||
-c "${BUILD_DIR}/el_native_vessel.c" \
|
||||
-o "${BUILD_DIR}/el_native_vessel.o"
|
||||
|
||||
echo "==> Patching el_native_vessel.o (hiding vessel main)..."
|
||||
# The vessel's compiled C has its own main(). Hide it so it does not
|
||||
# conflict with the app's main in native_hello.o.
|
||||
nm "${BUILD_DIR}/el_native_vessel.o" 2>/dev/null \
|
||||
| awk '/^[0-9a-f]+ T _/{print $3}' \
|
||||
| grep -v '^_main$' \
|
||||
> "${BUILD_DIR}/.vessel_keep.txt"
|
||||
nmedit -s "${BUILD_DIR}/.vessel_keep.txt" "${BUILD_DIR}/el_native_vessel.o"
|
||||
|
||||
# ── 7. Compile app object ─────────────────────────────────────────────────
|
||||
# Inject el_native_target.h (float ops + native decls) and the vessel's
|
||||
# forward-declarations header so the vessel functions are resolvable.
|
||||
echo "==> Compiling app object..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-include "${EL_RUNTIME}/el_native_target.h" \
|
||||
-include "${BUILD_DIR}/el_native_vessel.h" \
|
||||
-c "${BUILD_DIR}/native_hello.c" \
|
||||
-o "${BUILD_DIR}/native_hello.o"
|
||||
|
||||
# ── 8. Link ───────────────────────────────────────────────────────────────
|
||||
# el_seed.o = OS-boundary primitives (__-prefixed)
|
||||
# el_runtime.o (patched) = high-level builtins (str_len, json_*, etc.)
|
||||
# el_native_vessel.o (patched) = el-level widget API
|
||||
# el_appkit.o = AppKit C bridge (MRC)
|
||||
echo "==> Linking native-hello..."
|
||||
clang "${CLANG_FLAGS_COMMON[@]}" \
|
||||
-framework Cocoa \
|
||||
-lpthread \
|
||||
-lcurl \
|
||||
-ldl \
|
||||
"${BUILD_DIR}/native_hello.o" \
|
||||
"${BUILD_DIR}/el_native_vessel.o" \
|
||||
"${BUILD_DIR}/el_appkit.o" \
|
||||
"${BUILD_DIR}/el_runtime.o" \
|
||||
"${BUILD_DIR}/el_seed.o" \
|
||||
-o "${BUILD_DIR}/native-hello"
|
||||
|
||||
echo "==> Build complete: ${BUILD_DIR}/native-hello"
|
||||
}
|
||||
|
||||
run() {
|
||||
compile
|
||||
echo "==> Launching native-hello..."
|
||||
EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
|
||||
"${BUILD_DIR}/native-hello"
|
||||
}
|
||||
|
||||
case "${1:-run}" in
|
||||
clean) clean ;;
|
||||
compile) compile ;;
|
||||
platforms) "${EL_LANG_ROOT}/el-compiler/runtime/detect-platforms" ;;
|
||||
run|*) run ;;
|
||||
esac
|
||||
@@ -0,0 +1,397 @@
|
||||
___args_json
|
||||
___channel_close
|
||||
___channel_new
|
||||
___channel_recv
|
||||
___channel_send
|
||||
___channel_try_recv
|
||||
___env_get
|
||||
___exec
|
||||
___exec_bg
|
||||
___exit_program
|
||||
___float_to_str
|
||||
___fs_exists
|
||||
___fs_list_raw
|
||||
___fs_mkdir
|
||||
___fs_read
|
||||
___fs_write
|
||||
___fs_write_bytes
|
||||
___http_conn_fd
|
||||
___http_do
|
||||
___http_do_map
|
||||
___http_do_map_to_file
|
||||
___http_response
|
||||
___http_serve
|
||||
___http_serve_v2
|
||||
___http_sse_close
|
||||
___http_sse_open
|
||||
___http_sse_send
|
||||
___int_to_str
|
||||
___json_array_get
|
||||
___json_array_get_string
|
||||
___json_array_len
|
||||
___json_get
|
||||
___json_get_raw
|
||||
___json_parse_map
|
||||
___json_set
|
||||
___json_stringify_val
|
||||
___mutex_lock
|
||||
___mutex_new
|
||||
___mutex_unlock
|
||||
___print
|
||||
___print_raw
|
||||
___println
|
||||
___read_n
|
||||
___readline
|
||||
___sha256_hex
|
||||
___state_del
|
||||
___state_get
|
||||
___state_keys
|
||||
___state_set
|
||||
___str_alloc
|
||||
___str_char_at
|
||||
___str_cmp
|
||||
___str_concat_raw
|
||||
___str_len
|
||||
___str_ncmp
|
||||
___str_set_char
|
||||
___str_slice_raw
|
||||
___str_to_float
|
||||
___str_to_int
|
||||
___thread_create
|
||||
___thread_join
|
||||
___url_decode
|
||||
___url_encode
|
||||
___uuid_v4
|
||||
_aead_decrypt
|
||||
_aead_encrypt
|
||||
_append
|
||||
_args
|
||||
_base64_decode
|
||||
_base64_encode
|
||||
_base64url_decode
|
||||
_base64url_encode
|
||||
_bool_to_str
|
||||
_bytes_to_str
|
||||
_cal_cycle_phase
|
||||
_cal_format
|
||||
_cal_in
|
||||
_cal_to_instant
|
||||
_config
|
||||
_cycle_calendar
|
||||
_decimal_round
|
||||
_duration_millis
|
||||
_duration_nanos
|
||||
_duration_seconds
|
||||
_duration_to_millis
|
||||
_duration_to_nanos
|
||||
_duration_to_seconds
|
||||
_earth_calendar
|
||||
_earth_calendar_default
|
||||
_el_abs
|
||||
_el_arena_pop
|
||||
_el_arena_push
|
||||
_el_base64_encode_n
|
||||
_el_cgi_init
|
||||
_el_duration_add
|
||||
_el_duration_div
|
||||
_el_duration_eq
|
||||
_el_duration_from_nanos
|
||||
_el_duration_ge
|
||||
_el_duration_gt
|
||||
_el_duration_le
|
||||
_el_duration_lt
|
||||
_el_duration_ne
|
||||
_el_duration_scale
|
||||
_el_duration_sub
|
||||
_el_get_field
|
||||
_el_html_sanitize
|
||||
_el_instant_add_dur
|
||||
_el_instant_diff
|
||||
_el_instant_eq
|
||||
_el_instant_ge
|
||||
_el_instant_gt
|
||||
_el_instant_le
|
||||
_el_instant_lt
|
||||
_el_instant_ne
|
||||
_el_instant_sub_dur
|
||||
_el_list_append
|
||||
_el_list_clone
|
||||
_el_list_empty
|
||||
_el_list_get
|
||||
_el_list_len
|
||||
_el_list_new
|
||||
_el_local_date_add_dur
|
||||
_el_local_date_eq
|
||||
_el_local_date_lt
|
||||
_el_local_time_add_dur
|
||||
_el_map_get
|
||||
_el_map_new
|
||||
_el_map_set
|
||||
_el_max
|
||||
_el_mem_check
|
||||
_el_min
|
||||
_el_now_instant
|
||||
_el_release
|
||||
_el_request_end
|
||||
_el_request_start
|
||||
_el_retain
|
||||
_el_runtime_init_args
|
||||
_el_runtime_register_handler
|
||||
_el_runtime_register_handler_v2
|
||||
_el_sha256_bytes_n
|
||||
_el_sleep_duration
|
||||
_el_str_concat
|
||||
_engram_activate
|
||||
_engram_activate_json
|
||||
_engram_add_layer
|
||||
_engram_apply_decay_json
|
||||
_engram_compile_layered_json
|
||||
_engram_connect
|
||||
_engram_edge_between
|
||||
_engram_edge_count
|
||||
_engram_forget
|
||||
_engram_get_node
|
||||
_engram_get_node_json
|
||||
_engram_list_layers
|
||||
_engram_list_layers_json
|
||||
_engram_load
|
||||
_engram_neighbors
|
||||
_engram_neighbors_filtered
|
||||
_engram_neighbors_json
|
||||
_engram_node
|
||||
_engram_node_count
|
||||
_engram_node_full
|
||||
_engram_node_layered
|
||||
_engram_query_range
|
||||
_engram_remove_layer
|
||||
_engram_save
|
||||
_engram_scan_nodes
|
||||
_engram_scan_nodes_by_type_json
|
||||
_engram_scan_nodes_json
|
||||
_engram_search
|
||||
_engram_search_json
|
||||
_engram_stats_json
|
||||
_engram_strengthen
|
||||
_env
|
||||
_exec
|
||||
_exec_bg
|
||||
_exec_capture
|
||||
_exec_command
|
||||
_exit_program
|
||||
_float_to_int
|
||||
_float_to_str
|
||||
_format_float
|
||||
_fs_exists
|
||||
_fs_list
|
||||
_fs_list_json
|
||||
_fs_mkdir
|
||||
_fs_read
|
||||
_fs_write
|
||||
_fs_write_bytes
|
||||
_get
|
||||
_getpid_now
|
||||
_hash_sha256
|
||||
_hmac_sha256_bytes
|
||||
_hmac_sha256_hex
|
||||
_html_escape
|
||||
_html_raw
|
||||
_http_delete
|
||||
_http_get
|
||||
_http_get_engram
|
||||
_http_get_to_file
|
||||
_http_get_with_headers
|
||||
_http_patch
|
||||
_http_post
|
||||
_http_post_engram
|
||||
_http_post_form_auth
|
||||
_http_post_json
|
||||
_http_post_json_with_headers
|
||||
_http_post_to_file
|
||||
_http_post_with_headers
|
||||
_http_response
|
||||
_http_serve
|
||||
_http_serve_v2
|
||||
_http_set_handler
|
||||
_http_set_handler_v2
|
||||
_in_calendar
|
||||
_instant_from_iso8601
|
||||
_instant_to_iso8601
|
||||
_instant_to_unix_millis
|
||||
_instant_to_unix_seconds
|
||||
_int_to_float
|
||||
_int_to_str
|
||||
_is_alphanumeric
|
||||
_is_digit
|
||||
_is_letter
|
||||
_is_lowercase
|
||||
_is_punctuation
|
||||
_is_uppercase
|
||||
_is_whitespace
|
||||
_json_array_get
|
||||
_json_array_get_string
|
||||
_json_array_len
|
||||
_json_array_push
|
||||
_json_build_array
|
||||
_json_build_object
|
||||
_json_escape_string
|
||||
_json_get
|
||||
_json_get_bool
|
||||
_json_get_float
|
||||
_json_get_int
|
||||
_json_get_raw
|
||||
_json_get_string
|
||||
_json_parse
|
||||
_json_set
|
||||
_json_stringify
|
||||
_len
|
||||
_list_get
|
||||
_list_join
|
||||
_list_len
|
||||
_list_push
|
||||
_list_push_front
|
||||
_list_range
|
||||
_llm_call
|
||||
_llm_call_agentic
|
||||
_llm_call_system
|
||||
_llm_models
|
||||
_llm_register_tool
|
||||
_llm_vision
|
||||
_local_date
|
||||
_local_date_day
|
||||
_local_date_month
|
||||
_local_date_year
|
||||
_local_datetime
|
||||
_local_time
|
||||
_local_time_hour
|
||||
_local_time_minute
|
||||
_local_time_nanos
|
||||
_local_time_second
|
||||
_log_info
|
||||
_log_warn
|
||||
_map_get
|
||||
_map_set
|
||||
_mars_calendar
|
||||
_math_cos
|
||||
_math_ln
|
||||
_math_log
|
||||
_math_pi
|
||||
_math_sin
|
||||
_math_sqrt
|
||||
_native_int_to_str
|
||||
_native_list_append
|
||||
_native_list_clone
|
||||
_native_list_empty
|
||||
_native_list_get
|
||||
_native_list_len
|
||||
_native_str_to_int
|
||||
_native_string_chars
|
||||
_no_cycle_calendar
|
||||
_now
|
||||
_now_in
|
||||
_now_millis
|
||||
_now_ns
|
||||
_parse_int
|
||||
_pq_hybrid_handshake
|
||||
_pq_hybrid_keygen
|
||||
_pq_kem_decaps
|
||||
_pq_kem_encaps
|
||||
_pq_kem_keygen
|
||||
_pq_keygen_signature
|
||||
_pq_sign
|
||||
_pq_verify
|
||||
_print
|
||||
_println
|
||||
_readline
|
||||
_relative_calendar
|
||||
_rhythm_and
|
||||
_rhythm_cycle_phase
|
||||
_rhythm_cycle_start
|
||||
_rhythm_duration
|
||||
_rhythm_event
|
||||
_rhythm_matches
|
||||
_rhythm_next_after
|
||||
_rhythm_or
|
||||
_rhythm_session_start
|
||||
_rhythm_weekday
|
||||
_rhythm_weekly_at
|
||||
_sha256_bytes
|
||||
_sha256_hex
|
||||
_sha3_256_hex
|
||||
_sleep_ms
|
||||
_sleep_secs
|
||||
_state_del
|
||||
_state_get
|
||||
_state_get_or
|
||||
_state_has
|
||||
_state_keys
|
||||
_state_set
|
||||
_stdout_restore
|
||||
_stdout_to_file
|
||||
_str_char_at
|
||||
_str_char_code
|
||||
_str_concat
|
||||
_str_contains
|
||||
_str_count
|
||||
_str_count_bytes
|
||||
_str_count_chars
|
||||
_str_count_digits
|
||||
_str_count_letters
|
||||
_str_count_lines
|
||||
_str_count_words
|
||||
_str_ends_with
|
||||
_str_eq
|
||||
_str_find_chars
|
||||
_str_format
|
||||
_str_index_of
|
||||
_str_index_of_all
|
||||
_str_join
|
||||
_str_last_index_of
|
||||
_str_len
|
||||
_str_lower
|
||||
_str_lstrip
|
||||
_str_pad_left
|
||||
_str_pad_right
|
||||
_str_repeat
|
||||
_str_replace
|
||||
_str_reverse
|
||||
_str_rstrip
|
||||
_str_slice
|
||||
_str_split
|
||||
_str_split_chars
|
||||
_str_split_lines
|
||||
_str_split_n
|
||||
_str_starts_with
|
||||
_str_strip_chars
|
||||
_str_strip_prefix
|
||||
_str_strip_suffix
|
||||
_str_to_bytes
|
||||
_str_to_float
|
||||
_str_to_int
|
||||
_str_to_lower
|
||||
_str_to_upper
|
||||
_str_trim
|
||||
_str_upper
|
||||
_time_add
|
||||
_time_diff
|
||||
_time_format
|
||||
_time_from_parts
|
||||
_time_now
|
||||
_time_now_ms
|
||||
_time_now_utc
|
||||
_time_to_parts
|
||||
_ttl_cache_age
|
||||
_ttl_cache_get
|
||||
_ttl_cache_set
|
||||
_unix_millis
|
||||
_unix_seconds
|
||||
_unix_timestamp
|
||||
_unix_timestamp_ms
|
||||
_url_decode
|
||||
_url_encode
|
||||
_uuid_new
|
||||
_uuid_v4
|
||||
_zone
|
||||
_zone_local
|
||||
_zone_offset
|
||||
_zone_utc
|
||||
_zoned
|
||||
@@ -0,0 +1,340 @@
|
||||
___channel_close
|
||||
___channel_new
|
||||
___channel_recv
|
||||
___channel_send
|
||||
___channel_try_recv
|
||||
___http_do_map
|
||||
___http_do_map_to_file
|
||||
___print_raw
|
||||
___read_n
|
||||
_aead_decrypt
|
||||
_aead_encrypt
|
||||
_append
|
||||
_args
|
||||
_base64_decode
|
||||
_base64_encode
|
||||
_base64url_decode
|
||||
_base64url_encode
|
||||
_bool_to_str
|
||||
_bytes_to_str
|
||||
_cal_cycle_phase
|
||||
_cal_format
|
||||
_cal_in
|
||||
_cal_to_instant
|
||||
_config
|
||||
_cycle_calendar
|
||||
_decimal_round
|
||||
_duration_millis
|
||||
_duration_nanos
|
||||
_duration_seconds
|
||||
_duration_to_millis
|
||||
_duration_to_nanos
|
||||
_duration_to_seconds
|
||||
_earth_calendar
|
||||
_earth_calendar_default
|
||||
_el_abs
|
||||
_el_arena_pop
|
||||
_el_arena_push
|
||||
_el_base64_encode_n
|
||||
_el_cgi_init
|
||||
_el_duration_add
|
||||
_el_duration_div
|
||||
_el_duration_eq
|
||||
_el_duration_from_nanos
|
||||
_el_duration_ge
|
||||
_el_duration_gt
|
||||
_el_duration_le
|
||||
_el_duration_lt
|
||||
_el_duration_ne
|
||||
_el_duration_scale
|
||||
_el_duration_sub
|
||||
_el_get_field
|
||||
_el_html_sanitize
|
||||
_el_instant_add_dur
|
||||
_el_instant_diff
|
||||
_el_instant_eq
|
||||
_el_instant_ge
|
||||
_el_instant_gt
|
||||
_el_instant_le
|
||||
_el_instant_lt
|
||||
_el_instant_ne
|
||||
_el_instant_sub_dur
|
||||
_el_list_append
|
||||
_el_list_clone
|
||||
_el_list_empty
|
||||
_el_list_get
|
||||
_el_list_len
|
||||
_el_list_new
|
||||
_el_local_date_add_dur
|
||||
_el_local_date_eq
|
||||
_el_local_date_lt
|
||||
_el_local_time_add_dur
|
||||
_el_map_get
|
||||
_el_map_new
|
||||
_el_map_set
|
||||
_el_max
|
||||
_el_mem_check
|
||||
_el_min
|
||||
_el_now_instant
|
||||
_el_release
|
||||
_el_retain
|
||||
_el_runtime_init_args
|
||||
_el_runtime_register_handler
|
||||
_el_runtime_register_handler_v2
|
||||
_el_sha256_bytes_n
|
||||
_el_sleep_duration
|
||||
_el_str_concat
|
||||
_engram_activate
|
||||
_engram_activate_json
|
||||
_engram_add_layer
|
||||
_engram_apply_decay_json
|
||||
_engram_compile_layered_json
|
||||
_engram_connect
|
||||
_engram_edge_between
|
||||
_engram_edge_count
|
||||
_engram_forget
|
||||
_engram_get_node
|
||||
_engram_get_node_json
|
||||
_engram_list_layers
|
||||
_engram_list_layers_json
|
||||
_engram_load
|
||||
_engram_neighbors
|
||||
_engram_neighbors_filtered
|
||||
_engram_neighbors_json
|
||||
_engram_node
|
||||
_engram_node_count
|
||||
_engram_node_full
|
||||
_engram_node_layered
|
||||
_engram_query_range
|
||||
_engram_remove_layer
|
||||
_engram_save
|
||||
_engram_scan_nodes
|
||||
_engram_scan_nodes_by_type_json
|
||||
_engram_scan_nodes_json
|
||||
_engram_search
|
||||
_engram_search_json
|
||||
_engram_stats_json
|
||||
_engram_strengthen
|
||||
_env
|
||||
_exec
|
||||
_exec_bg
|
||||
_exec_capture
|
||||
_exec_command
|
||||
_exit_program
|
||||
_float_to_int
|
||||
_float_to_str
|
||||
_format_float
|
||||
_fs_exists
|
||||
_fs_list
|
||||
_fs_list_json
|
||||
_fs_mkdir
|
||||
_fs_read
|
||||
_fs_write
|
||||
_fs_write_bytes
|
||||
_get
|
||||
_getpid_now
|
||||
_hash_sha256
|
||||
_hmac_sha256_bytes
|
||||
_hmac_sha256_hex
|
||||
_html_escape
|
||||
_html_raw
|
||||
_http_delete
|
||||
_http_get
|
||||
_http_get_engram
|
||||
_http_get_to_file
|
||||
_http_get_with_headers
|
||||
_http_patch
|
||||
_http_post
|
||||
_http_post_engram
|
||||
_http_post_form_auth
|
||||
_http_post_json
|
||||
_http_post_json_with_headers
|
||||
_http_post_to_file
|
||||
_http_post_with_headers
|
||||
_http_response
|
||||
_http_serve
|
||||
_http_serve_v2
|
||||
_http_set_handler
|
||||
_http_set_handler_v2
|
||||
_in_calendar
|
||||
_instant_from_iso8601
|
||||
_instant_to_iso8601
|
||||
_instant_to_unix_millis
|
||||
_instant_to_unix_seconds
|
||||
_int_to_float
|
||||
_int_to_str
|
||||
_is_alphanumeric
|
||||
_is_digit
|
||||
_is_letter
|
||||
_is_lowercase
|
||||
_is_punctuation
|
||||
_is_uppercase
|
||||
_is_whitespace
|
||||
_json_array_get
|
||||
_json_array_get_string
|
||||
_json_array_len
|
||||
_json_array_push
|
||||
_json_build_array
|
||||
_json_build_object
|
||||
_json_escape_string
|
||||
_json_get
|
||||
_json_get_bool
|
||||
_json_get_float
|
||||
_json_get_int
|
||||
_json_get_raw
|
||||
_json_get_string
|
||||
_json_parse
|
||||
_json_set
|
||||
_json_stringify
|
||||
_len
|
||||
_list_get
|
||||
_list_join
|
||||
_list_len
|
||||
_list_push
|
||||
_list_push_front
|
||||
_list_range
|
||||
_llm_call
|
||||
_llm_call_agentic
|
||||
_llm_call_system
|
||||
_llm_models
|
||||
_llm_register_tool
|
||||
_llm_vision
|
||||
_local_date
|
||||
_local_date_day
|
||||
_local_date_month
|
||||
_local_date_year
|
||||
_local_datetime
|
||||
_local_time
|
||||
_local_time_hour
|
||||
_local_time_minute
|
||||
_local_time_nanos
|
||||
_local_time_second
|
||||
_log_info
|
||||
_log_warn
|
||||
_map_get
|
||||
_map_set
|
||||
_mars_calendar
|
||||
_math_cos
|
||||
_math_ln
|
||||
_math_log
|
||||
_math_pi
|
||||
_math_sin
|
||||
_math_sqrt
|
||||
_native_int_to_str
|
||||
_native_list_append
|
||||
_native_list_clone
|
||||
_native_list_empty
|
||||
_native_list_get
|
||||
_native_list_len
|
||||
_native_str_to_int
|
||||
_native_string_chars
|
||||
_no_cycle_calendar
|
||||
_now
|
||||
_now_in
|
||||
_now_millis
|
||||
_now_ns
|
||||
_parse_int
|
||||
_pq_hybrid_handshake
|
||||
_pq_hybrid_keygen
|
||||
_pq_kem_decaps
|
||||
_pq_kem_encaps
|
||||
_pq_kem_keygen
|
||||
_pq_keygen_signature
|
||||
_pq_sign
|
||||
_pq_verify
|
||||
_print
|
||||
_println
|
||||
_readline
|
||||
_relative_calendar
|
||||
_rhythm_and
|
||||
_rhythm_cycle_phase
|
||||
_rhythm_cycle_start
|
||||
_rhythm_duration
|
||||
_rhythm_event
|
||||
_rhythm_matches
|
||||
_rhythm_next_after
|
||||
_rhythm_or
|
||||
_rhythm_session_start
|
||||
_rhythm_weekday
|
||||
_rhythm_weekly_at
|
||||
_sha256_bytes
|
||||
_sha256_hex
|
||||
_sha3_256_hex
|
||||
_sleep_ms
|
||||
_sleep_secs
|
||||
_state_del
|
||||
_state_get
|
||||
_state_get_or
|
||||
_state_has
|
||||
_state_keys
|
||||
_state_set
|
||||
_stdout_restore
|
||||
_stdout_to_file
|
||||
_str_char_at
|
||||
_str_char_code
|
||||
_str_concat
|
||||
_str_contains
|
||||
_str_count
|
||||
_str_count_bytes
|
||||
_str_count_chars
|
||||
_str_count_digits
|
||||
_str_count_letters
|
||||
_str_count_lines
|
||||
_str_count_words
|
||||
_str_ends_with
|
||||
_str_eq
|
||||
_str_find_chars
|
||||
_str_format
|
||||
_str_index_of
|
||||
_str_index_of_all
|
||||
_str_join
|
||||
_str_last_index_of
|
||||
_str_len
|
||||
_str_lower
|
||||
_str_lstrip
|
||||
_str_pad_left
|
||||
_str_pad_right
|
||||
_str_repeat
|
||||
_str_replace
|
||||
_str_reverse
|
||||
_str_rstrip
|
||||
_str_slice
|
||||
_str_split
|
||||
_str_split_chars
|
||||
_str_split_lines
|
||||
_str_split_n
|
||||
_str_starts_with
|
||||
_str_strip_chars
|
||||
_str_strip_prefix
|
||||
_str_strip_suffix
|
||||
_str_to_bytes
|
||||
_str_to_float
|
||||
_str_to_int
|
||||
_str_to_lower
|
||||
_str_to_upper
|
||||
_str_trim
|
||||
_str_upper
|
||||
_time_add
|
||||
_time_diff
|
||||
_time_format
|
||||
_time_from_parts
|
||||
_time_now
|
||||
_time_now_ms
|
||||
_time_now_utc
|
||||
_time_to_parts
|
||||
_ttl_cache_age
|
||||
_ttl_cache_get
|
||||
_ttl_cache_set
|
||||
_unix_millis
|
||||
_unix_seconds
|
||||
_unix_timestamp
|
||||
_unix_timestamp_ms
|
||||
_url_decode
|
||||
_url_encode
|
||||
_uuid_new
|
||||
_uuid_v4
|
||||
_zone
|
||||
_zone_local
|
||||
_zone_offset
|
||||
_zone_utc
|
||||
_zoned
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user