Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11b6a32f27 | |||
| bdebc60dc1 | |||
| c2afcbddf5 | |||
| dfe4e83ed1 | |||
| 2ed6b26dde | |||
| d8e9fd12f4 | |||
| 8fa9c4ba20 | |||
| 9c7bde47dc | |||
| c0553459e1 | |||
| fd208583fe | |||
| 3e29fc43ab | |||
| 979a5677d5 | |||
| 17b1aa0736 | |||
| f0c731d2db | |||
| e7e0f7d3e5 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
// 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
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -2241,43 +2241,6 @@ el_val_t url_decode(el_val_t sv) {
|
||||
return el_wrap_str(out);
|
||||
}
|
||||
|
||||
/* ── html_raw ────────────────────────────────────────────────────────────────
|
||||
* Identity passthrough for raw HTML template interpolation.
|
||||
* El's {raw(expr)} compiles to html_raw(expr) — the value is output as-is
|
||||
* without any escaping. The caller is responsible for safety.
|
||||
*/
|
||||
el_val_t html_raw(el_val_t s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* ── html_escape ─────────────────────────────────────────────────────────────
|
||||
* Escape < > " ' & for safe HTML text interpolation.
|
||||
* El's {expr} in HTML templates compiles to html_escape(expr).
|
||||
*/
|
||||
el_val_t html_escape(el_val_t sv) {
|
||||
const char* src = EL_CSTR(sv);
|
||||
if (!src) return EL_STR("");
|
||||
size_t len = strlen(src);
|
||||
/* Worst case: every byte → 6 chars (") */
|
||||
char* out = (char*)malloc(len * 6 + 1);
|
||||
if (!out) return sv;
|
||||
el_arena_track(out);
|
||||
char* p = out;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)src[i];
|
||||
switch (c) {
|
||||
case '&': memcpy(p, "&", 5); p += 5; break;
|
||||
case '<': memcpy(p, "<", 4); p += 4; break;
|
||||
case '>': memcpy(p, ">", 4); p += 4; break;
|
||||
case '"': memcpy(p, """, 6); p += 6; break;
|
||||
case '\'': memcpy(p, "'", 5); p += 5; break;
|
||||
default: *p++ = (char)c; break;
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
return el_wrap_str(out);
|
||||
}
|
||||
|
||||
/* ── HTML allowlist sanitizer ────────────────────────────────────────────────
|
||||
* el_html_sanitize(input, allowlist_json)
|
||||
*
|
||||
|
||||
@@ -227,8 +227,6 @@ el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */
|
||||
* {"p":[],"a":["href","title"],"strong":[],...}
|
||||
* where each value is the array of attribute names allowed for that tag. */
|
||||
el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json);
|
||||
el_val_t html_raw(el_val_t s);
|
||||
el_val_t html_escape(el_val_t s);
|
||||
|
||||
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||
|
||||
|
||||
@@ -324,10 +324,6 @@ fn cg_html_parts(children: [Map<String, Any>], acc_var: String) -> String {
|
||||
let each_c: String = cg_html_each(child, acc_var)
|
||||
let parts = native_list_append(parts, each_c)
|
||||
}
|
||||
if str_eq(html_kind, "HtmlIf") {
|
||||
let if_c: String = cg_html_if(child, acc_var)
|
||||
let parts = native_list_append(parts, if_c)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
str_join(parts, "")
|
||||
@@ -417,17 +413,6 @@ fn cg_html_each(node: Map<String, Any>, acc_var: String) -> String {
|
||||
"{ el_val_t " + list_var + " = (" + list_c + "); el_val_t " + len_var + " = el_list_len(" + list_var + "); for (el_val_t " + idx_var + " = 0; " + idx_var + " < " + len_var + "; " + idx_var + "++) { el_val_t " + item_name + " = el_list_get(" + list_var + ", " + idx_var + "); " + inner_c + "} } "
|
||||
}
|
||||
|
||||
// Generate code for {#if cond} ... {/if} (with optional {#else}).
|
||||
fn cg_html_if(node: Map<String, Any>, acc_var: String) -> String {
|
||||
let cond_expr = node["cond"]
|
||||
let then_children: [Map<String, Any>] = node["then"]
|
||||
let else_children: [Map<String, Any>] = node["else"]
|
||||
let cond_c: String = cg_expr(cond_expr)
|
||||
let then_c: String = cg_html_parts(then_children, acc_var)
|
||||
let else_c: String = cg_html_parts(else_children, acc_var)
|
||||
"if (" + cond_c + ") { " + then_c + " } else { " + else_c + " } "
|
||||
}
|
||||
|
||||
// Top-level HTML template codegen — returns a C statement-expression string.
|
||||
fn cg_html_template(expr: Map<String, Any>) -> String {
|
||||
let root = expr["root"]
|
||||
|
||||
+93
-283
@@ -293,48 +293,6 @@ fn is_void_element(name: String) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
// Collect all tokens as raw text until </tag_name> is encountered.
|
||||
// Used for <style> and <script> elements to avoid parsing CSS/JS as El.
|
||||
// Returns { "text": "...", "pos": p_after_closing_tag }
|
||||
fn parse_raw_text_content(tokens: [Any], pos: Int, tag_name: String) -> Map<String, Any> {
|
||||
let parts: [String] = native_list_empty()
|
||||
let p = pos
|
||||
let running = true
|
||||
while running {
|
||||
let k = tok_kind(tokens, p)
|
||||
if str_eq(k, "Eof") {
|
||||
let running = false
|
||||
} else {
|
||||
if str_eq(k, "Lt") {
|
||||
let k2 = tok_kind(tokens, p + 1)
|
||||
if str_eq(k2, "Slash") {
|
||||
// Check if this is </tag_name>
|
||||
let close_name = tok_value(tokens, p + 2)
|
||||
if str_eq(close_name, tag_name) {
|
||||
// consume </tag_name>
|
||||
let p = p + 3
|
||||
let p = expect(tokens, p, "Gt")
|
||||
let running = false
|
||||
} else {
|
||||
let v = tok_value(tokens, p)
|
||||
let parts = native_list_append(parts, v)
|
||||
let p = p + 1
|
||||
}
|
||||
} else {
|
||||
let v = tok_value(tokens, p)
|
||||
let parts = native_list_append(parts, v)
|
||||
let p = p + 1
|
||||
}
|
||||
} else {
|
||||
let v = tok_value(tokens, p)
|
||||
let parts = native_list_append(parts, v)
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
{ "text": str_join(parts, " "), "pos": p }
|
||||
}
|
||||
|
||||
// Collect tokens as text content until we hit Lt, LBrace, Eof, or a
|
||||
// closing-tag marker (Lt Slash). Returns { "text": "...", "pos": p }
|
||||
fn parse_html_text_tokens(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||
@@ -477,125 +435,77 @@ fn parse_html_children(tokens: [Any], pos: Int, parent_tag: String) -> Map<Strin
|
||||
}
|
||||
} else {
|
||||
if str_eq(k, "LBrace") {
|
||||
// Interpolation: {expr}, {#each ...}, {#if ...}, {#else}, {/each}, {/if}
|
||||
// Note: '#' (ASCII 35) is skipped by the lexer, so {#each} lexes as
|
||||
// LBrace Ident:"each" ... and {#if} lexes as LBrace If ... and
|
||||
// {#else} lexes as LBrace Else RBrace.
|
||||
// Interpolation: {expr} or {#each ...} or {/each}
|
||||
let k2 = tok_kind(tokens, p + 1)
|
||||
if str_eq(k2, "Slash") {
|
||||
// {/each} or {/if} — end of block, stop
|
||||
// skip { /
|
||||
let p = p + 2
|
||||
// skip the close-tag name token (each, if, etc.)
|
||||
let p = p + 1
|
||||
// skip }
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
let running = false
|
||||
} else {
|
||||
if str_eq(k2, "If") {
|
||||
// {#if condition} ... {/if}
|
||||
// Skip { if (2 tokens; '#' was silently skipped by lexer)
|
||||
let p = p + 2
|
||||
// Parse condition expression (no block expr)
|
||||
if str_eq(k2, "Hash") {
|
||||
// {#each list as item}
|
||||
let k3_v = tok_value(tokens, p + 2)
|
||||
if str_eq(k3_v, "each") {
|
||||
let p = p + 3
|
||||
// parse list expr up to "as" keyword
|
||||
let prev_no_block: String = state_get("__no_block_expr")
|
||||
state_set("__no_block_expr", "1")
|
||||
let r_cond = parse_expr(tokens, p)
|
||||
let r_list = parse_expr(tokens, p)
|
||||
state_set("__no_block_expr", prev_no_block)
|
||||
let cond_expr = r_cond["node"]
|
||||
let p = r_cond["pos"]
|
||||
el_release(r_cond)
|
||||
let list_expr = r_list["node"]
|
||||
let p = r_list["pos"]
|
||||
// r_list result map fully consumed — release to free peak heap.
|
||||
el_release(r_list)
|
||||
// expect "as"
|
||||
let p = expect(tokens, p, "As")
|
||||
// item variable name
|
||||
let item_name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
// consume closing }
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// parse then-children until {#else} or {/if}
|
||||
let r_then = parse_html_children(tokens, p, "__if_then__")
|
||||
let then_children = r_then["children"]
|
||||
let p = r_then["pos"]
|
||||
el_release(r_then)
|
||||
// check for {#else} — lexed as LBrace Else RBrace
|
||||
let else_children: [Map<String, Any>] = native_list_empty()
|
||||
let ck = tok_kind(tokens, p)
|
||||
if str_eq(ck, "LBrace") {
|
||||
let ck2 = tok_kind(tokens, p + 1)
|
||||
if str_eq(ck2, "Else") {
|
||||
// consume { else }
|
||||
let p = p + 2
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// parse else-children until {/if}
|
||||
let r_else = parse_html_children(tokens, p, "__if_else__")
|
||||
let else_children = r_else["children"]
|
||||
let p = r_else["pos"]
|
||||
el_release(r_else)
|
||||
// parse body until {/each}
|
||||
let r_body = parse_html_each_body(tokens, p)
|
||||
let body_children = r_body["children"]
|
||||
let p = r_body["pos"]
|
||||
// r_body result map fully consumed — release to free peak heap.
|
||||
el_release(r_body)
|
||||
let each_node: Map<String, Any> = { "html": "Each", "list": list_expr, "item": item_name, "body": body_children }
|
||||
let children = native_list_append(children, each_node)
|
||||
} else {
|
||||
let p = p + 1
|
||||
}
|
||||
} else {
|
||||
if str_eq(k2, "Slash") {
|
||||
// {/each} — end of each block, stop
|
||||
// skip {/each}
|
||||
let p = p + 2
|
||||
// skip "each"
|
||||
let p = p + 1
|
||||
// skip }
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
let running = false
|
||||
} else {
|
||||
// regular {expr}
|
||||
let r = parse_expr(tokens, p + 1)
|
||||
let interp_val = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// Check if the expr is a call to raw()
|
||||
let is_raw_call = false
|
||||
let interp_kind: String = interp_val["expr"]
|
||||
if str_eq(interp_kind, "Call") {
|
||||
let fn_node = interp_val["func"]
|
||||
let fn_kind: String = fn_node["expr"]
|
||||
if str_eq(fn_kind, "Ident") {
|
||||
let fn_name_v: String = fn_node["name"]
|
||||
if str_eq(fn_name_v, "raw") {
|
||||
let is_raw_call = true
|
||||
}
|
||||
}
|
||||
}
|
||||
let if_node: Map<String, Any> = { "html": "HtmlIf", "cond": cond_expr, "then": then_children, "else": else_children }
|
||||
let children = native_list_append(children, if_node)
|
||||
} else {
|
||||
if str_eq(k2, "Else") {
|
||||
// {#else} sentinel — lexed as LBrace Else RBrace
|
||||
// Do NOT consume — leave position for caller ({#if} handler checks for it)
|
||||
let running = false
|
||||
if is_raw_call {
|
||||
let raw_args = interp_val["args"]
|
||||
let raw_inner = native_list_get(raw_args, 0)
|
||||
let children = native_list_append(children, { "html": "Raw", "value": raw_inner })
|
||||
} else {
|
||||
// Check for {#each list as item} — lexed as LBrace Ident:"each" ...
|
||||
let k2_v = tok_value(tokens, p + 1)
|
||||
if str_eq(k2_v, "each") {
|
||||
let p = p + 2
|
||||
// parse list expr up to "as" keyword
|
||||
let prev_no_block: String = state_get("__no_block_expr")
|
||||
state_set("__no_block_expr", "1")
|
||||
let r_list = parse_expr(tokens, p)
|
||||
state_set("__no_block_expr", prev_no_block)
|
||||
let list_expr = r_list["node"]
|
||||
let p = r_list["pos"]
|
||||
// r_list result map fully consumed — release to free peak heap.
|
||||
el_release(r_list)
|
||||
// expect "as"
|
||||
let p = expect(tokens, p, "As")
|
||||
// item variable name
|
||||
let item_name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
// consume closing }
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// parse body until {/each}
|
||||
let r_body = parse_html_each_body(tokens, p)
|
||||
let body_children = r_body["children"]
|
||||
let p = r_body["pos"]
|
||||
// r_body result map fully consumed — release to free peak heap.
|
||||
el_release(r_body)
|
||||
let each_node: Map<String, Any> = { "html": "Each", "list": list_expr, "item": item_name, "body": body_children }
|
||||
let children = native_list_append(children, each_node)
|
||||
} else {
|
||||
// regular {expr} — disable map-literal parsing so {fn(a,b)}
|
||||
// does not trigger the LBrace→map path inside parse_primary
|
||||
let prev_no_block: String = state_get("__no_block_expr")
|
||||
state_set("__no_block_expr", "1")
|
||||
let r = parse_expr(tokens, p + 1)
|
||||
state_set("__no_block_expr", prev_no_block)
|
||||
let interp_val = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// Check if the expr is a call to raw()
|
||||
let is_raw_call = false
|
||||
let interp_kind: String = interp_val["expr"]
|
||||
if str_eq(interp_kind, "Call") {
|
||||
let fn_node = interp_val["func"]
|
||||
let fn_kind: String = fn_node["expr"]
|
||||
if str_eq(fn_kind, "Ident") {
|
||||
let fn_name_v: String = fn_node["name"]
|
||||
if str_eq(fn_name_v, "raw") {
|
||||
let is_raw_call = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_raw_call {
|
||||
let raw_args = interp_val["args"]
|
||||
let raw_inner = native_list_get(raw_args, 0)
|
||||
let children = native_list_append(children, { "html": "Raw", "value": raw_inner })
|
||||
} else {
|
||||
let children = native_list_append(children, { "html": "Interp", "value": interp_val })
|
||||
}
|
||||
}
|
||||
let children = native_list_append(children, { "html": "Interp", "value": interp_val })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -655,27 +565,6 @@ fn parse_html_element(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||
if is_void_element(tag_name) {
|
||||
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": native_list_empty(), "self_closing": true }, p)
|
||||
}
|
||||
// raw-text mode for style/script — collect content as plain text without parsing CSS/JS as El
|
||||
if str_eq(tag_name, "style") {
|
||||
let r_raw = parse_raw_text_content(tokens, p, "style")
|
||||
let raw_text: String = r_raw["text"]
|
||||
let p = r_raw["pos"]
|
||||
el_release(r_raw)
|
||||
let raw_child: Map<String, Any> = { "html": "Text", "text": raw_text }
|
||||
let raw_children: [Map<String, Any>] = native_list_empty()
|
||||
let raw_children = native_list_append(raw_children, raw_child)
|
||||
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": raw_children, "self_closing": false }, p)
|
||||
}
|
||||
if str_eq(tag_name, "script") {
|
||||
let r_raw = parse_raw_text_content(tokens, p, "script")
|
||||
let raw_text: String = r_raw["text"]
|
||||
let p = r_raw["pos"]
|
||||
el_release(r_raw)
|
||||
let raw_child: Map<String, Any> = { "html": "Text", "text": raw_text }
|
||||
let raw_children: [Map<String, Any>] = native_list_empty()
|
||||
let raw_children = native_list_append(raw_children, raw_child)
|
||||
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": raw_children, "self_closing": false }, p)
|
||||
}
|
||||
// parse children
|
||||
let r_children = parse_html_children(tokens, p, tag_name)
|
||||
let children = r_children["children"]
|
||||
@@ -829,123 +718,44 @@ fn parse_primary(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||
// as the start of the block they're expecting.
|
||||
return make_result({ "expr": "Nil" }, pos)
|
||||
}
|
||||
// Distinguish map literal from interpolation chain.
|
||||
// A map literal requires { key: value } — the second token inside { must be Colon.
|
||||
// An empty {} is a map literal. Everything else is an interpolation chain.
|
||||
let first_k: String = tok_kind(tokens, pos + 1)
|
||||
let second_k: String = tok_kind(tokens, pos + 2)
|
||||
if str_eq(first_k, "RBrace") {
|
||||
// Empty map literal {}
|
||||
return make_result({ "expr": "Map", "pairs": native_list_empty() }, pos + 2)
|
||||
}
|
||||
if str_eq(second_k, "Colon") {
|
||||
// MAP LITERAL: { key: value, ... }
|
||||
let p = pos + 1
|
||||
let pairs: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBrace" {
|
||||
let p = pos + 1
|
||||
let pairs: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
if k2 == "Eof" {
|
||||
let running = false
|
||||
} else {
|
||||
if k2 == "Eof" {
|
||||
let running = false
|
||||
// key: Str token
|
||||
let key = tok_value(tokens, p)
|
||||
let new_p: Int = p + 1
|
||||
let new_p = expect(tokens, new_p, "Colon")
|
||||
let r = parse_expr(tokens, new_p)
|
||||
let val_node = r["node"]
|
||||
let new_p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let pair = { "key": key, "value": val_node }
|
||||
let pairs = native_list_append(pairs, pair)
|
||||
let k3 = tok_kind(tokens, new_p)
|
||||
if k3 == "Comma" {
|
||||
let new_p = new_p + 1
|
||||
}
|
||||
// Non-progress guard: malformed map content can leave
|
||||
// parse_expr returning the same pos. Force advance.
|
||||
if new_p <= p {
|
||||
let p = p + 1
|
||||
} else {
|
||||
// key: Str or Ident token
|
||||
let key = tok_value(tokens, p)
|
||||
let new_p: Int = p + 1
|
||||
let new_p = expect(tokens, new_p, "Colon")
|
||||
let r = parse_expr(tokens, new_p)
|
||||
let val_node = r["node"]
|
||||
let new_p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let pair = { "key": key, "value": val_node }
|
||||
let pairs = native_list_append(pairs, pair)
|
||||
let k3 = tok_kind(tokens, new_p)
|
||||
if k3 == "Comma" {
|
||||
let new_p = new_p + 1
|
||||
}
|
||||
// Non-progress guard: malformed map content can leave
|
||||
// parse_expr returning the same pos. Force advance.
|
||||
if new_p <= p {
|
||||
let p = p + 1
|
||||
} else {
|
||||
let p = new_p
|
||||
}
|
||||
let p = new_p
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
||||
}
|
||||
// INTERPOLATION CHAIN: {expr}, {expr}{expr}, {expr}<html>, etc.
|
||||
// Build a BinOp(Plus, ...) concatenation chain.
|
||||
let p = pos
|
||||
let chain_node: Map<String, Any> = { "expr": "Nil" }
|
||||
let chain_started = false
|
||||
let chain_running = true
|
||||
while chain_running {
|
||||
let ck: String = tok_kind(tokens, p)
|
||||
if str_eq(ck, "LBrace") {
|
||||
let prev_no_block: String = state_get("__no_block_expr")
|
||||
state_set("__no_block_expr", "1")
|
||||
let r = parse_expr(tokens, p + 1)
|
||||
state_set("__no_block_expr", prev_no_block)
|
||||
let part = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
if !chain_started {
|
||||
let chain_node = part
|
||||
let chain_started = true
|
||||
} else {
|
||||
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||
}
|
||||
} else {
|
||||
if str_eq(ck, "Lt") {
|
||||
let ck2: String = tok_kind(tokens, p + 1)
|
||||
if str_eq(ck2, "Not") {
|
||||
let r = parse_html_template(tokens, p)
|
||||
let part = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
if !chain_started {
|
||||
let chain_node = part
|
||||
let chain_started = true
|
||||
} else {
|
||||
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||
}
|
||||
} else {
|
||||
if str_eq(ck2, "Ident") {
|
||||
let tag_candidate: String = tok_value(tokens, p + 1)
|
||||
if is_html_tag_name(tag_candidate) {
|
||||
let r = parse_html_template(tokens, p)
|
||||
let part = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
if !chain_started {
|
||||
let chain_node = part
|
||||
let chain_started = true
|
||||
} else {
|
||||
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||
}
|
||||
} else {
|
||||
let chain_running = false
|
||||
}
|
||||
} else {
|
||||
let chain_running = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let chain_running = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return make_result(chain_node, p)
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
||||
}
|
||||
|
||||
// if expression
|
||||
|
||||
+2
-43
@@ -77,33 +77,6 @@ fn parse_manifest_entry(src: String) -> String {
|
||||
return ""
|
||||
}
|
||||
|
||||
// parse_manifest_c_sources - collect all `c_source "path"` lines from the
|
||||
// build block. Returns a flat list of path strings.
|
||||
fn parse_manifest_c_sources(src: String) -> [String] {
|
||||
let result: [String] = native_list_empty()
|
||||
let lines: [String] = str_split(src, "\n")
|
||||
let n: Int = native_list_len(lines)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let line: String = native_list_get(lines, i)
|
||||
let t: String = str_trim(line)
|
||||
if str_starts_with(t, "c_source ") {
|
||||
let after: String = str_slice(t, 9, str_len(t))
|
||||
let trimmed: String = str_trim(after)
|
||||
if str_starts_with(trimmed, "\"") {
|
||||
let inner: String = str_slice(trimmed, 1, str_len(trimmed))
|
||||
let q: Int = str_index_of(inner, "\"")
|
||||
if q >= 0 {
|
||||
let path: String = str_slice(inner, 0, q)
|
||||
let result = native_list_append(result, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fn parse_manifest_name(src: String) -> String {
|
||||
let lines: [String] = str_split(src, "\n")
|
||||
let n: Int = native_list_len(lines)
|
||||
@@ -301,11 +274,7 @@ fn link_binary(c_files: [String], out_bin: String, runtime_path: String, out_dir
|
||||
// Detect clang vs gcc: -fbracket-depth is clang-only; silently ignored
|
||||
// if unsupported but gcc rejects it with an error.
|
||||
let bracket_flag: String = "$(cc --version 2>&1 | grep -q clang && printf -- '-fbracket-depth=1024' || true)"
|
||||
// On macOS, OpenSSL is not on the default linker path. Detect homebrew
|
||||
// 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)
|
||||
let parts = native_list_append(parts, "cc -O2 " + bracket_flag + " -I " + dirname_of(runtime_path) + " -I " + out_dir)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let f: String = native_list_get(c_files, i)
|
||||
@@ -313,7 +282,7 @@ fn link_binary(c_files: [String], out_bin: String, runtime_path: String, out_dir
|
||||
let i = i + 1
|
||||
}
|
||||
let parts = native_list_append(parts, runtime_path)
|
||||
let parts = native_list_append(parts, ossl_lib_flag + " -lcurl -lssl -lcrypto -lpthread -lm")
|
||||
let parts = native_list_append(parts, "-lcurl -lssl -lcrypto -lpthread -lm")
|
||||
let parts = native_list_append(parts, "-o " + out_bin)
|
||||
let cmd: String = str_join(parts, " ")
|
||||
println(" link " + out_bin)
|
||||
@@ -346,7 +315,6 @@ fn main() -> Void {
|
||||
|
||||
let pkg_name: String = parse_manifest_name(manifest_src)
|
||||
let entry: String = parse_manifest_entry(manifest_src)
|
||||
let extra_c: [String] = parse_manifest_c_sources(manifest_src)
|
||||
if str_eq(entry, "") {
|
||||
println("elb: manifest.el has no 'entry' declaration")
|
||||
exit(1)
|
||||
@@ -425,15 +393,6 @@ fn main() -> Void {
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Append any extra C sources declared in the manifest (e.g. platform stubs)
|
||||
let ei = 0
|
||||
let en: Int = native_list_len(extra_c)
|
||||
while ei < en {
|
||||
let ec: String = native_list_get(extra_c, ei)
|
||||
let c_files = native_list_append(c_files, ec)
|
||||
let ei = ei + 1
|
||||
}
|
||||
|
||||
// Link
|
||||
let out_bin: String = out_dir + "/" + pkg_name
|
||||
let linked: Bool = link_binary(c_files, out_bin, runtime_path, out_dir, dry_run)
|
||||
|
||||
-10062
File diff suppressed because it is too large
Load Diff
+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 ---
|
||||
|
||||
@@ -84,10 +84,10 @@ fn json_get_float(json: String, key: String) -> Float {
|
||||
}
|
||||
|
||||
// json_get_bool — extract a boolean value for a key.
|
||||
// Returns true only when the raw JSON token is the literal "true".
|
||||
// Accepts "true" or "1" as truthy (UI sends integer 1, JSON spec uses true).
|
||||
fn json_get_bool(json: String, key: String) -> Bool {
|
||||
let s: String = __json_get(json, key)
|
||||
return str_eq(s, "true")
|
||||
return str_eq(s, "true") || str_eq(s, "1")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user