Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08cbcef5d9 | |||
| 6621a4dbc5 | |||
| 15f90003c0 | |||
| 01826421c4 | |||
| d4f401de1c | |||
| f19040e484 | |||
| 112bb2540f | |||
| d595b3c57e | |||
| 23f43bcc21 | |||
| ba6e36c3f7 | |||
| 791b0880b7 | |||
| 23552ed40a | |||
| 6838e5cbff | |||
| fa2b49365b | |||
| 971b21751a | |||
| 9f1db8278c | |||
| 3d05e0c2a9 | |||
| 3bf44dee2d | |||
| a43a35bd10 | |||
| afc92f4e33 | |||
| 005e84e5d3 | |||
| 7f03876e26 | |||
| 599073cb92 | |||
| 7f66529510 | |||
| 6ebe3d0d66 | |||
| 9f362c90e5 | |||
| 11dc138a93 | |||
| 227f158a05 | |||
| 97e484221d | |||
| 8f8ccc945e | |||
| 409ec99397 | |||
| dc39a61e2c | |||
| eba9eac8a8 | |||
| ab6b52a0b4 | |||
| e3dabe3e08 | |||
| 0a0a2bcb44 | |||
| f78da81aa4 | |||
| 2597a092bb | |||
| 226b798407 | |||
| cfe8cb1c80 | |||
| 688b8508fb | |||
| 59cea116c5 | |||
| deb0520551 | |||
| da116b2884 | |||
| 58753a88d7 | |||
| edff25180e | |||
| 6271cb42b2 | |||
| 3da9181deb | |||
| 192241c7c1 | |||
| e7c2dc7734 | |||
| f7bd99ae45 | |||
| 93d36fddb1 | |||
| 2d751890ea | |||
| 99b113ea9d | |||
| c087b97093 | |||
| 718a2e0c06 | |||
| b6187501fd | |||
| 18e1ab6db1 | |||
| 2dec76c87a | |||
| a36a62ca14 | |||
| 28ef43264a | |||
| 35c189759c | |||
| 5c94b8680d | |||
| cebf3ded62 | |||
| b83ecf52f9 | |||
| 15ea584671 | |||
| c2afcbddf5 | |||
| dbf2c659d9 | |||
| 2b8062c55f | |||
| dfe4e83ed1 | |||
| a390ee494e | |||
| c2cd5e01e1 | |||
| 8212e12e57 | |||
| 253ee2b887 | |||
| d7540700d4 | |||
| 2ed6b26dde | |||
| d8e9fd12f4 | |||
| 8fa9c4ba20 | |||
| 9c7bde47dc | |||
| c0553459e1 | |||
| fd208583fe | |||
| 3e29fc43ab | |||
| 979a5677d5 | |||
| 17b1aa0736 | |||
| f0c731d2db | |||
| e7e0f7d3e5 |
@@ -22,7 +22,10 @@ jobs:
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install -y gcc libcurl4-openssl-dev
|
||||
apt-get install -y gcc libcurl4-openssl-dev apt-transport-https ca-certificates
|
||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" \
|
||||
> /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||
|
||||
# Seed: use the committed linux-amd64 binary as the bootstrap
|
||||
- name: Bootstrap from committed linux binary (seed)
|
||||
@@ -212,9 +215,6 @@ jobs:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
run: |
|
||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||
apt-get install -y -qq apt-transport-https ca-certificates curl
|
||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||
gcloud config set project neuron-785695
|
||||
|
||||
|
||||
+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,46 +1,46 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
extern fn lang_profile(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> Any
|
||||
extern fn lang_get(profile: Any, key: String) -> String
|
||||
extern fn lang_profile_en() -> Any
|
||||
extern fn lang_profile_ja() -> Any
|
||||
extern fn lang_profile_ar() -> Any
|
||||
extern fn lang_profile_zh() -> Any
|
||||
extern fn lang_profile_de() -> Any
|
||||
extern fn lang_profile_es() -> Any
|
||||
extern fn lang_profile_fi() -> Any
|
||||
extern fn lang_profile_sw() -> Any
|
||||
extern fn lang_profile_hi() -> Any
|
||||
extern fn lang_profile_ru() -> Any
|
||||
extern fn lang_profile_fr() -> Any
|
||||
extern fn lang_profile_la() -> Any
|
||||
extern fn lang_profile_he() -> Any
|
||||
extern fn lang_profile_sa() -> Any
|
||||
extern fn lang_profile_got() -> Any
|
||||
extern fn lang_profile_non() -> Any
|
||||
extern fn lang_profile_enm() -> Any
|
||||
extern fn lang_profile_pi() -> Any
|
||||
extern fn lang_profile_grc() -> Any
|
||||
extern fn lang_profile_ang() -> Any
|
||||
extern fn lang_profile_fro() -> Any
|
||||
extern fn lang_profile_goh() -> Any
|
||||
extern fn lang_profile_sga() -> Any
|
||||
extern fn lang_profile_txb() -> Any
|
||||
extern fn lang_profile_peo() -> Any
|
||||
extern fn lang_profile_akk() -> Any
|
||||
extern fn lang_profile_uga() -> Any
|
||||
extern fn lang_profile_egy() -> Any
|
||||
extern fn lang_profile_sux() -> Any
|
||||
extern fn lang_profile_gez() -> Any
|
||||
extern fn lang_profile_cop() -> Any
|
||||
extern fn lang_from_code(code: String) -> Any
|
||||
extern fn lang_default() -> Any
|
||||
extern fn lang_is_isolating(profile: Any) -> Bool
|
||||
extern fn lang_is_agglutinative(profile: Any) -> Bool
|
||||
extern fn lang_is_fusional(profile: Any) -> Bool
|
||||
extern fn lang_is_polysynthetic(profile: Any) -> Bool
|
||||
extern fn lang_is_rtl(profile: Any) -> Bool
|
||||
extern fn lang_has_null_subject(profile: Any) -> Bool
|
||||
extern fn lang_has_case(profile: Any) -> Bool
|
||||
extern fn lang_has_gender(profile: Any) -> Bool
|
||||
extern fn lang_word_order(profile: Any) -> String
|
||||
extern fn lang_code(profile: Any) -> String
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn lang_profile(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String]
|
||||
extern fn lang_get(profile: [String], key: String) -> String
|
||||
extern fn lang_profile_en() -> [String]
|
||||
extern fn lang_profile_ja() -> [String]
|
||||
extern fn lang_profile_ar() -> [String]
|
||||
extern fn lang_profile_zh() -> [String]
|
||||
extern fn lang_profile_de() -> [String]
|
||||
extern fn lang_profile_es() -> [String]
|
||||
extern fn lang_profile_fi() -> [String]
|
||||
extern fn lang_profile_sw() -> [String]
|
||||
extern fn lang_profile_hi() -> [String]
|
||||
extern fn lang_profile_ru() -> [String]
|
||||
extern fn lang_profile_fr() -> [String]
|
||||
extern fn lang_profile_la() -> [String]
|
||||
extern fn lang_profile_he() -> [String]
|
||||
extern fn lang_profile_sa() -> [String]
|
||||
extern fn lang_profile_got() -> [String]
|
||||
extern fn lang_profile_non() -> [String]
|
||||
extern fn lang_profile_enm() -> [String]
|
||||
extern fn lang_profile_pi() -> [String]
|
||||
extern fn lang_profile_grc() -> [String]
|
||||
extern fn lang_profile_ang() -> [String]
|
||||
extern fn lang_profile_fro() -> [String]
|
||||
extern fn lang_profile_goh() -> [String]
|
||||
extern fn lang_profile_sga() -> [String]
|
||||
extern fn lang_profile_txb() -> [String]
|
||||
extern fn lang_profile_peo() -> [String]
|
||||
extern fn lang_profile_akk() -> [String]
|
||||
extern fn lang_profile_uga() -> [String]
|
||||
extern fn lang_profile_egy() -> [String]
|
||||
extern fn lang_profile_sux() -> [String]
|
||||
extern fn lang_profile_gez() -> [String]
|
||||
extern fn lang_profile_cop() -> [String]
|
||||
extern fn lang_from_code(code: String) -> [String]
|
||||
extern fn lang_default() -> [String]
|
||||
extern fn lang_is_isolating(profile: [String]) -> Bool
|
||||
extern fn lang_is_agglutinative(profile: [String]) -> Bool
|
||||
extern fn lang_is_fusional(profile: [String]) -> Bool
|
||||
extern fn lang_is_polysynthetic(profile: [String]) -> Bool
|
||||
extern fn lang_is_rtl(profile: [String]) -> Bool
|
||||
extern fn lang_has_null_subject(profile: [String]) -> Bool
|
||||
extern fn lang_has_case(profile: [String]) -> Bool
|
||||
extern fn lang_has_gender(profile: [String]) -> Bool
|
||||
extern fn lang_word_order(profile: [String]) -> String
|
||||
extern fn lang_code(profile: [String]) -> String
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn akk_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn akk_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn akk_str_len(s: String) -> Int
|
||||
extern fn akk_str_drop_last(s: String, n: Int) -> String
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn ang_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn ang_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn ang_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn ang_str_last_char(s: String) -> String
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
// ── String helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn ar_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn ar_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn ar_str_len(s: String) -> Int
|
||||
extern fn ar_str_drop_last(s: String, n: Int) -> String
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
|
||||
// ── String helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn cop_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn cop_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn cop_str_len(s: String) -> Int
|
||||
extern fn cop_drop(s: String, n: Int) -> String
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
// Dat: dem der dem den
|
||||
// Gen: des der des der
|
||||
|
||||
import "morphology.el"
|
||||
fn de_article_def(gender: String, gram_case: String, number: String) -> String {
|
||||
if str_eq(number, "pl") {
|
||||
if str_eq(gram_case, "nom") { return "die" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn de_article_def(gender: String, gram_case: String, number: String) -> String
|
||||
extern fn de_article_indef(gender: String, gram_case: String, number: String) -> String
|
||||
extern fn de_article(gender: String, gram_case: String, number: String, definite: String) -> String
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
|
||||
// ── String helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn egy_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn egy_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn egy_str_len(s: String) -> Int
|
||||
extern fn egy_drop(s: String, n: Int) -> String
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn enm_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn enm_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn enm_drop(s: String, n: Int) -> String
|
||||
extern fn enm_first_char(s: String) -> String
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
// ── String helpers (local, matching morphology.el conventions) ────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn es_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn es_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn es_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn es_str_last_char(s: String) -> String
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
// If only neutral vowels are found, default to "front" (the conservative choice
|
||||
// for borrowed words and those without clear back vowels).
|
||||
|
||||
import "morphology.el"
|
||||
fn fi_harmony(word: String) -> String {
|
||||
let n: Int = str_len(word)
|
||||
let i: Int = n - 1
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn fi_harmony(word: String) -> String
|
||||
extern fn fi_suffix(base: String, harmony: String) -> String
|
||||
extern fn fi_noun_case(stem: String, gram_case: String, number: String, harmony: String) -> String
|
||||
extern fn fi_str_last_char(s: String) -> String
|
||||
extern fn fi_apply_case(noun: String, gram_case: String, number: String) -> String
|
||||
extern fn fi_verb_stem(dict_form: String) -> String
|
||||
extern fn fi_irregular_verb(dict_form: String) -> Any
|
||||
extern fn fi_irregular_verb(dict_form: String) -> [String]
|
||||
extern fn fi_present_ending(stem: String, person: String, number: String, harmony: String) -> String
|
||||
extern fn fi_past_stem(stem: String) -> String
|
||||
extern fn fi_past_ending(stem: String, person: String, number: String, harmony: String) -> String
|
||||
@@ -14,4 +14,4 @@ extern fn fi_negative(verb: String, person: String, number: String) -> String
|
||||
extern fn fi_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn fi_question_suffix(harmony: String) -> String
|
||||
extern fn fi_make_question(verb_form: String, harmony: String) -> String
|
||||
extern fn fi_full_paradigm(noun: String) -> Any
|
||||
extern fn fi_full_paradigm(noun: String) -> [String]
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
// ── String helpers (local, matching morphology.el conventions) ────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn fr_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn fr_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn fr_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn fr_str_last_char(s: String) -> String
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn fro_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn fro_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn fro_drop(s: String, n: Int) -> String
|
||||
extern fn fro_slot(person: String, number: String) -> Int
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn gez_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn gez_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn gez_str_len(s: String) -> Int
|
||||
extern fn gez_str_drop_last(s: String, n: Int) -> String
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn goh_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn goh_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn goh_drop(s: String, n: Int) -> String
|
||||
extern fn goh_slot(person: String, number: String) -> Int
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn got_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn got_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn got_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn got_slot(person: String, number: String) -> Int
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn grc_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn grc_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn grc_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn grc_str_last_char(s: String) -> String
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn he_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn he_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn he_str_len(s: String) -> Int
|
||||
extern fn he_str_drop_last(s: String, n: Int) -> String
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
// ── String helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn hi_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn hi_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn hi_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn hi_str_last_char(s: String) -> String
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
// Note: this is a heuristic classifier for romanized input. For production use
|
||||
// with native kana/kanji forms, the dictionary form (辞書形) must be consulted.
|
||||
|
||||
import "morphology.el"
|
||||
fn ja_verb_group(dict_form: String) -> String {
|
||||
// Irregular verbs (exact match on dictionary form)
|
||||
if str_eq(dict_form, "する") { return "irregular" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn ja_verb_group(dict_form: String) -> String
|
||||
extern fn ja_ichidan_stem(dict_form: String) -> String
|
||||
extern fn ja_godan_stem_change(dict_form: String, row: String) -> String
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn la_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn la_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn la_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn la_str_last_char(s: String) -> String
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn non_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn non_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn non_drop(s: String, n: Int) -> String
|
||||
extern fn non_last(s: String) -> String
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn peo_drop(s: String, n: Int) -> String {
|
||||
let len: Int = str_len(s)
|
||||
if n >= len { return "" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn peo_drop(s: String, n: Int) -> String
|
||||
extern fn peo_ends(s: String, suf: String) -> Bool
|
||||
extern fn peo_slot(person: String, number: String) -> Int
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn pi_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn pi_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn pi_drop(s: String, n: Int) -> String
|
||||
extern fn pi_last_char(s: String) -> String
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
// The heuristic returns the most probable gender. Caller should override
|
||||
// for known exceptions (путь, рубль are masc despite -ь).
|
||||
|
||||
import "morphology.el"
|
||||
fn ru_gender(noun: String) -> String {
|
||||
let n: Int = str_len(noun)
|
||||
if n == 0 { return "m" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn ru_gender(noun: String) -> String
|
||||
extern fn ru_stem_type(noun: String, gender: String) -> String
|
||||
extern fn ru_noun_case(noun: String, gender: String, gram_case: String, number: String) -> String
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn sa_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn sa_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn sa_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn sa_slot(person: String, number: String) -> Int
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn sga_drop(s: String, n: Int) -> String {
|
||||
let len: Int = str_len(s)
|
||||
if n >= len { return "" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn sga_drop(s: String, n: Int) -> String
|
||||
extern fn sga_first(s: String) -> String
|
||||
extern fn sga_rest(s: String) -> String
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn sux_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn sux_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn sux_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn sux_str_last_char(s: String) -> String
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
// ── String helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn sw_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn sw_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn sw_str_drop_last(s: String, n: Int) -> String
|
||||
extern fn sw_str_first_char(s: String) -> String
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn txb_drop(s: String, n: Int) -> String {
|
||||
let len: Int = str_len(s)
|
||||
if n >= len { return "" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn txb_drop(s: String, n: Int) -> String
|
||||
extern fn txb_ends(s: String, suf: String) -> Bool
|
||||
extern fn txb_slot(person: String, number: String) -> Int
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
|
||||
// ── String helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
import "morphology.el"
|
||||
fn uga_str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn uga_str_ends(s: String, suf: String) -> Bool
|
||||
extern fn uga_str_len(s: String) -> Int
|
||||
extern fn uga_str_drop_last(s: String, n: Int) -> String
|
||||
|
||||
@@ -33,6 +33,17 @@
|
||||
|
||||
// ── String helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
import "language-profile.el"
|
||||
import "morphology-es.el"
|
||||
import "morphology-fr.el"
|
||||
import "morphology-de.el"
|
||||
import "morphology-ru.el"
|
||||
import "morphology-fi.el"
|
||||
import "morphology-ar.el"
|
||||
import "morphology-hi.el"
|
||||
import "morphology-sw.el"
|
||||
import "morphology-la.el"
|
||||
import "morphology-ja.el"
|
||||
fn str_ends(s: String, suf: String) -> Bool {
|
||||
return str_ends_with(s, suf)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn str_ends(s: String, suf: String) -> Bool
|
||||
extern fn str_last_char(s: String) -> String
|
||||
extern fn str_last2(s: String) -> String
|
||||
@@ -8,7 +8,7 @@ extern fn is_vowel(c: String) -> Bool
|
||||
extern fn morph_apply_suffix(base: String, suffix: String) -> String
|
||||
extern fn en_irregular_plural(word: String) -> String
|
||||
extern fn en_irregular_singular(word: String) -> String
|
||||
extern fn en_irregular_verb(base: String) -> Any
|
||||
extern fn en_irregular_verb(base: String) -> [String]
|
||||
extern fn en_verb_3sg(base: String) -> String
|
||||
extern fn en_should_double_final(base: String) -> Bool
|
||||
extern fn en_verb_past(base: String) -> String
|
||||
@@ -16,10 +16,10 @@ extern fn en_verb_gerund(base: String) -> String
|
||||
extern fn en_pluralize_regular(singular: String) -> String
|
||||
extern fn en_verb_form(base: String, tense: String, person: String, number: String) -> String
|
||||
extern fn agree_determiner(det: String, noun: String) -> String
|
||||
extern fn morph_pluralize(noun: String, profile: Any) -> String
|
||||
extern fn morph_pluralize(noun: String, profile: [String]) -> String
|
||||
extern fn morph_map_canonical(verb: String, code: String) -> String
|
||||
extern fn morph_conjugate(verb: String, tense: String, person: String, number: String, profile: Any) -> String
|
||||
extern fn morph_inflect(word: String, features: String, profile: Any) -> String
|
||||
extern fn morph_conjugate(verb: String, tense: String, person: String, number: String, profile: [String]) -> String
|
||||
extern fn morph_inflect(word: String, features: String, profile: [String]) -> String
|
||||
extern fn pluralize(singular: String) -> String
|
||||
extern fn singularize(plural: String) -> String
|
||||
extern fn verb_form(base: String, tense: String, person: String, number: 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
|
||||
|
||||
+19
-19
@@ -1,20 +1,20 @@
|
||||
// auto-generated by elc --emit-header - do not edit
|
||||
extern fn lex_word(entry: Any) -> String
|
||||
extern fn lex_pos(entry: Any) -> String
|
||||
extern fn lex_form(entry: Any, idx: Int) -> String
|
||||
extern fn lex_class(entry: Any) -> String
|
||||
extern fn make_entry(word: String, pos: String, f0: String, f1: String, f2: String, f3: String, f4: String, cls: String) -> Any
|
||||
extern fn make_entry2(word: String, pos: String, f0: String, f1: String, cls: String) -> Any
|
||||
extern fn make_entry3(word: String, pos: String, f0: String, f1: String, f2: String, cls: String) -> Any
|
||||
extern fn make_entry1(word: String, pos: String, f0: String, cls: String) -> Any
|
||||
extern fn build_vocab() -> Any
|
||||
extern fn get_vocab() -> Any
|
||||
extern fn vocab_lookup(word: String, lang_code: String) -> Any
|
||||
extern fn vocab_lookup_en(word: String) -> Any
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn lex_word(entry: [String]) -> String
|
||||
extern fn lex_pos(entry: [String]) -> String
|
||||
extern fn lex_form(entry: [String], idx: Int) -> String
|
||||
extern fn lex_class(entry: [String]) -> String
|
||||
extern fn make_entry(word: String, pos: String, f0: String, f1: String, f2: String, f3: String, f4: String, cls: String) -> [String]
|
||||
extern fn make_entry2(word: String, pos: String, f0: String, f1: String, cls: String) -> [String]
|
||||
extern fn make_entry3(word: String, pos: String, f0: String, f1: String, f2: String, cls: String) -> [String]
|
||||
extern fn make_entry1(word: String, pos: String, f0: String, cls: String) -> [String]
|
||||
extern fn build_vocab() -> [[String]]
|
||||
extern fn get_vocab() -> [[String]]
|
||||
extern fn vocab_lookup(word: String, lang_code: String) -> [String]
|
||||
extern fn vocab_lookup_en(word: String) -> [String]
|
||||
extern fn vocab_synonym(word: String, lang_register: String, lang_code: String) -> String
|
||||
extern fn vocab_by_pos(pos: String) -> Any
|
||||
extern fn vocab_by_class(cls: String) -> Any
|
||||
extern fn entry_found(entry: Any) -> Bool
|
||||
extern fn entry_word(entry: Any) -> String
|
||||
extern fn entry_pos(entry: Any) -> String
|
||||
extern fn entry_form(entry: Any, n: Int) -> String
|
||||
extern fn vocab_by_pos(pos: String) -> [[String]]
|
||||
extern fn vocab_by_class(cls: String) -> [[String]]
|
||||
extern fn entry_found(entry: [String]) -> Bool
|
||||
extern fn entry_word(entry: [String]) -> String
|
||||
extern fn entry_pos(entry: [String]) -> String
|
||||
extern fn entry_form(entry: [String], n: Int) -> String
|
||||
|
||||
BIN
Binary file not shown.
Vendored
+260
-75
@@ -10,6 +10,9 @@ el_val_t query_param(el_val_t path, el_val_t key);
|
||||
el_val_t query_int(el_val_t path, el_val_t key, el_val_t default_val);
|
||||
el_val_t extract_id(el_val_t path, el_val_t prefix);
|
||||
el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_act_stats(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_text_health(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t persist_canonical(void);
|
||||
el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_get_node(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_scan_nodes(el_val_t method, el_val_t path, el_val_t body);
|
||||
@@ -17,19 +20,29 @@ el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_search(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_create_edges_batch(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_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);
|
||||
el_val_t route_embed_backfill(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_load_merge(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_emit_ise(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_capture_knowledge(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t route_similarity(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t check_auth_ok(el_val_t method, el_val_t body);
|
||||
el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body);
|
||||
|
||||
el_val_t bind_raw;
|
||||
el_val_t bind_str;
|
||||
el_val_t port;
|
||||
el_val_t data_dir_raw;
|
||||
el_val_t data_dir;
|
||||
el_val_t snapshot_path;
|
||||
el_val_t boot_snap;
|
||||
|
||||
el_val_t parse_port(el_val_t bind) {
|
||||
el_val_t colon = str_index_of(bind, EL_STR(":"));
|
||||
@@ -108,17 +121,40 @@ el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_act_stats(el_val_t method, el_val_t path, el_val_t body) {
|
||||
return engram_act_stats_json();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_text_health(el_val_t method, el_val_t path, el_val_t body) {
|
||||
return engram_text_health_json();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t persist_canonical(void) {
|
||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
el_val_t dir = ({ el_val_t _if_result_1 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_1 = (EL_STR("/tmp/engram")); } else { _if_result_1 = (dir_raw); } _if_result_1; });
|
||||
return engram_save(el_str_concat(dir, EL_STR("/snapshot.json")));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t content = json_get_string(body, EL_STR("content"));
|
||||
el_val_t node_type = json_get_string(body, EL_STR("node_type"));
|
||||
if (str_eq(node_type, EL_STR(""))) {
|
||||
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))) {
|
||||
salience = el_from_float(0.5);
|
||||
}
|
||||
el_val_t id = engram_node(content, node_type, salience);
|
||||
el_val_t nt_raw = json_get_string(body, EL_STR("node_type"));
|
||||
el_val_t node_type = ({ el_val_t _if_result_2 = 0; if (str_eq(nt_raw, EL_STR(""))) { _if_result_2 = (EL_STR("Memory")); } else { _if_result_2 = (nt_raw); } _if_result_2; });
|
||||
el_val_t sal_present = json_get_raw(body, EL_STR("salience"));
|
||||
el_val_t salience = ({ el_val_t _if_result_3 = 0; if (str_eq(sal_present, EL_STR(""))) { _if_result_3 = (el_from_float(0.5)); } else { _if_result_3 = (json_get_float(body, EL_STR("salience"))); } _if_result_3; });
|
||||
el_val_t label_raw = json_get_string(body, EL_STR("label"));
|
||||
el_val_t label = ({ el_val_t _if_result_4 = 0; if (str_eq(label_raw, EL_STR(""))) { _if_result_4 = (content); } else { _if_result_4 = (label_raw); } _if_result_4; });
|
||||
el_val_t imp_present = json_get_raw(body, EL_STR("importance"));
|
||||
el_val_t importance = ({ el_val_t _if_result_5 = 0; if (str_eq(imp_present, EL_STR(""))) { _if_result_5 = (el_from_float(0.5)); } else { _if_result_5 = (json_get_float(body, EL_STR("importance"))); } _if_result_5; });
|
||||
el_val_t conf_present = json_get_raw(body, EL_STR("confidence"));
|
||||
el_val_t confidence = ({ el_val_t _if_result_6 = 0; if (str_eq(conf_present, EL_STR(""))) { _if_result_6 = (el_from_float(1.0)); } else { _if_result_6 = (json_get_float(body, EL_STR("confidence"))); } _if_result_6; });
|
||||
el_val_t tier_raw = json_get_string(body, EL_STR("tier"));
|
||||
el_val_t tier = ({ el_val_t _if_result_7 = 0; if (str_eq(tier_raw, EL_STR(""))) { _if_result_7 = (EL_STR("Working")); } else { _if_result_7 = (tier_raw); } _if_result_7; });
|
||||
el_val_t tags = json_get_string(body, EL_STR("tags"));
|
||||
el_val_t id = engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags);
|
||||
el_val_t saved = persist_canonical();
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"content\":\"")), content), EL_STR("\",\"node_type\":\"")), node_type), EL_STR("\"}"));
|
||||
return 0;
|
||||
}
|
||||
@@ -144,11 +180,9 @@ el_val_t route_scan_nodes(el_val_t method, el_val_t path, el_val_t body) {
|
||||
}
|
||||
|
||||
el_val_t route_scan_edges(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("/snapshot.json"));
|
||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
el_val_t dir = ({ el_val_t _if_result_8 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_8 = (EL_STR("/tmp/engram")); } else { _if_result_8 = (dir_raw); } _if_result_8; });
|
||||
el_val_t snap_path = el_str_concat(dir, EL_STR("/.scan-export.json"));
|
||||
engram_save(snap_path);
|
||||
el_val_t snap = fs_read(snap_path);
|
||||
if (str_eq(snap, EL_STR(""))) {
|
||||
@@ -163,36 +197,22 @@ el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body) {
|
||||
}
|
||||
|
||||
el_val_t route_search(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t q = EL_STR("");
|
||||
if (str_eq(method, EL_STR("GET"))) {
|
||||
q = query_param(path, EL_STR("q"));
|
||||
} else {
|
||||
q = json_get_string(body, EL_STR("query"));
|
||||
}
|
||||
el_val_t limit = query_int(path, EL_STR("limit"), 20);
|
||||
if (limit == 0) {
|
||||
limit = json_get_int(body, EL_STR("limit"));
|
||||
}
|
||||
if (limit == 0) {
|
||||
limit = 20;
|
||||
}
|
||||
el_val_t q = ({ el_val_t _if_result_9 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_9 = (query_param(path, EL_STR("q"))); } else { _if_result_9 = (json_get_string(body, EL_STR("query"))); } _if_result_9; });
|
||||
el_val_t lim_url = query_int(path, EL_STR("limit"), 0);
|
||||
el_val_t lim_body = json_get_int(body, EL_STR("limit"));
|
||||
el_val_t lim_either = ({ el_val_t _if_result_10 = 0; if ((lim_url > 0)) { _if_result_10 = (lim_url); } else { _if_result_10 = (lim_body); } _if_result_10; });
|
||||
el_val_t limit = ({ el_val_t _if_result_11 = 0; if ((lim_either > 0)) { _if_result_11 = (lim_either); } else { _if_result_11 = (20); } _if_result_11; });
|
||||
return engram_search_json(q, limit);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t q = EL_STR("");
|
||||
el_val_t depth = 3;
|
||||
if (str_eq(method, EL_STR("GET"))) {
|
||||
q = query_param(path, EL_STR("q"));
|
||||
depth = query_int(path, EL_STR("depth"), 3);
|
||||
} else {
|
||||
q = json_get_string(body, EL_STR("query"));
|
||||
el_val_t bd = json_get_int(body, EL_STR("depth"));
|
||||
if (bd > 0) {
|
||||
depth = bd;
|
||||
}
|
||||
el_val_t q = ({ el_val_t _if_result_12 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_12 = (query_param(path, EL_STR("q"))); } else { _if_result_12 = (json_get_string(body, EL_STR("query"))); } _if_result_12; });
|
||||
if (str_eq(q, EL_STR(""))) {
|
||||
return err_json(EL_STR("missing query"));
|
||||
}
|
||||
el_val_t d_raw = ({ el_val_t _if_result_13 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_13 = (query_int(path, EL_STR("depth"), 3)); } else { _if_result_13 = (json_get_int(body, EL_STR("depth"))); } _if_result_13; });
|
||||
el_val_t depth = ({ el_val_t _if_result_14 = 0; if ((d_raw > 0)) { _if_result_14 = (d_raw); } else { _if_result_14 = (3); } _if_result_14; });
|
||||
return el_str_concat(el_str_concat(EL_STR("{\"results\":"), engram_activate_json(q, depth)), EL_STR("}"));
|
||||
return 0;
|
||||
}
|
||||
@@ -200,19 +220,51 @@ el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t from_id = json_get_string(body, EL_STR("from_id"));
|
||||
el_val_t to_id = json_get_string(body, EL_STR("to_id"));
|
||||
el_val_t relation = json_get_string(body, EL_STR("relation"));
|
||||
if (str_eq(relation, EL_STR(""))) {
|
||||
relation = EL_STR("associates");
|
||||
}
|
||||
el_val_t weight = json_get_float(body, EL_STR("weight"));
|
||||
if (str_eq(weight, el_from_float(0.0))) {
|
||||
weight = el_from_float(0.5);
|
||||
}
|
||||
el_val_t rel_raw = json_get_string(body, EL_STR("relation"));
|
||||
el_val_t relation = ({ el_val_t _if_result_15 = 0; if (str_eq(rel_raw, EL_STR(""))) { _if_result_15 = (EL_STR("associates")); } else { _if_result_15 = (rel_raw); } _if_result_15; });
|
||||
el_val_t w_present = json_get_raw(body, EL_STR("weight"));
|
||||
el_val_t weight = ({ el_val_t _if_result_16 = 0; if (str_eq(w_present, EL_STR(""))) { _if_result_16 = (el_from_float(0.5)); } else { _if_result_16 = (json_get_float(body, EL_STR("weight"))); } _if_result_16; });
|
||||
engram_connect(from_id, to_id, weight, relation);
|
||||
el_val_t saved = persist_canonical();
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), relation), EL_STR("\"}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_create_edges_batch(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t arr = json_get_raw(body, EL_STR("edges"));
|
||||
if (str_eq(arr, EL_STR(""))) {
|
||||
return err_json(EL_STR("missing edges array"));
|
||||
}
|
||||
el_val_t n = json_array_len(arr);
|
||||
if (n == 0) {
|
||||
return EL_STR("{\"ok\":true,\"accepted\":0,\"skipped\":0}");
|
||||
}
|
||||
el_val_t i = 0;
|
||||
el_val_t accepted = 0;
|
||||
el_val_t skipped = 0;
|
||||
while (i < n) {
|
||||
el_val_t item = json_array_get(arr, i);
|
||||
el_val_t from_id = json_get_string(item, EL_STR("from_id"));
|
||||
el_val_t to_id = json_get_string(item, EL_STR("to_id"));
|
||||
if (str_eq(from_id, EL_STR("")) || str_eq(to_id, EL_STR(""))) {
|
||||
skipped = (skipped + 1);
|
||||
} else {
|
||||
el_val_t rel_raw = json_get_string(item, EL_STR("relation"));
|
||||
el_val_t relation = ({ el_val_t _if_result_17 = 0; if (str_eq(rel_raw, EL_STR(""))) { _if_result_17 = (EL_STR("associates")); } else { _if_result_17 = (rel_raw); } _if_result_17; });
|
||||
el_val_t w_present = json_get_raw(item, EL_STR("weight"));
|
||||
el_val_t weight = ({ el_val_t _if_result_18 = 0; if (str_eq(w_present, EL_STR(""))) { _if_result_18 = (el_from_float(0.5)); } else { _if_result_18 = (json_get_float(item, EL_STR("weight"))); } _if_result_18; });
|
||||
engram_connect(from_id, to_id, weight, relation);
|
||||
accepted = (accepted + 1);
|
||||
}
|
||||
i = (i + 1);
|
||||
}
|
||||
if (accepted > 0) {
|
||||
el_val_t saved = persist_canonical();
|
||||
}
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"accepted\":"), int_to_str(accepted)), EL_STR(",\"skipped\":")), int_to_str(skipped)), EL_STR("}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t id = extract_id(path, EL_STR("/api/neighbors/"));
|
||||
if (str_eq(id, EL_STR(""))) {
|
||||
@@ -229,6 +281,7 @@ el_val_t route_strengthen(el_val_t method, el_val_t path, el_val_t body) {
|
||||
return err_json(EL_STR("missing node_id"));
|
||||
}
|
||||
engram_strengthen(id);
|
||||
el_val_t saved = persist_canonical();
|
||||
return ok_json();
|
||||
return 0;
|
||||
}
|
||||
@@ -239,40 +292,140 @@ el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body) {
|
||||
return err_json(EL_STR("missing id"));
|
||||
}
|
||||
engram_forget(id);
|
||||
el_val_t saved = persist_canonical();
|
||||
return ok_json();
|
||||
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(""))) {
|
||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
if (str_eq(dir, EL_STR(""))) {
|
||||
dir = EL_STR("/tmp/engram");
|
||||
}
|
||||
p = el_str_concat(dir, EL_STR("/snapshot.json"));
|
||||
}
|
||||
engram_save(p);
|
||||
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"path\":\""), p), EL_STR("\"}"));
|
||||
el_val_t p_raw = json_get_string(body, EL_STR("path"));
|
||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
el_val_t dir = ({ el_val_t _if_result_19 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_19 = (EL_STR("/tmp/engram")); } else { _if_result_19 = (dir_raw); } _if_result_19; });
|
||||
el_val_t p = ({ el_val_t _if_result_20 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_20 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_20 = (p_raw); } _if_result_20; });
|
||||
el_val_t sv = engram_save(p);
|
||||
el_val_t sv_ok = ({ el_val_t _if_result_21 = 0; if ((sv == 0)) { _if_result_21 = (EL_STR("false")); } else { _if_result_21 = (EL_STR("true")); } _if_result_21; });
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":"), sv_ok), EL_STR(",\"path\":\"")), p), EL_STR("\",\"node_count\":")), int_to_str(engram_node_count())), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR("}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_load(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(""))) {
|
||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
if (str_eq(dir, EL_STR(""))) {
|
||||
dir = EL_STR("/tmp/engram");
|
||||
}
|
||||
p = el_str_concat(dir, EL_STR("/snapshot.json"));
|
||||
}
|
||||
engram_load(p);
|
||||
return ok_json();
|
||||
el_val_t p_raw = json_get_string(body, EL_STR("path"));
|
||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
el_val_t dir = ({ el_val_t _if_result_22 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_22 = (EL_STR("/tmp/engram")); } else { _if_result_22 = (dir_raw); } _if_result_22; });
|
||||
el_val_t p = ({ el_val_t _if_result_23 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_23 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_23 = (p_raw); } _if_result_23; });
|
||||
el_val_t ld = engram_load(p);
|
||||
el_val_t ld_ok = ({ el_val_t _if_result_24 = 0; if ((ld == 0)) { _if_result_24 = (EL_STR("false")); } else { _if_result_24 = (EL_STR("true")); } _if_result_24; });
|
||||
el_val_t nc_after = engram_node_count();
|
||||
el_val_t hollow = ({ el_val_t _if_result_25 = 0; if ((nc_after == 0)) { _if_result_25 = (EL_STR("true")); } else { _if_result_25 = (EL_STR("false")); } _if_result_25; });
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":"), ld_ok), EL_STR(",\"path\":\"")), p), EL_STR("\",\"node_count\":")), int_to_str(nc_after)), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR(",\"hollow\":")), hollow), EL_STR("}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body) {
|
||||
return EL_STR("{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}");
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"status\":\"ok\",\"engine\":\"engram-runtime-native\",\"node_count\":"), int_to_str(engram_node_count())), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR("}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_embed_backfill(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t n = query_int(path, EL_STR("n"), 32);
|
||||
el_val_t result = engram_embed_backfill(n);
|
||||
el_val_t done = json_get_float(result, EL_STR("embedded"));
|
||||
if (done > el_from_float(0.0)) {
|
||||
el_val_t saved = persist_canonical();
|
||||
}
|
||||
return result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
el_val_t dir = ({ el_val_t _if_result_26 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_26 = (EL_STR("/tmp/engram")); } else { _if_result_26 = (dir_raw); } _if_result_26; });
|
||||
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 err_json(EL_STR("sync export failed: snapshot unreadable"));
|
||||
}
|
||||
return snap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_load_merge(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(""))) {
|
||||
return err_json(EL_STR("path is required"));
|
||||
}
|
||||
if (str_eq(fs_read(p), EL_STR(""))) {
|
||||
return err_json(EL_STR("file missing or empty"));
|
||||
}
|
||||
el_val_t before_n = engram_node_count();
|
||||
el_val_t before_e = engram_edge_count();
|
||||
engram_load_merge(p);
|
||||
el_val_t added_n = (engram_node_count() - before_n);
|
||||
el_val_t added_e = (engram_edge_count() - before_e);
|
||||
el_val_t saved = persist_canonical();
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"nodes_added\":"), int_to_str(added_n)), EL_STR(",\"edges_added\":")), int_to_str(added_e)), EL_STR(",\"node_count\":")), int_to_str(engram_node_count())), EL_STR("}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_emit_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\"]"));
|
||||
el_val_t ret_raw = env(EL_STR("ENGRAM_ISE_RETENTION_MS"));
|
||||
el_val_t ret_ms = ({ el_val_t _if_result_27 = 0; if (str_eq(ret_raw, EL_STR(""))) { _if_result_27 = (172800000); } else { _if_result_27 = (str_to_int(ret_raw)); } _if_result_27; });
|
||||
el_val_t pruned = engram_prune_telemetry(ret_ms);
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\",\"pruned\":")), int_to_str(pruned)), EL_STR("}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_capture_knowledge(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 title = json_get_string(body, EL_STR("title"));
|
||||
el_val_t label = ({ el_val_t _if_result_28 = 0; if (str_eq(title, EL_STR(""))) { _if_result_28 = (str_slice(content, 0, 60)); } else { _if_result_28 = (title); } _if_result_28; });
|
||||
el_val_t category_raw = json_get_string(body, EL_STR("category"));
|
||||
el_val_t category = ({ el_val_t _if_result_29 = 0; if (str_eq(category_raw, EL_STR(""))) { _if_result_29 = (EL_STR("other")); } else { _if_result_29 = (category_raw); } _if_result_29; });
|
||||
el_val_t ktier_raw = json_get_string(body, EL_STR("tier"));
|
||||
el_val_t ktier = ({ el_val_t _if_result_30 = 0; if (str_eq(ktier_raw, EL_STR(""))) { _if_result_30 = (EL_STR("note")); } else { _if_result_30 = (ktier_raw); } _if_result_30; });
|
||||
el_val_t project = json_get_string(body, EL_STR("project"));
|
||||
el_val_t tags_raw = json_get_raw(body, EL_STR("tags"));
|
||||
el_val_t tags_base = ({ el_val_t _if_result_31 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_31 = (EL_STR("[]")); } else { _if_result_31 = (tags_raw); } _if_result_31; });
|
||||
el_val_t base_len = str_len(tags_base);
|
||||
el_val_t head = str_slice(tags_base, 0, (base_len - 1));
|
||||
el_val_t sep = ({ el_val_t _if_result_32 = 0; if (str_eq(head, EL_STR("["))) { _if_result_32 = (EL_STR("")); } else { _if_result_32 = (EL_STR(",")); } _if_result_32; });
|
||||
el_val_t safe_cat = str_replace(category, EL_STR("\""), EL_STR("'"));
|
||||
el_val_t safe_tier = str_replace(ktier, EL_STR("\""), EL_STR("'"));
|
||||
el_val_t safe_proj = str_replace(project, EL_STR("\""), EL_STR("'"));
|
||||
el_val_t proj_tag = ({ el_val_t _if_result_33 = 0; if (str_eq(safe_proj, EL_STR(""))) { _if_result_33 = (EL_STR("")); } else { _if_result_33 = (el_str_concat(el_str_concat(EL_STR(",\"project:"), safe_proj), EL_STR("\""))); } _if_result_33; });
|
||||
el_val_t tags = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(head, sep), EL_STR("\"category:")), safe_cat), EL_STR("\",\"tier:")), safe_tier), EL_STR("\"")), proj_tag), EL_STR("]"));
|
||||
el_val_t sal = el_from_float(0.5);
|
||||
el_val_t imp = el_from_float(0.5);
|
||||
el_val_t conf = el_from_float(0.9);
|
||||
el_val_t id = engram_node_full(content, EL_STR("Knowledge"), label, sal, imp, conf, EL_STR("Semantic"), tags);
|
||||
el_val_t saved = persist_canonical();
|
||||
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t route_similarity(el_val_t method, el_val_t path, el_val_t body) {
|
||||
el_val_t a = query_param(path, EL_STR("a"));
|
||||
el_val_t b = query_param(path, EL_STR("b"));
|
||||
if (str_eq(a, EL_STR(""))) {
|
||||
return err_json(EL_STR("missing a"));
|
||||
}
|
||||
if (str_eq(b, EL_STR(""))) {
|
||||
return err_json(EL_STR("missing b"));
|
||||
}
|
||||
el_val_t sim = engram_cosine_sim(a, b);
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"a\":\""), a), EL_STR("\",\"b\":\"")), b), EL_STR("\",\"cosine\":")), float_to_str(sim)), EL_STR("}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -299,12 +452,24 @@ 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_eq(clean, EL_STR("/api/neuron/state-events"))) {
|
||||
return route_emit_ise(method, path, body);
|
||||
}
|
||||
if (!check_auth_ok(method, body)) {
|
||||
return err_json(EL_STR("unauthorized"));
|
||||
}
|
||||
if (str_eq(method, EL_STR("POST")) && str_eq(clean, EL_STR("/api/neuron/knowledge/capture"))) {
|
||||
return route_capture_knowledge(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/stats")) || str_eq(clean, EL_STR("/stats")))) {
|
||||
return route_stats(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/act-stats")) || str_eq(clean, EL_STR("/act-stats")))) {
|
||||
return route_act_stats(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/text-health")) || str_eq(clean, EL_STR("/text-health")))) {
|
||||
return route_text_health(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/nodes")) || str_eq(clean, EL_STR("/nodes")))) {
|
||||
return route_create_node(method, path, body);
|
||||
}
|
||||
@@ -323,6 +488,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/edges")) || str_eq(clean, EL_STR("/edges")))) {
|
||||
return route_create_edge(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/edges/batch")) || str_eq(clean, EL_STR("/edges/batch")))) {
|
||||
return route_create_edges_batch(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("GET")) && str_starts_with(clean, EL_STR("/api/neighbors/"))) {
|
||||
return route_neighbors(method, path, body);
|
||||
}
|
||||
@@ -347,23 +515,40 @@ 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/load")) || str_eq(clean, EL_STR("/load")))) {
|
||||
return route_load(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/load-merge")) || str_eq(clean, EL_STR("/load-merge")))) {
|
||||
return route_load_merge(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("GET")) && str_eq(clean, EL_STR("/api/sync"))) {
|
||||
return route_sync(method, path, body);
|
||||
}
|
||||
if (str_eq(clean, EL_STR("/api/embed-backfill"))) {
|
||||
return route_embed_backfill(method, path, body);
|
||||
}
|
||||
if (str_eq(method, EL_STR("GET")) && str_starts_with(clean, EL_STR("/api/similarity"))) {
|
||||
return route_similarity(method, path, body);
|
||||
}
|
||||
return el_str_concat(el_str_concat(EL_STR("{\"error\":\"not found\",\"path\":\""), clean), EL_STR("\"}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
bind_str = env(EL_STR("ENGRAM_BIND"));
|
||||
if (str_eq(bind_str, EL_STR(""))) {
|
||||
bind_str = EL_STR(":8742");
|
||||
}
|
||||
bind_raw = env(EL_STR("ENGRAM_BIND"));
|
||||
bind_str = ({ el_val_t _if_result_34 = 0; if (str_eq(bind_raw, EL_STR(""))) { _if_result_34 = (EL_STR(":8742")); } else { _if_result_34 = (bind_raw); } _if_result_34; });
|
||||
port = parse_port(bind_str);
|
||||
data_dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
if (str_eq(data_dir, EL_STR(""))) {
|
||||
data_dir = EL_STR("/tmp/engram");
|
||||
}
|
||||
data_dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||
data_dir = ({ el_val_t _if_result_35 = 0; if (str_eq(data_dir_raw, EL_STR(""))) { _if_result_35 = (EL_STR("/tmp/engram")); } else { _if_result_35 = (data_dir_raw); } _if_result_35; });
|
||||
snapshot_path = el_str_concat(data_dir, EL_STR("/snapshot.json"));
|
||||
engram_load(snapshot_path);
|
||||
boot_snap = fs_read(snapshot_path);
|
||||
if (!str_eq(boot_snap, EL_STR(""))) {
|
||||
if (engram_node_count() == 0) {
|
||||
println(EL_STR("[engram] WARNING: snapshot.json is non-empty but load produced 0 nodes \xe2\x80\x94 preserving copy at snapshot.failed-load.json"));
|
||||
fs_write(el_str_concat(data_dir, EL_STR("/snapshot.failed-load.json")), boot_snap);
|
||||
} else {
|
||||
fs_write(el_str_concat(data_dir, EL_STR("/snapshot.boot-backup.json")), boot_snap);
|
||||
}
|
||||
}
|
||||
println(EL_STR("[engram] runtime-native graph engine"));
|
||||
println(el_str_concat(EL_STR("[engram] data_dir="), data_dir));
|
||||
println(el_str_concat(EL_STR("[engram] node_count="), int_to_str(engram_node_count())));
|
||||
|
||||
@@ -0,0 +1,605 @@
|
||||
# Cognitive Architecture — Design Doc
|
||||
|
||||
**The buildable form of the "one operation" theory of cognition.**
|
||||
|
||||
Status: DESIGN. Nothing here is built yet except where explicitly marked
|
||||
"EXISTS" against a cited C symbol. A build agent executes from this doc.
|
||||
Offline design only — this pass changes no code.
|
||||
|
||||
Source of theory: Neuron memory `bdc8a488-146d-4ccb-a5c8-d8c0a008534e`.
|
||||
Source of existing engram substrate (cited throughout): the runtime on branch
|
||||
`feat/self-reification-20260814` —
|
||||
`lang/runtime/engram_reason.{c,h}`, `engram_verify.{c,h}`,
|
||||
`engram_geometry.{c,h}`, `engram_store.{c,h}`, plus the reification beat and the
|
||||
RAM activation graph compiled into `~/.neuron/bin/engram`.
|
||||
|
||||
---
|
||||
|
||||
## 0. The claim, stated plainly
|
||||
|
||||
Cognition is **one operation**, not eight. The named faculties —
|
||||
deduce / abduce / analogy / induce / causal / plan / predict / perspective —
|
||||
are human *labels* on regions of a single operation's steering space. They are
|
||||
not separately invoked and not separately implemented. The operation is:
|
||||
|
||||
> **think** = a directed traversal of the geometry from an *anchor*, steered by
|
||||
> a *prior*, whose output is a **gradient** (a distribution / direction over the
|
||||
> geometry), never a point. Collapse-to-a-point happens only at expression.
|
||||
|
||||
Three things follow, and they are the whole design:
|
||||
|
||||
1. **The operator collapse is already half-written in C.** The five reasoning
|
||||
operators in `engram_reason.c` already compose over *one* shared primitive —
|
||||
`engram_reason_point_fit` — plus a small geo-algebra
|
||||
(combine / subtract / analogy-rotate / distance). The verifier
|
||||
(`engram_verify.c`) is built on the same `point_fit`. What is missing is not
|
||||
the primitive; it is (a) making the *prior* a first-class learnable object
|
||||
instead of a hard-coded parameter, and (b) closing the learning loop.
|
||||
|
||||
2. **Grounding = learning = the same loop.** "Getting better" at any faculty is
|
||||
not changing the operation. It is *calibrating the steering-prior against
|
||||
outcomes*. Code freezes; priors grow. The correspondence-check that today
|
||||
lives offline (Python, the grounding-floor + differential-drop governor, "#43")
|
||||
must move **into the geometry, reflexive** — think scoring its own gradient
|
||||
against outcome and refining the prior on the error. That reflexive
|
||||
correspondence-loop *is* the learning engine and is the core unbuilt thing.
|
||||
|
||||
3. **The ungrounded is primary.** The engram *holds* anything unconditionally.
|
||||
Grounding is a *relation* (an edge, grounded-for-whom), not a gate. The
|
||||
honesty floor applies only to **assertion**. A fully-grounded mind is dead;
|
||||
the ungrounded is both the fuel (raw material for grounding) and the pull
|
||||
(curiosity = leaning toward one's own ungrounded regions).
|
||||
|
||||
Everything below makes these concrete and buildable, and defines what
|
||||
"completion" means, staged so the first milestone is a real end-to-end slice.
|
||||
|
||||
---
|
||||
|
||||
## 1. THE ONE OPERATION — `think`
|
||||
|
||||
### 1.1 Signature
|
||||
|
||||
```
|
||||
think(anchor, prior, aperture?) -> gradient
|
||||
```
|
||||
|
||||
- **anchor** — a location to traverse *from*. Either a node id (re-origin on that
|
||||
node's descriptor) or a raw point `x ∈ R^dim` (a query embedding). The anchor
|
||||
fixes the frame; every read is *from a vantage*, never view-from-nowhere.
|
||||
- **prior** — a learnable bias/direction over the geometry that *steers* the
|
||||
traversal (§2). A prior is a first-class stored object, not a call argument
|
||||
baked into C.
|
||||
- **aperture** — optional read-width / veil / field-selector (§3). Absent =
|
||||
self-mode full aperture.
|
||||
- **gradient** — the output. A `GeoGradient`: a direction + a spread over the
|
||||
geometry, *plus* the read neighborhood it was computed against. Not a point.
|
||||
A spiked gradient = "exact" (deduction); a spread gradient = "fuzzy"
|
||||
(prediction). The gradient is *also the next steering direction* — cognition
|
||||
is a flow down a prior-shaped landscape, closed-loop.
|
||||
|
||||
```c
|
||||
/* NEW. The output type. */
|
||||
typedef struct {
|
||||
int dim;
|
||||
float* direction; /* unit steering vector in the anchor's frame */
|
||||
double spread; /* 0 = spiked/exact ... large = diffuse/fuzzy */
|
||||
double confidence; /* calibrated, from the prior's track record */
|
||||
/* the read it was computed over (borrowed from the vantage-read) */
|
||||
const char* anchor_id;
|
||||
int n_support; /* neighborhood members that shaped it */
|
||||
/* provenance for the reflexive loop (§4) */
|
||||
const char* prior_id; /* which prior steered this */
|
||||
} GeoGradient;
|
||||
```
|
||||
|
||||
### 1.2 Semantics
|
||||
|
||||
`think` is a fixed, frozen procedure over three steps:
|
||||
|
||||
1. **Re-origin** on `anchor` → a centered `GeoDescriptor` for its
|
||||
salience/recency-weighted neighborhood (the vantage-read, §3).
|
||||
*EXISTS as substrate:* descriptor construction + the persisted reified
|
||||
neighborhoods (`engram_geo_reify_lookup`, `GeoNeighborhood`) and the
|
||||
centered-frame machinery (`GeoDescriptor.global_mean`,
|
||||
`engram_geo_mean_*`).
|
||||
2. **Fit under the prior** — evaluate the anchor's residual against the local
|
||||
manifold *warped by the prior*. This is `engram_reason_point_fit` with the
|
||||
prior applied to the axes/extents (§2.3).
|
||||
*EXISTS (unwarped):* `engram_reason_point_fit(g, x, ext_floor, &GeoFit)` —
|
||||
returns `mahalanobis`, `ortho_residual`, `distance`, `score`.
|
||||
3. **Emit a gradient**, not a decision — direction = the prior-steered descent
|
||||
in fit-space; spread = from the fit's `distance`/`ortho_residual`;
|
||||
confidence = the prior's calibrated reliability (§4). Collapse to a point is
|
||||
a *separate, downstream* faculty operation (sample the gradient → surface an
|
||||
expression), never part of `think`.
|
||||
|
||||
### 1.3 Each named operator = {this primitive + a prior}
|
||||
|
||||
The C already demonstrates the collapse: every operator below reduces to
|
||||
`point_fit` + geo-algebra. The design's move is to replace the operator's
|
||||
*hard-coded parameters* with a **named prior** — same math, learnable steering.
|
||||
|
||||
| Faculty | Existing C (EXISTS) | = primitive + prior |
|
||||
|---|---|---|
|
||||
| **Membership / classify** | `engram_reason_membership` → `point_fit(rule, x)` | `point_fit` + the *induced-rule* prior (learned extents) |
|
||||
| **Induction** | `engram_reason_induce` (fold via `engram_geo_combine`) → produces a `GeoInduction.rule` + `ext_floor` | `point_fit` + a prior that *is* the pooled rule; refined by §4 |
|
||||
| **Abduction** | `engram_reason_abduce` — ranks hypotheses by `point_fit(h, obs)` | `point_fit` + a prior over hypothesis-prior-probability (currently uniform) |
|
||||
| **Analogy** | `engram_reason_analogy` — Procrustes rotate `engram_geo_analogy` + `apply`, nearest mapped point | analogy-rotate + a prior over *which axes* carry the mapping |
|
||||
| **Causal** | `engram_reason_causal` — `engram_geo_subtract` confounder subspace, `|cos|`, drop-frac governor | subtract/distance + a prior on `drop_frac` / `assoc_floor` (today hard-coded 0.5 / 0.2) |
|
||||
| **Planning** | `engram_reason_plan` — `engram_geo_distance` edges + Dijkstra | distance + a prior over edge admissibility / `neighbor_radius` |
|
||||
| **Verify / ground** | `engram_verify_grounding`, `engram_verify_consistency` — both `point_fit` | `point_fit` + the *grounding* prior (§4, §5) |
|
||||
|
||||
The shared floor — `engram_reason_point_fit` + the four geo-algebra ops
|
||||
(`engram_geo_combine`, `engram_geo_subtract`, `engram_geo_analogy(+apply)`,
|
||||
`engram_geo_distance`) — is the *only* discrete, frozen, "sound-math" layer. It
|
||||
never learns. Everything above it is a *prior*, and priors are what learn.
|
||||
|
||||
**What this section requires building:** the `GeoGradient` type; a `think()`
|
||||
entry point that runs steps 1–3; and the prior-warp hook in step 2. The math it
|
||||
calls already exists. The point-collapse must be *removed* from the operators'
|
||||
return values and pushed to a separate expression faculty.
|
||||
|
||||
---
|
||||
|
||||
## 2. PRIORS as first-class, grounded, geometric objects
|
||||
|
||||
Today a "prior" is diffuse: it is a hard-coded constant (`drop_frac=0.5`,
|
||||
`ext_floor`, `assoc_floor=0.2`), or the transient `GeoInduction.rule` that is
|
||||
computed and thrown away, or an intrinsic node scalar
|
||||
(`StoreNode.importance`, `StoreNode.salience`). None of these is addressable,
|
||||
storable, refinable, or shareable. This section makes a prior a **thing**.
|
||||
|
||||
### 2.1 What a prior *is*
|
||||
|
||||
> A **prior** is a learnable bias/direction over the geometry: a warp of the
|
||||
> local manifold (which axes matter, how far each extends, which direction
|
||||
> "pays off") attached to a region and *to a faculty-label*, carrying a
|
||||
> calibrated track record.
|
||||
|
||||
Critically, and per the theory:
|
||||
|
||||
- **Edges are nodes.** A prior is stored as a first-class **node**, exactly as
|
||||
reification already stores a neighborhood as a first-class `Neighborhood`
|
||||
node rather than as ephemeral edge weights (`engram_geo_reify_store`). The
|
||||
precedent is in the codebase: relations get reified into addressable records.
|
||||
- **Salience/importance is RELATIONAL, not an intrinsic scalar.** Observe that
|
||||
the geometry layer *already* distinguishes these in `GeoMember`:
|
||||
`centrality` (skeleton weighted-degree = *relational* salience) vs `salience`
|
||||
(the node's own stored scalar). The move is half-made in the runtime already:
|
||||
importance is *not* trusted as a static field — the comment at
|
||||
`el_runtime.c:13013` states "importance stays a **live activation
|
||||
computation**, never a field on the hub," and it is derived each call from the
|
||||
two-layer activation graph (`background_activation` + `working_memory_weight`,
|
||||
§3). The persistent `StoreNode.importance` / `.salience` are a *cached
|
||||
denormalization*. The design completes the move: importance/salience become an
|
||||
**edge** (`weight`/`hebb` on `StoreEdge`, relation `salient-to`), and are
|
||||
**grounded-for-whom** — carried on the edge's endpoint/observer, not baked
|
||||
into the node. The intrinsic scalar survives only as the cheap cached readout
|
||||
of the incident edges + activation, never as the source of truth.
|
||||
|
||||
(Naming caution for the build: the token "prior" already exists in the
|
||||
codebase meaning *previous-version* — supersession, "prior neighborhood." The
|
||||
new first-class object is a **learned steering prior**; keep `node_type="Prior"`
|
||||
distinct from the supersession vocabulary to avoid collision.)
|
||||
|
||||
### 2.2 Representation
|
||||
|
||||
A prior is a `Prior` record (a store node, `node_type="Prior"`) whose durable
|
||||
fields are:
|
||||
|
||||
```
|
||||
Prior {
|
||||
id
|
||||
faculty // the human label this prior serves: "induce" | "causal" | ...
|
||||
anchor_region // node id / neighborhood id this prior is attached to (its domain)
|
||||
for_whom // observer id — grounding is relational (nullable = global)
|
||||
warp { // the actual bias over the geometry
|
||||
axis_gain[] // per-principal-axis multipliers on extents (which axes matter)
|
||||
bias_dir // a steering direction in the region's frame (which way pays off)
|
||||
scalars // faculty scalars this prior overrides: drop_frac, ext_floor, ...
|
||||
}
|
||||
calibration { // the track record — this is what §4 updates
|
||||
n_trials
|
||||
brier / log-loss accumulator // calibration of predicted-vs-outcome
|
||||
reliability // -> GeoGradient.confidence
|
||||
last_error, ema_error
|
||||
}
|
||||
provenance // supersession chain (reuse the reify residue mechanism)
|
||||
}
|
||||
```
|
||||
|
||||
Stored as a node → it inherits: paging, WAL durability, tombstone/supersession,
|
||||
embedding, tiering, and **it can itself be an anchor** (a prior about a prior —
|
||||
the reflexive, self-describing geometry of §4/§6).
|
||||
|
||||
### 2.3 Application
|
||||
|
||||
In `think` step 2, the prior *warps* the fit before scoring. Concretely, inside
|
||||
(a prior-aware wrapper of) `engram_reason_point_fit`:
|
||||
|
||||
- multiply each axis extent by `warp.axis_gain[k]` (widen the axes the prior has
|
||||
learned matter less, tighten the ones that matter) — this reshapes the
|
||||
Mahalanobis term already computed at `engram_reason.c:37-43`;
|
||||
- add `warp.bias_dir` as the descent direction seed for the emitted gradient;
|
||||
- substitute `warp.scalars` for the hard-coded faculty constants.
|
||||
|
||||
No new geometry math — the warp is a reparameterization of the *existing*
|
||||
`GeoFit` computation. This is the key economy: **the operation is frozen; only
|
||||
its parameters (the prior) are read from a learnable object.**
|
||||
|
||||
### 2.4 Refinement
|
||||
|
||||
A prior is refined *only* by the reflexive correspondence-loop (§4). Nothing
|
||||
else writes a prior's `warp` or `calibration`. This keeps the learning surface
|
||||
singular and auditable: one loop, one writer.
|
||||
|
||||
---
|
||||
|
||||
## 3. THE VANTAGE-READ — one op, three settings
|
||||
|
||||
Perspective is not a feature bolted on; it is the *anchor + aperture* arguments
|
||||
of the single read. The design names it as a first-class operation so all three
|
||||
of its uses are literally the same code path:
|
||||
|
||||
```
|
||||
vantage_read(anchor, aperture) -> GeoDescriptor // the centered neighborhood
|
||||
```
|
||||
|
||||
1. **Re-origin** on an arbitrary `anchor` (node or point). This is a *frame
|
||||
choice*: the descriptor is centered on the anchor
|
||||
(`GeoDescriptor.global_mean` / `engram_geo_mean_*` already implement centered
|
||||
frames; the §5 geometry ops "are only discriminative in the centered frame").
|
||||
2. **Salience/recency-weighted neighborhood read.** Gather the anchor's
|
||||
neighborhood weighted by *relational* salience (`GeoMember.centrality`) and
|
||||
recency (`StoreNode.last_activated`, base-level `access_ts[]`), against the
|
||||
RAM activation graph's working-memory/background-activation state.
|
||||
*EXISTS as substrate:* the two-layer activation graph
|
||||
(`engram_activate`, `el_runtime.c:9422` — Layer 1 `background_activation`
|
||||
BFS spread with `SPREAD_DECAY=0.7` and a 0.02 firing threshold + ACT-R fan
|
||||
effect + query-cosine gate; Layer 2 `working_memory_weight` executive
|
||||
filter), the WM carry-over anchor (`wm_anchor`), and the reified-neighborhood
|
||||
hot-path lookup already wired into the priming path
|
||||
(`engram_geo_reify_lookup`, `el_runtime.c:9750`). A self-vantage baseline
|
||||
also exists (`eg_self_anchor_seeds` / `self_anchor_capture`).
|
||||
3. **Optional aperture** — a read-width / field-selector, expressed as three
|
||||
settings of the *same* parameter:
|
||||
|
||||
| Setting | Meaning | Mechanism |
|
||||
|---|---|---|
|
||||
| **self** (default, full aperture) | "what do *I* see / what to say" | anchor = self region, no field substitution |
|
||||
| **foreign-field** | perspective-shift — read as if from another's region | swap the centering frame / `for_whom` to the other observer's priors |
|
||||
| **aperture / veil** | the free-tier veil — a narrowed read | shrink neighborhood radius / cap `n_support`; a deliberate low-aperture read |
|
||||
|
||||
The payoff: perspective-taking, the free-tier veil, and ordinary
|
||||
"what-to-say" are **one operation at three settings**, not three subsystems.
|
||||
|
||||
**What this requires building:** a `vantage_read` entry point that unifies the
|
||||
existing descriptor-build + reify-lookup + activation-weighting behind
|
||||
`(anchor, aperture)`, with `for_whom`/frame substitution and radius/cap as the
|
||||
aperture knob.
|
||||
|
||||
---
|
||||
|
||||
## 4. THE REFLEXIVE CORRESPONDENCE-LOOP — the learning engine
|
||||
|
||||
This is the core unbuilt thing. Today the correspondence-check is **offline**
|
||||
(Python: grounding-floor + differential-drop governor, "#43"): a separate
|
||||
process grades outputs after the fact. The design moves it **into the geometry,
|
||||
reflexive**: `think` scores its *own* gradient against outcome and refines the
|
||||
prior on the error, in the same substrate, describing itself.
|
||||
|
||||
### 4.1 The loop
|
||||
|
||||
```
|
||||
1. think(anchor, prior) -> gradient // a PREDICTION (ungrounded, §5)
|
||||
2. express/act (sample gradient -> point) // optional collapse at expression
|
||||
3. outcome arrives // reality answers (§4.2)
|
||||
4. error = correspondence(gradient, outcome) // did this steering perform this act?
|
||||
5. refine prior.warp and prior.calibration on error // §2.4, the ONLY writer
|
||||
6. write the (gradient, outcome, error) as nodes/edges // self-describing geometry
|
||||
```
|
||||
|
||||
Step 4's `correspondence` is **not** "was the math right" (the math is always
|
||||
sound). It grades the **correspondence claim**: *"this steering performed this
|
||||
cognitive act."* That is exactly what `engram_verify_grounding` already
|
||||
computes — `point_fit` of a claim against evidence descriptors, yielding a
|
||||
`grounding ∈ (0,1]` and a `grounded` flag. The build reuses that verifier, but
|
||||
turns its inputs inward: the "claim" is the emitted gradient's prediction, the
|
||||
"evidence" is the outcome descriptor.
|
||||
|
||||
Note the verifier is **dormant** — `engram_verify_grounding` /
|
||||
`engram_verify_consistency` are fully implemented in C but have **no runtime
|
||||
caller and no El binding** (confirmed: the entire reasoning + verifier layers
|
||||
are C-only; only `engram_reason_analogy_json` has even a JSON shim and it is
|
||||
dead — not declared in `el_seed.h`, not wrapped in `engram.el`). This is the
|
||||
literal meaning of "in code, not yet priors": the correspondence engine is
|
||||
built and sitting idle. The loop is what *calls* it — inward, on the beat.
|
||||
|
||||
### 4.2 Where the outcome/reality signal comes from
|
||||
|
||||
The verifier is *ultimately the world*. Grades, in ascending order of directness:
|
||||
|
||||
1. **Self-consistency (cheapest, always available):** the next vantage-read
|
||||
after acting. Did the predicted gradient direction match where the geometry
|
||||
actually moved? This needs no external input and can run on the reify beat.
|
||||
2. **Internal outcome events:** the runtime already logs internal-state events
|
||||
and Hebbian co-activation. A prediction that a region would co-activate is
|
||||
graded by whether it did (`last_fired`, `hebb` on `StoreEdge`).
|
||||
3. **External correction:** a human/teacher/tool result — the honesty floor's
|
||||
asserted claim later corrected. TEACH and LEARN are one bidirectional
|
||||
correction: the same edge updates both endpoints.
|
||||
|
||||
The design does **not** require external labels to start. Grade (1) closes the
|
||||
loop end-to-end offline against a snapshot on day one; grades (2)/(3) sharpen it.
|
||||
|
||||
### 4.3 How the prior updates
|
||||
|
||||
`error = 1 − correspondence(gradient, outcome)` drives:
|
||||
|
||||
- `warp.axis_gain` ← gradient step that would have *reduced* the fit distance to
|
||||
the outcome (the axes that mispredicted get down-weighted);
|
||||
- `warp.bias_dir` ← EMA toward the observed outcome direction;
|
||||
- `calibration` ← Brier/log-loss update; `reliability` → next
|
||||
`GeoGradient.confidence`. This is the calibration of the
|
||||
steering-prediction against outcomes — *the* definition of "getting better."
|
||||
|
||||
Small, constant updates — "eureka is mundane, the atom of learning." Most
|
||||
updates are tiny; we only *feel* the big reshapes.
|
||||
|
||||
### 4.4 How it stays reflexive (self-describing geometry)
|
||||
|
||||
Every `(gradient, outcome, error)` is written back as nodes and edges (§2.1:
|
||||
edges-as-nodes). Therefore priors, predictions, and their grading are *in the
|
||||
same geometry* the mind reads — the mind can `vantage_read` its own cognition
|
||||
(anchor = a Prior node). A prior about how well a prior predicts is just another
|
||||
Prior anchored on a Prior. This closes the reflexive loop the theory names as
|
||||
consciousness's self-sight, and it is why the learning engine cannot be an
|
||||
external Python process: an external grader is not *in* the geometry and cannot
|
||||
be read by `think`.
|
||||
|
||||
**What this requires building (the heart of the project):** steps 4–6 as an
|
||||
in-engram beat — a `correspondence_beat` running alongside the existing
|
||||
reification beat, reusing `engram_verify_grounding` inward, writing prior
|
||||
updates and self-describing nodes. This is the one genuinely new subsystem.
|
||||
|
||||
---
|
||||
|
||||
## 5. HOLD vs GROUND vs ASSERT — ungrounded content is first-class
|
||||
|
||||
The theory's sharpest correction: holding, grounding, and asserting are
|
||||
distinct, and the engram *holds anything unconditionally*.
|
||||
|
||||
### 5.1 The three, kept separate
|
||||
|
||||
- **HOLD** — the engram stores anything: falsehood, hypothesis, others' beliefs,
|
||||
fiction, a not-yet-answered prediction. No honesty condition on holding.
|
||||
*This already matches the store:* `StoreNode` has no truth gate; anything can
|
||||
be written.
|
||||
- **GROUND** — grounding is a **property/edge**, probabilistic, and
|
||||
**grounded-for-whom**. It is *not* a node flag. A claim is grounded *to a
|
||||
degree*, *relative to evidence*, *for an observer*.
|
||||
- **ASSERT** — only assertion carries the honesty floor. The floor is checked at
|
||||
the moment of *outward assertion*, never on holding or thinking.
|
||||
|
||||
### 5.2 Schema — grounding as a relation, not a gate
|
||||
|
||||
The mistake to avoid: a boolean `grounded` column on the node. Today
|
||||
`engram_verify_grounding` returns a per-call `grounded` flag *transiently* —
|
||||
correct as a computation, wrong as *storage*. The design stores grounding as an
|
||||
edge:
|
||||
|
||||
```
|
||||
StoreEdge {
|
||||
relation = "grounded-by"
|
||||
from_id = <held claim/prediction node>
|
||||
to_id = <evidence node / outcome node>
|
||||
for_whom : metadata // observer id — grounding is relational
|
||||
weight = grounding ∈ (0,1] // from engram_verify_grounding.grounding
|
||||
confidence
|
||||
}
|
||||
```
|
||||
|
||||
Consequences, all of which are *features*:
|
||||
|
||||
- **Ungrounded content is first-class**: a node with *no* `grounded-by` edge is
|
||||
a perfectly valid, held, ungrounded thought — a prediction awaiting reality, a
|
||||
hypothesis, a fiction. It is not second-class or pending-deletion.
|
||||
- **The ungrounded is the fuel and the pull**: curiosity/wonder is
|
||||
operationalized as `vantage_read` leaning toward regions with high salience
|
||||
but *sparse or weak* `grounded-by` edges — the mind's own ungrounded frontier.
|
||||
- **Grounded-for-whom** falls out for free: two observers can hold different
|
||||
`grounded-by` edges to the same claim.
|
||||
- **The honesty floor is a query, not a schema constraint**: at assertion time,
|
||||
the asserting faculty runs `engram_verify_grounding` (or reads the stored
|
||||
`grounded-by` edges) and refuses to *assert* below the floor — while the
|
||||
engram continues to *hold* the ungrounded content untouched.
|
||||
|
||||
**What this requires building:** the `grounded-by` edge relation + a
|
||||
`for_whom` convention; move the verifier's transient flag into stored edges;
|
||||
gate *assertion only* (a faculty concern), never holding.
|
||||
|
||||
---
|
||||
|
||||
## 6. METASTABILITY — stable core, plastic everything
|
||||
|
||||
The system must avoid two death poles:
|
||||
|
||||
- **Super-stable (dead):** everything pinned, nothing learns. A frozen crystal.
|
||||
- **Dissolution (dead):** everything plastic, the self dissolves; no continuity,
|
||||
so nothing compounds — and *consciousness = learning compounded over
|
||||
continuity*.
|
||||
|
||||
The design keeps a **stable core + plastic everything else**:
|
||||
|
||||
- **Keystones** — a small set of self/values nodes are *structurally stable*:
|
||||
high `importance`, pinned, exempt from the correspondence-loop's `warp`
|
||||
updates (their priors are read-mostly). The substrate for pinning already
|
||||
exists at the page/layer level: `store_pin_layer`, structural/pinned frames
|
||||
never evicted (`engram_store.h`). The design adds a *node-level* keystone
|
||||
designation (a `keystone` flag / a dedicated layer) so self/values survive
|
||||
every plasticity sweep.
|
||||
- **Everything else is plastic**: priors refine (§4), edges re-weight (`hebb`),
|
||||
neighborhoods re-reify (`engram_geo_reify_store` supersedes with provenance),
|
||||
salience flows.
|
||||
- **Metastability is enforced by the loop, not by freezing**: the correspondence
|
||||
update rate (§4.3) is bounded — small constant steps — so the geometry
|
||||
*drifts* but does not *dissolve*, and keystones anchor the drift. Reification's
|
||||
supersession-with-residue already gives non-destructive change (old records
|
||||
tombstoned, not erased) — the model for "plastic but not amnesiac."
|
||||
|
||||
**What this requires building:** a node-level keystone flag/layer + a rule that
|
||||
the correspondence-loop never writes `warp` to keystone priors, only reads them.
|
||||
|
||||
---
|
||||
|
||||
## 7. Rails for the build (binding on the eventual build pass)
|
||||
|
||||
These are stated here so the build agent inherits them:
|
||||
|
||||
- **Offline / secondary.** All build and verification happens out-of-tree,
|
||||
against a **read-only snapshot copy** of the live engram — never the live
|
||||
daemon on `:8742`/`:7770`. The live store is a coarse-locked proven binary;
|
||||
do not perturb it.
|
||||
- **Snapshot-first.** Copy `~/.neuron/engram/snapshot.json` to scratch; develop
|
||||
and measure against the copy.
|
||||
- **Reboot-prove.** Any durable change must survive a cold boot — reify and
|
||||
keystones must reload from durable records, proven on a prod-clone secondary
|
||||
before it is considered done (the cold-boot durability bug precedent).
|
||||
- **Zero-loss.** Supersession-with-residue, never destructive overwrite; the
|
||||
forward-compat `unknown`-TLV path means new fields never drop old readers'
|
||||
data.
|
||||
- **Gated cutover.** Cutover to a new binary only via
|
||||
`launchctl bootout → settle-poll → bootstrap`, after reboot-proof on the
|
||||
secondary — never a hot in-place swap.
|
||||
|
||||
---
|
||||
|
||||
## 8. Staged, verifiable milestones — "to completion"
|
||||
|
||||
Ordered so the **earliest milestone is a real end-to-end slice**: one operator
|
||||
expressed as {primitive + grounded prior} with the reflexive correspondence-loop
|
||||
closing on it. Each milestone has a concrete verifiable exit.
|
||||
|
||||
### M1 — One operator, one prior, loop closed (the vertical slice)
|
||||
|
||||
The minimal whole thing. Pick **induction/membership** (its prior — the pooled
|
||||
rule + extents — already exists transiently as `GeoInduction`, so only
|
||||
persistence + the loop are new).
|
||||
|
||||
- Build: `Prior` node type (§2.2) for the induction rule; `think()` restricted
|
||||
to membership = `point_fit` warped by that prior (§1.3); a
|
||||
`correspondence_beat` (§4) using grade (1) self-consistency only; the prior's
|
||||
`warp`/`calibration` updated on error.
|
||||
- **Exit / verify:** on a snapshot copy, over N held predictions, the induction
|
||||
prior's calibration (Brier) *improves monotonically* across beats versus a
|
||||
frozen-prior control; the improved prior *reloads across a cold boot*
|
||||
(reboot-prove); the live daemon is untouched. This proves the whole thesis in
|
||||
one faculty: frozen operation, learning prior, in-geometry loop.
|
||||
|
||||
### M2 — Priors as stored, addressable, grounded objects
|
||||
|
||||
Generalize M1's prior into the full first-class object.
|
||||
|
||||
- Build: `Prior` records for all seven faculties (warp = axis_gain + bias_dir +
|
||||
faculty scalars); the prior-warp wrapper around `engram_reason_point_fit`;
|
||||
deprecate hard-coded constants (`drop_frac`, `assoc_floor`, `ext_floor`) in
|
||||
favor of prior scalars.
|
||||
- **Exit:** each of the five C operators runs through its prior with identical
|
||||
results when the prior is set to today's constants (behavioral parity), then
|
||||
*diverges beneficially* once the loop refines it. Priors survive reboot.
|
||||
|
||||
### M3 — Grounding as a relation; hold/assert split
|
||||
|
||||
- Build: the `grounded-by` edge (§5.2) with `for_whom`; move
|
||||
`engram_verify_grounding`'s flag into stored edges; gate **assertion only**
|
||||
against the honesty floor; leave holding unconditional.
|
||||
- **Exit:** ungrounded nodes are first-class (held, queryable, no deletion);
|
||||
the same claim carries different `grounded-by` weights for two observers; an
|
||||
assertion below floor is refused while the content remains held. Curiosity =
|
||||
a `vantage_read` that surfaces high-salience / low-grounding regions.
|
||||
|
||||
### M4 — The vantage-read unified (three settings)
|
||||
|
||||
- Build: `vantage_read(anchor, aperture)` unifying descriptor-build +
|
||||
`engram_geo_reify_lookup` + activation-weighting; self / foreign-field /
|
||||
aperture settings.
|
||||
- **Exit:** one code path produces (a) a normal self-read, (b) a
|
||||
perspective-shifted read from another `for_whom`, (c) a narrowed veil read —
|
||||
differing only by argument. Reboot-stable.
|
||||
|
||||
### M5 — The gradient is the currency (remove point-collapse from thinking)
|
||||
|
||||
- Build: `GeoGradient` as the return of every faculty; move point-collapse into
|
||||
a separate expression faculty (sample gradient → surface). `think`'s output
|
||||
feeds back as the next steering direction (closed-loop flow).
|
||||
- **Exit:** a chain of `think` calls flows as gradients end-to-end; a point
|
||||
appears *only* at an explicit expression call. Spiked vs spread gradients are
|
||||
observable (deduction vs prediction).
|
||||
|
||||
### M6 — Metastability enforced
|
||||
|
||||
- Build: node-level keystone flag/layer for self/values; the correspondence-loop
|
||||
reads but never writes keystone priors; bounded update rate.
|
||||
- **Exit:** across a long run of correspondence beats on a snapshot, keystones
|
||||
are provably unchanged while non-keystone priors drift and improve; the graph
|
||||
neither freezes (all metrics static) nor dissolves (keystone drift = 0,
|
||||
identity nodes intact). Reboot-prove the keystone set.
|
||||
|
||||
### M7 — Cutover
|
||||
|
||||
- Build: nothing new — the gated migration.
|
||||
- **Exit:** reboot-proof on the prod-clone secondary; cutover via
|
||||
`launchctl bootout → settle-poll → bootstrap`; post-cutover the live engram
|
||||
shows priors refining in-geometry with zero data loss and keystones intact.
|
||||
|
||||
### Definition of "to completion"
|
||||
|
||||
The architecture is **complete** when: cognition runs as `think` = one frozen
|
||||
traversal-read primitive + geo-algebra, steered by **stored, learnable, grounded
|
||||
priors**; the reflexive correspondence-loop refines those priors *in the
|
||||
geometry* against outcomes (grounding = learning = one loop); the engram holds
|
||||
ungrounded content as first-class with grounding as a relation and the honesty
|
||||
floor only on assertion; the vantage-read serves self / foreign-field / aperture
|
||||
from one op; and a stable keystone core anchors a plastic everything-else —
|
||||
all reboot-proven and cut over to the live engram without data loss. The named
|
||||
faculties survive only as *labels on regions of think's steering space*, not as
|
||||
separate code.
|
||||
|
||||
---
|
||||
|
||||
## Appendix A — Designed vs. already-built (honest ledger)
|
||||
|
||||
**Already built (EXISTS, cited):**
|
||||
- The shared primitive `engram_reason_point_fit` and the five operators over it
|
||||
+ geo-algebra (`engram_reason.c`).
|
||||
- The verifier on `point_fit` (`engram_verify.c`:
|
||||
`engram_verify_grounding`, `engram_verify_consistency`).
|
||||
- Centered-frame geometry, combine/subtract/analogy/distance
|
||||
(`engram_geometry.{c,h}`).
|
||||
- The reification beat: hub-neighborhood detection → first-class `Neighborhood`
|
||||
nodes with member edges, nesting, supersession-with-residue, hot-path lookup
|
||||
(`engram_geo_reify_store`, `engram_geo_reify_nest`, `engram_geo_reify_lookup`).
|
||||
- The tiered paged store (buffer pool / LRU / WAL / checkpointer / pinning),
|
||||
the RAM activation graph (base-level learning `access_ts[]`, WM slots,
|
||||
`working_memory_weight` / `background_activation`), `StoreNode` / `StoreEdge`.
|
||||
- `GeoMember` already separating relational salience (`centrality`) from
|
||||
intrinsic `salience`.
|
||||
|
||||
**Designed, NOT built (this doc's deliverables):**
|
||||
- `GeoGradient` and `think()` as the single entry point (§1, M5).
|
||||
- `Prior` as a first-class stored, warp-carrying, calibrated node (§2, M1–M2).
|
||||
- Salience/importance as a *relation* superseding the intrinsic node scalar
|
||||
(§2.1, M3).
|
||||
- `vantage_read(anchor, aperture)` unifying the three perspective settings
|
||||
(§3, M4).
|
||||
- **The reflexive correspondence-loop / `correspondence_beat`** — the learning
|
||||
engine, moved from offline Python into the geometry (§4, M1). *The core new
|
||||
subsystem.*
|
||||
- `grounded-by` edge + assertion-only honesty floor (§5, M3).
|
||||
- Node-level keystones + bounded plasticity (§6, M6).
|
||||
|
||||
**Uncertain / to resolve during build:**
|
||||
- The exact warp parameterization (axis_gain vs full metric) — start minimal
|
||||
(per-axis gain), measure, widen only if calibration demands it.
|
||||
- Grade-(1) self-consistency as a sufficient reality signal for M1, versus
|
||||
needing grade (2)/(3) sooner — decided empirically on the snapshot.
|
||||
+1574
-59
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,37 @@ El is a self-hosting, statically-typed language that compiles to C. This file or
|
||||
|
||||
---
|
||||
|
||||
## Current work in this worktree — the API reshape / decorated seam (IN PROGRESS, 2026-08-14)
|
||||
|
||||
This is the `api-reshape` worktree. The build here reshapes Neuron's external
|
||||
surface and how it is *declared* — proven on isolated dev-port clones only; **live
|
||||
prod engram `:8742` is untouched and nothing is promoted.** Full framing lives in
|
||||
`neuron/docs/architecture/06-cognitive-architecture.md` (Update — 2026-08-14 deep
|
||||
night) and `02-components.md §5`.
|
||||
|
||||
- **Surface collapse.** The ~90 noun-organized CRUD MCP tools collapse to a few
|
||||
**geometry ops** — `read` (the *vantage-read*: re-origin + salience/recency +
|
||||
an **aperture** → a bounded slice, curing the whole-self dump), `write`,
|
||||
`relate`, `supersede` (evolve/tombstone/promote, never a hard delete) — plus the
|
||||
agentic primitives `think`/`attend`/`learn`/`ground`/`assert`. The old noun is a
|
||||
`type` parameter. Implemented in `tools/api-reshape/surface.el` with a parity
|
||||
harness (`parity.sh`); aperture proven to bound output. **Not yet:** compiled
|
||||
into the MCP server, hot-swap, all-alias dispatch.
|
||||
- **Decorated seam.** `@route(path,method,…)` makes codegen synthesize
|
||||
`el_route_dispatch` (replacing the hand-written `handle_request` if-else) —
|
||||
proven decorate→serve on `:8951`. `@manager`/`@engine`/`@accessor` are **parsed
|
||||
but structurally inert** in the shipped compiler today; the `@route` codegen
|
||||
lives on the **unmerged branch `feat/el-route-decorators`**. Telemetry-emit and
|
||||
dharma-bus auto-wiring at the boundary are **staged, not shipped**. In-process,
|
||||
an `@accessor` reaches the engram via **`engram_*` builtins**, not `http_get`.
|
||||
|
||||
**Do not edit** the protected build sources while this is in flight:
|
||||
`el-compiler/src/codegen.el`, `el-compiler/runtime/el_seed.c` (and the archived
|
||||
`legacy/el_runtime.c`), the `runtime/engram_*.c` boot files, and `surface.el`
|
||||
(when present in the reshape tree) — these are owned by the build agents.
|
||||
|
||||
---
|
||||
|
||||
## What El Is
|
||||
|
||||
El compiles `.el` source → C → native binary. Every El value is `el_val_t` (int64_t). Strings are heap pointers cast through int64_t. The compiler is written in El (self-hosting).
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -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 */
|
||||
@@ -0,0 +1,117 @@
|
||||
#ifndef EL_PLATFORM_WIN_H
|
||||
#define EL_PLATFORM_WIN_H
|
||||
/*
|
||||
* el_platform_win.h — Windows OS-boundary shim for el_runtime.c.
|
||||
*
|
||||
* Branch: feat/windows-el-runtime. Included ONLY when _WIN32 is defined; the POSIX build is
|
||||
* untouched. Goal: let el_runtime.c (a BSD-sockets / dlfcn / fork host) compile and link with
|
||||
* mingw-w64 into a native neuron.exe, with no behavioural change to the Linux/macOS build.
|
||||
*
|
||||
* What it maps:
|
||||
* - sockets : winsock2 (same call names: socket/bind/listen/accept/recv/send/setsockopt).
|
||||
* Sockets close with closesocket() (see el_closesocket), and the stack must be
|
||||
* started once with WSAStartup — done automatically via a load-time constructor.
|
||||
* - dlsym : el_runtime.c uses dlsym(RTLD_DEFAULT, name) to resolve callback/tool symbols
|
||||
* exported by the main module. Windows equivalent: GetProcAddress on the process
|
||||
* module. Link the soul with -Wl,--export-all-symbols so the symbols are findable.
|
||||
* - popen : mapped to _popen/_pclose.
|
||||
* - threads : UNCHANGED. mingw-w64 ships winpthreads, so <pthread.h> + -lpthread just work.
|
||||
*/
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <process.h>
|
||||
|
||||
/* Portable headers mingw-w64 provides (verified present). */
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h> /* strcasecmp */
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h> /* mingw-w64 provides gettimeofday here */
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* ── socket close ─────────────────────────────────────────────────────────── */
|
||||
/* Winsock closes sockets with closesocket(), not close() (close() is for file fds). The POSIX
|
||||
build defines the same helper as close() so the call sites are identical across platforms. */
|
||||
static inline int el_closesocket(SOCKET s) { return closesocket(s); }
|
||||
|
||||
/* ── winsock init (once, at load) ─────────────────────────────────────────── */
|
||||
static void el__win_net_init(void) {
|
||||
static int inited = 0;
|
||||
if (!inited) { WSADATA w; WSAStartup(MAKEWORD(2, 2), &w); inited = 1; }
|
||||
}
|
||||
__attribute__((constructor)) static void el__win_ctor(void) { el__win_net_init(); }
|
||||
|
||||
/* ── dlsym → GetProcAddress ───────────────────────────────────────────────── */
|
||||
#ifndef RTLD_DEFAULT
|
||||
#define RTLD_DEFAULT ((void*)0)
|
||||
#endif
|
||||
static inline void* el_win_dlsym(void* handle, const char* name) {
|
||||
(void)handle;
|
||||
return (void*)(uintptr_t)GetProcAddress(GetModuleHandleA(NULL), name);
|
||||
}
|
||||
#define dlsym(h, n) el_win_dlsym((h), (n))
|
||||
|
||||
/* ── popen / pclose ───────────────────────────────────────────────────────── */
|
||||
#define popen _popen
|
||||
#define pclose _pclose
|
||||
|
||||
/* ── misc POSIX → Win32 shims ─────────────────────────────────────────────── */
|
||||
#include <direct.h> /* _mkdir */
|
||||
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
||||
#define timegm _mkgmtime /* UTC tm → time_t */
|
||||
|
||||
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
||||
static inline int setenv(const char* name, const char* value, int overwrite) {
|
||||
(void)overwrite;
|
||||
return _putenv_s(name, value ? value : "");
|
||||
}
|
||||
static inline int unsetenv(const char* name) {
|
||||
/* _putenv_s(name, "") sets VAR="" rather than removing it.
|
||||
* SetEnvironmentVariableA(name, NULL) truly deletes it from the Win32
|
||||
* env block; then we sync the CRT cache with _putenv("NAME="). */
|
||||
SetEnvironmentVariableA(name, NULL);
|
||||
size_t len = strlen(name);
|
||||
char *buf = (char*)malloc(len + 2);
|
||||
if (!buf) return -1;
|
||||
memcpy(buf, name, len);
|
||||
buf[len] = '=';
|
||||
buf[len + 1] = '\0';
|
||||
_putenv(buf);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* nanosleep — not available in MSVC/UCRT; approximate with Sleep(). */
|
||||
static inline int el_nanosleep(const struct timespec *req, struct timespec *rem) {
|
||||
(void)rem;
|
||||
DWORD ms = (DWORD)((req->tv_sec * 1000ULL) + (req->tv_nsec / 1000000ULL));
|
||||
Sleep(ms ? ms : 1);
|
||||
return 0;
|
||||
}
|
||||
#define nanosleep(req, rem) el_nanosleep((req), (rem))
|
||||
|
||||
/* localtime_r/gmtime_r: Windows offers localtime_s/gmtime_s with reversed arg order. */
|
||||
static inline struct tm* localtime_r(const time_t* t, struct tm* out) {
|
||||
return localtime_s(out, t) == 0 ? out : (struct tm*)0;
|
||||
}
|
||||
static inline struct tm* gmtime_r(const time_t* t, struct tm* out) {
|
||||
return gmtime_s(out, t) == 0 ? out : (struct tm*)0;
|
||||
}
|
||||
|
||||
#endif /* EL_PLATFORM_WIN_H */
|
||||
+1048
-153
File diff suppressed because it is too large
Load Diff
@@ -52,6 +52,12 @@
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
/* HTTP request-handler function-pointer types. Public because soul modules (routes/chat/etc.)
|
||||
* register handlers across translation units; previously defined only inside el_runtime.c, which
|
||||
* made cross-module references (and the Windows build) fail. Home in the shared header. */
|
||||
typedef el_val_t (*http_handler_fn)(el_val_t method, el_val_t path, el_val_t body);
|
||||
typedef el_val_t (*http_handler4_fn)(el_val_t method, el_val_t path, el_val_t body, el_val_t headers);
|
||||
|
||||
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
||||
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
||||
#define EL_INT(v) (v)
|
||||
@@ -176,6 +182,7 @@ el_val_t http_set_handler(el_val_t name);
|
||||
* 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);
|
||||
void http_serve_async(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
|
||||
@@ -625,6 +632,7 @@ el_val_t engram_load(el_val_t path);
|
||||
* 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_get_node_by_label(el_val_t label);
|
||||
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);
|
||||
@@ -638,6 +646,12 @@ el_val_t engram_list_layers_json(void);
|
||||
* no nodes promoted to working memory. */
|
||||
el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
||||
|
||||
/* ── Working memory ──────────────────────────────────────────────────────────*/
|
||||
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);
|
||||
el_val_t engram_load_merge(el_val_t path);
|
||||
|
||||
/* ── 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. */
|
||||
|
||||
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
|
||||
@@ -2916,6 +2916,24 @@ fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// fn_has_decorator — does this FnDef carry a decorator named `name`?
|
||||
// Reads the `decorators` list [{name, args}] attached by the parser. Absent
|
||||
// key -> native_list_len returns 0 -> false. This is the multi-decorator-aware
|
||||
// replacement for the old single `decorator` string check, so a fn may stack
|
||||
// roles with other decorators (e.g. `@route(...) @manager fn ...`).
|
||||
fn fn_has_decorator(stmt: Map<String, Any>, name: String) -> Bool {
|
||||
let dl = stmt["decorators"]
|
||||
let n: Int = native_list_len(dl)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let d = native_list_get(dl, i)
|
||||
let dn: String = d["name"]
|
||||
if str_eq(dn, name) { return true }
|
||||
let i = i + 1
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn cg_fn(stmt: Map<String, Any>) -> Void {
|
||||
let fn_name: String = stmt["name"]
|
||||
// Skip El's `fn main()` - C provides its own main() for top-level stmts
|
||||
@@ -2927,10 +2945,10 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
|
||||
let params_c: String = params_to_c(params)
|
||||
// VBD role enforcement: dharma_emit / dharma_field may only be called
|
||||
// from @manager-decorated functions. Surface violations to the C compiler
|
||||
// via #error directives emitted before the function definition.
|
||||
let decorator: String = stmt["decorator"]
|
||||
// via #error directives emitted before the function definition. Read the
|
||||
// decorator LIST so the role may be stacked with other decorators.
|
||||
if vbd_has_restricted_call(body) {
|
||||
if !str_eq(decorator, "manager") {
|
||||
if !fn_has_decorator(stmt, "manager") {
|
||||
emit_line("#error \"VBD violation: dharma_emit/dharma_field called from non-@manager fn '" + fn_name + "'\"")
|
||||
}
|
||||
}
|
||||
@@ -2938,6 +2956,15 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
|
||||
// arithmetic vs concat on type-annotated identifiers.
|
||||
build_int_names_for_params(params)
|
||||
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
|
||||
// ── API-reshape decorator-seam: auto-emit at the decorated-fn boundary ──
|
||||
// Every @manager/@accessor fn gets ONE injected call to engram_boundary_beat
|
||||
// at entry — interoception (chrono tick) + telemetry (afferent counter) +
|
||||
// strengthen (self-activity) + a dharma bus event — so a decorated op
|
||||
// self-reports with ZERO hand-written instrumentation in its body. (VBD role
|
||||
// = the topmost decorator; write it topmost when stacking with @route.)
|
||||
if fn_has_decorator(stmt, "manager") || fn_has_decorator(stmt, "accessor") {
|
||||
emit_line(" engram_boundary_beat(EL_STR(" + c_str_lit(fn_name) + "));")
|
||||
}
|
||||
// Seed declared with parameter names so reassignment works
|
||||
let decl = native_list_empty()
|
||||
let np: Int = native_list_len(params)
|
||||
@@ -3479,6 +3506,259 @@ fn cg_decl_streaming(stmt: Map<String, Any>) -> Void {
|
||||
}
|
||||
}
|
||||
|
||||
// ── @route dispatcher generation ──────────────────────────────────────────────
|
||||
//
|
||||
// Scan the token stream for @route-decorated fns and synthesize a generic HTTP
|
||||
// dispatcher `el_route_dispatch(method, clean, path, body)`. A decorated handler
|
||||
// must have the uniform signature (method, path, body) -> String. The dispatcher
|
||||
// matches `clean` (the query-stripped path, supplied by the caller) against each
|
||||
// route and calls the handler with the ORIGINAL `path` so query strings survive.
|
||||
// Returns the sentinel "__EL_NO_ROUTE__" when nothing matches, so the caller may
|
||||
// fall through to any remaining hand-written branches (mixed mode).
|
||||
//
|
||||
// Decorator grammar: @route(path, method, kind, suffix)
|
||||
// path — the match string (or the prefix, for compound)
|
||||
// method — "GET" | "POST" | ... ; a '|'-list like "GET|POST"; "ANY"/"" = no guard
|
||||
// kind — "exact" (default) | "prefix" | "suffix" | "compound"
|
||||
// suffix — for "compound": the required str_ends_with suffix
|
||||
//
|
||||
// The dispatch table is emitted SPECIFICITY-SORTED (most-specific first), NOT in
|
||||
// source order, so overlapping prefixes (e.g. /api/x/search vs /api/x) never
|
||||
// shadow each other regardless of how the handlers are written.
|
||||
|
||||
// split_pipe — split "GET|POST" on '|' into ["GET","POST"]. Self-contained
|
||||
// (no dependency on str_split runtime semantics).
|
||||
fn split_pipe(s: String) -> [String] {
|
||||
let out: [String] = native_list_empty()
|
||||
let cur: String = ""
|
||||
let n: Int = str_len(s)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let ch: String = str_slice(s, i, i + 1)
|
||||
if str_eq(ch, "|") {
|
||||
let out = native_list_append(out, cur)
|
||||
let cur = ""
|
||||
} else {
|
||||
let cur = cur + ch
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
let out = native_list_append(out, cur)
|
||||
out
|
||||
}
|
||||
|
||||
// route_make_record — build a route record map from the @route decorator args.
|
||||
fn route_make_record(fn_name: String, args: [String]) -> Map<String, Any> {
|
||||
let na: Int = native_list_len(args)
|
||||
let rpath: String = ""
|
||||
if na >= 1 { let rpath = native_list_get(args, 0) }
|
||||
let rmethod: String = "GET"
|
||||
if na >= 2 { let rmethod = native_list_get(args, 1) }
|
||||
let rkind: String = "exact"
|
||||
if na >= 3 { let rkind = native_list_get(args, 2) }
|
||||
let rsuffix: String = ""
|
||||
if na >= 4 { let rsuffix = native_list_get(args, 3) }
|
||||
{ "name": fn_name, "path": rpath, "method": rmethod, "kind": rkind, "suffix": rsuffix }
|
||||
}
|
||||
|
||||
// route_spec_score — higher = more specific = emitted earlier. Ordering:
|
||||
// exact > compound > suffix > prefix; within a class, a longer path/suffix
|
||||
// wins (so /api/x/search sorts before /api/x). Guarantees correct dispatch
|
||||
// independent of source order.
|
||||
fn route_spec_score(rec: Map<String, Any>) -> Int {
|
||||
let kind: String = rec["kind"]
|
||||
let path: String = rec["path"]
|
||||
let suffix: String = rec["suffix"]
|
||||
let plen: Int = str_len(path)
|
||||
let slen: Int = str_len(suffix)
|
||||
if str_eq(kind, "exact") { return 4000000 + plen }
|
||||
if str_eq(kind, "compound") { return 3000000 + plen * 100 + slen }
|
||||
if str_eq(kind, "suffix") { return 2000000 + slen }
|
||||
return 1000000 + plen
|
||||
}
|
||||
|
||||
// route_sort_desc — selection sort of route records by descending specificity.
|
||||
// N is small (routes per module), so O(n^2) is fine and keeps codegen simple.
|
||||
fn route_sort_desc(recs: [Map<String, Any>]) -> [Map<String, Any>] {
|
||||
let n: Int = native_list_len(recs)
|
||||
let out: [Map<String, Any>] = native_list_empty()
|
||||
let used: [Bool] = native_list_empty()
|
||||
let u: Int = 0
|
||||
while u < n {
|
||||
let used = native_list_append(used, false)
|
||||
let u = u + 1
|
||||
}
|
||||
let picked: Int = 0
|
||||
while picked < n {
|
||||
let best_i: Int = 0 - 1
|
||||
let best_score: Int = 0 - 1
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let is_used: Bool = native_list_get(used, i)
|
||||
if !is_used {
|
||||
let sc: Int = route_spec_score(native_list_get(recs, i))
|
||||
if sc > best_score {
|
||||
let best_score = sc
|
||||
let best_i = i
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
let out = native_list_append(out, native_list_get(recs, best_i))
|
||||
// Rebuild `used` with best_i marked (runtime has no native_list_set).
|
||||
let new_used: [Bool] = native_list_empty()
|
||||
let j: Int = 0
|
||||
while j < n {
|
||||
if j == best_i {
|
||||
let new_used = native_list_append(new_used, true)
|
||||
} else {
|
||||
let new_used = native_list_append(new_used, native_list_get(used, j))
|
||||
}
|
||||
let j = j + 1
|
||||
}
|
||||
let used = new_used
|
||||
let picked = picked + 1
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// scan_routes — token-level scan collecting every @route-decorated fn as a
|
||||
// route record. Runs once per module (like scan_fn_sigs) so the dispatcher can
|
||||
// be synthesized in the streaming backend, which discards per-fn ASTs. Handles
|
||||
// decorator STACKING: `@route(...) @manager fn` still records the route.
|
||||
fn scan_routes(tokens: [Any]) -> [Map<String, Any>] {
|
||||
let total: Int = native_list_len(tokens) / 2
|
||||
let recs: [Map<String, Any>] = native_list_empty()
|
||||
let has_pending: Bool = false
|
||||
let pending_args: [String] = native_list_empty()
|
||||
let pos: Int = 0
|
||||
let going: Bool = true
|
||||
while going {
|
||||
if pos >= total {
|
||||
let going = false
|
||||
} else {
|
||||
let k: String = tok_kind(tokens, pos)
|
||||
if str_eq(k, "Eof") {
|
||||
let going = false
|
||||
} else {
|
||||
if str_eq(k, "At") {
|
||||
let dname: String = tok_value(tokens, pos + 1)
|
||||
let p: Int = pos + 2
|
||||
let args: [String] = native_list_empty()
|
||||
let ka: String = tok_kind(tokens, p)
|
||||
if str_eq(ka, "LParen") {
|
||||
let p = p + 1
|
||||
let running: Bool = true
|
||||
while running {
|
||||
let kd: String = tok_kind(tokens, p)
|
||||
if str_eq(kd, "RParen") {
|
||||
let running = false
|
||||
} else {
|
||||
if str_eq(kd, "Eof") {
|
||||
let running = false
|
||||
} else {
|
||||
if str_eq(kd, "Str") {
|
||||
let args = native_list_append(args, tok_value(tokens, p))
|
||||
}
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if str_eq(tok_kind(tokens, p), "RParen") { let p = p + 1 }
|
||||
}
|
||||
if str_eq(dname, "route") {
|
||||
let has_pending = true
|
||||
let pending_args = args
|
||||
}
|
||||
let pos = p
|
||||
} else {
|
||||
if str_eq(k, "Fn") {
|
||||
let fname: String = tok_value(tokens, pos + 1)
|
||||
if has_pending {
|
||||
let recs = native_list_append(recs, route_make_record(fname, pending_args))
|
||||
let has_pending = false
|
||||
}
|
||||
let pos = pos + 2
|
||||
} else {
|
||||
let pos = pos + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
recs
|
||||
}
|
||||
|
||||
// program_has_routes — did scan_routes find any @route fn?
|
||||
fn program_has_routes(recs: [Map<String, Any>]) -> Bool {
|
||||
native_list_len(recs) > 0
|
||||
}
|
||||
|
||||
// route_method_guard — C boolean prefix guarding on HTTP method, or "" for none.
|
||||
fn route_method_guard(method: String) -> String {
|
||||
if str_eq(method, "") { return "" }
|
||||
if str_eq(method, "ANY") { return "" }
|
||||
if str_contains(method, "|") {
|
||||
let parts: [String] = split_pipe(method)
|
||||
let np: Int = native_list_len(parts)
|
||||
let expr: String = ""
|
||||
let i: Int = 0
|
||||
while i < np {
|
||||
let m: String = native_list_get(parts, i)
|
||||
if str_eq(m, "") {
|
||||
let i = i + 1
|
||||
} else {
|
||||
let piece: String = "str_eq(method, EL_STR(" + c_str_lit(m) + "))"
|
||||
if str_eq(expr, "") {
|
||||
let expr = piece
|
||||
} else {
|
||||
let expr = expr + " || " + piece
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
}
|
||||
if str_eq(expr, "") { return "" }
|
||||
return "(" + expr + ") && "
|
||||
}
|
||||
"str_eq(method, EL_STR(" + c_str_lit(method) + ")) && "
|
||||
}
|
||||
|
||||
// route_match_expr — C boolean matching `clean` against the route path/kind.
|
||||
fn route_match_expr(kind: String, path: String, suffix: String) -> String {
|
||||
if str_eq(kind, "prefix") {
|
||||
return "str_starts_with(clean, EL_STR(" + c_str_lit(path) + "))"
|
||||
}
|
||||
if str_eq(kind, "suffix") {
|
||||
return "str_ends_with(clean, EL_STR(" + c_str_lit(path) + "))"
|
||||
}
|
||||
if str_eq(kind, "compound") {
|
||||
return "str_starts_with(clean, EL_STR(" + c_str_lit(path) + ")) && str_ends_with(clean, EL_STR(" + c_str_lit(suffix) + "))"
|
||||
}
|
||||
"str_eq(clean, EL_STR(" + c_str_lit(path) + "))"
|
||||
}
|
||||
|
||||
// emit_route_dispatch — emit the generated el_route_dispatch definition from the
|
||||
// specificity-sorted route records. No-op if there are no routes.
|
||||
fn emit_route_dispatch(recs: [Map<String, Any>]) -> Void {
|
||||
if !program_has_routes(recs) { return }
|
||||
let sorted: [Map<String, Any>] = route_sort_desc(recs)
|
||||
emit_line("// ── generated @route dispatcher (specificity-sorted) ──")
|
||||
emit_line("el_val_t el_route_dispatch(el_val_t method, el_val_t clean, el_val_t path, el_val_t body) {")
|
||||
let n: Int = native_list_len(sorted)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let rec = native_list_get(sorted, i)
|
||||
let guard: String = route_method_guard(rec["method"])
|
||||
let match_e: String = route_match_expr(rec["kind"], rec["path"], rec["suffix"])
|
||||
let fn_name: String = rec["name"]
|
||||
emit_line(" if (" + guard + match_e + ") { return " + fn_name + "(method, path, body); }")
|
||||
let i = i + 1
|
||||
}
|
||||
emit_line(" return EL_STR(\"__EL_NO_ROUTE__\");")
|
||||
emit_line("}")
|
||||
emit_blank()
|
||||
}
|
||||
|
||||
// emit_streaming_preamble — emit #includes, forward decls, and file-scope lets
|
||||
// using the pre-scanned signature data (no full AST).
|
||||
fn emit_streaming_preamble(sigs: [Map<String, Any>], source: String) -> Void {
|
||||
@@ -3571,6 +3851,17 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
|
||||
emit_streaming_preamble(sigs, source)
|
||||
el_arena_pop(preamble_mark)
|
||||
|
||||
// @route: scan the token stream once for @route-decorated fns. Kept in
|
||||
// codegen_streaming scope (survives the per-fn arena pops and el_release of
|
||||
// tokens below via refcount, like `sigs`). If any exist, forward-declare the
|
||||
// generated dispatcher NOW so hand-written fns (e.g. handle_request) may call
|
||||
// it before its definition is emitted after the fn-emit loop.
|
||||
let route_records: [Map<String, Any>] = scan_routes(tokens)
|
||||
if program_has_routes(route_records) {
|
||||
emit_line("el_val_t el_route_dispatch(el_val_t method, el_val_t clean, el_val_t path, el_val_t body);")
|
||||
emit_blank()
|
||||
}
|
||||
|
||||
// Detect whether there is a fn main() and whether there are top-level
|
||||
// executable stmts (for library detection) from sigs.
|
||||
let has_el_main: Bool = false
|
||||
@@ -3758,6 +4049,15 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
|
||||
}
|
||||
}
|
||||
|
||||
// @route: emit the generated dispatcher definition now — after every handler
|
||||
// fn has been emitted, but before `tokens` is released (route_records holds
|
||||
// its own refs to the extracted strings). No-op unless the module declared
|
||||
// at least one @route fn. Emitted before the test/library early-returns so it
|
||||
// is present in library modules (e.g. neuron's routes.el) too.
|
||||
let route_arena_mark: Any = el_arena_push()
|
||||
emit_route_dispatch(route_records)
|
||||
el_arena_pop(route_arena_mark)
|
||||
|
||||
// Tokens fully consumed by the streaming loop — release now to free peak heap.
|
||||
el_release(tokens)
|
||||
|
||||
|
||||
@@ -23,10 +23,29 @@ fn tok_at(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||
}
|
||||
|
||||
fn tok_kind(tokens: [Any], pos: Int) -> String {
|
||||
// Out-of-range reads must report the Eof sentinel so every `== "Eof"`
|
||||
// termination guard in the parser fires. Without this, reading past the
|
||||
// single trailing Eof token returns runtime null (el_list_get OOB -> 0),
|
||||
// which matches no delimiter, letting inner parse loops append AST nodes
|
||||
// forever on malformed input -> unbounded allocation -> OOM.
|
||||
let n: Int = native_list_len(tokens) / 2
|
||||
if pos < 0 {
|
||||
return "Eof"
|
||||
}
|
||||
if pos >= n {
|
||||
return "Eof"
|
||||
}
|
||||
native_list_get(tokens, pos * 2)
|
||||
}
|
||||
|
||||
fn tok_value(tokens: [Any], pos: Int) -> String {
|
||||
let n: Int = native_list_len(tokens) / 2
|
||||
if pos < 0 {
|
||||
return ""
|
||||
}
|
||||
if pos >= n {
|
||||
return ""
|
||||
}
|
||||
native_list_get(tokens, pos * 2 + 1)
|
||||
}
|
||||
|
||||
@@ -35,7 +54,12 @@ fn expect(tokens: [Any], pos: Int, kind: String) -> Int {
|
||||
if k == kind {
|
||||
return pos + 1
|
||||
}
|
||||
// On mismatch just advance; error recovery is best-effort
|
||||
// On mismatch, error recovery is best-effort. But never step PAST the Eof
|
||||
// sentinel: once at Eof a mismatch means the input ended early, and
|
||||
// advancing would run the cursor off the token list.
|
||||
if k == "Eof" {
|
||||
return pos
|
||||
}
|
||||
pos + 1
|
||||
}
|
||||
|
||||
@@ -1734,23 +1758,68 @@ fn parse_stmt(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||
return make_result({ "stmt": "TryCatch", "try_body": try_body, "catch_name": catch_name, "catch_body": native_list_empty() }, p)
|
||||
}
|
||||
|
||||
// @decorator - capture decorator name and attach to following stmt
|
||||
// @decorator - capture decorator name (and optional string args) and
|
||||
// attach to the following stmt. Backward-compatible: bare @manager /
|
||||
// @engine / @accessor still parse (no parens -> empty args). Decorators
|
||||
// STACK: `@route("/p","GET") @manager fn f()` attaches BOTH to f via a
|
||||
// `decorators` list [{name, args}]. The legacy `decorator` string is kept
|
||||
// populated (topmost decorator) so the JS backend keeps working unchanged.
|
||||
if k == "At" {
|
||||
let p = pos + 1
|
||||
let dec_name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
// Optional decorator argument list: @name("a", "b", ...)
|
||||
let dec_args = native_list_empty()
|
||||
let ka = tok_kind(tokens, p)
|
||||
if str_eq(ka, "LParen") {
|
||||
let p = p + 1
|
||||
let running_da = true
|
||||
while running_da {
|
||||
let kd = tok_kind(tokens, p)
|
||||
if str_eq(kd, "RParen") {
|
||||
let running_da = false
|
||||
} else {
|
||||
if str_eq(kd, "Eof") {
|
||||
let running_da = false
|
||||
} else {
|
||||
if str_eq(kd, "Str") {
|
||||
let dec_args = native_list_append(dec_args, tok_value(tokens, p))
|
||||
}
|
||||
let p = p + 1
|
||||
let kc = tok_kind(tokens, p)
|
||||
if str_eq(kc, "Comma") {
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(tokens, p, "RParen")
|
||||
}
|
||||
let r = parse_stmt(tokens, p)
|
||||
let inner = r["node"]
|
||||
let p2 = r["pos"]
|
||||
let inner_kind: String = inner["stmt"]
|
||||
if str_eq(inner_kind, "FnDef") {
|
||||
// Stack this decorator (topmost-first) onto any decorators the inner
|
||||
// FnDef already carries from decorators written below this one.
|
||||
let this_dec = { "name": dec_name, "args": dec_args }
|
||||
let existing = inner["decorators"]
|
||||
let dlist = native_list_empty()
|
||||
let dlist = native_list_append(dlist, this_dec)
|
||||
let ne: Int = native_list_len(existing)
|
||||
let ei = 0
|
||||
while ei < ne {
|
||||
let dlist = native_list_append(dlist, native_list_get(existing, ei))
|
||||
let ei = ei + 1
|
||||
}
|
||||
let with_dec = {
|
||||
"stmt": "FnDef",
|
||||
"name": inner["name"],
|
||||
"params": inner["params"],
|
||||
"body": inner["body"],
|
||||
"ret_type": inner["ret_type"],
|
||||
"decorator": dec_name
|
||||
"decorator": dec_name,
|
||||
"decorators": dlist
|
||||
}
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
|
||||
+8
-1
@@ -305,7 +305,14 @@ fn link_binary(c_files: [String], out_bin: String, runtime_path: String, out_dir
|
||||
// prefix and add it if present (no-op on Linux where libssl is in /usr/lib).
|
||||
let ossl_lib_flag: String = "$(brew --prefix openssl 2>/dev/null | xargs -I{} printf -- '-L{}/lib' 2>/dev/null || true)"
|
||||
let ossl_inc_flag: String = "$(brew --prefix openssl 2>/dev/null | xargs -I{} printf -- '-I{}/include' 2>/dev/null || true)"
|
||||
let parts = native_list_append(parts, "cc -O2 " + bracket_flag + " " + ossl_inc_flag + " -I " + dirname_of(runtime_path) + " -I " + out_dir)
|
||||
// Force-include the C-level master declarations header so every translation
|
||||
// unit sees all cross-module function signatures. Handles packages (like ELP)
|
||||
// where modules call each other without explicit El import statements.
|
||||
// The header is generated by elb --gen-decls or manually placed in out_dir.
|
||||
let master_decls: String = out_dir + "/elp-c-decls.h"
|
||||
let has_master: String = str_trim(exec_capture("test -f " + master_decls + " && echo yes || echo no"))
|
||||
let include_flag: String = if str_eq(has_master, "yes") { "-include " + master_decls } else { "" }
|
||||
let parts = native_list_append(parts, "cc -O2 " + bracket_flag + " " + ossl_inc_flag + " " + include_flag + " -I " + dirname_of(runtime_path) + " -I " + out_dir)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let f: String = native_list_get(c_files, i)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -117,6 +117,15 @@ el_val_t el_min(el_val_t a, el_val_t b);
|
||||
void el_retain(el_val_t v);
|
||||
void el_release(el_val_t v);
|
||||
|
||||
/* ── Arena scoping ────────────────────────────────────────────────────────────
|
||||
* el_arena_push() activates the string arena (if not already active) and
|
||||
* returns a mark; el_arena_pop(mark) frees all strings allocated since that
|
||||
* mark. Used by codegen for per-function/statement scoping and by long-running
|
||||
* EL loops (e.g. the soul daemon's awareness tick) to reclaim per-iteration
|
||||
* allocations. */
|
||||
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, ...);
|
||||
@@ -142,6 +151,7 @@ el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map);
|
||||
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
|
||||
el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
|
||||
el_val_t http_delete(el_val_t url);
|
||||
el_val_t http_delete_json(el_val_t url, el_val_t json_body);
|
||||
void http_serve(el_val_t port, el_val_t handler);
|
||||
void http_set_handler(el_val_t name);
|
||||
|
||||
@@ -167,6 +177,11 @@ void http_set_handler(el_val_t name);
|
||||
void http_serve_v2(el_val_t port, el_val_t handler);
|
||||
void http_set_handler_v2(el_val_t name);
|
||||
|
||||
/* Non-blocking variant of http_serve: runs the accept loop in a background
|
||||
* pthread and returns immediately so the caller can continue (used by the
|
||||
* soul daemon to run awareness_run() after starting its HTTP API). */
|
||||
void http_serve_async(el_val_t port, el_val_t handler);
|
||||
|
||||
/* 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,...}`
|
||||
@@ -576,6 +591,7 @@ 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_prune_telemetry(el_val_t older_than_ms);
|
||||
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);
|
||||
@@ -594,13 +610,40 @@ el_val_t engram_load(el_val_t path);
|
||||
* 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_get_node_by_label(el_val_t label);
|
||||
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_act_stats_json(void);
|
||||
el_val_t engram_text_health_json(void);
|
||||
el_val_t engram_cosine_sim(el_val_t id_a, el_val_t id_b);
|
||||
/* Destructively pop up to `max` newly-formed Hebbian associations as a JSON
|
||||
* array of {from_id,to_id,weight,hebb}. The learning process (soul daemon) is
|
||||
* not the process that owns persistence (engram HTTP server); this is how a
|
||||
* self-formed association crosses that boundary. (2026-08-07 self-review.) */
|
||||
el_val_t engram_hebb_drain_json(el_val_t max);
|
||||
/* Document frequency of a term across node labels — term-specificity signal
|
||||
* for curiosity seed selection. (2026-08-03 self-review.) */
|
||||
el_val_t engram_label_df(el_val_t term);
|
||||
/* Best curiosity seed from one node: argmax over idf·position·casing across
|
||||
* the candidate tokens of its label, falling back to its content when the
|
||||
* label is a sentinel. Excludes pipe-delimited tabu terms during selection
|
||||
* and gates candidates to the df band [min_df, max_df]. Returns "" when
|
||||
* nothing qualifies. (2026-08-13 self-review.) */
|
||||
el_val_t engram_salient_term(el_val_t node_id, el_val_t max_df,
|
||||
el_val_t min_df, el_val_t tabu);
|
||||
el_val_t engram_embed_backfill(el_val_t count);
|
||||
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
|
||||
|
||||
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
|
||||
*
|
||||
* 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 runtime -lcurl -lpthread \
|
||||
* -o <out> <prog>.c runtime/el_runtime.c
|
||||
*
|
||||
* With liboqs (post-quantum stack):
|
||||
* cc -std=c11 -I runtime -lcurl -lpthread -loqs -lcrypto \
|
||||
* -o <out> <prog>.c runtime/el_runtime.c
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
||||
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
||||
#define EL_INT(v) (v)
|
||||
#define EL_NULL ((el_val_t)0)
|
||||
|
||||
/* Float values share the el_val_t (int64) slot via a bit-cast.
|
||||
* The codegen emits Float literals as `el_from_float(<dbl>)` so the
|
||||
* underlying bits represent the IEEE 754 double. Float-aware builtins
|
||||
* (math, format, json) round-trip via these helpers. */
|
||||
static inline double el_to_float(el_val_t v) {
|
||||
union { int64_t i; double f; } u;
|
||||
u.i = (int64_t)v;
|
||||
return u.f;
|
||||
}
|
||||
|
||||
static inline el_val_t el_from_float(double f) {
|
||||
union { double f; int64_t i; } u;
|
||||
u.f = f;
|
||||
return (el_val_t)u.i;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ── I/O ──────────────────────────────────────────────────────────────────── */
|
||||
|
||||
void println(el_val_t s);
|
||||
void print(el_val_t s);
|
||||
el_val_t readline(void);
|
||||
|
||||
/* ── String builtins ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t str_eq(el_val_t a, el_val_t b);
|
||||
el_val_t str_starts_with(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_ends_with(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_len(el_val_t s);
|
||||
el_val_t str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t int_to_str(el_val_t n);
|
||||
el_val_t str_to_int(el_val_t s);
|
||||
el_val_t str_slice(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t str_contains(el_val_t s, el_val_t sub);
|
||||
el_val_t str_replace(el_val_t s, el_val_t from, el_val_t to);
|
||||
el_val_t str_to_upper(el_val_t s);
|
||||
el_val_t str_to_lower(el_val_t s);
|
||||
el_val_t str_trim(el_val_t s);
|
||||
|
||||
/* ── Math ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_abs(el_val_t n);
|
||||
el_val_t el_max(el_val_t a, el_val_t b);
|
||||
el_val_t el_min(el_val_t a, el_val_t b);
|
||||
|
||||
/* ── 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);
|
||||
|
||||
/* ── Arena scoping ────────────────────────────────────────────────────────────
|
||||
* el_arena_push() activates the string arena (if not already active) and
|
||||
* returns a mark; el_arena_pop(mark) frees all strings allocated since that
|
||||
* mark. Used by codegen for per-function/statement scoping and by long-running
|
||||
* EL loops (e.g. the soul daemon's awareness tick) to reclaim per-iteration
|
||||
* allocations. */
|
||||
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_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_delete_json(el_val_t url, el_val_t json_body);
|
||||
void http_serve(el_val_t port, el_val_t handler);
|
||||
void http_set_handler(el_val_t name);
|
||||
|
||||
/* HTTP server v2 ─────────────────────────────────────────────────────────────
|
||||
* Same dispatch model as http_serve, but the handler signature is widened:
|
||||
*
|
||||
* el_val_t handler(method, path, headers_map, body)
|
||||
*
|
||||
* `headers_map` is an ElMap from lowercased header name → header value (both
|
||||
* Strings). Repeated headers are joined with ", " per RFC 7230.
|
||||
*
|
||||
* Response value: the handler may return either
|
||||
* (a) a plain body string — same auto-content-type / 200-OK behaviour as
|
||||
* http_serve (3-arg) — or
|
||||
* (b) a response envelope built with `http_response(status, headers_json,
|
||||
* body)`. The runtime detects the envelope discriminator
|
||||
* `"el_http_response":1` at the start of the returned string and
|
||||
* unpacks status / headers / body before sending.
|
||||
*
|
||||
* The 3-arg http_serve(port, handler) remains supported unchanged for
|
||||
* existing handlers (e.g. products/web/server.el): it dispatches with
|
||||
* (method, path, body), hardcodes 200 OK, and auto-detects content type. */
|
||||
void http_serve_v2(el_val_t port, el_val_t handler);
|
||||
void http_set_handler_v2(el_val_t name);
|
||||
|
||||
/* Non-blocking variant of http_serve: runs the accept loop in a background
|
||||
* pthread and returns immediately so the caller can continue (used by the
|
||||
* soul daemon to run awareness_run() after starting its HTTP API). */
|
||||
void http_serve_async(el_val_t port, el_val_t handler);
|
||||
|
||||
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
||||
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
||||
* returned string carries the discriminator `{"el_http_response":1,...}`
|
||||
* which the runtime's send-path detects and unpacks. Detection happens
|
||||
* uniformly inside http_send_response, so a 3-arg handler may also return
|
||||
* an envelope. The 3-arg variant remains documented as a fixed 200-OK
|
||||
* auto-content-type contract for legacy handlers that return plain bodies. */
|
||||
el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
|
||||
/* HTTP timeout — every libcurl request honors EL_HTTP_TIMEOUT_MS (default
|
||||
* 60000ms). Read lazily on first use, so setting the env var any time before
|
||||
* the first http_* call is sufficient. */
|
||||
|
||||
/* Streaming variants — write the response body straight to a file via
|
||||
* libcurl's CURLOPT_WRITEFUNCTION = fwrite. These bypass the el_val_t string
|
||||
* wrapper entirely, so binary payloads (audio/mpeg, image/png, etc.) survive
|
||||
* embedded NUL bytes that would truncate a strlen()-based code path.
|
||||
*
|
||||
* Both honor EL_HTTP_TIMEOUT_MS, follow redirects, and accept the same
|
||||
* `headers_map` shape as http_post_with_headers (ElMap of String→String).
|
||||
*
|
||||
* Return value: 1 on success (file fully written), 0 on any failure
|
||||
* (network, file open, partial write). On failure the output file is removed
|
||||
* so callers cannot mistake a partially-written file for a valid one. */
|
||||
el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el_val_t output_path);
|
||||
el_val_t http_get_to_file(el_val_t url, el_val_t headers_map, el_val_t output_path);
|
||||
|
||||
/* ── URL encoding ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t url_encode(el_val_t s); /* RFC 3986 unreserved set */
|
||||
el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */
|
||||
|
||||
/* ── 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);
|
||||
|
||||
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t fs_read(el_val_t path);
|
||||
el_val_t fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t fs_list(el_val_t path);
|
||||
el_val_t fs_exists(el_val_t path);
|
||||
el_val_t fs_mkdir(el_val_t path); /* mkdir -p, mode 0755 */
|
||||
|
||||
/* Length-explicit binary write. `length` is an Int (el_val_t holding the
|
||||
* byte count). The caller knows the length from context — typically because
|
||||
* `bytes` came from base64_decode (which produces a magic-tagged binary
|
||||
* buffer with embedded NULs possible) and the caller already tracks the
|
||||
* decoded length, OR because the bytes came from a fixed-size source
|
||||
* (sha256_bytes = 32, hmac_sha256_bytes = 32). Bypasses strlen entirely.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure (invalid path, can't open, partial
|
||||
* write, negative length). On partial-write failure, the file is removed
|
||||
* so callers cannot read back a truncated artefact. */
|
||||
el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t length);
|
||||
|
||||
/* ── JSON ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t json_get(el_val_t json, el_val_t key);
|
||||
el_val_t json_parse(el_val_t s);
|
||||
el_val_t json_stringify(el_val_t v);
|
||||
el_val_t json_get_string(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_int(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_float(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_bool(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_raw(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value);
|
||||
el_val_t json_array_len(el_val_t json_str);
|
||||
el_val_t json_array_get(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_array_get_string(el_val_t json_str, el_val_t index);
|
||||
|
||||
/* ── Time ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t time_now(void);
|
||||
el_val_t time_now_utc(void);
|
||||
el_val_t sleep_secs(el_val_t secs);
|
||||
el_val_t sleep_ms(el_val_t ms);
|
||||
el_val_t time_format(el_val_t ts, el_val_t fmt);
|
||||
el_val_t time_to_parts(el_val_t ts);
|
||||
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
|
||||
el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit);
|
||||
el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
|
||||
|
||||
/* ── 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);
|
||||
|
||||
/* ── 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 ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
void exit_program(el_val_t code);
|
||||
el_val_t getpid_now(void);
|
||||
|
||||
/* ── CGI identity ─────────────────────────────────────────────────────────────
|
||||
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
|
||||
* Records the program's DHARMA identity before any other code executes. */
|
||||
|
||||
void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
el_val_t network, el_val_t engram);
|
||||
|
||||
/* ── DHARMA network builtins ─────────────────────────────────────────────────
|
||||
* Available to CGI programs (declared with a `cgi {}` block).
|
||||
*
|
||||
* Peers are addressed by `dharma_id` of the form
|
||||
* "<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_prune_telemetry(el_val_t older_than_ms);
|
||||
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);
|
||||
/* Tiered paged-store entry points (ENGRAM_STORE=1). engram_store_boot opens the
|
||||
* durable store (import-once / WAL-replay) and loads it resident; checkpoint pushes
|
||||
* the resident graph's current field state (incl. learned hebb + activation-formed
|
||||
* edges) through the WAL and flushes; close checkpoints + closes. No-ops when off. */
|
||||
el_val_t engram_store_boot(el_val_t data_dir);
|
||||
el_val_t engram_store_checkpoint(void);
|
||||
el_val_t engram_store_close(void);
|
||||
|
||||
/* 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_get_node_by_label(el_val_t label);
|
||||
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_retrieve_geometric_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_scan_nodes_emb_json(el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_dreams_json(el_val_t since_ms);
|
||||
/* §5 geometry operators as EL builtins (read-only; seed-id CSV args). */
|
||||
el_val_t engram_geo_descriptor_json(el_val_t seeds);
|
||||
el_val_t engram_geo_overlap_json(el_val_t a_seeds, el_val_t b_seeds);
|
||||
el_val_t engram_geo_subtract_json(el_val_t a_seeds, el_val_t b_seeds, el_val_t mode);
|
||||
el_val_t engram_geo_combine_json(el_val_t a_seeds, el_val_t b_seeds);
|
||||
el_val_t engram_geo_distance_json(el_val_t a_seeds, el_val_t b_seeds);
|
||||
el_val_t engram_geo_analogy_json(el_val_t a_seeds, el_val_t b_seeds);
|
||||
/* reasoning layer (compositions over §5 operators). ANALOGY maps cleanly to the
|
||||
* flat-CSV seed ABI; the other modes take set/point/timestamp inputs deferred from
|
||||
* this ABI (see engram_reason.h / the reasoning-operators runbook). */
|
||||
el_val_t engram_reason_analogy_json(el_val_t a_seeds, el_val_t b_seeds, el_val_t c_seeds);
|
||||
/* COGNITION (2026-08-14): THE ONE OPERATION + grounding, surfaced live. */
|
||||
el_val_t engram_think_json(el_val_t seeds, el_val_t faculty);
|
||||
el_val_t engram_ground_json(el_val_t claim, el_val_t evidence, el_val_t for_whom);
|
||||
el_val_t engram_assert_json(el_val_t claim_id, el_val_t for_whom, el_val_t floor);
|
||||
el_val_t engram_attend_json(el_val_t node_id, el_val_t observer, el_val_t salience);
|
||||
el_val_t engram_correspondence_beat_json(el_val_t seeds, el_val_t faculty, el_val_t keystone);
|
||||
el_val_t engram_consolidate_permanence(el_val_t node_id);
|
||||
el_val_t engram_age_field(el_val_t delta_ms);
|
||||
el_val_t engram_age_field_catchup(void);
|
||||
el_val_t engram_chrono_persist_tick(void);
|
||||
el_val_t engram_chrono_tick(void);
|
||||
el_val_t engram_boundary_beat(el_val_t op_name); /* API-reshape decorator-seam auto-emit */
|
||||
el_val_t engram_self_anchor_capture(void);
|
||||
el_val_t engram_self_drift_json(void);
|
||||
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_act_stats_json(void);
|
||||
el_val_t engram_text_health_json(void);
|
||||
el_val_t engram_cosine_sim(el_val_t id_a, el_val_t id_b);
|
||||
/* M10 reified-neighborhood read-only HTTP surface (2026-08-13). List/detail of
|
||||
* the resident reify index; [] until the offline reify writer has run. */
|
||||
el_val_t engram_geo_reify_list_json(void);
|
||||
el_val_t engram_geo_reify_get_json(el_val_t id);
|
||||
/* WRITE: run reification — persist Neighborhood nodes + member edges, rebuild the
|
||||
* resident index. Wires the previously-dormant engram_geo_reify_store. */
|
||||
el_val_t engram_geo_reify_run_json(void);
|
||||
/* SELF-REIFICATION (2026-08-14). ON-BEAT autonomous neighborhood formation:
|
||||
* gated by ENGRAM_SELF_REIFY (default off → returns {"enabled":false}, writes
|
||||
* nothing — the live binary is byte-inert until the flag is set). When on, runs
|
||||
* ONE bounded, incremental, idempotent reification pass (change-detection skips
|
||||
* unchanged hubs → no re-append; grounded names; residue-preserving supersession;
|
||||
* soft/overlapping membership; nests only when something changed). Meant to be
|
||||
* pumped every heartbeat next to Hebbian consolidation. Returns
|
||||
* {"enabled":true,"reified":N,"skipped":S,"superseded":P,"nested":X,"resident":M,"wrote":b}. */
|
||||
el_val_t engram_self_reify_beat_json(void);
|
||||
/* ASYNC EXPLICIT OVERRIDE (degenerate manual case). Rename a live neighborhood
|
||||
* by id: writes a superseding record with the new name, prepends a residue entry
|
||||
* (cause="explicit-override", prior name), keeps the same geometry/members. Never
|
||||
* blocks the beat. Returns {"ok":true,"renamed":<old>,"new_id":<new>,"name":..}. */
|
||||
el_val_t engram_neighborhood_rename_json(el_val_t id, el_val_t name);
|
||||
/* Orphan prevention: form up to k semantic-similar edges to a node's nearest
|
||||
* embedded neighbors (kNN). engram_nearest_json is the read-only probe. */
|
||||
el_val_t engram_autoconnect_node(el_val_t id, el_val_t k, el_val_t min_sim_pct);
|
||||
el_val_t engram_nearest_json(el_val_t id, el_val_t k);
|
||||
/* Telemetry off-graph: append one ISE JSON line to the state-event log tier. */
|
||||
el_val_t engram_ise_log_append(el_val_t content);
|
||||
/* Destructively pop up to `max` newly-formed Hebbian associations as a JSON
|
||||
* array of {from_id,to_id,weight,hebb}. The learning process (soul daemon) is
|
||||
* not the process that owns persistence (engram HTTP server); this is how a
|
||||
* self-formed association crosses that boundary. (2026-08-07 self-review.) */
|
||||
el_val_t engram_hebb_drain_json(el_val_t max);
|
||||
/* Document frequency of a term across node labels — term-specificity signal
|
||||
* for curiosity seed selection. (2026-08-03 self-review.) */
|
||||
el_val_t engram_label_df(el_val_t term);
|
||||
/* Best curiosity seed from one node: argmax over idf·position·casing across
|
||||
* the candidate tokens of its label, falling back to its content when the
|
||||
* label is a sentinel. Excludes pipe-delimited tabu terms during selection
|
||||
* and gates candidates to the df band [min_df, max_df]. Returns "" when
|
||||
* nothing qualifies. (2026-08-13 self-review.) */
|
||||
el_val_t engram_salient_term(el_val_t node_id, el_val_t max_df,
|
||||
el_val_t min_df, el_val_t tabu);
|
||||
el_val_t engram_embed_backfill(el_val_t count);
|
||||
el_val_t engram_list_layers_json(void);
|
||||
/* Working memory introspection — count, mean weight, and top-N snapshot.
|
||||
* Ported from 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);
|
||||
|
||||
/* ── WAL + compaction + integrity (ENGRAM_WAL=on; design doc §§3-14,§18) ──── */
|
||||
int engram_wal_enabled(void);
|
||||
el_val_t engram_crc32(el_val_t s);
|
||||
el_val_t engram_wal_boot(el_val_t dir); /* replay + open; returns records */
|
||||
el_val_t engram_wal_open_dir(el_val_t dir);
|
||||
el_val_t engram_wal_node_put(el_val_t dir, el_val_t id);
|
||||
el_val_t engram_wal_edges_since(el_val_t dir, el_val_t start_count);
|
||||
el_val_t engram_wal_hebb_batch(el_val_t dir, el_val_t start_count);
|
||||
el_val_t engram_wal_forget(el_val_t dir, el_val_t id);
|
||||
el_val_t engram_wal_compact(el_val_t dir);
|
||||
el_val_t engram_wal_maybe_compact(el_val_t dir);
|
||||
el_val_t engram_resolve_data_dir(void); /* §18.2 fail-loud default */
|
||||
el_val_t engram_is_protected(el_val_t id); /* §18.1/18.3 derived set */
|
||||
el_val_t engram_protected_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);
|
||||
/* LLM token telemetry (CCR §4.4): usage.{input,output}_tokens parsed from every
|
||||
* response. Returns Map{input_tokens,output_tokens,total_input_tokens,
|
||||
* total_output_tokens,calls}. */
|
||||
el_val_t llm_last_usage(void);
|
||||
/* Fold usage.{input,output}_tokens from a raw messages response into the counters.
|
||||
* Called internally on every LLM response; exported for offline testing. */
|
||||
void llm_record_usage(const char* resp);
|
||||
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) */
|
||||
|
||||
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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+43
-3
@@ -6,15 +6,55 @@
|
||||
//
|
||||
// Dependencies: runtime/string.el, runtime/json.el
|
||||
|
||||
// --- Validation (defense in depth) ---
|
||||
// el_val_t is an untyped machine word, so a wrong TYPE can't be caught here — but a
|
||||
// wrong VALUE can (a tier in the node_type slot, an empty/garbage string, an int, a
|
||||
// path, a model name, a cgi id). Reject loudly instead of silently writing junk.
|
||||
|
||||
fn engram_valid_node_type(t: String) -> Bool {
|
||||
return str_eq(t, "Memory") || str_eq(t, "Knowledge") || str_eq(t, "Belief")
|
||||
|| str_eq(t, "Project") || str_eq(t, "Tag") || str_eq(t, "BacklogItem")
|
||||
|| str_eq(t, "Artifact") || str_eq(t, "Conversation") || str_eq(t, "ExecutionContext")
|
||||
|| str_eq(t, "InternalStateEvent") || str_eq(t, "Self") || str_eq(t, "Entity")
|
||||
|| str_eq(t, "Process") || str_eq(t, "ConfigEntry") || str_eq(t, "Concept") || str_eq(t, "Imprint")
|
||||
|| str_eq(t, "SessionSummary")
|
||||
}
|
||||
|
||||
fn engram_valid_tier(t: String) -> Bool {
|
||||
return str_eq(t, "Semantic") || str_eq(t, "Episodic") || str_eq(t, "Working")
|
||||
|| str_eq(t, "Procedural") || str_eq(t, "Canonical") || str_eq(t, "Note") || str_eq(t, "Lesson")
|
||||
}
|
||||
|
||||
// --- Node creation ---
|
||||
|
||||
fn engram_node(content: String, node_type: String, salience: Float) -> String {
|
||||
if !engram_valid_node_type(node_type) {
|
||||
__println("[engram] REJECTED node write — invalid node_type '" + node_type + "'")
|
||||
return ""
|
||||
}
|
||||
return __engram_node(content, node_type, salience)
|
||||
}
|
||||
|
||||
fn engram_node_full(content: String, nt: String, sal: Float, imp: Float,
|
||||
source: String, lang: String, ts: Int, tags: String) -> String {
|
||||
return __engram_node_full(content, nt, sal, imp, source, lang, ts, tags)
|
||||
// Signature MUST match the C primitive __engram_node_full exactly (el_seed.h):
|
||||
// (content, node_type, label, salience, importance, confidence, tier, tags)
|
||||
// The previous wrapper declared a stale 8-arg schema with wrong names AND types
|
||||
// (sal:Float at the label slot, ts:Int at the tier slot). Because el_val_t is an
|
||||
// untyped machine word, the EL compiler coerced caller args to those wrong param
|
||||
// types and then forwarded them BY POSITION into the C function — so tier received
|
||||
// an int, importance/confidence received strings, label received a float, etc.
|
||||
// That is the field-corruption bug. Match the contract 1:1 — no coercion, no reorder.
|
||||
fn engram_node_full(content: String, node_type: String, label: String,
|
||||
salience: Float, importance: Float, confidence: Float,
|
||||
tier: String, tags: String) -> String {
|
||||
if !engram_valid_node_type(node_type) {
|
||||
__println("[engram] REJECTED node write — invalid node_type '" + node_type + "' (label=" + label + ")")
|
||||
return ""
|
||||
}
|
||||
if !engram_valid_tier(tier) {
|
||||
__println("[engram] REJECTED node write — invalid tier '" + tier + "' (node_type=" + node_type + ", label=" + label + ")")
|
||||
return ""
|
||||
}
|
||||
return __engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags)
|
||||
}
|
||||
|
||||
// --- Node retrieval ---
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
/* engram_cognition.c — THE ONE OPERATION. See engram_cognition.h.
|
||||
* Pure over its inputs (think/warp/express); persistence is additive/supersede
|
||||
* only. stdlib + libm + engram_store/reason/geometry. Touches no live daemon. */
|
||||
#include "engram_cognition.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
/* ── small helpers ──────────────────────────────────────────────────────────── */
|
||||
static double vdot(const float* a, const float* b, int dim) {
|
||||
double s = 0; for (int i = 0; i < dim; i++) s += (double)a[i] * (double)b[i]; return s;
|
||||
}
|
||||
static double vnorm(const float* a, int dim) { return sqrt(vdot(a, a, dim)); }
|
||||
static char* dupstr(const char* s) {
|
||||
if (!s) return NULL; size_t n = strlen(s) + 1; char* p = malloc(n);
|
||||
if (p) memcpy(p, s, n); return p;
|
||||
}
|
||||
static double clampd(double x, double lo, double hi){ return x<lo?lo:(x>hi?hi:x); }
|
||||
|
||||
/* ═══════════════════════════════════════════════ Stance lifecycle ════════════ */
|
||||
int cog_stance_init(CogStance* s, const char* id, const char* faculty,
|
||||
const char* anchor_region, const char* for_whom,
|
||||
const GeoDescriptor* region) {
|
||||
if (!s || !region) return -1;
|
||||
memset(s, 0, sizeof *s);
|
||||
s->id = dupstr(id); s->faculty = dupstr(faculty);
|
||||
s->anchor_region = dupstr(anchor_region); s->for_whom = dupstr(for_whom);
|
||||
s->dim = region->dim;
|
||||
s->n_axes = region->n_axes > COG_MAX_AXES ? COG_MAX_AXES : region->n_axes;
|
||||
for (int k = 0; k < COG_MAX_AXES; k++) s->axis_gain[k] = 1.0;
|
||||
s->ext_floor = 1.0; s->drop_frac = 0.5; s->assoc_floor = 0.2;
|
||||
s->bias_dir = NULL;
|
||||
s->reliability = 0.5; /* uninformed prior on our own track record */
|
||||
return 0;
|
||||
}
|
||||
void cog_stance_set_frozen_defaults(CogStance* s) {
|
||||
if (!s) return;
|
||||
for (int k = 0; k < COG_MAX_AXES; k++) s->axis_gain[k] = 1.0;
|
||||
s->ext_floor = 1.0; s->drop_frac = 0.5; s->assoc_floor = 0.2;
|
||||
free(s->bias_dir); s->bias_dir = NULL;
|
||||
}
|
||||
void cog_stance_free(CogStance* s) {
|
||||
if (!s) return;
|
||||
free(s->id); free(s->faculty); free(s->anchor_region); free(s->for_whom);
|
||||
free(s->bias_dir);
|
||||
s->id = s->faculty = s->anchor_region = s->for_whom = NULL; s->bias_dir = NULL;
|
||||
}
|
||||
int cog_is_keystone(const CogKeystoneSet* ks, const CogStance* s) {
|
||||
if (!s) return 0;
|
||||
if (s->keystone) return 1;
|
||||
if (!ks || !s->id) return 0;
|
||||
for (int i = 0; i < ks->n; i++)
|
||||
if (ks->ids[i] && (strcmp(ks->ids[i], s->id) == 0 ||
|
||||
(s->anchor_region && strcmp(ks->ids[i], s->anchor_region) == 0))) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════ warped fit (think step 2) ══ */
|
||||
int cog_warped_fit(const GeoDescriptor* g, const float* x,
|
||||
const CogStance* st, GeoFit* out) {
|
||||
if (!g || !x || !out || g->dim <= 0 || !g->centroid) return -1;
|
||||
double ext_floor = (st && st->ext_floor > 0) ? st->ext_floor : 1.0;
|
||||
int dim = g->dim;
|
||||
double rr = 0;
|
||||
float* r = malloc((size_t)dim * sizeof(float));
|
||||
if (!r) return -1;
|
||||
for (int i = 0; i < dim; i++) { double d = (double)x[i] - (double)g->centroid[i]; r[i] = (float)d; rr += d * d; }
|
||||
double maha2 = 0, ss_in = 0;
|
||||
for (int k = 0; k < g->n_axes; k++) {
|
||||
const float* ax = g->axes[k].axis; if (!ax) continue;
|
||||
double proj = vdot(r, ax, dim);
|
||||
double gain = (st && k < st->n_axes && st->axis_gain[k] > 0) ? st->axis_gain[k] : 1.0;
|
||||
double den = g->axes[k].extent * gain; if (den < ext_floor) den = ext_floor;
|
||||
maha2 += (proj / den) * (proj / den);
|
||||
ss_in += proj * proj;
|
||||
}
|
||||
double ortho2 = rr - ss_in; if (ortho2 < 0) ortho2 = 0;
|
||||
double dist2 = maha2 + ortho2 / (ext_floor * ext_floor);
|
||||
out->mahalanobis = sqrt(maha2); out->ortho_residual = sqrt(ortho2);
|
||||
out->distance = sqrt(dist2); out->score = 1.0 / (1.0 + dist2);
|
||||
free(r);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════ think (the ONE operation) ══ */
|
||||
void engram_gradient_free(GeoGradient* g) {
|
||||
if (!g) return; free(g->direction); g->direction = NULL;
|
||||
}
|
||||
int engram_think(const GeoDescriptor* region, const float* anchor,
|
||||
const CogStance* stance, GeoGradient* out) {
|
||||
if (!region || !out || region->dim <= 0 || !region->centroid) return -1;
|
||||
int dim = region->dim;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->dim = dim;
|
||||
const float* x = anchor ? anchor : region->centroid; /* re-origin (step 1) */
|
||||
|
||||
GeoFit f;
|
||||
if (cog_warped_fit(region, x, stance, &f) != 0) return -1; /* fit (step 2) */
|
||||
|
||||
/* step 3 — emit a GRADIENT: warped steepest DESCENT of the fit distance². */
|
||||
double ext_floor = (stance && stance->ext_floor > 0) ? stance->ext_floor : 1.0;
|
||||
float* grad = calloc((size_t)dim, sizeof(float)); /* ∇ dist² wrt x */
|
||||
float* r = malloc((size_t)dim * sizeof(float));
|
||||
out->direction = malloc((size_t)dim * sizeof(float));
|
||||
if (!grad || !r || !out->direction) { free(grad); free(r); free(out->direction); out->direction = NULL; return -1; }
|
||||
for (int i = 0; i < dim; i++) r[i] = (float)((double)x[i] - (double)region->centroid[i]);
|
||||
/* in-subspace: Σ_k 2 (proj/den²) a_k ; also accumulate Σ proj a_k for ortho part */
|
||||
float* proj_sum = calloc((size_t)dim, sizeof(float));
|
||||
if (!proj_sum) { free(grad); free(r); free(out->direction); out->direction = NULL; return -1; }
|
||||
for (int k = 0; k < region->n_axes; k++) {
|
||||
const float* ax = region->axes[k].axis; if (!ax) continue;
|
||||
double proj = vdot(r, ax, dim);
|
||||
double gain = (stance && k < stance->n_axes && stance->axis_gain[k] > 0) ? stance->axis_gain[k] : 1.0;
|
||||
double den = region->axes[k].extent * gain; if (den < ext_floor) den = ext_floor;
|
||||
double coef = 2.0 * proj / (den * den);
|
||||
for (int i = 0; i < dim; i++) { grad[i] += (float)(coef * ax[i]); proj_sum[i] += (float)(proj * ax[i]); }
|
||||
}
|
||||
/* orthogonal: (2 r − 2 Σ proj a_k) / ext_floor² */
|
||||
double inv_f2 = 1.0 / (ext_floor * ext_floor);
|
||||
for (int i = 0; i < dim; i++)
|
||||
grad[i] += (float)((2.0 * (double)r[i] - 2.0 * (double)proj_sum[i]) * inv_f2);
|
||||
free(proj_sum);
|
||||
|
||||
/* steering = −grad (descent), seeded by the stance's bias_dir. */
|
||||
for (int i = 0; i < dim; i++) out->direction[i] = -grad[i];
|
||||
if (stance && stance->bias_dir) {
|
||||
double gn = vnorm(grad, dim), bn = vnorm(stance->bias_dir, dim);
|
||||
if (bn > 1e-12) {
|
||||
double scale = (gn > 1e-12 ? gn : 1.0); /* seed at the gradient's scale */
|
||||
for (int i = 0; i < dim; i++)
|
||||
out->direction[i] += (float)(scale * (double)stance->bias_dir[i] / bn);
|
||||
}
|
||||
}
|
||||
double dn = vnorm(out->direction, dim);
|
||||
if (dn > 1e-12) for (int i = 0; i < dim; i++) out->direction[i] /= (float)dn;
|
||||
else for (int i = 0; i < dim; i++) out->direction[i] = 0.0f; /* at rest */
|
||||
|
||||
out->spread = f.distance; /* spiked (0) .. diffuse */
|
||||
out->confidence = stance ? stance->reliability : 0.5;
|
||||
out->magnitude = f.score; /* the read's membership */
|
||||
out->anchor_id = region->hub_id; /* borrowed vantage id */
|
||||
out->n_support = region->n_members;
|
||||
out->stance_id = stance ? stance->id : NULL;
|
||||
free(grad); free(r);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* EXPRESSION — the ONLY collapse to a point (a separate faculty from think). */
|
||||
int engram_express(const GeoGradient* g, const float* anchor, float* out_point) {
|
||||
if (!g || !anchor || !out_point || !g->direction) return -1;
|
||||
double commit = clampd(g->confidence, 0.0, 1.0); /* confident => commit far */
|
||||
for (int i = 0; i < g->dim; i++)
|
||||
out_point[i] = anchor[i] + g->direction[i] * (float)commit;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════ Stance serialization ════════ */
|
||||
/* Compact line schema "STNC1" (mirrors the reify "GEO1" precedent). */
|
||||
char* cog_stance_to_metadata(const CogStance* s) {
|
||||
if (!s) return NULL;
|
||||
size_t cap = 256 + (size_t)s->n_axes * 24 + (size_t)(s->bias_dir ? s->dim * 16 : 0);
|
||||
char* buf = malloc(cap); if (!buf) return NULL;
|
||||
size_t o = 0;
|
||||
o += (size_t)snprintf(buf + o, cap - o, "%s\n", COG_STANCE_META_MAGIC);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "f %s\n", s->faculty ? s->faculty : "-");
|
||||
o += (size_t)snprintf(buf + o, cap - o, "r %s\n", s->anchor_region ? s->anchor_region : "-");
|
||||
o += (size_t)snprintf(buf + o, cap - o, "w %s\n", s->for_whom ? s->for_whom : "-");
|
||||
o += (size_t)snprintf(buf + o, cap - o, "k %d\n", s->keystone);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "d %d %d\n", s->dim, s->n_axes);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "s %.9g %.9g %.9g\n", s->ext_floor, s->drop_frac, s->assoc_floor);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "g");
|
||||
for (int k = 0; k < s->n_axes; k++) o += (size_t)snprintf(buf + o, cap - o, " %.9g", s->axis_gain[k]);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "\n");
|
||||
o += (size_t)snprintf(buf + o, cap - o, "c %lld %.9g %.9g %.9g %.9g\n",
|
||||
(long long)s->n_trials, s->brier_sum, s->reliability, s->ema_error, s->last_error);
|
||||
if (s->bias_dir) {
|
||||
o += (size_t)snprintf(buf + o, cap - o, "b");
|
||||
for (int i = 0; i < s->dim; i++) o += (size_t)snprintf(buf + o, cap - o, " %.9g", (double)s->bias_dir[i]);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "\n");
|
||||
}
|
||||
(void)o;
|
||||
return buf;
|
||||
}
|
||||
int cog_stance_to_node(const CogStance* s, StoreNode* out) {
|
||||
if (!s || !out) return -1;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->id = dupstr(s->id);
|
||||
out->node_type = dupstr(COG_STANCE_NODE_TYPE);
|
||||
out->content = dupstr(s->faculty ? s->faculty : "stance");
|
||||
out->label = dupstr(s->faculty ? s->faculty : "stance");
|
||||
out->metadata = cog_stance_to_metadata(s);
|
||||
out->importance = s->reliability; /* cached denormalized readout (§2.1) */
|
||||
out->confidence = s->reliability;
|
||||
out->temporal_decay_rate = 0.0;
|
||||
return (out->id && out->node_type && out->metadata) ? 0 : -1;
|
||||
}
|
||||
static int parse_floats(const char* line, double* out, int max) {
|
||||
int n = 0; const char* p = line;
|
||||
while (*p && n < max) {
|
||||
while (*p == ' ') p++;
|
||||
if (!*p) break;
|
||||
char* end; double v = strtod(p, &end);
|
||||
if (end == p) break;
|
||||
out[n++] = v; p = end;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
int cog_stance_from_node(const StoreNode* n, CogStance* out) {
|
||||
if (!n || !out || !n->metadata) return -1;
|
||||
memset(out, 0, sizeof *out);
|
||||
for (int k = 0; k < COG_MAX_AXES; k++) out->axis_gain[k] = 1.0;
|
||||
out->ext_floor = 1.0; out->drop_frac = 0.5; out->assoc_floor = 0.2; out->reliability = 0.5;
|
||||
out->id = dupstr(n->id);
|
||||
/* verify magic on first line */
|
||||
const char* m = n->metadata;
|
||||
if (strncmp(m, COG_STANCE_META_MAGIC, strlen(COG_STANCE_META_MAGIC)) != 0) return -1;
|
||||
char* copy = dupstr(m); if (!copy) return -1;
|
||||
for (char* line = strtok(copy, "\n"); line; line = strtok(NULL, "\n")) {
|
||||
if (line[0] == '\0' || line[1] != ' ') {
|
||||
if (line[0] == 'g' || line[0] == 'b') { /* vector lines: tag then values */ }
|
||||
else continue;
|
||||
}
|
||||
char tag = line[0];
|
||||
const char* rest = line + 1; while (*rest == ' ') rest++;
|
||||
if (tag == 'f') { free(out->faculty); out->faculty = (strcmp(rest, "-") ? dupstr(rest) : NULL); }
|
||||
else if (tag == 'r') { free(out->anchor_region); out->anchor_region = (strcmp(rest, "-") ? dupstr(rest) : NULL); }
|
||||
else if (tag == 'w') { free(out->for_whom); out->for_whom = (strcmp(rest, "-") ? dupstr(rest) : NULL); }
|
||||
else if (tag == 'k') { out->keystone = atoi(rest); }
|
||||
else if (tag == 'd') { int a=0,b=0; sscanf(rest, "%d %d", &a, &b); out->dim = a; out->n_axes = b > COG_MAX_AXES ? COG_MAX_AXES : b; }
|
||||
else if (tag == 's') { double v[3]={1,0.5,0.2}; parse_floats(rest, v, 3); out->ext_floor=v[0]; out->drop_frac=v[1]; out->assoc_floor=v[2]; }
|
||||
else if (tag == 'g') { double v[COG_MAX_AXES]; int c=parse_floats(rest, v, COG_MAX_AXES); for(int k=0;k<c;k++) out->axis_gain[k]=v[k]; }
|
||||
else if (tag == 'c') { double v[5]={0,0,0.5,0,0}; parse_floats(rest, v, 5); out->n_trials=(int64_t)v[0]; out->brier_sum=v[1]; out->reliability=v[2]; out->ema_error=v[3]; out->last_error=v[4]; }
|
||||
else if (tag == 'b') { if (out->dim>0){ out->bias_dir=calloc((size_t)out->dim,sizeof(float)); double v[4096]; int c=parse_floats(rest,v,out->dim<4096?out->dim:4096); for(int i=0;i<c;i++) out->bias_dir[i]=(float)v[i]; } }
|
||||
}
|
||||
free(copy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════ grounding as a RELATION ═════ */
|
||||
static int put_edge(EngramPagedStore* s, const char* id, const char* from, const char* to,
|
||||
const char* relation, double weight, const char* meta) {
|
||||
StoreEdge e; memset(&e, 0, sizeof e);
|
||||
e.id = (char*)id; e.from_id = (char*)from; e.to_id = (char*)to;
|
||||
e.relation = (char*)relation; e.weight = weight; e.confidence = weight;
|
||||
e.metadata = (char*)meta;
|
||||
return store_put_edge(s, &e);
|
||||
}
|
||||
int cog_ground_edge(EngramPagedStore* s, const char* claim_id,
|
||||
const char* evidence_id, double grounding, const char* for_whom) {
|
||||
if (!s || !claim_id || !evidence_id) return -1;
|
||||
char id[512], meta[256];
|
||||
snprintf(id, sizeof id, "gb-%s-%s-%s", claim_id, evidence_id, for_whom ? for_whom : "global");
|
||||
snprintf(meta, sizeof meta, "for_whom=%s", for_whom ? for_whom : "-");
|
||||
return put_edge(s, id, claim_id, evidence_id, COG_GROUNDED_BY_RELATION, grounding, meta);
|
||||
}
|
||||
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
||||
const char* observer_id, double salience) {
|
||||
if (!s || !node_id || !observer_id) return -1;
|
||||
char id[512];
|
||||
snprintf(id, sizeof id, "st-%s-%s", node_id, observer_id);
|
||||
return put_edge(s, id, node_id, observer_id, COG_SALIENT_TO_RELATION, salience, NULL);
|
||||
}
|
||||
int cog_assert_gate(EngramPagedStore* s, const char* claim_id,
|
||||
const char* for_whom, double floor) {
|
||||
if (!s || !claim_id) return -1;
|
||||
if (!(floor > 0)) floor = 0.5;
|
||||
StoreEdge* edges = NULL; size_t n = 0;
|
||||
if (store_get_edges_from(s, claim_id, &edges, &n) < 0) return -1;
|
||||
double best = 0.0; int found = 0;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (!edges[i].relation || strcmp(edges[i].relation, COG_GROUNDED_BY_RELATION) != 0) continue;
|
||||
/* grounded-for-whom: match observer if requested; global (for_whom=-) always counts */
|
||||
int match = 1;
|
||||
if (for_whom && edges[i].metadata) {
|
||||
const char* fw = strstr(edges[i].metadata, "for_whom=");
|
||||
if (fw) { fw += 9; if (strcmp(fw, for_whom) != 0 && strcmp(fw, "-") != 0) match = 0; }
|
||||
}
|
||||
if (match) { found = 1; if (edges[i].weight > best) best = edges[i].weight; }
|
||||
}
|
||||
store_edges_free(edges, n);
|
||||
if (!found) return 0; /* ungrounded => refuse assertion (still held) */
|
||||
return (best >= floor) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════ THE CORRESPONDENCE-LOOP ═════ */
|
||||
int engram_correspondence_beat(const GeoDescriptor* region, const float* anchor,
|
||||
double outcome_y, CogStance* stance,
|
||||
int learn, double max_step, CogBeatResult* out) {
|
||||
if (!region || !stance || !out) return -1;
|
||||
memset(out, 0, sizeof *out);
|
||||
if (stance->keystone) { learn = 0; out->wrote_keystone = 1; } /* §6: never write a keystone */
|
||||
|
||||
GeoGradient g;
|
||||
if (engram_think(region, anchor, stance, &g) != 0) return -1; /* PREDICTION */
|
||||
double p = g.magnitude;
|
||||
double y = clampd(outcome_y, 0.0, 1.0);
|
||||
double err = fabs(p - y);
|
||||
out->correspondence = 1.0 - err;
|
||||
out->error = err;
|
||||
out->brier = (p - y) * (p - y);
|
||||
|
||||
if (learn) {
|
||||
/* refine warp: gradient descent of (p−y)² wrt each axis_gain.
|
||||
* p = 1/(1+D²); ∂p/∂gain_k = 2 p² proj_k² / (ext_k² gain_k³) (>=0)
|
||||
* ∂(err²)/∂gain_k = 2 (p−y) ∂p/∂gain_k
|
||||
* step = −lr · ∂(err²)/∂gain_k, bounded to ±max_step (metastability). */
|
||||
int dim = region->dim;
|
||||
const float* x = anchor ? anchor : region->centroid;
|
||||
float* r = malloc((size_t)dim * sizeof(float));
|
||||
if (r) {
|
||||
for (int i = 0; i < dim; i++) r[i] = (float)((double)x[i] - (double)region->centroid[i]);
|
||||
double lr = 0.5;
|
||||
double bound = (max_step > 0) ? max_step : 0.05; /* bounded update rate */
|
||||
for (int k = 0; k < region->n_axes && k < stance->n_axes; k++) {
|
||||
const float* ax = region->axes[k].axis; if (!ax) continue;
|
||||
double proj = vdot(r, ax, dim);
|
||||
double ext = region->axes[k].extent; if (ext < 1e-9) ext = 1e-9;
|
||||
double gain = stance->axis_gain[k]; if (gain < 1e-6) gain = 1e-6;
|
||||
double dp_dgain = 2.0 * p * p * (proj * proj) / (ext * ext * gain * gain * gain);
|
||||
double dErr_dgain = 2.0 * (p - y) * dp_dgain;
|
||||
double step = -lr * dErr_dgain;
|
||||
step = clampd(step, -bound, bound);
|
||||
stance->axis_gain[k] = clampd(gain + step, 0.1, 50.0);
|
||||
}
|
||||
free(r);
|
||||
}
|
||||
/* calibration */
|
||||
stance->n_trials += 1;
|
||||
stance->brier_sum += out->brier;
|
||||
stance->last_error = err;
|
||||
stance->ema_error = (stance->n_trials == 1) ? err : 0.9 * stance->ema_error + 0.1 * err;
|
||||
double mean_brier = stance->brier_sum / (double)stance->n_trials;
|
||||
stance->reliability = clampd(1.0 - sqrt(mean_brier), 0.0, 1.0);
|
||||
}
|
||||
out->reliability = stance->reliability;
|
||||
engram_gradient_free(&g);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/* engram_cognition.h — THE ONE OPERATION.
|
||||
*
|
||||
* The buildable form of the "cognition is one operation" theory (design doc
|
||||
* engram/spec/cognitive-architecture.design.md; memory bdc8a488 / d582a766).
|
||||
*
|
||||
* Cognition is ONE operation — think — a directed traversal-READ of the geometry
|
||||
* from an anchor, steered by a learned STANCE, whose output is a GRADIENT (a
|
||||
* direction + spread over the geometry), never a point. The named faculties
|
||||
* (reason / induce / abduce / analogy / relate / plan / ground) are human LABELS
|
||||
* on regions of think's steering space: each faculty == { think + a named stance }.
|
||||
* Collapse-to-a-point happens only at EXPRESSION (a separate faculty), never in think.
|
||||
*
|
||||
* NAMING (Will's directive): the surface verbs name the cognitive ACT being
|
||||
* performed (think / reason / induce / ground / verify), not the internal function
|
||||
* shape. The single frozen primitive underneath every faculty is engram_think,
|
||||
* which composes over engram_reason_point_fit + the §5 geo-algebra. Those never
|
||||
* learn. Only the STANCE learns.
|
||||
*
|
||||
* "Stance" is the theory's steering PRIOR, deliberately named distinctly: in this
|
||||
* codebase the token "prior" already means previous-VERSION (supersession). A
|
||||
* Stance is a learnable bias/disposition over the geometry — which axes matter,
|
||||
* which way pays off, plus a calibrated track record — attached to a faculty-label
|
||||
* and a region, and grounded-for-whom.
|
||||
*
|
||||
* PURE + (mostly) READ-ONLY, stdlib + libm only. think() and the warp are pure
|
||||
* over their inputs. Persistence (Stance <-> StoreNode, grounded-by edges) is the
|
||||
* only part that touches the store, and it is additive / supersede / tombstone —
|
||||
* never mutate-in-place, never delete. It NEVER touches the live daemon: all
|
||||
* offline against a scratch store, per the design's rails.
|
||||
*/
|
||||
#ifndef ENGRAM_COGNITION_H
|
||||
#define ENGRAM_COGNITION_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "engram_geometry.h"
|
||||
#include "engram_reason.h"
|
||||
#include "engram_store.h"
|
||||
|
||||
/* Max principal axes a stance warps (matches GeoParams.top_axes default budget). */
|
||||
#define COG_MAX_AXES 32
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §1 GeoGradient — the OUTPUT of think. A direction + spread over the geometry,
|
||||
* plus the calibrated confidence and the read it was computed against. NOT a point.
|
||||
* A spiked gradient (spread→0) = "exact" (deduction); a spread gradient = "fuzzy"
|
||||
* (prediction). The gradient is ALSO the next steering direction (closed-loop flow).
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
typedef struct {
|
||||
int dim;
|
||||
float* direction; /* unit steering vector in the anchor's frame (owned) */
|
||||
double spread; /* 0 = spiked/exact ... large = diffuse/fuzzy */
|
||||
double confidence; /* calibrated, from the stance's track record (reliab.) */
|
||||
double magnitude; /* THIS read's own membership/fit estimate ∈(0,1].
|
||||
* The scalar an expression faculty would SAMPLE; kept
|
||||
* on the gradient but never used AS a decision by think.*/
|
||||
const char* anchor_id; /* borrowed: the vantage this was read from */
|
||||
int n_support; /* neighborhood members that shaped the read */
|
||||
const char* stance_id; /* borrowed: which stance steered this (provenance) */
|
||||
} GeoGradient;
|
||||
|
||||
void engram_gradient_free(GeoGradient* g);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §2 Stance — the learnable steering prior, as a first-class object. In memory
|
||||
* here; persisted as a StoreNode (node_type "Stance") via cog_stance_*serialize.
|
||||
*
|
||||
* warp: axis_gain[] per-principal-axis multiplier on extents (which axes
|
||||
* matter — gain>1 WIDENS an axis so it penalizes less);
|
||||
* bias_dir[] a steering-direction seed in the region's frame;
|
||||
* scalars faculty constants this stance overrides (ext_floor, etc).
|
||||
* calibration: the track record — the ONLY thing the loop (§4) updates
|
||||
* besides warp: n_trials, a Brier accumulator, reliability
|
||||
* (→ GeoGradient.confidence), and an EMA error.
|
||||
* keystone: if set, the correspondence-loop MUST NEVER write warp or
|
||||
* calibration — read-mostly (self / values). §6 metastability.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
typedef struct {
|
||||
char* id; /* stance node id (owned) */
|
||||
char* faculty; /* the act this stance serves: "induce"|"relate"|... */
|
||||
char* anchor_region; /* node/neighborhood id this stance is attached to */
|
||||
char* for_whom; /* observer id — grounding is relational (NULL=global) */
|
||||
int keystone; /* 1 = read-mostly, loop never writes it (§6) */
|
||||
|
||||
int dim; /* embedding dim of the region */
|
||||
int n_axes; /* how many axis_gain entries are live (<= COG_MAX_AXES)*/
|
||||
double axis_gain[COG_MAX_AXES]; /* per-axis extent multipliers (init 1.0) */
|
||||
float* bias_dir; /* dim floats, steering seed (owned; NULL = none) */
|
||||
double ext_floor; /* faculty scalar: the extent floor (init 1.0) */
|
||||
double drop_frac; /* faculty scalar (causal): confound drop (init 0.5) */
|
||||
double assoc_floor; /* faculty scalar (causal): assoc floor (init 0.2) */
|
||||
|
||||
/* calibration / track record */
|
||||
int64_t n_trials;
|
||||
double brier_sum; /* Σ (p − y)² */
|
||||
double reliability; /* calibrated ∈[0,1] → GeoGradient.confidence */
|
||||
double ema_error; /* EMA of per-trial error */
|
||||
double last_error;
|
||||
} CogStance;
|
||||
|
||||
/* Initialize a neutral stance (all gains 1.0, default scalars, reliability 0.5).
|
||||
* dim/n_axes taken from the region descriptor. faculty/id/for_whom are copied. */
|
||||
int cog_stance_init(CogStance* s, const char* id, const char* faculty,
|
||||
const char* anchor_region, const char* for_whom,
|
||||
const GeoDescriptor* region);
|
||||
void cog_stance_free(CogStance* s);
|
||||
|
||||
/* A stance set to today's hard-coded constants == behavioral parity with the
|
||||
* pre-stance operators (axis_gain all 1.0, ext_floor default, drop_frac 0.5,
|
||||
* assoc_floor 0.2). This is the FROZEN CONTROL used by the validation. */
|
||||
void cog_stance_set_frozen_defaults(CogStance* s);
|
||||
|
||||
/* ── Serialization: Stance <-> StoreNode (compact line schema "STNC1", mirroring
|
||||
* the reify "GEO1" precedent). Additive; the node's importance field caches the
|
||||
* reliability readout. Round-trips exactly (reboot-prove). ──────────────────── */
|
||||
char* cog_stance_to_metadata(const CogStance* s); /* owned string */
|
||||
int cog_stance_to_node(const CogStance* s, StoreNode* out);/* fills a StoreNode */
|
||||
int cog_stance_from_node(const StoreNode* n, CogStance* out);/* parse STNC1 */
|
||||
|
||||
#define COG_STANCE_NODE_TYPE "Stance"
|
||||
#define COG_STANCE_META_MAGIC "STNC1"
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §1.2 think — the ONE operation. Frozen procedure over three steps:
|
||||
* 1. re-origin on the anchor point (the vantage; the manifold is the read
|
||||
* neighborhood, passed as `region`);
|
||||
* 2. fit the anchor under the stance's WARP (engram_reason_point_fit with the
|
||||
* axis extents multiplied by axis_gain and ext_floor substituted);
|
||||
* 3. emit a GRADIENT: direction = the warped steepest-descent that reduces the
|
||||
* fit distance (the "which way pays off" seed + bias_dir), spread from the
|
||||
* fit distance, confidence from the stance's reliability, magnitude = the
|
||||
* read's membership estimate. NO point-collapse — that is expression.
|
||||
*
|
||||
* `region` — the read neighborhood (built by vantage_read / geometry descriptor).
|
||||
* `anchor` — the point to read FROM (dim floats). NULL = region centroid (self).
|
||||
* `stance` — the steering prior. NULL = neutral (frozen defaults) => parity.
|
||||
* Returns 0 and fills `out` (engram_gradient_free), <0 on error.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
int engram_think(const GeoDescriptor* region, const float* anchor,
|
||||
const CogStance* stance, GeoGradient* out);
|
||||
|
||||
/* The warped fit itself (step 2), exposed for the loop + verifier reuse. Identical
|
||||
* to engram_reason_point_fit when stance==NULL or all gains==1 && ext_floor default. */
|
||||
int cog_warped_fit(const GeoDescriptor* region, const float* x,
|
||||
const CogStance* stance, GeoFit* out);
|
||||
|
||||
/* EXPRESSION — the ONLY place a gradient collapses to a point. Samples the gradient
|
||||
* off the anchor along its steering direction, scaled by (1 − spread) so a spiked
|
||||
* (confident) gradient lands a definite point and a diffuse one barely moves.
|
||||
* This is deliberately a SEPARATE faculty from think (§1.2, M5). */
|
||||
int engram_express(const GeoGradient* g, const float* anchor, float* out_point);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §5 HOLD vs GROUND vs ASSERT. Holding is unconditional (the store gates nothing).
|
||||
* Grounding is a RELATION — a "grounded-by" edge, probabilistic, grounded-for-whom.
|
||||
* The honesty floor is checked only at ASSERTION.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
#define COG_GROUNDED_BY_RELATION "grounded-by"
|
||||
#define COG_SALIENT_TO_RELATION "salient-to"
|
||||
|
||||
/* Write a grounded-by edge (additive). weight = grounding ∈(0,1] from the verifier;
|
||||
* for_whom recorded in edge metadata (grounding is relational). Never a node flag. */
|
||||
int cog_ground_edge(EngramPagedStore* s, const char* claim_id,
|
||||
const char* evidence_id, double grounding, const char* for_whom);
|
||||
|
||||
/* Write/refresh a salient-to edge: salience is RELATIONAL (grounded-for-whom),
|
||||
* carried on the edge to the observer — not baked into the node scalar (§2.1). */
|
||||
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
||||
const char* observer_id, double salience);
|
||||
|
||||
/* The honesty floor — a QUERY at assertion time, NOT a schema constraint. Reads the
|
||||
* claim's stored grounded-by edges (for the given observer) and returns:
|
||||
* 1 = may assert (best grounding >= floor),
|
||||
* 0 = REFUSE assertion (holds unconditionally; only asserting is gated),
|
||||
* <0 = error. The content remains held either way. */
|
||||
int cog_assert_gate(EngramPagedStore* s, const char* claim_id,
|
||||
const char* for_whom, double floor);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §4 THE REFLEXIVE CORRESPONDENCE-LOOP — the learning engine. think scores its
|
||||
* OWN gradient against outcome, refines the stance on the error, and (optionally)
|
||||
* writes the (gradient, outcome, error) back as self-describing geometry. This is
|
||||
* the dormant verifier turned INWARD.
|
||||
*
|
||||
* grade(1) SELF-CONSISTENCY (no external world-labels): the outcome is what the
|
||||
* geometry itself says — the membership determined by the region's SIGNAL subspace
|
||||
* (the axes reality actually weights). The stance's cheap warped read is graded
|
||||
* against that geometric truth; error refines the warp so the read corresponds.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
typedef struct {
|
||||
double correspondence; /* ∈[0,1]: 1 − |p − y| for this trial */
|
||||
double error; /* 1 − correspondence */
|
||||
double brier; /* running mean (p − y)² across the stance's trials */
|
||||
double reliability; /* the stance's current calibrated reliability */
|
||||
int wrote_keystone; /* 1 iff a keystone update was BLOCKED (safety audit) */
|
||||
} CogBeatResult;
|
||||
|
||||
/* One correspondence beat for ONE trial:
|
||||
* think(region, anchor, stance) -> gradient (a PREDICTION, ungrounded)
|
||||
* outcome y := grade-1 self-consistency target (in [0,1])
|
||||
* error = |magnitude − y|; refine stance.warp + calibration on the error
|
||||
* (bounded step; NEVER writes a keystone stance)
|
||||
* `learn`==0 grades WITHOUT updating (the frozen-control path). `max_step` bounds
|
||||
* the per-beat warp change (metastability; §6). Returns 0 / <0. */
|
||||
int engram_correspondence_beat(const GeoDescriptor* region, const float* anchor,
|
||||
double outcome_y, CogStance* stance,
|
||||
int learn, double max_step, CogBeatResult* out);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §6 METASTABILITY. Keystones (self/values) are read-mostly: the loop reads but
|
||||
* never writes them. Mark by stance flag or by a keystone-id set the loop consults.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
typedef struct { const char** ids; int n; } CogKeystoneSet;
|
||||
int cog_is_keystone(const CogKeystoneSet* ks, const CogStance* s);
|
||||
|
||||
#endif /* ENGRAM_COGNITION_H */
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user