// codegen.el — El compiler C source code generator // // Input: list of AST statement maps (from parser.el) // Output: C source printed to stdout (streamed, one line at a time) // // Each El program compiles to a single .c file that #includes el_runtime.h. // Functions map directly to C functions; top-level statements become main(). // // Entry point: fn codegen(stmts: [Map], source: String) -> String // Returns "" — output goes to stdout via println(). // // Streaming output avoids O(n²) string concatenation: each emitted line is // printed immediately rather than appended to a growing string. // ── String helpers ──────────────────────────────────────────────────────────── // Escape a C string literal (double-quotes and backslashes). fn c_escape(s: String) -> String { let chars: [String] = native_string_chars(s) let total: Int = native_list_len(chars) let out = "" let i = 0 while i < total { let ch: String = native_list_get(chars, i) if ch == "\"" { let out = out + "\\\"" } else { if ch == "\\" { let out = out + "\\\\" } else { if ch == "\n" { let out = out + "\\n" } else { if ch == "\r" { let out = out + "\\r" } else { if ch == "\t" { let out = out + "\\t" } else { let out = out + ch } } } } } let i = i + 1 } out } fn c_str_lit(s: String) -> String { "\"" + c_escape(s) + "\"" } // ── Type mapping ────────────────────────────────────────────────────────────── fn el_type_to_c(type_str: String) -> String { if type_str == "String" { return "const char*" } if type_str == "Int" { return "int64_t" } if type_str == "Bool" { return "int" } if type_str == "Float" { return "double" } if type_str == "Void" { return "void" } if type_str == "void" { return "void" } "void*" } // ── Code emission ───────────────────────────────────────────────────────────── // // emit_line/emit_blank stream output directly via println. // This avoids building a large string in memory. fn emit_line(line: String) -> Void { println(line) } fn emit_blank() -> Void { println("") } // ── Operator helpers ────────────────────────────────────────────────────────── fn binop_to_c(op: String) -> String { if op == "Plus" { return "+" } if op == "Minus" { return "-" } if op == "Star" { return "*" } if op == "Slash" { return "/" } if op == "EqEq" { return "==" } if op == "NotEq" { return "!=" } if op == "Lt" { return "<" } if op == "Gt" { return ">" } if op == "LtEq" { return "<=" } if op == "GtEq" { return ">=" } if op == "And" { return "&&" } if op == "Or" { return "||" } op } // ── Expression codegen ──────────────────────────────────────────────────────── // // cg_expr returns a C expression string (not a statement). // duration_unit_nanos — multiplier from a postfix-literal unit name to // nanoseconds. Singular and plural forms collapse to the same multiplier; // the parser already restricted `unit` to the set is_duration_unit accepts. // Returns the multiplier as a decimal string suitable for splicing into // the generated C as a literal int64 expression. fn duration_unit_nanos(unit: String) -> String { if str_eq(unit, "nano") { return "1LL" } if str_eq(unit, "nanos") { return "1LL" } if str_eq(unit, "milli") { return "1000000LL" } if str_eq(unit, "millis") { return "1000000LL" } if str_eq(unit, "millisecond") { return "1000000LL" } if str_eq(unit, "milliseconds") { return "1000000LL" } if str_eq(unit, "second") { return "1000000000LL" } if str_eq(unit, "seconds") { return "1000000000LL" } if str_eq(unit, "minute") { return "60000000000LL" } if str_eq(unit, "minutes") { return "60000000000LL" } if str_eq(unit, "hour") { return "3600000000000LL" } if str_eq(unit, "hours") { return "3600000000000LL" } if str_eq(unit, "day") { return "86400000000000LL" } if str_eq(unit, "days") { return "86400000000000LL" } "1LL" } fn cg_expr(expr: Map) -> String { let kind: String = expr["expr"] if kind == "Int" { let v: String = expr["value"] return v } // DurationLit — postfix-literal time value (e.g. 30.seconds, 1.hour). // Lowered to a literal int64 nanosecond count, wrapped in the runtime // entry point so the intent is explicit at the C level. The arithmetic // is fully constant-folded by any optimising C compiler. if kind == "DurationLit" { let count: String = expr["count"] let unit: String = expr["unit"] let mult: String = duration_unit_nanos(unit) return "el_duration_from_nanos((el_val_t)(" + count + "LL * " + mult + "))" } if kind == "Float" { // Wrap Float literals in el_from_float() so the bit pattern is // preserved through the el_val_t (int64) slot. Without this, // implicit double→int64 conversion in C truncates `0.8` to `0` // when passed to a builtin that expects el_val_t. let v: String = expr["value"] return "el_from_float(" + v + ")" } if kind == "Str" { let v: String = expr["value"] return "EL_STR(" + c_str_lit(v) + ")" } if kind == "Bool" { let v: String = expr["value"] if v == "true" { return "1" } return "0" } if kind == "Nil" { return "EL_NULL" } if kind == "Ident" { let name: String = expr["name"] return name } if kind == "Not" { let inner = expr["inner"] let inner_c: String = cg_expr(inner) return "!" + inner_c } if kind == "Neg" { let inner = expr["inner"] let inner_c: String = cg_expr(inner) return "(-" + inner_c + ")" } if kind == "BinOp" { let op: String = expr["op"] let left = expr["left"] let right = expr["right"] let left_c: String = cg_expr(left) let right_c: String = cg_expr(right) let left_kind: String = left["expr"] let right_kind: String = right["expr"] // ── Temporal-type dispatch (Instant + Duration first-class) ──────── // Run BEFORE the int / string / generic paths so typed temporal // operands route through the runtime wrappers and invalid combos // become #error directives rather than silently falling through to // raw int arithmetic. The wrappers are no-op casts at the C level // but make the intent explicit and centralise future changes (e.g. // saturating arithmetic, overflow guards). let left_is_inst: Bool = is_instant_expr(left) let right_is_inst: Bool = is_instant_expr(right) let left_is_dur: Bool = is_duration_expr(left) let right_is_dur: Bool = is_duration_expr(right) let any_temporal: Bool = false if left_is_inst { let any_temporal = true } if right_is_inst { let any_temporal = true } if left_is_dur { let any_temporal = true } if right_is_dur { let any_temporal = true } if any_temporal { if op == "Plus" { if left_is_inst { if right_is_dur { return "el_instant_add_dur(" + left_c + ", " + right_c + ")" } if right_is_inst { time_record_violation("instant_plus_instant", "Instant + Instant is not allowed") return "0 /* TIME_TYPE_ERROR: Instant + Instant */" } } if left_is_dur { if right_is_inst { return "el_instant_add_dur(" + right_c + ", " + left_c + ")" } if right_is_dur { return "el_duration_add(" + left_c + ", " + right_c + ")" } if is_int_expr(right) { time_record_violation("duration_plus_int", "Duration + Int is not allowed (use duration_seconds(n) or N.seconds)") return "0 /* TIME_TYPE_ERROR: Duration + Int */" } } if right_is_dur { if is_int_expr(left) { time_record_violation("duration_plus_int", "Int + Duration is not allowed") return "0 /* TIME_TYPE_ERROR: Int + Duration */" } } } if op == "Minus" { if left_is_inst { if right_is_dur { return "el_instant_sub_dur(" + left_c + ", " + right_c + ")" } if right_is_inst { return "el_instant_diff(" + left_c + ", " + right_c + ")" } } if left_is_dur { if right_is_dur { return "el_duration_sub(" + left_c + ", " + right_c + ")" } if is_int_expr(right) { time_record_violation("duration_minus_int", "Duration - Int is not allowed") return "0 /* TIME_TYPE_ERROR: Duration - Int */" } } } if op == "Star" { if left_is_dur { if is_int_expr(right) { return "el_duration_scale(" + left_c + ", " + right_c + ")" } } if right_is_dur { if is_int_expr(left) { return "el_duration_scale(" + right_c + ", " + left_c + ")" } } } if op == "Slash" { if left_is_dur { if is_int_expr(right) { return "el_duration_div(" + left_c + ", " + right_c + ")" } } } // Comparisons. Cross-type comparisons are forbidden. if op == "Lt" { if left_is_inst { if right_is_inst { return "el_instant_lt(" + left_c + ", " + right_c + ")" } if right_is_dur { time_record_violation("instant_cmp_duration", "Instant < Duration is not allowed") return "0 /* TIME_TYPE_ERROR: Instant < Duration */" } } if left_is_dur { if right_is_dur { return "el_duration_lt(" + left_c + ", " + right_c + ")" } if right_is_inst { time_record_violation("duration_cmp_instant", "Duration < Instant is not allowed") return "0 /* TIME_TYPE_ERROR: Duration < Instant */" } } } if op == "LtEq" { if left_is_inst { if right_is_inst { return "el_instant_le(" + left_c + ", " + right_c + ")" } } if left_is_dur { if right_is_dur { return "el_duration_le(" + left_c + ", " + right_c + ")" } } } if op == "Gt" { if left_is_inst { if right_is_inst { return "el_instant_gt(" + left_c + ", " + right_c + ")" } } if left_is_dur { if right_is_dur { return "el_duration_gt(" + left_c + ", " + right_c + ")" } } } if op == "GtEq" { if left_is_inst { if right_is_inst { return "el_instant_ge(" + left_c + ", " + right_c + ")" } } if left_is_dur { if right_is_dur { return "el_duration_ge(" + left_c + ", " + right_c + ")" } } } if op == "EqEq" { if left_is_inst { if right_is_inst { return "el_instant_eq(" + left_c + ", " + right_c + ")" } } if left_is_dur { if right_is_dur { return "el_duration_eq(" + left_c + ", " + right_c + ")" } } } if op == "NotEq" { if left_is_inst { if right_is_inst { return "el_instant_ne(" + left_c + ", " + right_c + ")" } } if left_is_dur { if right_is_dur { return "el_duration_ne(" + left_c + ", " + right_c + ")" } } } // Fall through — let the existing path handle anything we // didn't explicitly cover (typically string-concat with a // typed temporal value, e.g. for debug prints, which works // because both share the int64 slot). } if op == "Plus" { // If either side is a string literal, always concat if left_kind == "Str" { return "el_str_concat(" + left_c + ", " + right_c + ")" } if right_kind == "Str" { return "el_str_concat(" + left_c + ", " + right_c + ")" } // Type-driven dispatch via recursive is_int_expr: any expression // whose value is provably Int (literal, typed Ident, known-Int // builtin, or BinOp arithmetic over Ints) participates in // arithmetic, not string concat. Recursion into BinOp lets // `a + b + c` (chained Int adds) and `acc * 16 + d` route to // arithmetic instead of falling to el_str_concat — both sides // are Int so the outer `+` is too. if is_int_expr(left) { if is_int_expr(right) { let op_c: String = binop_to_c(op) return "(" + left_c + " " + op_c + " " + right_c + ")" } } // Mixed cases: at least one side is provably Int but the other // is not provably anything. Historical heuristic biases to // arithmetic when a literal Int is present (preserves prior // behaviour for `pos + 1` where `pos` is an untyped param). if left_kind == "Int" { let op_c: String = binop_to_c(op) return "(" + left_c + " " + op_c + " " + right_c + ")" } if right_kind == "Int" { let op_c: String = binop_to_c(op) return "(" + left_c + " " + op_c + " " + right_c + ")" } // Otherwise: BinOp(+) with a Call/Ident side without int-typed // evidence — fall back to string concat (the historical default). if left_kind == "Call" { return "el_str_concat(" + left_c + ", " + right_c + ")" } if right_kind == "Call" { return "el_str_concat(" + left_c + ", " + right_c + ")" } if left_kind == "BinOp" { let left_op: String = left["op"] if left_op == "Plus" { return "el_str_concat(" + left_c + ", " + right_c + ")" } } if right_kind == "BinOp" { let right_op: String = right["op"] if right_op == "Plus" { return "el_str_concat(" + left_c + ", " + right_c + ")" } } if left_kind == "Ident" { return "el_str_concat(" + left_c + ", " + right_c + ")" } if right_kind == "Ident" { return "el_str_concat(" + left_c + ", " + right_c + ")" } } // String equality: use str_eq() when either side is a string literal or ident. // Use plain == when comparing integer literals OR when both sides are // identifiers tracked in __int_names (typed Int via `let x: Int = ...`). // Without the int-name check, `seen == idx` between two Int locals // miscompiles to str_eq(seen, idx), strcmp'ing what are integer values // dressed as char* — segfault on the first non-printable byte. if op == "EqEq" { if left_kind == "Int" { return "(" + left_c + " == " + right_c + ")" } if right_kind == "Int" { return "(" + left_c + " == " + right_c + ")" } if left_kind == "Bool" { return "(" + left_c + " == " + right_c + ")" } if right_kind == "Bool" { return "(" + left_c + " == " + right_c + ")" } if left_kind == "Ident" { if right_kind == "Ident" { let lname: String = left["name"] let rname: String = right["name"] if is_int_name(lname) { if is_int_name(rname) { return "(" + left_c + " == " + right_c + ")" } } } } // Extend int-equality to mixed Ident/BinOp cases: `i == n - 1` // where the left is an int-name Ident and the right is an // arithmetic BinOp (or vice-versa). Without this check the // fallthrough to str_eq produces str_eq(int_value, int_value) // which reads the integer as a char* and segfaults. if is_int_expr(left) { if is_int_expr(right) { return "(" + left_c + " == " + right_c + ")" } } if left_kind == "Str" { return "str_eq(" + left_c + ", " + right_c + ")" } if right_kind == "Str" { return "str_eq(" + left_c + ", " + right_c + ")" } if left_kind == "Ident" { return "str_eq(" + left_c + ", " + right_c + ")" } if right_kind == "Ident" { return "str_eq(" + left_c + ", " + right_c + ")" } if left_kind == "Call" { return "str_eq(" + left_c + ", " + right_c + ")" } if right_kind == "Call" { return "str_eq(" + left_c + ", " + right_c + ")" } } if op == "NotEq" { if left_kind == "Int" { return "(" + left_c + " != " + right_c + ")" } if right_kind == "Int" { return "(" + left_c + " != " + right_c + ")" } if left_kind == "Bool" { return "(" + left_c + " != " + right_c + ")" } if right_kind == "Bool" { return "(" + left_c + " != " + right_c + ")" } if left_kind == "Ident" { if right_kind == "Ident" { let lname: String = left["name"] let rname: String = right["name"] if is_int_name(lname) { if is_int_name(rname) { return "(" + left_c + " != " + right_c + ")" } } } } // Same mixed Ident/BinOp fix as EqEq: use is_int_expr to detect // integer-typed operands before falling through to !str_eq. if is_int_expr(left) { if is_int_expr(right) { return "(" + left_c + " != " + right_c + ")" } } if left_kind == "Str" { return "!str_eq(" + left_c + ", " + right_c + ")" } if right_kind == "Str" { return "!str_eq(" + left_c + ", " + right_c + ")" } if left_kind == "Ident" { return "!str_eq(" + left_c + ", " + right_c + ")" } if right_kind == "Ident" { return "!str_eq(" + left_c + ", " + right_c + ")" } if left_kind == "Call" { return "!str_eq(" + left_c + ", " + right_c + ")" } if right_kind == "Call" { return "!str_eq(" + left_c + ", " + right_c + ")" } } let op_c: String = binop_to_c(op) return "(" + left_c + " " + op_c + " " + right_c + ")" } if kind == "Call" { let func = expr["func"] let args = expr["args"] let arity: Int = native_list_len(args) let func_kind: String = func["expr"] let args_c = "" let i = 0 while i < arity { let arg = native_list_get(args, i) let arg_c: String = cg_expr(arg) if i > 0 { let args_c = args_c + ", " } let args_c = args_c + arg_c let i = i + 1 } if func_kind == "Ident" { let fn_name: String = func["name"] // Capability-kind enforcement: services can't call // self-formation primitives; utilities can't call any // DHARMA or LLM primitives. cap_check_call records // violations to be emitted as #error directives at the // top of the generated C, so cc fails with a clear msg. cap_check_call(fn_name) // Arity check against the builtin table — refuse, with a clear // El-source message, when a known builtin gets the wrong arg // count (e.g. `http_serve(port)` instead of `http_serve(port, // handler)`). User-defined fns and variadic builtins pass // through (builtin_arity returns -1). arity_check_call(fn_name, arity) // sleep(Duration) — Phase 1 of the typed-time work. When the // single arg is provably a Duration we lower to el_sleep_duration // so the runtime sees nanos directly. Existing sleep() callers // that pass an Int still emit `sleep()`, which falls through // to the no-such-symbol path — those call sites must migrate to // a typed Duration. Acceptable: the spec marks them out for an // audit pass during Phase 1. if str_eq(fn_name, "sleep") { if arity == 1 { let only_arg = native_list_get(args, 0) if is_duration_expr(only_arg) { return "el_sleep_duration(" + args_c + ")" } } } return fn_name + "(" + args_c + ")" } if func_kind == "Field" { let obj = func["object"] let field: String = func["field"] let obj_c: String = cg_expr(obj) if arity > 0 { return field + "(" + obj_c + ", " + args_c + ")" } return field + "(" + obj_c + ")" } let fn_c: String = cg_expr(func) return fn_c + "(" + args_c + ")" } if kind == "Field" { let obj = expr["object"] let field: String = expr["field"] let obj_c: String = cg_expr(obj) // el_get_field takes el_val_t for both args, so the field name // string literal must be wrapped in EL_STR(). Without the wrap // the C compiler treats the bare const char* as an int64 (warns // -Wint-conversion) and the runtime reads gibberish at the address // when looking up the key. return "el_get_field(" + obj_c + ", EL_STR(" + c_str_lit(field) + "))" } if kind == "Index" { // El programs use `t["field"]` for map access and `arr[i]` for // list access. The parser emits the same Index node for both. // Dispatch at codegen time on the index expression kind: string- // literal index → map field access (`el_get_field`); anything // else → list element access (`el_list_get`). let obj = expr["object"] let idx = expr["index"] let obj_c: String = cg_expr(obj) let idx_c: String = cg_expr(idx) let idx_kind: String = idx["expr"] if str_eq(idx_kind, "Str") { return "el_get_field(" + obj_c + ", " + idx_c + ")" } return "el_list_get(" + obj_c + ", " + idx_c + ")" } if kind == "Array" { let elems = expr["elems"] let n: Int = native_list_len(elems) // Empty literal: el_list_new(0, ) generates malformed C (trailing // comma in a varargs call). Emit el_list_empty() directly. if n == 0 { return "el_list_empty()" } let items = "" let i = 0 while i < n { let elem = native_list_get(elems, i) let elem_c: String = cg_expr(elem) if i > 0 { let items = items + ", " } let items = items + elem_c let i = i + 1 } return "el_list_new(" + native_int_to_str(n) + ", " + items + ")" } if kind == "Map" { let pairs = expr["pairs"] let n: Int = native_list_len(pairs) // Empty literal: `el_map_new(0, )` is malformed C (trailing comma in // a varargs call). Emit `el_map_new(0)` directly so empty-map // shadowing inside for/while/if bodies — `let acc: Map = {}` — // doesn't fail downstream cc with parse errors. if n == 0 { return "el_map_new(0)" } let items = "" let i = 0 while i < n { let pair = native_list_get(pairs, i) let key: String = pair["key"] let val = pair["value"] let val_c: String = cg_expr(val) if i > 0 { let items = items + ", " } let items = items + c_str_lit(key) + ", " + val_c let i = i + 1 } return "el_map_new(" + native_int_to_str(n) + ", " + items + ")" } if kind == "Try" { let inner = expr["inner"] return cg_expr(inner) } if kind == "If" { return cg_if_expr(expr) } if kind == "Match" { return cg_match(expr) } "EL_NULL" } // ── Match codegen ───────────────────────────────────────────────────────────── // // Lower a match expression to a GCC/Clang statement-expression. // A unique label suffix is allocated per match via state_set("__match_counter"). fn next_match_id() -> String { let csv: String = state_get("__match_counter") let n = 0 if !str_eq(csv, "") { let n = str_to_int(csv) } let n = n + 1 state_set("__match_counter", native_int_to_str(n)) native_int_to_str(n) } fn cg_match(expr: Map) -> String { let subject = expr["subject"] let arms = expr["arms"] let subj_c: String = cg_expr(subject) let id: String = next_match_id() let subj_var: String = "_match_subj_" + id let result_var: String = "_match_result_" + id let done_label: String = "_match_done_" + id let out: String = "({ el_val_t " + subj_var + " = " + subj_c + "; el_val_t " + result_var + " = 0; " let n: Int = native_list_len(arms) let i = 0 while i < n { let arm = native_list_get(arms, i) let pat = arm["pattern"] let body = arm["body"] let pkind: String = pat["pattern"] let body_c: String = cg_expr(body) if str_eq(pkind, "Wildcard") { let out = out + "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } " } else { if str_eq(pkind, "Binding") { let bname: String = pat["name"] let out = out + "{ el_val_t " + bname + " = " + subj_var + "; " + result_var + " = (" + body_c + "); goto " + done_label + "; } " } else { if str_eq(pkind, "LitInt") { let v: String = pat["value"] let out = out + "if (" + subj_var + " == " + v + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } " } else { if str_eq(pkind, "LitStr") { let v: String = pat["value"] let out = out + "if (str_eq(" + subj_var + ", EL_STR(" + c_str_lit(v) + "))) { " + result_var + " = (" + body_c + "); goto " + done_label + "; } " } else { if str_eq(pkind, "LitBool") { let v: String = pat["value"] let bv = "0" if str_eq(v, "true") { let bv = "1" } let out = out + "if (" + subj_var + " == " + bv + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } " } else { // unknown pattern → wildcard let out = out + "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } " } } } } } let i = i + 1 } let out = out + done_label + ":; " + result_var + "; })" out } // ── If-as-expression codegen ───────────────────────────────────────────────── // // Lower `if cond { thenBody } else { elseBody }` used in expression position // (e.g. `let x = if a { b } else { c }`) to a GCC/Clang statement-expression // so the actual arm bodies are evaluated, not just `(cond ? 1 : 0)`. // // Each arm body is a list of statements; the result of the arm is the value // of its final Expr statement (mirroring transform_implicit_return at function // scope). Statements before the final Expr are emitted as expression-statements // for their side effects. fn next_if_id() -> String { let csv: String = state_get("__if_expr_counter") let n = 0 if !str_eq(csv, "") { let n = str_to_int(csv) } let n = n + 1 state_set("__if_expr_counter", native_int_to_str(n)) native_int_to_str(n) } // Render a single arm of the if-as-expression: emit each statement-before-last // as a side-effecting expression, then assign the final Expr's value to the // result var. If the arm body is empty or its last stmt isn't an Expr, the // result var stays at its initial 0. fn cg_if_expr_arm(stmts: [Map], result_var: String) -> String { let n: Int = native_list_len(stmts) let out = "" let i = 0 while i < n { let s = native_list_get(stmts, i) let sk: String = s["stmt"] let is_last: Bool = false if i == n - 1 { let is_last = true } if str_eq(sk, "Let") { let name: String = s["name"] let val = s["value"] let val_c: String = cg_expr(val) let out = out + "el_val_t " + name + " = " + val_c + "; " } else { if str_eq(sk, "Return") { let val = s["value"] let val_c: String = cg_expr(val) let out = out + result_var + " = (" + val_c + "); " } else { if str_eq(sk, "Expr") { let val = s["value"] let val_c: String = cg_expr(val) if is_last { let out = out + result_var + " = (" + val_c + "); " } else { let out = out + "(void)(" + val_c + "); " } } else { if str_eq(sk, "Assign") { // Real reassignment in an expression-position arm — // emit the store; the arm's "value" stays whatever // result_var was last set to, which is the El // semantics (assignment is a statement, not a value). let aname: String = s["name"] let aval = s["value"] let aval_c: String = cg_expr(aval) let out = out + aname + " = " + aval_c + "; " } else { // Non-trivial stmt kinds (While/For) shouldn't appear in // expression-position arm bodies; emit nothing rather // than malformed C. } } } } let i = i + 1 } out } fn cg_if_expr(expr: Map) -> String { let cond = expr["cond"] let then_stmts = expr["then"] let else_stmts = expr["else"] let has_else: Bool = expr["has_else"] let cond_c: String = cg_expr(cond) let id: String = next_if_id() let result_var: String = "_if_result_" + id let then_c: String = cg_if_expr_arm(then_stmts, result_var) let else_c: String = "" if has_else { let else_c = cg_if_expr_arm(else_stmts, result_var) } let out: String = "({ el_val_t " + result_var + " = 0; if (" + cond_c + ") { " + then_c + "} else { " + else_c + "} " + result_var + "; })" out } // ── Variable scope tracking ─────────────────────────────────────────────────── // // El allows `let x = expr` to both declare and reassign x in the same scope. // C doesn't allow redeclaring the same name in the same block. // We track declared names in a list and emit `x = expr` (no type prefix) // when x is already declared. The declared list is passed through all // statement emitters. fn list_contains(lst: [String], s: String) -> Bool { let n: Int = native_list_len(lst) let i = 0 while i < n { let item: String = native_list_get(lst, i) if item == s { return true } let i = i + 1 } false } // ── Statement codegen ───────────────────────────────────────────────────────── // // cg_stmt emits C lines via println. declared is a list of already-declared // variable names in the current C scope; returns updated declared list. fn cg_stmt(stmt: Map, indent: String, declared: [String]) -> [String] { let kind: String = stmt["stmt"] if kind == "Let" { let name: String = stmt["name"] let val = stmt["value"] let val_c: String = cg_expr(val) // If the binding is annotated `: Int` and val is an Int literal, // register `name` in the per-function int-name set so that later // `name + ...` dispatches to arithmetic, not concat. let ltype: String = stmt["type"] if str_eq(ltype, "Int") { add_int_name(name) } // Temporal type annotations register the name with the matching // typed-set so BinOp / comparison codegen routes through the // typed wrappers and forbids cross-type ops. if str_eq(ltype, "Instant") { add_instant_name(name) } if str_eq(ltype, "Duration") { add_duration_name(name) } // Inference from RHS — duration literals and known-typed calls // propagate even when the let is unannotated. if is_instant_expr(val) { add_instant_name(name) } if is_duration_expr(val) { add_duration_name(name) } let vk: String = val["expr"] if str_eq(vk, "Int") { add_int_name(name) } if list_contains(declared, name) { emit_line(indent + name + " = " + val_c + ";") return declared } else { emit_line(indent + "el_val_t " + name + " = " + val_c + ";") return native_list_append(declared, name) } } if kind == "Return" { let val = stmt["value"] let val_kind: String = val["expr"] if val_kind == "Nil" { emit_line(indent + "return 0;") } else { let val_c: String = cg_expr(val) emit_line(indent + "return " + val_c + ";") } return declared } // Bare reassignment: `name = expr`. Always emits a plain C assignment // (no `el_val_t` prefix) — by construction the parser only produces // Assign for an existing identifier. If the name happens NOT to be in // `declared` for the current C scope (it was let-bound by an enclosing // block) the emit still resolves at C level because the variable lives // in the surrounding scope. if kind == "Assign" { let name: String = stmt["name"] let val = stmt["value"] let val_c: String = cg_expr(val) emit_line(indent + name + " = " + val_c + ";") return declared } if kind == "Expr" { let val = stmt["value"] let val_kind: String = val["expr"] if val_kind == "If" { cg_if_stmt(val, indent, declared) return declared } if val_kind == "For" { cg_for_stmt(val, indent, declared) return declared } let val_c: String = cg_expr(val) emit_line(indent + val_c + ";") return declared } if kind == "While" { let cond = stmt["cond"] let body = stmt["body"] let cond_c: String = cg_expr(cond) let cond_c = strip_outer_parens(cond_c) emit_line(indent + "while (" + cond_c + ") {") // Body lives in its own C block — clone so let-bindings inside the // loop don't leak into the parent's `declared` list (which would make // a sibling scope's `let x` emit assignment on an undeclared name). cg_stmts(body, indent + " ", native_list_clone(declared)) emit_line(indent + "}") return declared } if kind == "For" { let item: String = stmt["item"] let list_expr = stmt["list"] let body = stmt["body"] cg_for_body(item, list_expr, body, indent, declared) return declared } if kind == "FnDef" { return declared } if kind == "TypeDef" { return declared } if kind == "EnumDef" { return declared } if kind == "Import" { return declared } if kind == "CgiBlock" { return declared } declared } // Strip a single layer of surrounding parentheses from a C expression string. fn strip_outer_parens(s: String) -> String { let chars: [String] = native_string_chars(s) let n: Int = native_list_len(chars) if n < 2 { return s } let first: String = native_list_get(chars, 0) let last: String = native_list_get(chars, n - 1) if first == "(" { if last == ")" { let depth = 1 let i = 1 let balanced = true while i < n - 1 { let ch: String = native_list_get(chars, i) if ch == "(" { let depth = depth + 1 } if ch == ")" { let depth = depth - 1 if depth == 0 { let balanced = false let i = n } } let i = i + 1 } if balanced { let inner = "" let j = 1 while j < n - 1 { let ch: String = native_list_get(chars, j) let inner = inner + ch let j = j + 1 } return inner } } } s } fn cg_if_stmt(expr: Map, indent: String, declared: [String]) -> Void { let cond = expr["cond"] let then_stmts = expr["then"] let else_stmts = expr["else"] let has_else: Bool = expr["has_else"] let cond_c: String = cg_expr(cond) let cond_c = strip_outer_parens(cond_c) emit_line(indent + "if (" + cond_c + ") {") // Each branch gets its own clone of `declared` — variables let-bound // inside the then/else block live only in that C scope, and must not // leak back to the parent (or to the sibling branch) through shared // list mutation. Cheap shallow copy; the entries (variable name strings) // are shared. cg_stmts(then_stmts, indent + " ", native_list_clone(declared)) if has_else { emit_line(indent + "} else {") cg_stmts(else_stmts, indent + " ", native_list_clone(declared)) } emit_line(indent + "}") } fn cg_for_body(item: String, list_expr: Map, body: [Map], indent: String, declared: [String]) -> Void { let list_c: String = cg_expr(list_expr) let idx = "_el_i" let list_tmp = "_el_lst" let len_tmp = "_el_len" emit_line(indent + "{") emit_line(indent + " el_val_t " + list_tmp + " = " + list_c + ";") emit_line(indent + " el_val_t " + len_tmp + " = el_list_len(" + list_tmp + ");") emit_line(indent + " for (el_val_t " + idx + " = 0; " + idx + " < " + len_tmp + "; " + idx + "++) {") emit_line(indent + " el_val_t " + item + " = el_list_get(" + list_tmp + ", " + idx + ");") // Body lives inside its own C block; the loop variable and any locally // let-bound names go out of scope at the closing brace, so we mustn't // pollute the parent's `declared` with them. let body_decl = native_list_clone(declared) let body_decl = native_list_append(body_decl, item) cg_stmts(body, indent + " ", body_decl) emit_line(indent + " }") emit_line(indent + "}") } fn cg_for_stmt(expr: Map, indent: String, declared: [String]) -> Void { let item: String = expr["item"] let list_expr = expr["list"] let body = expr["body"] cg_for_body(item, list_expr, body, indent, declared) } fn cg_stmts(stmts: [Map], indent: String, declared: [String]) -> [String] { let n: Int = native_list_len(stmts) let i = 0 let decl = declared while i < n { let stmt = native_list_get(stmts, i) let decl = cg_stmt(stmt, indent, decl) let i = i + 1 } decl } // ── Function declaration codegen ─────────────────────────────────────────────── fn param_decl(param: Map, idx: Int) -> String { let name: String = param["name"] "el_val_t " + name } fn params_to_c(params: [Map]) -> String { let n: Int = native_list_len(params) if n == 0 { return "void" } let out = "" let i = 0 while i < n { let param = native_list_get(params, i) let decl: String = param_decl(param, i) if i > 0 { let out = out + ", " } let out = out + decl let i = i + 1 } out } // Transform a function body so that an implicit-return final expression // becomes an explicit Return. El allows the last expression in a function // body to be the return value (e.g. `fn lex(s) { ... tokens }` returns // `tokens`). Without this transform, the codegen emits the bare expression // and falls through to the trailing `return 0;`, losing the value. // // Rules: a body ending in a bare Expr whose inner expr is NOT a control- // flow construct (If/For) is rewritten so that final Expr becomes a // Return statement carrying the same value. Bodies whose final statement // is already a Return, While, For, or a non-value-producing form pass // through unchanged. fn transform_implicit_return(body: [Map]) -> [Map] { let n: Int = native_list_len(body) if n == 0 { return body } let last: Map = native_list_get(body, n - 1) let last_kind: String = last["stmt"] if last_kind == "Expr" { let val = last["value"] let val_kind: String = val["expr"] // Skip control-flow expressions used as statements if val_kind == "If" { return body } if val_kind == "For" { return body } // Replace the last bare Expr with a Return carrying the same value let new_body: [Map] = native_list_empty() let i = 0 while i < n - 1 { let new_body = native_list_append(new_body, native_list_get(body, i)) let i = i + 1 } let return_stmt: Map = { "stmt": "Return", "value": val } let new_body = native_list_append(new_body, return_stmt) return new_body } body } // Test whether `name` is currently registered as an Int-typed identifier // for the function being codegened. The set is maintained as a comma- // bounded CSV in process state; cg_fn seeds it from typed parameters, // cg_stmt extends it from typed `let` bindings. fn is_int_name(name: String) -> Bool { let csv: String = state_get("__int_names") if str_eq(csv, "") { return false } return str_contains(csv, "," + name + ",") } // Same shape as is_int_name, for Instant- and Duration-typed bindings. // Used by the BinOp/comparison codegen to dispatch arithmetic through the // typed runtime wrappers (el_instant_add_dur, el_duration_lt, …) and to // surface mismatches (Instant + Instant, Duration + Int) as #error // directives at the top of the generated C. fn is_instant_name(name: String) -> Bool { let csv: String = state_get("__instant_names") if str_eq(csv, "") { return false } return str_contains(csv, "," + name + ",") } fn is_duration_name(name: String) -> Bool { let csv: String = state_get("__duration_names") if str_eq(csv, "") { return false } return str_contains(csv, "," + name + ",") } // Known runtime builtins that return Int. Used to dispatch arithmetic vs // string-concat on `+` when one side is a Call. New builtins must be added // here when they return Int and may participate in arithmetic. fn is_int_call(call_expr: Map) -> Bool { let func = call_expr["func"] let fk: String = func["expr"] if !str_eq(fk, "Ident") { return false } let name: String = func["name"] if str_eq(name, "str_len") { return true } if str_eq(name, "str_index_of") { return true } if str_eq(name, "str_to_int") { return true } if str_eq(name, "str_char_code") { return true } if str_eq(name, "native_list_len") { return true } if str_eq(name, "el_list_len") { return true } if str_eq(name, "len") { return true } if str_eq(name, "json_get_int") { return true } if str_eq(name, "json_array_len") { return true } if str_eq(name, "engram_node_count") { return true } if str_eq(name, "engram_edge_count") { return true } if str_eq(name, "time_now") { return true } if str_eq(name, "time_now_utc") { return true } if str_eq(name, "time_diff") { return true } if str_eq(name, "time_add") { return true } if str_eq(name, "time_from_parts") { return true } if str_eq(name, "el_abs") { return true } if str_eq(name, "el_max") { return true } if str_eq(name, "el_min") { return true } if str_eq(name, "float_to_int") { return true } if str_eq(name, "unix_timestamp") { return true } if str_eq(name, "instant_to_unix_seconds") { return true } if str_eq(name, "instant_to_unix_millis") { return true } if str_eq(name, "duration_to_seconds") { return true } if str_eq(name, "duration_to_millis") { return true } if str_eq(name, "duration_to_nanos") { return true } return false } // Builtins that return an Instant. Used by is_instant_expr and the BinOp // dispatch — `now() + 5.seconds` types as Instant only because we can see // that now() is an Instant-returning Call. fn is_instant_call(call_expr: Map) -> Bool { let func = call_expr["func"] let fk: String = func["expr"] if !str_eq(fk, "Ident") { return false } let name: String = func["name"] if str_eq(name, "now") { return true } if str_eq(name, "el_now_instant") { return true } if str_eq(name, "unix_seconds") { return true } if str_eq(name, "unix_millis") { return true } if str_eq(name, "instant_from_iso8601") { return true } if str_eq(name, "el_instant_add_dur") { return true } if str_eq(name, "el_instant_sub_dur") { return true } return false } // Builtins that return a Duration. Same role as is_instant_call. fn is_duration_call(call_expr: Map) -> Bool { let func = call_expr["func"] let fk: String = func["expr"] if !str_eq(fk, "Ident") { return false } let name: String = func["name"] if str_eq(name, "el_duration_from_nanos") { return true } if str_eq(name, "duration_seconds") { return true } if str_eq(name, "duration_millis") { return true } if str_eq(name, "duration_nanos") { return true } if str_eq(name, "el_instant_diff") { return true } if str_eq(name, "el_duration_add") { return true } if str_eq(name, "el_duration_sub") { return true } if str_eq(name, "el_duration_scale") { return true } if str_eq(name, "el_duration_div") { return true } if str_eq(name, "ttl_cache_age") { return true } return false } // Recursive type predicates for Instant / Duration. Mirror is_int_expr. // is_instant_expr / is_duration_expr return true only when the expression // is provably of that type at codegen time. Anything ambiguous returns // false — the BinOp dispatcher then leaves the expression on the // untyped-int path, which is the safest fallback because at the runtime // level all three types share the int64 slot. fn is_instant_expr(expr: Map) -> Bool { let k: String = expr["expr"] if str_eq(k, "Ident") { let name: String = expr["name"] return is_instant_name(name) } if str_eq(k, "Call") { return is_instant_call(expr) } if str_eq(k, "BinOp") { let op: String = expr["op"] if str_eq(op, "Plus") { // Instant + Duration → Instant // Duration + Instant → Instant if is_instant_expr(expr["left"]) { if is_duration_expr(expr["right"]) { return true } } if is_duration_expr(expr["left"]) { if is_instant_expr(expr["right"]) { return true } } return false } if str_eq(op, "Minus") { // Instant - Duration → Instant if is_instant_expr(expr["left"]) { if is_duration_expr(expr["right"]) { return true } } return false } return false } return false } fn is_duration_expr(expr: Map) -> Bool { let k: String = expr["expr"] if str_eq(k, "DurationLit") { return true } if str_eq(k, "Ident") { let name: String = expr["name"] return is_duration_name(name) } if str_eq(k, "Call") { return is_duration_call(expr) } if str_eq(k, "Neg") { return is_duration_expr(expr["inner"]) } if str_eq(k, "BinOp") { let op: String = expr["op"] if str_eq(op, "Plus") { // Duration + Duration → Duration if is_duration_expr(expr["left"]) { if is_duration_expr(expr["right"]) { return true } } return false } if str_eq(op, "Minus") { // Duration - Duration → Duration // Instant - Instant → Duration (caught here, not in is_instant_expr) if is_duration_expr(expr["left"]) { if is_duration_expr(expr["right"]) { return true } } if is_instant_expr(expr["left"]) { if is_instant_expr(expr["right"]) { return true } } return false } if str_eq(op, "Star") { // Duration * Int → Duration // Int * Duration → Duration if is_duration_expr(expr["left"]) { if is_int_expr(expr["right"]) { return true } } if is_int_expr(expr["left"]) { if is_duration_expr(expr["right"]) { return true } } return false } if str_eq(op, "Slash") { // Duration / Int → Duration if is_duration_expr(expr["left"]) { if is_int_expr(expr["right"]) { return true } } return false } return false } return false } // Record a temporal-type violation. Surfaced as `#error` directives at the // top of the generated C, identical machinery to cap_record_violation. // kinds: "instant_plus_instant", "duration_plus_int", etc. fn time_record_violation(kind: String, detail: String) -> Bool { let csv: String = state_get("__time_violations") if str_eq(csv, "") { let csv = "," } let entry: String = kind + ":" + detail let key: String = "," + entry + "," if str_contains(csv, key) { return true } state_set("__time_violations", csv + entry + ",") return true } // Recursive type-propagation: is `expr` known-Int at codegen time? // This unifies the BinOp(+) dispatch so chained arithmetic over Int // operands stays arithmetic. Without recursion, a wrapping `+` between // `BinOp(+) of two Ints` and another Int falls to el_str_concat because // the outer dispatch only checks the immediate kind, not the inner. // // Rules: // Int literal → Int // Ident in __int_names → Int // Call to known-Int builtin → Int // Neg of Int → Int // BinOp arithmetic of two Ints → Int (Plus, Minus, Star, Slash, Percent) // BinOp comparison/logical → Int (yields 0/1; safe to treat as Int) // anything else → not provably Int fn is_int_expr(expr: Map) -> Bool { let k: String = expr["expr"] if str_eq(k, "Int") { return true } if str_eq(k, "Ident") { let name: String = expr["name"] return is_int_name(name) } if str_eq(k, "Call") { return is_int_call(expr) } if str_eq(k, "Neg") { return is_int_expr(expr["inner"]) } if str_eq(k, "Not") { return true } if str_eq(k, "BinOp") { let op: String = expr["op"] // Comparisons and logicals always yield 0/1 — safe Int. if str_eq(op, "EqEq") { return true } if str_eq(op, "NotEq") { return true } if str_eq(op, "Lt") { return true } if str_eq(op, "Gt") { return true } if str_eq(op, "LtEq") { return true } if str_eq(op, "GtEq") { return true } if str_eq(op, "And") { return true } if str_eq(op, "Or") { return true } // Arithmetic propagates: Int op Int → Int. if str_eq(op, "Plus") { if is_int_expr(expr["left"]) { if is_int_expr(expr["right"]) { return true } } return false } if str_eq(op, "Minus") { if is_int_expr(expr["left"]) { if is_int_expr(expr["right"]) { return true } } return false } if str_eq(op, "Star") { if is_int_expr(expr["left"]) { if is_int_expr(expr["right"]) { return true } } return false } if str_eq(op, "Slash") { if is_int_expr(expr["left"]) { if is_int_expr(expr["right"]) { return true } } return false } return false } return false } // ── Capability-kind enforcement ────────────────────────────────────────────── // // A program's top-level block (cgi / service / none) determines which // runtime primitives it may call. The compiler records violations in // process state during cg_expr's Call emission; codegen's entry point // then emits #error directives at the top of the generated C so the // downstream cc step fails with a clear message. // // Capability tiers: // "cgi" — full self-formation. All primitives. // "service" — bounded. Cannot call self-formation primitives: // llm_call_agentic, llm_register_tool, dharma_emit, // dharma_field. Single-turn LLM calls are allowed. // "utility" — default. No DHARMA, no LLM. Pure compute + I/O. // // The compiler-level rule is structural: the binary either CAN or CANNOT // emit the call. There is no runtime check, no opt-in, no override. fn cap_record_violation(kind: String, fn_name: String) -> Bool { let csv: String = state_get("__cap_violations") if str_eq(csv, "") { let csv = "," } let entry: String = kind + ":" + fn_name let key: String = "," + entry + "," if str_contains(csv, key) { return true } state_set("__cap_violations", csv + entry + ",") return true } // Self-formation primitives — the cut between CGI and service. A program // that emits these calls IS structurally a CGI; we forbid them everywhere // else. fn is_self_formation_call(fn_name: String) -> Bool { if str_eq(fn_name, "llm_call_agentic") { return true } if str_eq(fn_name, "llm_register_tool") { return true } if str_eq(fn_name, "dharma_emit") { return true } if str_eq(fn_name, "dharma_field") { return true } return false } // Any DHARMA primitive — utilities have zero network presence. fn is_dharma_call(fn_name: String) -> Bool { if str_eq(fn_name, "dharma_connect") { return true } if str_eq(fn_name, "dharma_send") { return true } if str_eq(fn_name, "dharma_activate") { return true } if str_eq(fn_name, "dharma_emit") { return true } if str_eq(fn_name, "dharma_field") { return true } if str_eq(fn_name, "dharma_strengthen") { return true } if str_eq(fn_name, "dharma_relationship") { return true } if str_eq(fn_name, "dharma_peers") { return true } return false } // Any LLM primitive — utilities have no LLM access at all. fn is_llm_call(fn_name: String) -> Bool { if str_eq(fn_name, "llm_call") { return true } if str_eq(fn_name, "llm_call_system") { return true } if str_eq(fn_name, "llm_call_agentic") { return true } if str_eq(fn_name, "llm_vision") { return true } if str_eq(fn_name, "llm_register_tool") { return true } if str_eq(fn_name, "llm_models") { return true } return false } fn cap_check_call(fn_name: String) -> Bool { let kind: String = state_get("__program_kind") if str_eq(kind, "cgi") { return true } if str_eq(kind, "service") { if is_self_formation_call(fn_name) { cap_record_violation("service", fn_name) return false } return true } // utility (default) if is_dharma_call(fn_name) { cap_record_violation("utility", fn_name) return false } if is_llm_call(fn_name) { cap_record_violation("utility", fn_name) return false } return true } // Emit collected capability violations as #error directives. Called // from codegen()'s entry point right after the cgi/service-block scan, // so they appear at the very top of the generated C. fn emit_cap_violations() -> Void { let csv: String = state_get("__cap_violations") if str_eq(csv, "") { return } if str_eq(csv, ",") { return } let n: Int = str_len(csv) let i: Int = 1 while i < n { let next_comma: Int = str_index_of(str_slice(csv, i, n), ",") if next_comma < 0 { return } let entry: String = str_slice(csv, i, i + next_comma) let colon: Int = str_index_of(entry, ":") if colon > 0 { let kind: String = str_slice(entry, 0, colon) let fn_name: String = str_slice(entry, colon + 1, str_len(entry)) emit_line("#error \"capability violation: '" + kind + "' programs may not call '" + fn_name + "' (self-formation primitive — only 'cgi' programs may use it)\"") } let i = i + next_comma + 1 } } // Surface temporal-type violations as #error directives. The cg_expr BinOp // dispatcher records each violation (Instant + Instant, Duration + Int, …) // as a CSV entry "kind:detail" via time_record_violation. Each entry maps // to a single #error so downstream cc fails the build with a clear El- // source-level message before the bogus C even links. fn emit_time_violations() -> Void { let csv: String = state_get("__time_violations") if str_eq(csv, "") { return } if str_eq(csv, ",") { return } let n: Int = str_len(csv) let i: Int = 1 while i < n { let next_comma: Int = str_index_of(str_slice(csv, i, n), ",") if next_comma < 0 { return } let entry: String = str_slice(csv, i, i + next_comma) let colon: Int = str_index_of(entry, ":") if colon > 0 { let detail: String = str_slice(entry, colon + 1, str_len(entry)) emit_line("#error \"temporal type error: " + detail + "\"") } let i = i + next_comma + 1 } } // ── Builtin arity table ─────────────────────────────────────────────────────── // // El programs sometimes call runtime builtins with the wrong number of // arguments (e.g. `http_serve(port)` instead of `http_serve(port, handler)`). // Without this check the generated C compiles to a call with too few / // too many args and fails downstream cc with a generic "too few arguments" // message that doesn't point to the El source line. // // Strategy: a small static table mirrors el_runtime.h. Variadic builtins // (el_list_new, el_map_new, args) and unknown identifiers (user fns, // dynamic dispatch) return -1 → no check. A mismatch records a violation // in process state, which emit_arity_violations() turns into #error // directives at the top of the generated C. fn builtin_arity(name: String) -> Int { // I/O if str_eq(name, "println") { return 1 } if str_eq(name, "print") { return 1 } if str_eq(name, "readline") { return 0 } // String if str_eq(name, "el_str_concat") { return 2 } if str_eq(name, "str_eq") { return 2 } if str_eq(name, "str_starts_with") { return 2 } if str_eq(name, "str_ends_with") { return 2 } if str_eq(name, "str_len") { return 1 } if str_eq(name, "str_concat") { return 2 } if str_eq(name, "int_to_str") { return 1 } if str_eq(name, "str_to_int") { return 1 } if str_eq(name, "str_slice") { return 3 } if str_eq(name, "str_contains") { return 2 } if str_eq(name, "str_replace") { return 3 } if str_eq(name, "str_to_upper") { return 1 } if str_eq(name, "str_to_lower") { return 1 } if str_eq(name, "str_trim") { return 1 } if str_eq(name, "str_index_of") { return 2 } if str_eq(name, "str_split") { return 2 } if str_eq(name, "str_char_at") { return 2 } if str_eq(name, "str_char_code") { return 2 } if str_eq(name, "str_pad_left") { return 3 } if str_eq(name, "str_pad_right") { return 3 } if str_eq(name, "str_format") { return 2 } if str_eq(name, "str_lower") { return 1 } if str_eq(name, "str_upper") { return 1 } // HTML sanitizer if str_eq(name, "el_html_sanitize") { return 2 } // Math if str_eq(name, "el_abs") { return 1 } if str_eq(name, "el_max") { return 2 } if str_eq(name, "el_min") { return 2 } // List if str_eq(name, "el_list_len") { return 1 } if str_eq(name, "el_list_get") { return 2 } if str_eq(name, "el_list_append") { return 2 } if str_eq(name, "el_list_empty") { return 0 } if str_eq(name, "el_list_clone") { return 1 } if str_eq(name, "list_push") { return 2 } if str_eq(name, "list_push_front") { return 2 } if str_eq(name, "list_join") { return 2 } if str_eq(name, "list_range") { return 2 } // Map if str_eq(name, "el_get_field") { return 2 } if str_eq(name, "el_map_get") { return 2 } if str_eq(name, "el_map_set") { return 3 } // HTTP if str_eq(name, "http_get") { return 1 } if str_eq(name, "http_post") { return 2 } if str_eq(name, "http_post_json") { return 2 } if str_eq(name, "http_get_with_headers") { return 2 } if str_eq(name, "http_post_with_headers") { return 3 } if str_eq(name, "http_post_form_auth") { return 3 } if str_eq(name, "http_serve") { return 2 } if str_eq(name, "http_set_handler") { return 1 } // Filesystem if str_eq(name, "fs_read") { return 1 } if str_eq(name, "fs_write") { return 2 } if str_eq(name, "fs_list") { return 1 } // JSON if str_eq(name, "json_get") { return 2 } if str_eq(name, "json_parse") { return 1 } if str_eq(name, "json_stringify") { return 1 } if str_eq(name, "json_get_string") { return 2 } if str_eq(name, "json_get_int") { return 2 } if str_eq(name, "json_get_float") { return 2 } if str_eq(name, "json_get_bool") { return 2 } if str_eq(name, "json_get_raw") { return 2 } if str_eq(name, "json_set") { return 3 } if str_eq(name, "json_array_len") { return 1 } // Time if str_eq(name, "time_now") { return 0 } if str_eq(name, "time_now_utc") { return 0 } if str_eq(name, "sleep_secs") { return 1 } if str_eq(name, "sleep_ms") { return 1 } if str_eq(name, "time_format") { return 2 } if str_eq(name, "time_to_parts") { return 1 } if str_eq(name, "time_from_parts") { return 3 } if str_eq(name, "time_add") { return 3 } if str_eq(name, "time_diff") { return 3 } // UUID if str_eq(name, "uuid_new") { return 0 } if str_eq(name, "uuid_v4") { return 0 } // Env / state if str_eq(name, "env") { return 1 } if str_eq(name, "state_set") { return 2 } if str_eq(name, "state_get") { return 1 } if str_eq(name, "state_del") { return 1 } if str_eq(name, "state_keys") { return 0 } // Float if str_eq(name, "float_to_str") { return 1 } if str_eq(name, "int_to_float") { return 1 } if str_eq(name, "float_to_int") { return 1 } if str_eq(name, "format_float") { return 2 } if str_eq(name, "decimal_round") { return 2 } if str_eq(name, "str_to_float") { return 1 } // Math (Float) if str_eq(name, "math_sqrt") { return 1 } if str_eq(name, "math_log") { return 1 } if str_eq(name, "math_ln") { return 1 } if str_eq(name, "math_sin") { return 1 } if str_eq(name, "math_cos") { return 1 } if str_eq(name, "math_pi") { return 0 } // Bool if str_eq(name, "bool_to_str") { return 1 } // Process if str_eq(name, "exit_program") { return 1 } // CGI / DHARMA if str_eq(name, "dharma_connect") { return 1 } if str_eq(name, "dharma_send") { return 2 } if str_eq(name, "dharma_activate") { return 1 } if str_eq(name, "dharma_emit") { return 2 } if str_eq(name, "dharma_field") { return 1 } if str_eq(name, "dharma_strengthen") { return 2 } if str_eq(name, "dharma_relationship") { return 1 } if str_eq(name, "dharma_peers") { return 0 } // Engram if str_eq(name, "engram_node") { return 3 } if str_eq(name, "engram_node_full") { return 8 } if str_eq(name, "engram_get_node") { return 1 } if str_eq(name, "engram_strengthen") { return 1 } if str_eq(name, "engram_forget") { return 1 } if str_eq(name, "engram_node_count") { return 0 } if str_eq(name, "engram_search") { return 2 } if str_eq(name, "engram_scan_nodes") { return 2 } if str_eq(name, "engram_connect") { return 4 } if str_eq(name, "engram_edge_between") { return 2 } if str_eq(name, "engram_neighbors") { return 1 } if str_eq(name, "engram_neighbors_filtered") { return 3 } if str_eq(name, "engram_edge_count") { return 0 } if str_eq(name, "engram_activate") { return 2 } if str_eq(name, "engram_save") { return 1 } if str_eq(name, "engram_load") { return 1 } if str_eq(name, "engram_get_node_json") { return 1 } if str_eq(name, "engram_search_json") { return 2 } if str_eq(name, "engram_scan_nodes_json") { return 2 } if str_eq(name, "engram_neighbors_json") { return 3 } if str_eq(name, "engram_activate_json") { return 2 } if str_eq(name, "engram_stats_json") { return 0 } // LLM if str_eq(name, "llm_call") { return 2 } if str_eq(name, "llm_call_system") { return 3 } if str_eq(name, "llm_call_agentic") { return 4 } if str_eq(name, "llm_vision") { return 4 } if str_eq(name, "llm_models") { return 0 } if str_eq(name, "llm_register_tool") { return 2 } // Crypto if str_eq(name, "sha256_hex") { return 1 } if str_eq(name, "sha256_bytes") { return 1 } if str_eq(name, "hmac_sha256_hex") { return 2 } if str_eq(name, "hmac_sha256_bytes") { return 2 } if str_eq(name, "base64_encode") { return 1 } if str_eq(name, "base64_decode") { return 1 } if str_eq(name, "base64url_encode") { return 1 } if str_eq(name, "base64url_decode") { return 1 } // Native VM aliases if str_eq(name, "native_list_get") { return 2 } if str_eq(name, "native_list_len") { return 1 } if str_eq(name, "native_list_append") { return 2 } if str_eq(name, "native_list_empty") { return 0 } if str_eq(name, "native_list_clone") { return 1 } if str_eq(name, "native_string_chars") { return 1 } if str_eq(name, "native_int_to_str") { return 1 } // Method-call aliases if str_eq(name, "append") { return 2 } if str_eq(name, "len") { return 1 } if str_eq(name, "get") { return 2 } if str_eq(name, "map_get") { return 2 } if str_eq(name, "map_set") { return 3 } // -1 sentinel: variadic / unknown / user-defined → no check. return -1 } fn arity_record_violation(fn_name: String, expected: Int, actual: Int) -> Bool { let csv: String = state_get("__arity_violations") if str_eq(csv, "") { let csv = "," } // Encode as fn_name|expected|actual to recover all three at emit time. let entry: String = fn_name + "|" + native_int_to_str(expected) + "|" + native_int_to_str(actual) let key: String = "," + entry + "," if str_contains(csv, key) { return true } state_set("__arity_violations", csv + entry + ",") return true } // Validate the call's arity against the builtin table. Returns true (always) // because cg_expr ignores the result; -1 from builtin_arity signals // "no check possible" (variadic or user-defined). A mismatch is recorded // and surfaced as an #error at the bottom of the generated C, so cc fails // before it ever attempts to type-check the wrong call. fn arity_check_call(fn_name: String, actual: Int) -> Bool { let expected: Int = builtin_arity(fn_name) if expected < 0 { return true } if expected == actual { return true } arity_record_violation(fn_name, expected, actual) return true } // Emit recorded arity violations as #error directives. fn emit_arity_violations() -> Void { let csv: String = state_get("__arity_violations") if str_eq(csv, "") { return } if str_eq(csv, ",") { return } let n: Int = str_len(csv) let i: Int = 1 while i < n { let next_comma: Int = str_index_of(str_slice(csv, i, n), ",") if next_comma < 0 { return } let entry: String = str_slice(csv, i, i + next_comma) let p1: Int = str_index_of(entry, "|") if p1 > 0 { let fn_name: String = str_slice(entry, 0, p1) let rest: String = str_slice(entry, p1 + 1, str_len(entry)) let p2: Int = str_index_of(rest, "|") if p2 > 0 { let exp_s: String = str_slice(rest, 0, p2) let act_s: String = str_slice(rest, p2 + 1, str_len(rest)) emit_line("#error \"arity error: '" + fn_name + "' takes " + exp_s + " arguments, but called with " + act_s + "\"") } } let i = i + next_comma + 1 } } fn add_int_name(name: String) -> Bool { let csv: String = state_get("__int_names") if str_eq(csv, "") { csv = "," } let key: String = "," + name + "," if str_contains(csv, key) { return true } state_set("__int_names", csv + name + ",") return true } fn add_instant_name(name: String) -> Bool { let csv: String = state_get("__instant_names") if str_eq(csv, "") { csv = "," } let key: String = "," + name + "," if str_contains(csv, key) { return true } state_set("__instant_names", csv + name + ",") return true } fn add_duration_name(name: String) -> Bool { let csv: String = state_get("__duration_names") if str_eq(csv, "") { csv = "," } let key: String = "," + name + "," if str_contains(csv, key) { return true } state_set("__duration_names", csv + name + ",") return true } fn build_int_names_for_params(params: [Map]) -> Bool { state_set("__int_names", ",") state_set("__instant_names", ",") state_set("__duration_names", ",") let np: Int = native_list_len(params) let pi = 0 while pi < np { let param = native_list_get(params, pi) let pname: String = param["name"] let ptype: String = param["type"] if str_eq(ptype, "Int") { add_int_name(pname) } if str_eq(ptype, "Instant") { add_instant_name(pname) } if str_eq(ptype, "Duration") { add_duration_name(pname) } let pi = pi + 1 } return true } fn cg_fn(stmt: Map) -> Void { let fn_name: String = stmt["name"] // Skip El's `fn main()` — C provides its own main() for top-level stmts // and a duplicate `el_val_t main(void)` would collide with it. if fn_name == "main" { return } let params = stmt["params"] let body = stmt["body"] let ret_type: String = stmt["ret_type"] 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"] if vbd_has_restricted_call(body) { if !str_eq(decorator, "manager") { emit_line("#error \"VBD violation: dharma_emit/dharma_field called from non-@manager fn '" + fn_name + "'\"") } } // Seed the per-function int-name set so the `+` codegen can dispatch // arithmetic vs concat on type-annotated identifiers. build_int_names_for_params(params) emit_line("el_val_t " + fn_name + "(" + params_c + ") {") // Seed declared with parameter names so reassignment works let decl = native_list_empty() let np: Int = native_list_len(params) let pi = 0 while pi < np { let param = native_list_get(params, pi) let pname: String = param["name"] let decl = native_list_append(decl, pname) let pi = pi + 1 } // Lift the final bare expression into an explicit return so implicit // returns ("fn lex(s) { ... tokens }") actually return their value. // Void-returning functions skip this — wrapping `println(x)` in // `return …` is a C type error. let body_xformed = body if !str_eq(ret_type, "Void") { let body_xformed = transform_implicit_return(body) } cg_stmts(body_xformed, " ", decl) emit_line(" return 0;") emit_line("}") emit_blank() } // ── Top-level codegen ───────────────────────────────────────────────────────── fn is_fndef(stmt: Map) -> Bool { let kind: String = stmt["stmt"] if kind == "FnDef" { return true } false } fn is_top_level_decl(stmt: Map) -> Bool { let kind: String = stmt["stmt"] if kind == "TypeDef" { return true } if kind == "EnumDef" { return true } if kind == "Import" { return true } if kind == "CgiBlock" { return true } false } // Format a string-or-EL_NULL argument for el_cgi_init. fn cgi_arg(value: String, has_value: Bool) -> String { if has_value { return "EL_STR(" + c_str_lit(value) + ")" } return "EL_NULL" } // ── VBD role enforcement ────────────────────────────────────────────────────── // // Scan a function body for direct calls to DHARMA-restricted builtins // (dharma_emit, dharma_field). These may only appear inside @manager fns. fn vbd_is_restricted_name(name: String) -> Bool { if str_eq(name, "dharma_emit") { return true } if str_eq(name, "dharma_field") { return true } false } fn vbd_expr_has_restricted_call(expr: Map) -> Bool { let kind: String = expr["expr"] if str_eq(kind, "Call") { let func = expr["func"] let fk: String = func["expr"] if str_eq(fk, "Ident") { let fname: String = func["name"] if vbd_is_restricted_name(fname) { return true } } if vbd_expr_has_restricted_call(func) { return true } let args = expr["args"] let an: Int = native_list_len(args) let ai = 0 while ai < an { let a = native_list_get(args, ai) if vbd_expr_has_restricted_call(a) { return true } let ai = ai + 1 } return false } if str_eq(kind, "BinOp") { let l = expr["left"] let r = expr["right"] if vbd_expr_has_restricted_call(l) { return true } if vbd_expr_has_restricted_call(r) { return true } return false } if str_eq(kind, "Not") { return vbd_expr_has_restricted_call(expr["inner"]) } if str_eq(kind, "Neg") { return vbd_expr_has_restricted_call(expr["inner"]) } if str_eq(kind, "Field") { return vbd_expr_has_restricted_call(expr["object"]) } if str_eq(kind, "Index") { if vbd_expr_has_restricted_call(expr["object"]) { return true } if vbd_expr_has_restricted_call(expr["index"]) { return true } return false } if str_eq(kind, "Try") { return vbd_expr_has_restricted_call(expr["inner"]) } if str_eq(kind, "Array") { let elems = expr["elems"] let n: Int = native_list_len(elems) let i = 0 while i < n { let e = native_list_get(elems, i) if vbd_expr_has_restricted_call(e) { return true } let i = i + 1 } return false } if str_eq(kind, "Map") { let pairs = expr["pairs"] let n: Int = native_list_len(pairs) let i = 0 while i < n { let pair = native_list_get(pairs, i) let v = pair["value"] if vbd_expr_has_restricted_call(v) { return true } let i = i + 1 } return false } if str_eq(kind, "If") { if vbd_expr_has_restricted_call(expr["cond"]) { return true } if vbd_has_restricted_call(expr["then"]) { return true } if vbd_has_restricted_call(expr["else"]) { return true } return false } if str_eq(kind, "For") { if vbd_expr_has_restricted_call(expr["list"]) { return true } if vbd_has_restricted_call(expr["body"]) { return true } return false } if str_eq(kind, "Match") { if vbd_expr_has_restricted_call(expr["subject"]) { return true } let arms = expr["arms"] let n: Int = native_list_len(arms) let i = 0 while i < n { let arm = native_list_get(arms, i) let body = arm["body"] if vbd_expr_has_restricted_call(body) { return true } let i = i + 1 } return false } false } fn vbd_has_restricted_call(stmts: [Map]) -> Bool { let n: Int = native_list_len(stmts) let i = 0 while i < n { let s = native_list_get(stmts, i) let sk: String = s["stmt"] if str_eq(sk, "Let") { if vbd_expr_has_restricted_call(s["value"]) { return true } } if str_eq(sk, "Return") { if vbd_expr_has_restricted_call(s["value"]) { return true } } if str_eq(sk, "Expr") { if vbd_expr_has_restricted_call(s["value"]) { return true } } if str_eq(sk, "While") { if vbd_expr_has_restricted_call(s["cond"]) { return true } if vbd_has_restricted_call(s["body"]) { return true } } if str_eq(sk, "For") { if vbd_expr_has_restricted_call(s["list"]) { return true } if vbd_has_restricted_call(s["body"]) { return true } } let i = i + 1 } false } // ── Entry point ──────────────────────────────────────────────────────────────── fn codegen(stmts: [Map], source: String) -> String { // Detect cgi/service blocks: at most one declarative top-level block. // The block determines the program's CAPABILITY KIND: // "cgi" — full self-formation. Calls all primitives. // "service" — bounded. Cannot call self-formation primitives // (llm_call_agentic, llm_register_tool, dharma_emit, // dharma_field, mindlink-creation). // "utility" — default; no DHARMA membership, no LLM, no agentic. // Codegen enforces this with #error directives at every restricted // call site. The capability boundary is structural: a binary either // CAN or CANNOT do a thing, and the compiler decides at emission time. let n_top: Int = native_list_len(stmts) let cgi_count = 0 let cgi_block: Map = { "stmt": "None" } let svc_count = 0 let svc_block: Map = { "stmt": "None" } let ti = 0 while ti < n_top { let s = native_list_get(stmts, ti) let sk: String = s["stmt"] if str_eq(sk, "CgiBlock") { let cgi_count = cgi_count + 1 if cgi_count == 1 { let cgi_block = s } } if str_eq(sk, "ServiceBlock") { let svc_count = svc_count + 1 if svc_count == 1 { let svc_block = s } } let ti = ti + 1 } if cgi_count > 1 { emit_line("#error \"El: multiple cgi blocks in program (only one allowed)\"") } if svc_count > 1 { emit_line("#error \"El: multiple service blocks in program (only one allowed)\"") } if cgi_count >= 1 { if svc_count >= 1 { emit_line("#error \"El: program declares both cgi and service blocks (mutually exclusive — pick one)\"") } } // Stash the program kind so cg_expr's Call branch can enforce // per-kind capability restrictions on every emitted call. let kind: String = "utility" if cgi_count >= 1 { let kind = "cgi" } if svc_count >= 1 { let kind = "service" } state_set("__program_kind", kind) // Clear capability-violation accumulator from any prior compile. state_set("__cap_violations", "") // Clear arity-violation accumulator from any prior compile. state_set("__arity_violations", "") // Clear temporal-type-violation accumulator from any prior compile. state_set("__time_violations", "") // Preamble emit_line("#include ") emit_line("#include ") emit_line("#include \"el_runtime.h\"") emit_blank() // Forward declarations (skip `main` — C provides its own) let n: Int = native_list_len(stmts) let i = 0 while i < n { let stmt = native_list_get(stmts, i) let kind: String = stmt["stmt"] if kind == "FnDef" { let fn_name: String = stmt["name"] if !str_eq(fn_name, "main") { let params = stmt["params"] let params_c: String = params_to_c(params) emit_line("el_val_t " + fn_name + "(" + params_c + ");") } } let i = i + 1 } emit_blank() // Top-level `let` bindings → file-scope storage. El programs use // top-level `let GREETING = "..."` as module constants that any // function below should be able to read. Without this pass, a top- // level Let only declares the name inside main()'s scope and any // function referencing it compiles to an undefined-symbol use of // the bare name (or, with non-static linkage, fails to link). // // We emit each top-level Let as `el_val_t NAME = VALUE;` at file // scope and seed the int-name set when the binding is `: Int` so // arithmetic/concat dispatch on the name works inside functions. // Runtime-call initializers (e.g. `let m = el_map_new(...)`) cannot // appear in C static initializers, so we emit a non-const slot and // initialize it at the top of main() before any user statements run. let has_toplevel_lets = false let i = 0 while i < n { let stmt = native_list_get(stmts, i) let kind: String = stmt["stmt"] if str_eq(kind, "Let") { let name: String = stmt["name"] let ltype: String = stmt["type"] if str_eq(ltype, "Int") { add_int_name(name) } let val = stmt["value"] let vk: String = val["expr"] if str_eq(vk, "Int") { add_int_name(name) } emit_line("el_val_t " + name + ";") let has_toplevel_lets = true } let i = i + 1 } if has_toplevel_lets { emit_blank() } // Function definitions let i = 0 while i < n { let stmt = native_list_get(stmts, i) if is_fndef(stmt) { cg_fn(stmt) } let i = i + 1 } // main(). Use _argc/_argv so El programs are free to declare their own // local `argv` / `argc` (compiler.el itself does this) without colliding // with the C-side parameters when fn main()'s body is folded in below. emit_line("int main(int _argc, char** _argv) {") emit_line(" el_runtime_init_args(_argc, _argv);") if cgi_count >= 1 { let cname: String = cgi_block["name"] let cdid: String = cgi_block["dharma_id"] let cprin: String = cgi_block["principal"] let cnet: String = cgi_block["network"] let ceng: String = cgi_block["engram"] let has_did: Bool = cgi_block["has_dharma_id"] let has_prin: Bool = cgi_block["has_principal"] let has_net: Bool = cgi_block["has_network"] let has_eng: Bool = cgi_block["has_engram"] let arg_name: String = "EL_STR(" + c_str_lit(cname) + ")" let arg_did: String = cgi_arg(cdid, has_did) let arg_prin: String = cgi_arg(cprin, has_prin) let arg_net: String = cgi_arg(cnet, has_net) let arg_eng: String = cgi_arg(ceng, has_eng) emit_line(" el_cgi_init(" + arg_name + ", " + arg_did + ", " + arg_prin + ", " + arg_net + ", " + arg_eng + ");") } // Seed `declared` with the names of every top-level Let so that // cg_stmt emits plain assignment (`X = ...;`) instead of a redundant // `el_val_t X = ...;` shadowing the file-scope slot. let main_decl = native_list_empty() let i = 0 while i < n { let stmt = native_list_get(stmts, i) let kind: String = stmt["stmt"] if str_eq(kind, "Let") { let name: String = stmt["name"] let main_decl = native_list_append(main_decl, name) } let i = i + 1 } // First pass: capture the body of `fn main()` if the source declared // one. We've already skipped emitting it as a regular el_val_t // function (see cg_fn early return); fold its body into C's main // alongside top-level statements so the program actually runs. let el_main_body = native_list_empty() let i = 0 while i < n { let stmt = native_list_get(stmts, i) if is_fndef(stmt) { let fn_name: String = stmt["name"] if str_eq(fn_name, "main") { let body = stmt["body"] let bn: Int = native_list_len(body) let bi: Int = 0 while bi < bn { let el_main_body = native_list_append(el_main_body, native_list_get(body, bi)) let bi = bi + 1 } } } let i = i + 1 } let i = 0 while i < n { let stmt = native_list_get(stmts, i) if is_fndef(stmt) { // skip - fn defs already emitted above; fn main body folded later } else { if is_top_level_decl(stmt) { // skip } else { let main_decl = cg_stmt(stmt, " ", main_decl) } } // Release AST node after final use — each stmt is fully processed // by this point (forward decls, fn defs, top-level lets, and now // the main-body pass are all done). Releasing here prevents the // accumulated AST from exhausting memory on large source files. el_release(stmt) let i = i + 1 } // Fold fn main()'s body in here, after top-level statements. let mn: Int = native_list_len(el_main_body) let mi: Int = 0 while mi < mn { let mstmt = native_list_get(el_main_body, mi) let main_decl = cg_stmt(mstmt, " ", main_decl) let mi = mi + 1 } emit_line(" return 0;") emit_line("}") emit_blank() // Emit any accumulated capability-violation #error directives. cc // will fail on the first one and surface the message; placement at // the bottom is fine — preprocessor errors halt the build wherever // they appear. emit_cap_violations() // Same for builtin-arity violations: cc halts on the first #error, // so a misuse of a known builtin (wrong arg count) fails the build // with a clear message naming the builtin and its expected arity. emit_arity_violations() // Temporal-type violations (Instant + Instant, Duration + Int, …). emit_time_violations() // Return empty string — output was streamed via println "" }