Files
el/lang/el-compiler/src/codegen.el
T
Will Anderson ec889e1e53 Add --test mode to elc with Assert stmt and full native test suite passing
Implement compile_test() entry point that emits a C test harness instead
of a normal program. Test blocks (previously skipped) now compile to
static functions with per-assertion pass/fail tracking. Assert statement
added to parser and codegen. Runtime extended with now_ns, fs_list_json,
json_build_object, json_build_array, json_escape_string, state_has,
state_get_or. Fix float negation codegen, float equality comparisons,
time_to_parts return type (JSON string), time_format empty-fmt, json_set
raw-value semantics, state_keys JSON array return. All 310 native tests
pass across 9 suites (core, text, string, math, env, state, json, time, fs).
2026-05-06 14:33:47 -05:00

3893 lines
156 KiB
EmacsLisp

// 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<String, Any>], 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).
// Hex-encode a single nibble (0-15) as a lowercase hex character.
fn nibble_to_hex(n: Int) -> String {
str_char_at("0123456789abcdef", n)
}
// Encode a byte value (0-255) as a two-character hex string.
fn byte_to_hex2(b: Int) -> String {
let hi: Int = (b / 16)
let lo: Int = (b - hi * 16)
nibble_to_hex(hi) + nibble_to_hex(lo)
}
// Return true if the byte value is a C hex digit (0-9, a-f, A-F).
// Used to determine whether a \xNN escape needs a string-literal split
// to prevent the C preprocessor from greedily consuming following hex chars.
fn is_hex_digit_byte(b: Int) -> Bool {
if b >= 48 { if b <= 57 { return true } } // 0-9
if b >= 65 { if b <= 70 { return true } } // A-F
if b >= 97 { if b <= 102 { return true } } // a-f
false
}
fn c_escape(s: String) -> String {
// Batch ASCII chars using str_slice instead of str_char_at per byte.
// Track clean_start: the beginning of the current run of bytes that need
// no escaping. On each special byte, flush the accumulated clean run via
// str_slice, then append the escape. This reduces parts-list appends from
// O(N) to O(K) where K = number of special bytes << N for normal strings.
//
// Special bytes: '"'=34, '\\'=92, '\n'=10, '\r'=13, '\t'=9, any byte>=128.
//
// IMPORTANT: after a \xNN hex escape, if the next byte is a hex digit
// (0-9, a-f, A-F), we emit `""` to split the C string literal so the C
// compiler does not greedily read extra hex digits as part of the escape.
// E.g. "\xad" followed by "bamos" must become "\xad" "bamos" because 'b'
// is a hex digit and C would otherwise read "\xadb" (= 0xADB, out of range).
let total: Int = str_len(s)
let parts: [String] = native_list_empty()
let i: Int = 0
let clean_start: Int = 0
let prev_was_hex_escape: Bool = false
while i < total {
let bval: Int = str_char_code(s, i)
// Handle the hex-escape split case first: if prev was \xNN and this
// byte is a hex digit, we must flush the clean run and insert "".
// (At this point clean_start == i since the previous special byte
// already reset it, so flush is a no-op unless something is pending.)
if prev_was_hex_escape {
if is_hex_digit_byte(bval) {
// Flush any accumulated clean bytes before the split marker.
if clean_start < i {
let parts = native_list_append(parts, str_slice(s, clean_start, i))
}
let parts = native_list_append(parts, "\"\"")
let clean_start = i
}
}
let prev_was_hex_escape = false
if bval == 34 {
// 34 = '"' flush clean run, then escape
if clean_start < i {
let parts = native_list_append(parts, str_slice(s, clean_start, i))
}
let parts = native_list_append(parts, "\\\"")
let clean_start = i + 1
} else {
if bval == 92 {
// 92 = '\\'
if clean_start < i {
let parts = native_list_append(parts, str_slice(s, clean_start, i))
}
let parts = native_list_append(parts, "\\\\")
let clean_start = i + 1
} else {
if bval == 10 {
// 10 = '\n'
if clean_start < i {
let parts = native_list_append(parts, str_slice(s, clean_start, i))
}
let parts = native_list_append(parts, "\\n")
let clean_start = i + 1
} else {
if bval == 13 {
// 13 = '\r'
if clean_start < i {
let parts = native_list_append(parts, str_slice(s, clean_start, i))
}
let parts = native_list_append(parts, "\\r")
let clean_start = i + 1
} else {
if bval == 9 {
// 9 = '\t'
if clean_start < i {
let parts = native_list_append(parts, str_slice(s, clean_start, i))
}
let parts = native_list_append(parts, "\\t")
let clean_start = i + 1
} else {
if bval >= 128 {
// Non-ASCII: flush, then \xNN
if clean_start < i {
let parts = native_list_append(parts, str_slice(s, clean_start, i))
}
let parts = native_list_append(parts, "\\x" + byte_to_hex2(bval))
let prev_was_hex_escape = true
let clean_start = i + 1
}
// else: plain ASCII extends the current clean run (no append)
}
}
}
}
}
let i = i + 1
}
// Flush the final clean run if any
if clean_start < total {
let parts = native_list_append(parts, str_slice(s, clean_start, total))
}
let result: String = str_join(parts, "")
// parts list fully consumed release to free peak heap.
el_release(parts)
result
}
fn c_str_lit(s: String) -> String {
"\"" + c_escape(s) + "\""
}
// sanitize_test_name convert a test name string to a valid C identifier fragment.
// "int-to-str" -> "int_to_str", "lex empty" -> "lex_empty"
fn sanitize_test_name(name: String) -> String {
let n: Int = str_len(name)
let i: Int = 0
let out: String = ""
while i < n {
let code: Int = str_char_code(name, i)
// a-z: 97-122, A-Z: 65-90, 0-9: 48-57 keep; everything else -> '_'
if code >= 97 {
if code <= 122 {
let out = out + str_char_at(name, i)
} else {
let out = out + "_"
}
} else {
if code >= 65 {
if code <= 90 {
let out = out + str_char_at(name, i)
} else {
if code >= 48 {
if code <= 57 {
let out = out + str_char_at(name, i)
} else {
let out = out + "_"
}
} else {
let out = out + "_"
}
}
} else {
if code >= 48 {
if code <= 57 {
let out = out + str_char_at(name, i)
} else {
let out = out + "_"
}
} else {
let out = out + "_"
}
}
}
let i = i + 1
}
out
}
// -- 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 == "Percent" { 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"
}
// HTML template codegen
//
// cg_html_template(expr) emits a C statement-expression `({ ... })` that
// builds the HTML string by chaining el_str_concat calls.
//
// Interpolated values are passed through html_escape(); the raw() form
// bypasses escaping. {#each} blocks compile to C for-loops that index into
// the list with el_list_get / el_list_len.
//
// A per-template accumulator variable `_html_N` holds the growing string.
// A global counter stored in state keeps names unique.
fn next_html_id() -> String {
let csv: String = state_get("__html_counter")
let n = 0
if !str_eq(csv, "") {
let n = str_to_int(csv)
}
let n = n + 1
state_set("__html_counter", native_int_to_str(n))
native_int_to_str(n)
}
// Emit children nodes into a flat list of C fragment strings (parts).
// Each part is either a static string fragment (already C-literal form) or
// a dynamic expression that produces an el_val_t string.
// We build them all into parts, then the caller wraps with concat chain.
fn cg_html_parts(children: [Map<String, Any>], acc_var: String) -> String {
// Accumulate fragments into a list to avoid O(n²) string growth.
// Each append is O(1); the single str_join at the end is O(total_size).
let n: Int = native_list_len(children)
let i = 0
let parts: [String] = native_list_empty()
while i < n {
let child: Map<String, Any> = native_list_get(children, i)
let html_kind: String = child["html"]
if str_eq(html_kind, "Text") {
let text: String = child["text"]
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(" + c_str_lit(text) + ")); ")
}
if str_eq(html_kind, "Doctype") {
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"<!doctype html>\")); ")
}
if str_eq(html_kind, "Interp") {
let val_node = child["value"]
let val_c: String = cg_expr(val_node)
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", html_escape(" + val_c + ")); ")
}
if str_eq(html_kind, "Raw") {
let val_node = child["value"]
let val_c: String = cg_expr(val_node)
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", html_raw(" + val_c + ")); ")
}
if str_eq(html_kind, "Element") {
let elem_c: String = cg_html_element_str(child, acc_var)
let parts = native_list_append(parts, elem_c)
}
if str_eq(html_kind, "Each") {
let each_c: String = cg_html_each(child, acc_var)
let parts = native_list_append(parts, each_c)
}
let i = i + 1
}
str_join(parts, "")
}
// Generate open-tag attribute fragments inline.
// Parser stores attrs with "kind": "static" | "dynamic" | "bool".
// Static: "value" is the raw string value (not an expr node).
// Dynamic: "value" is an expr node.
// Bool: no "value" field.
fn cg_html_attrs_str(attrs: [Map<String, Any>], acc_var: String) -> String {
// Accumulate fragments into a list to avoid O(n²) string growth.
let n: Int = native_list_len(attrs)
let i = 0
let parts: [String] = native_list_empty()
// Closing-quote snippet: EL_STR("\"") in C text.
let close_q: String = "EL_STR(" + c_str_lit("\"") + ")"
while i < n {
let attr: Map<String, Any> = native_list_get(attrs, i)
let attr_name: String = attr["name"]
let kind: String = attr["kind"]
// Build: EL_STR(" name=\"")
let open_val: String = " " + attr_name + "=\""
let open_attr: String = "EL_STR(" + c_str_lit(open_val) + ")"
if str_eq(kind, "static") {
// Static attribute: value is a raw string.
let sv: String = attr["value"]
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + open_attr + "); ")
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(" + c_str_lit(sv) + ")); ")
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + close_q + "); ")
} else {
if str_eq(kind, "dynamic") {
// Dynamic attribute: value is an expr node html_escape it.
let val_node = attr["value"]
let val_c: String = cg_expr(val_node)
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + open_attr + "); ")
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", html_escape(" + val_c + ")); ")
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + close_q + "); ")
} else {
// Boolean attribute (no value): emit " name"
let bool_attr: String = "EL_STR(" + c_str_lit(" " + attr_name) + ")"
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + bool_attr + "); ")
}
}
let i = i + 1
}
str_join(parts, "")
}
// Generate code for a single element, appending into acc_var.
fn cg_html_element_str(elem: Map<String, Any>, acc_var: String) -> String {
let tag: String = elem["tag"]
let attrs: [Map<String, Any>] = elem["attrs"]
let children: [Map<String, Any>] = elem["children"]
let self_closing: Bool = elem["self_closing"]
// Accumulate into a list to avoid O(n²) string growth for deeply nested trees.
let parts: [String] = native_list_empty()
// Open tag: <tagname
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"<" + tag + "\")); ")
let parts = native_list_append(parts, cg_html_attrs_str(attrs, acc_var))
if self_closing {
// Self-closing void element: />
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"/>\")); ")
} else {
// Close open tag: >
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\">\")); ")
let parts = native_list_append(parts, cg_html_parts(children, acc_var))
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"</" + tag + ">\")); ")
}
str_join(parts, "")
}
// Generate code for {#each list as item} ... {/each}.
fn cg_html_each(node: Map<String, Any>, acc_var: String) -> String {
let list_expr = node["list"]
let item_name: String = node["item"]
let body_children: [Map<String, Any>] = node["body"]
let id: String = next_html_id()
let list_var: String = "_html_list_" + id
let len_var: String = "_html_len_" + id
let idx_var: String = "_html_i_" + id
let list_c: String = cg_expr(list_expr)
let inner_c: String = cg_html_parts(body_children, acc_var)
// Emit: { el_val_t _list = expr; int _len = el_list_len(_list);
// for (int _i = 0; _i < _len; _i++) {
// el_val_t item = el_list_get(_list, _i); inner_c } }
"{ 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 + "} } "
}
// Top-level HTML template codegen returns a C statement-expression string.
fn cg_html_template(expr: Map<String, Any>) -> String {
let root = expr["root"]
let id: String = next_html_id()
let acc: String = "_html_" + id
// If the root element has doctype:true the parser tagged it from <!doctype html>
let doctype_flag: Bool = root["doctype"]
let doctype_prefix: String = ""
if doctype_flag {
let doctype_prefix = acc + " = el_str_concat(" + acc + ", EL_STR(\"<!doctype html>\")); "
}
let body: String = cg_html_element_str(root, acc)
"({ el_val_t " + acc + " = EL_STR(\"\"); " + doctype_prefix + body + acc + "; })"
}
fn cg_expr(expr: Map<String, Any>) -> 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_kind: String = inner["expr"]
// Float literal negation: emit el_from_float(-n) so the IEEE 754 sign
// bit is set correctly. Arithmetic negation of the int64 bit pattern
// (the el_val_t representation) produces garbage, not -f.
if str_eq(inner_kind, "Float") {
let fval: String = inner["value"]
return "el_from_float(-" + fval + ")"
}
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"]
// -- String/equality fast-path: skip O(N-) temporal traversals --------
// The 10 temporal predicates below each recurse into the left subtree:
// O(depth) state_get calls per predicate, O(N-) total for a chain of N
// string-concat BinOps (e.g. the 70-100-part HTML chains in soul.el).
// When either operand is a bare Str literal the result is always concat
// or str_eq - no temporal dispatch is possible. Exit immediately.
if str_eq(op, "Plus") {
if str_eq(left_kind, "Str") { return "el_str_concat(" + left_c + ", " + right_c + ")" }
if str_eq(right_kind, "Str") { return "el_str_concat(" + left_c + ", " + right_c + ")" }
}
if str_eq(op, "EqEq") {
if str_eq(left_kind, "Str") { return "str_eq(" + left_c + ", " + right_c + ")" }
if str_eq(right_kind, "Str") { return "str_eq(" + left_c + ", " + right_c + ")" }
}
if str_eq(op, "NotEq") {
if str_eq(left_kind, "Str") { return "!str_eq(" + left_c + ", " + right_c + ")" }
if str_eq(right_kind, "Str") { return "!str_eq(" + left_c + ", " + right_c + ")" }
}
// -- 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)
// Phase 1.5 LocalDate / LocalTime / CalendarTime dispatch. These
// route through their typed runtime wrappers (el_local_date_add_dur,
// el_local_time_add_dur, el_local_date_lt, el_local_date_eq) and
// forbid mismatched ops at codegen time. Cross-calendar arithmetic
// (CalendarTime + CalendarTime, CalendarTime - CalendarTime under
// mismatched calendars) is structurally meaningless: a CalendarTime
// already projects an Instant under a Calendar, so subtraction
// between two of them only makes sense in instant-space (use
// cal_to_instant first).
let left_is_ld: Bool = is_localdate_expr(left)
let right_is_ld: Bool = is_localdate_expr(right)
let left_is_lt: Bool = is_localtime_expr(left)
let right_is_lt: Bool = is_localtime_expr(right)
let left_is_ct: Bool = is_caltime_expr(left)
let right_is_ct: Bool = is_caltime_expr(right)
if left_is_ld {
if op == "Plus" {
if right_is_dur {
return "el_local_date_add_dur(" + left_c + ", " + right_c + ")"
}
}
if op == "Lt" {
if right_is_ld { return "el_local_date_lt(" + left_c + ", " + right_c + ")" }
}
if op == "EqEq" {
if right_is_ld { return "el_local_date_eq(" + left_c + ", " + right_c + ")" }
}
}
if left_is_lt {
if op == "Plus" {
if right_is_dur {
return "el_local_time_add_dur(" + left_c + ", " + right_c + ")"
}
}
}
if left_is_ct {
if op == "Plus" {
if right_is_ct {
time_record_violation("caltime_plus_caltime", "CalendarTime + CalendarTime is not allowed (use cal_to_instant + Duration)")
return "0 /* TIME_TYPE_ERROR: CalendarTime + CalendarTime */"
}
}
}
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 + ")"
}
}
// Float literal or negative float literal: use plain == (bit-equal
// el_val_t comparison). This handles `r0 == 3.0`, `neg == -3.0`, etc.
if is_float_expr(left) {
return "(" + left_c + " == " + right_c + ")"
}
if is_float_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 + ")"
}
}
// Float-typed operands use plain != (bit-equal comparison).
if is_float_expr(left) {
return "(" + left_c + " != " + right_c + ")"
}
if is_float_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_parts: [String] = native_list_empty()
let i = 0
while i < arity {
let arg = native_list_get(args, i)
let arg_c: String = cg_expr(arg)
let args_parts = native_list_append(args_parts, arg_c)
let i = i + 1
}
let args_c: String = str_join(args_parts, ", ")
// args_parts list fully consumed release to free peak heap.
el_release(args_parts)
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(<int>)`, 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 + ")"
}
}
}
// el_from_float takes a raw C double - do not wrap the float
// argument in el_from_float() again. Without this, the float
// literal codegen (which wraps every Float in el_from_float())
// produces el_from_float(el_from_float(0.7)) - double-encoded.
if str_eq(fn_name, "el_from_float") {
if arity == 1 {
let only_arg = native_list_get(args, 0)
let arg_kind: String = only_arg["expr"]
if str_eq(arg_kind, "Float") {
let v: String = only_arg["value"]
return "el_from_float(" + v + ")"
}
}
}
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_parts: [String] = native_list_empty()
let i = 0
while i < n {
let elem = native_list_get(elems, i)
let elem_c: String = cg_expr(elem)
let items_parts = native_list_append(items_parts, elem_c)
let i = i + 1
}
let items_joined: String = str_join(items_parts, ", ")
// items_parts fully consumed release to free peak heap.
el_release(items_parts)
return "el_list_new(" + native_int_to_str(n) + ", " + items_joined + ")"
}
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_parts: [String] = native_list_empty()
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)
let items_parts = native_list_append(items_parts, c_str_lit(key) + ", " + val_c)
let i = i + 1
}
let items_joined: String = str_join(items_parts, ", ")
// items_parts fully consumed release to free peak heap.
el_release(items_parts)
return "el_map_new(" + native_int_to_str(n) + ", " + items_joined + ")"
}
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)
}
if kind == "HtmlTemplate" {
return cg_html_template(expr)
}
if kind == "Lambda" {
// Lambda expressions are JS-target only. In the C target, emit EL_NULL.
"EL_NULL"
} else {
"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, Any>) -> 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
// Accumulate arm fragments into a list to avoid O(n-) string growth.
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, "({ 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 parts = native_list_append(parts, "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
} else {
if str_eq(pkind, "Binding") {
let bname: String = pat["name"]
let parts = native_list_append(parts, "{ 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 parts = native_list_append(parts, "if (" + subj_var + " == " + v + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
} else {
if str_eq(pkind, "LitStr") {
let v: String = pat["value"]
let parts = native_list_append(parts, "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 parts = native_list_append(parts, "if (" + subj_var + " == " + bv + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
} else {
if str_eq(pkind, "Variant") {
// Enum::Variant pattern match against the variant name
// string (El enums compile to plain strings).
let variant: String = pat["variant"]
let parts = native_list_append(parts, "if (str_eq(" + subj_var + ", EL_STR(" + c_str_lit(variant) + "))) { " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
} else {
// unknown pattern -> wildcard
let parts = native_list_append(parts, "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
}
}
}
}
}
}
let i = i + 1
}
let parts = native_list_append(parts, done_label + ":; " + result_var + "; })")
let result: String = str_join(parts, "")
// parts list fully consumed release to free peak heap.
el_release(parts)
result
}
// Lower a match statement (used for side effects, not as an expression) to a
// chain of C if/else if/else blocks. The subject is evaluated once into a
// scoped temporary; each arm generates a condition and a braced body; the
// wildcard/binding arm becomes the final `else` branch.
//
// Pattern dispatch:
// LitStr -> str_eq(subj, EL_STR("..."))
// LitInt -> subj == N
// LitBool -> subj == 1 / subj == 0
// Binding -> else { el_val_t name = subj; <body> }
// Wildcard -> else { <body> }
fn cg_match_stmt(expr: Map<String, Any>, indent: String, declared: [String]) -> Void {
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 inner: String = indent + " "
emit_line(indent + "{")
emit_line(inner + "el_val_t " + subj_var + " = " + subj_c + ";")
let n: Int = native_list_len(arms)
let i = 0
let first_cond: Bool = true
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, "LitStr") {
let v: String = pat["value"]
let cond_str = "str_eq(" + subj_var + ", EL_STR(" + c_str_lit(v) + "))"
if first_cond {
emit_line(inner + "if (" + cond_str + ") {")
let first_cond = false
} else {
emit_line(inner + "} else if (" + cond_str + ") {")
}
emit_line(inner + " " + body_c + ";")
} else {
if str_eq(pkind, "LitInt") {
let v: String = pat["value"]
let cond_str = subj_var + " == " + v
if first_cond {
emit_line(inner + "if (" + cond_str + ") {")
let first_cond = false
} else {
emit_line(inner + "} else if (" + cond_str + ") {")
}
emit_line(inner + " " + body_c + ";")
} else {
if str_eq(pkind, "LitBool") {
let v: String = pat["value"]
let bv = "0"
if str_eq(v, "true") {
let bv = "1"
}
let cond_str = subj_var + " == " + bv
if first_cond {
emit_line(inner + "if (" + cond_str + ") {")
let first_cond = false
} else {
emit_line(inner + "} else if (" + cond_str + ") {")
}
emit_line(inner + " " + body_c + ";")
} else {
// Wildcard or Binding - becomes the else branch
if first_cond {
emit_line(inner + "{")
} else {
emit_line(inner + "} else {")
}
if str_eq(pkind, "Binding") {
let bname: String = pat["name"]
emit_line(inner + " el_val_t " + bname + " = " + subj_var + ";")
}
emit_line(inner + " " + body_c + ";")
emit_line(inner + "}")
let first_cond = true
}
}
}
let i = i + 1
}
// Close any open if/else-if chain (only reached when last arm was a literal pattern)
if !first_cond {
emit_line(inner + "}")
}
emit_line(indent + "}")
}
// -- 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<String, Any>], result_var: String) -> String {
let n: Int = native_list_len(stmts)
// Collect statement fragments into a list to avoid O(n-) string growth.
let parts: [String] = native_list_empty()
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 parts = native_list_append(parts, "el_val_t " + name + " = " + val_c + "; ")
} else {
if str_eq(sk, "Return") {
let val = s["value"]
let val_c: String = cg_expr(val)
let parts = native_list_append(parts, 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 parts = native_list_append(parts, result_var + " = (" + val_c + "); ")
} else {
let parts = native_list_append(parts, "(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 parts = native_list_append(parts, 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
}
let result: String = str_join(parts, "")
// parts list fully consumed release to free peak heap.
el_release(parts)
result
}
fn cg_if_expr(expr: Map<String, Any>) -> 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<String, Any>, 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)
}
if str_eq(ltype, "Calendar") {
add_calendar_name(name)
}
if str_eq(ltype, "CalendarTime") {
add_caltime_name(name)
}
if str_eq(ltype, "Rhythm") {
add_rhythm_name(name)
}
if str_eq(ltype, "LocalDate") {
add_localdate_name(name)
}
if str_eq(ltype, "LocalTime") {
add_localtime_name(name)
}
if str_eq(ltype, "LocalDateTime") {
add_localdt_name(name)
}
if str_eq(ltype, "Zone") {
add_zone_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)
}
if is_calendar_expr(val) {
add_calendar_name(name)
}
if is_caltime_expr(val) {
add_caltime_name(name)
}
if is_rhythm_expr(val) {
add_rhythm_name(name)
}
if is_localdate_expr(val) {
add_localdate_name(name)
}
if is_localtime_expr(val) {
add_localtime_name(name)
}
if is_localdt_expr(val) {
add_localdt_name(name)
}
if is_zone_expr(val) {
add_zone_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
}
if kind == "Break" {
emit_line(indent + "break;")
return declared
}
if kind == "Continue" {
emit_line(indent + "continue;")
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
}
if val_kind == "Match" {
cg_match_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 == "ForRange" {
let var_name: String = stmt["var"]
let start_expr = stmt["start"]
let end_expr = stmt["end"]
let inclusive: Bool = stmt["inclusive"]
let body = stmt["body"]
let start_c: String = cg_expr(start_expr)
let end_c: String = cg_expr(end_expr)
// Loop variable introduced as a C local scoped to the for statement.
// Body gets its own declared clone so let-bindings don't leak out.
let body_decl = native_list_clone(declared)
let body_decl = native_list_append(body_decl, var_name)
if inclusive {
emit_line(indent + "for (el_val_t " + var_name + " = " + start_c + "; " + var_name + " <= " + end_c + "; " + var_name + "++) {")
} else {
emit_line(indent + "for (el_val_t " + var_name + " = " + start_c + "; " + var_name + " < " + end_c + "; " + var_name + "++) {")
}
cg_stmts(body, indent + " ", body_decl)
emit_line(indent + "}")
return declared
}
if kind == "FnDef" { return declared }
if kind == "TypeDef" { return declared }
if kind == "EnumDef" { return declared }
if kind == "Import" { return declared }
if kind == "ExternFn" { return declared }
if kind == "CgiBlock" { return declared }
// TryCatch: browser-only control flow. In the C target, emit a comment
// noting that the try body runs unconditionally; error handling is a no-op.
// Programs that rely on catching JS exceptions should compile with --target=js.
if kind == "TryCatch" {
let try_body = stmt["try_body"]
emit_line(indent + "/* try (C target: exception handling not supported) */")
cg_stmts(try_body, indent, native_list_clone(declared))
return declared
}
// assert <cond> , <msg> test harness assertion
if kind == "Assert" {
let cond_node = stmt["cond"]
let msg_node = stmt["msg"]
let c_cond: String = cg_expr(cond_node)
let c_msg: String = ""
let msg_kind: String = msg_node["expr"]
if str_eq(msg_kind, "Str") {
let raw_msg: String = msg_node["value"]
let c_msg = "\"" + c_escape(raw_msg) + "\""
} else {
let c_msg = "EL_STR_PTR(" + cg_expr(msg_node) + ")"
}
emit_line(indent + "if (!(" + c_cond + ")) {")
emit_line(indent + " __el_test_fail(__el_cur_test, " + c_msg + "); __el_fail++;")
emit_line(indent + "} else { __el_pass++; }")
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 {
return str_slice(s, 1, n - 1)
}
}
}
s
}
fn cg_if_stmt(expr: Map<String, Any>, 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<String, Any>, body: [Map<String, Any>], 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<String, Any>, 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<String, Any>], 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)
// Per-statement arena scope: free intermediate strings (str_concat
// fragments, cg_expr results) after each statement is emitted.
let s_mark: Any = el_arena_push()
let decl = cg_stmt(stmt, indent, decl)
el_arena_pop(s_mark)
let i = i + 1
}
decl
}
// -- Function declaration codegen -----------------------------------------------
fn param_decl(param: Map<String, Any>, idx: Int) -> String {
let name: String = param["name"]
"el_val_t " + name
}
fn params_to_c(params: [Map<String, Any>]) -> String {
let n: Int = native_list_len(params)
if n == 0 { return "void" }
let parts: [String] = native_list_empty()
let i = 0
while i < n {
let param = native_list_get(params, i)
let decl: String = param_decl(param, i)
let parts = native_list_append(parts, decl)
let i = i + 1
}
let result: String = str_join(parts, ", ")
// parts list fully consumed release to free peak heap.
el_release(parts)
result
}
// 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<String, Any>]) -> [Map<String, Any>] {
let n: Int = native_list_len(body)
if n == 0 { return body }
let last: Map<String, Any> = 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<String, Any>] = 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<String, Any> = { "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<String, Any>) -> 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, "str_count") { return true }
if str_eq(name, "str_count_chars") { return true }
if str_eq(name, "str_count_bytes") { return true }
if str_eq(name, "str_count_lines") { return true }
if str_eq(name, "str_count_words") { return true }
if str_eq(name, "str_count_letters") { return true }
if str_eq(name, "str_count_digits") { return true }
if str_eq(name, "str_last_index_of") { return true }
if str_eq(name, "str_find_chars") { 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<String, Any>) -> 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<String, Any>) -> 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
}
// Phase 1.5 - Calendar / CalendarTime / Rhythm / LocalDate / LocalTime /
// LocalDateTime / Zone are first-class boxed types. Each has its own name
// set in process state, populated from typed `let` bindings and parameter
// annotations. The BinOp dispatcher consults these to forbid mismatched
// arithmetic (e.g. CalendarTime + CalendarTime, LocalDate < CalendarTime).
fn is_calendar_name(name: String) -> Bool {
let csv: String = state_get("__calendar_names")
if str_eq(csv, "") { return false }
return str_contains(csv, "," + name + ",")
}
fn is_caltime_name(name: String) -> Bool {
let csv: String = state_get("__caltime_names")
if str_eq(csv, "") { return false }
return str_contains(csv, "," + name + ",")
}
fn is_rhythm_name(name: String) -> Bool {
let csv: String = state_get("__rhythm_names")
if str_eq(csv, "") { return false }
return str_contains(csv, "," + name + ",")
}
fn is_localdate_name(name: String) -> Bool {
let csv: String = state_get("__localdate_names")
if str_eq(csv, "") { return false }
return str_contains(csv, "," + name + ",")
}
fn is_localtime_name(name: String) -> Bool {
let csv: String = state_get("__localtime_names")
if str_eq(csv, "") { return false }
return str_contains(csv, "," + name + ",")
}
fn is_localdt_name(name: String) -> Bool {
let csv: String = state_get("__localdt_names")
if str_eq(csv, "") { return false }
return str_contains(csv, "," + name + ",")
}
fn is_zone_name(name: String) -> Bool {
let csv: String = state_get("__zone_names")
if str_eq(csv, "") { return false }
return str_contains(csv, "," + name + ",")
}
// Calendar-returning builtins. earth_calendar / mars_calendar / cycle_calendar
// / no_cycle_calendar / relative_calendar all box a calendar struct.
fn is_calendar_call(call_expr: Map<String, Any>) -> 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, "earth_calendar") { return true }
if str_eq(name, "earth_calendar_default") { return true }
if str_eq(name, "mars_calendar") { return true }
if str_eq(name, "cycle_calendar") { return true }
if str_eq(name, "no_cycle_calendar") { return true }
if str_eq(name, "relative_calendar") { return true }
return false
}
// CalendarTime-returning builtins.
fn is_caltime_call(call_expr: Map<String, Any>) -> 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_in") { return true }
if str_eq(name, "in_calendar") { return true }
if str_eq(name, "cal_in") { return true }
if str_eq(name, "zoned") { return true }
return false
}
// Rhythm-returning builtins.
fn is_rhythm_call(call_expr: Map<String, Any>) -> 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, "rhythm_cycle_start") { return true }
if str_eq(name, "rhythm_cycle_phase") { return true }
if str_eq(name, "rhythm_duration") { return true }
if str_eq(name, "rhythm_session_start") { return true }
if str_eq(name, "rhythm_event") { return true }
if str_eq(name, "rhythm_and") { return true }
if str_eq(name, "rhythm_or") { return true }
if str_eq(name, "rhythm_weekday") { return true }
if str_eq(name, "rhythm_weekly_at") { return true }
return false
}
// LocalDate-returning builtins.
fn is_localdate_call(call_expr: Map<String, Any>) -> 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, "local_date") { return true }
if str_eq(name, "el_local_date_add_dur") { return true }
return false
}
fn is_localtime_call(call_expr: Map<String, Any>) -> 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, "local_time") { return true }
if str_eq(name, "el_local_time_add_dur") { return true }
return false
}
fn is_localdt_call(call_expr: Map<String, Any>) -> 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, "local_datetime") { return true }
return false
}
fn is_zone_call(call_expr: Map<String, Any>) -> 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, "zone") { return true }
if str_eq(name, "zone_utc") { return true }
if str_eq(name, "zone_local") { return true }
if str_eq(name, "zone_offset") { return true }
return false
}
fn is_calendar_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"]
if str_eq(k, "Ident") { return is_calendar_name(expr["name"]) }
if str_eq(k, "Call") { return is_calendar_call(expr) }
return false
}
fn is_caltime_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"]
if str_eq(k, "Ident") { return is_caltime_name(expr["name"]) }
if str_eq(k, "Call") { return is_caltime_call(expr) }
return false
}
fn is_rhythm_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"]
if str_eq(k, "Ident") { return is_rhythm_name(expr["name"]) }
if str_eq(k, "Call") { return is_rhythm_call(expr) }
return false
}
fn is_localdate_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"]
if str_eq(k, "Ident") { return is_localdate_name(expr["name"]) }
if str_eq(k, "Call") { return is_localdate_call(expr) }
return false
}
fn is_localtime_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"]
if str_eq(k, "Ident") { return is_localtime_name(expr["name"]) }
if str_eq(k, "Call") { return is_localtime_call(expr) }
return false
}
fn is_localdt_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"]
if str_eq(k, "Ident") { return is_localdt_name(expr["name"]) }
if str_eq(k, "Call") { return is_localdt_call(expr) }
return false
}
fn is_zone_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"]
if str_eq(k, "Ident") { return is_zone_name(expr["name"]) }
if str_eq(k, "Call") { return is_zone_call(expr) }
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<String, Any>) -> 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<String, Any>) -> 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<String, Any>) -> 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
}
if str_eq(op, "Percent") {
if is_int_expr(expr["left"]) {
if is_int_expr(expr["right"]) { return true }
}
return false
}
return false
}
return false
}
// is_float_expr true when expr is (or evaluates to) a Float-typed value.
// Used in EqEq/NotEq codegen to avoid str_eq on float values.
fn is_float_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"]
if str_eq(k, "Float") { return true }
if str_eq(k, "Neg") {
let inner = expr["inner"]
let ik: String = inner["expr"]
if str_eq(ik, "Float") { return true }
}
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 }
// LSP seed primitives
if str_eq(name, "__read_n") { return 1 }
if str_eq(name, "__print_raw") { return 1 }
// 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 }
// Text-processing primitives (Phase 1)
if str_eq(name, "str_count") { return 2 }
if str_eq(name, "str_count_chars") { return 1 }
if str_eq(name, "str_count_bytes") { return 1 }
if str_eq(name, "str_count_lines") { return 1 }
if str_eq(name, "str_count_words") { return 1 }
if str_eq(name, "str_count_letters") { return 1 }
if str_eq(name, "str_count_digits") { return 1 }
if str_eq(name, "str_index_of_all") { return 2 }
if str_eq(name, "str_last_index_of") { return 2 }
if str_eq(name, "str_find_chars") { return 2 }
if str_eq(name, "str_repeat") { return 2 }
if str_eq(name, "str_reverse") { return 1 }
if str_eq(name, "str_strip_prefix") { return 2 }
if str_eq(name, "str_strip_suffix") { return 2 }
if str_eq(name, "str_strip_chars") { return 2 }
if str_eq(name, "str_lstrip") { return 1 }
if str_eq(name, "str_rstrip") { return 1 }
if str_eq(name, "is_letter") { return 1 }
if str_eq(name, "is_digit") { return 1 }
if str_eq(name, "is_alphanumeric") { return 1 }
if str_eq(name, "is_whitespace") { return 1 }
if str_eq(name, "is_punctuation") { return 1 }
if str_eq(name, "is_uppercase") { return 1 }
if str_eq(name, "is_lowercase") { return 1 }
if str_eq(name, "str_split_lines") { return 1 }
if str_eq(name, "str_split_chars") { return 1 }
if str_eq(name, "str_split_n") { return 3 }
if str_eq(name, "str_join") { return 2 }
// 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 }
// Seed primitives (__-prefix) runtime/el_seed.c
if str_eq(name, "__str_len") { return 1 }
if str_eq(name, "__str_char_at") { return 2 }
if str_eq(name, "__str_alloc") { return 1 }
if str_eq(name, "__str_set_char") { return 3 }
if str_eq(name, "__str_cmp") { return 2 }
if str_eq(name, "__str_ncmp") { return 3 }
if str_eq(name, "__str_concat_raw") { return 2 }
if str_eq(name, "__str_slice_raw") { return 3 }
if str_eq(name, "__int_to_str") { return 1 }
if str_eq(name, "__str_to_int") { return 1 }
if str_eq(name, "__float_to_str") { return 1 }
if str_eq(name, "__str_to_float") { return 1 }
if str_eq(name, "__println") { return 1 }
if str_eq(name, "__print") { return 1 }
if str_eq(name, "__readline") { return 0 }
if str_eq(name, "__fs_read") { return 1 }
if str_eq(name, "__fs_write") { return 2 }
if str_eq(name, "__fs_exists") { return 1 }
if str_eq(name, "__fs_list_raw") { return 1 }
if str_eq(name, "__fs_mkdir") { return 1 }
if str_eq(name, "__fs_write_bytes") { return 3 }
if str_eq(name, "__http_do") { return 5 }
if str_eq(name, "__http_do_map") { return 5 }
if str_eq(name, "__http_do_to_file") { return 5 }
if str_eq(name, "__http_serve") { return 2 }
if str_eq(name, "__http_serve_v2") { return 2 }
if str_eq(name, "__http_response") { return 3 }
if str_eq(name, "__thread_create") { return 2 }
if str_eq(name, "__thread_join") { return 1 }
if str_eq(name, "__mutex_new") { return 0 }
if str_eq(name, "__mutex_lock") { return 1 }
if str_eq(name, "__mutex_unlock") { return 1 }
if str_eq(name, "__exec") { return 1 }
if str_eq(name, "__exec_bg") { return 1 }
if str_eq(name, "__env_get") { return 1 }
if str_eq(name, "__args_json") { return 0 }
if str_eq(name, "__exit_program") { return 1 }
if str_eq(name, "__time_now_ns") { return 0 }
if str_eq(name, "__sleep_ms") { return 1 }
if str_eq(name, "__uuid_v4") { return 0 }
if str_eq(name, "__sqrt_f") { return 1 }
if str_eq(name, "__log_f") { return 1 }
if str_eq(name, "__ln_f") { return 1 }
if str_eq(name, "__sin_f") { return 1 }
if str_eq(name, "__cos_f") { return 1 }
if str_eq(name, "__pi_f") { return 0 }
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 }
if str_eq(name, "__html_sanitize") { return 2 }
if str_eq(name, "__url_encode") { return 1 }
if str_eq(name, "__url_decode") { return 1 }
if str_eq(name, "__json_get") { return 2 }
if str_eq(name, "__json_get_raw") { return 2 }
if str_eq(name, "__json_parse_map") { return 1 }
if str_eq(name, "__json_stringify_val") { return 1 }
if str_eq(name, "__json_array_len") { return 1 }
if str_eq(name, "__json_array_get") { return 2 }
if str_eq(name, "__json_array_get_string") { return 2 }
if str_eq(name, "__json_set") { return 3 }
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_activate") { return 2 }
if str_eq(name, "__engram_activate_json") { return 2 }
if str_eq(name, "__engram_scan_nodes_json") { return 2 }
if str_eq(name, "__generate") { 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 }
// Process info
if str_eq(name, "getpid_now") { return 0 }
// stdout redirect (used by elc post-processing)
if str_eq(name, "stdout_to_file") { return 1 }
if str_eq(name, "stdout_restore") { return 0 }
// Subprocess execution
if str_eq(name, "exec_command") { return 1 }
if str_eq(name, "exec_capture") { return 1 }
if str_eq(name, "exec") { return 1 }
if str_eq(name, "exec_bg") { 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 }
// Threading seed primitives
if str_eq(name, "__thread_create") { return 2 }
if str_eq(name, "__thread_join") { return 1 }
if str_eq(name, "__mutex_new") { return 0 }
if str_eq(name, "__mutex_lock") { return 1 }
if str_eq(name, "__mutex_unlock") { return 1 }
// Channel seed primitives
if str_eq(name, "__channel_new") { return 1 }
if str_eq(name, "__channel_send") { return 2 }
if str_eq(name, "__channel_recv") { return 1 }
if str_eq(name, "__channel_try_recv") { return 1 }
if str_eq(name, "__channel_close") { return 1 }
// Arena mark/restore builtins
if str_eq(name, "el_arena_push") { return 0 }
if str_eq(name, "el_arena_pop") { return 1 }
// -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 add_calendar_name(name: String) -> Bool {
let csv: String = state_get("__calendar_names")
if str_eq(csv, "") { csv = "," }
let key: String = "," + name + ","
if str_contains(csv, key) { return true }
state_set("__calendar_names", csv + name + ",")
return true
}
fn add_caltime_name(name: String) -> Bool {
let csv: String = state_get("__caltime_names")
if str_eq(csv, "") { csv = "," }
let key: String = "," + name + ","
if str_contains(csv, key) { return true }
state_set("__caltime_names", csv + name + ",")
return true
}
fn add_rhythm_name(name: String) -> Bool {
let csv: String = state_get("__rhythm_names")
if str_eq(csv, "") { csv = "," }
let key: String = "," + name + ","
if str_contains(csv, key) { return true }
state_set("__rhythm_names", csv + name + ",")
return true
}
fn add_localdate_name(name: String) -> Bool {
let csv: String = state_get("__localdate_names")
if str_eq(csv, "") { csv = "," }
let key: String = "," + name + ","
if str_contains(csv, key) { return true }
state_set("__localdate_names", csv + name + ",")
return true
}
fn add_localtime_name(name: String) -> Bool {
let csv: String = state_get("__localtime_names")
if str_eq(csv, "") { csv = "," }
let key: String = "," + name + ","
if str_contains(csv, key) { return true }
state_set("__localtime_names", csv + name + ",")
return true
}
fn add_localdt_name(name: String) -> Bool {
let csv: String = state_get("__localdt_names")
if str_eq(csv, "") { csv = "," }
let key: String = "," + name + ","
if str_contains(csv, key) { return true }
state_set("__localdt_names", csv + name + ",")
return true
}
fn add_zone_name(name: String) -> Bool {
let csv: String = state_get("__zone_names")
if str_eq(csv, "") { csv = "," }
let key: String = "," + name + ","
if str_contains(csv, key) { return true }
state_set("__zone_names", csv + name + ",")
return true
}
fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
state_set("__int_names", ",")
state_set("__instant_names", ",")
state_set("__duration_names", ",")
state_set("__calendar_names", ",")
state_set("__caltime_names", ",")
state_set("__rhythm_names", ",")
state_set("__localdate_names", ",")
state_set("__localtime_names", ",")
state_set("__localdt_names", ",")
state_set("__zone_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)
}
if str_eq(ptype, "Calendar") {
add_calendar_name(pname)
}
if str_eq(ptype, "CalendarTime") {
add_caltime_name(pname)
}
if str_eq(ptype, "Rhythm") {
add_rhythm_name(pname)
}
if str_eq(ptype, "LocalDate") {
add_localdate_name(pname)
}
if str_eq(ptype, "LocalTime") {
add_localtime_name(pname)
}
if str_eq(ptype, "LocalDateTime") {
add_localdt_name(pname)
}
if str_eq(ptype, "Zone") {
add_zone_name(pname)
}
let pi = pi + 1
}
return true
}
fn cg_fn(stmt: Map<String, Any>) -> Void {
let fn_name: String = stmt["name"]
// Skip El's `fn main()` - C provides its own main() for top-level stmts
// 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)
}
let final_decl = cg_stmts(body_xformed, " ", decl)
el_release(final_decl)
emit_line(" return 0;")
emit_line("}")
emit_blank()
}
// -- Top-level codegen ---------------------------------------------------------
fn is_fndef(stmt: Map<String, Any>) -> Bool {
let kind: String = stmt["stmt"]
if kind == "FnDef" { return true }
false
}
fn is_top_level_decl(stmt: Map<String, Any>) -> 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 }
if kind == "ExternFn" { 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<String, Any>) -> 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<String, Any>]) -> 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<String, Any>], 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<String, Any> = { "stmt": "None" }
let svc_count = 0
let svc_block: Map<String, Any> = { "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 <stdint.h>")
emit_line("#include <stdlib.h>")
emit_line("#include \"el_runtime.h\"")
// Cross-module forward declarations: for each imported module, emit
// #include "module.elh" so Clang sees the function signatures from
// that module without needing the full source inlined. The .elh files
// are generated by `elc --emit-header` and live in the same dist/
// directory as the generated .c files. We use basename only (strip
// the directory prefix and .el extension) so the include resolves
// correctly regardless of the source tree layout.
let imp_n: Int = native_list_len(stmts)
let imp_i = 0
while imp_i < imp_n {
let imp_stmt = native_list_get(stmts, imp_i)
let imp_kind: String = imp_stmt["stmt"]
if str_eq(imp_kind, "Import") {
let imp_path: String = imp_stmt["path"]
// Extract basename: find last '/' and strip from there.
let imp_path_len: Int = str_len(imp_path)
let imp_last_slash: Int = -1
let imp_j: Int = 0
while imp_j < imp_path_len {
let imp_c: String = str_slice(imp_path, imp_j, imp_j + 1)
if str_eq(imp_c, "/") { let imp_last_slash = imp_j }
let imp_j = imp_j + 1
}
let imp_base: String = str_slice(imp_path, imp_last_slash + 1, imp_path_len)
// Strip .el extension if present.
let imp_base_len: Int = str_len(imp_base)
let imp_bname: String = imp_base
if str_ends_with(imp_base, ".el") {
let imp_bname = str_slice(imp_base, 0, imp_base_len - 3)
}
emit_line("#include \"" + imp_bname + ".elh\"")
}
let imp_i = imp_i + 1
}
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 + ");")
}
}
if kind == "ExternFn" {
let fn_name: String = stmt["name"]
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()
}
// Detect whether this compilation unit has an entry point.
// A unit is a library (no C main emitted) when there is no fn main()
// and no top-level executable statements. This supports separate
// compilation: library .c files contain only function definitions.
let has_el_main = false
let has_toplevel_stmts = false
let i = 0
while i < n {
let stmt = native_list_get(stmts, i)
let sk: String = stmt["stmt"]
if str_eq(sk, "FnDef") {
let fn_name_chk: String = stmt["name"]
if str_eq(fn_name_chk, "main") { let has_el_main = true }
}
if !is_fndef(stmt) {
if !is_top_level_decl(stmt) {
if !str_eq(sk, "Let") {
let has_toplevel_stmts = true
}
}
}
let i = i + 1
}
let is_library = false
if !has_el_main {
if !has_toplevel_stmts {
let is_library = true
}
}
// 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
}
// Skip C main() for library units (no fn main, no top-level stmts)
if is_library { return "" }
// 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
""
}
// Streaming codegen (JIT function-at-a-time)
//
// codegen_streaming is a memory-efficient alternative to codegen().
// Instead of receiving the full parsed AST, it receives the raw token list
// and a pre-scanned signature list (from scan_fn_sigs in parser.el).
//
// Pipeline:
// 1. Scan phase (already done by caller): scan_fn_sigs(tokens) -> sigs
// 2. Emit preamble using sigs (no full AST needed)
// 3. For each top-level statement:
// parse_one(tokens, pos) -> { node, pos }
// cg_decl_streaming(node) <- emit C for this one decl
// el_release(node) <- discard AST immediately
//
// Peak memory: O(one function's AST) instead of O(whole program AST).
//
// Entry point: codegen_streaming(tokens, sigs, source) -> String
// cg_decl_streaming emit C for a single top-level declaration.
// Handles FnDef, ExternFn, TypeDef, EnumDef, Import, CgiBlock, ServiceBlock.
// Top-level Let statements go into the main() body, not here.
// Top-level executable statements (non-fn, non-let, non-decl) are
// accumulated into state and emitted later in main().
fn cg_decl_streaming(stmt: Map<String, Any>) -> Void {
let sk: String = stmt["stmt"]
if str_eq(sk, "FnDef") {
cg_fn(stmt)
return
}
// All other top-level decl kinds are either no-ops (Import, TypeDef,
// EnumDef, ExternFn forward decl already emitted) or capability markers
// (CgiBlock, ServiceBlock already handled in preamble).
// Top-level Lets are also no-ops here (file-scope slots already emitted).
// Executable top-level stmts (Expr, Return, etc.) are accumulated in state.
if !str_eq(sk, "FnDef") {
if !is_top_level_decl(stmt) {
if !str_eq(sk, "Let") {
// This is an executable top-level statement.
// We can't emit it into main() yet because we haven't started
// emitting main(). Accumulate in state as a list index.
// We'll collect these into a list and emit after all fns.
state_set("__streaming_has_toplevel_stmts", "1")
}
}
}
}
// emit_streaming_preamble emit #includes, forward decls, and file-scope lets
// using the pre-scanned signature data (no full AST).
fn emit_streaming_preamble(sigs: [Map<String, Any>], source: String) -> Void {
let n: Int = native_list_len(sigs)
// Detect program kind from sigs
let cgi_count: Int = 0
let svc_count: Int = 0
let i: Int = 0
while i < n {
let sig = native_list_get(sigs, i)
let sk: String = sig["kind"]
if str_eq(sk, "cgi_block") { let cgi_count = cgi_count + 1 }
if str_eq(sk, "service_block") { let svc_count = svc_count + 1 }
let i = i + 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)\"")
}
}
let kind: String = "utility"
if cgi_count >= 1 { let kind = "cgi" }
if svc_count >= 1 { let kind = "service" }
state_set("__program_kind", kind)
state_set("__cap_violations", "")
state_set("__arity_violations", "")
state_set("__time_violations", "")
emit_line("#include <stdint.h>")
emit_line("#include <stdlib.h>")
emit_line("#include \"el_runtime.h\"")
emit_blank()
// Forward declarations use pre-computed params_c strings from scan.
let i = 0
while i < n {
let sig = native_list_get(sigs, i)
let sk: String = sig["kind"]
if str_eq(sk, "fn") {
let fn_name: String = sig["name"]
if !str_eq(fn_name, "main") {
let params_c: String = sig["params_c"]
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
}
}
if str_eq(sk, "extern_fn") {
let fn_name: String = sig["name"]
let params_c: String = sig["params_c"]
emit_line("el_val_t " + fn_name + "(" + params_c + ");")
}
let i = i + 1
}
emit_blank()
// File-scope let slots
let has_toplevel_lets: Bool = false
let i = 0
while i < n {
let sig = native_list_get(sigs, i)
let sk: String = sig["kind"]
if str_eq(sk, "toplevel_let") {
let name: String = sig["name"]
let ltype: String = sig["ltype"]
if str_eq(ltype, "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() }
}
// codegen_streaming JIT function-at-a-time compiler backend.
// tokens: flat token list from lex()
// sigs: pre-scanned signature list from scan_fn_sigs(tokens)
// source: original source string (for string literal lookup)
fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) -> String {
let total_tokens: Int = native_list_len(tokens) / 2
// Emit preamble (forward decls, file-scope lets, #includes)
// Arena scope: free intermediate strings built during preamble emission.
let preamble_mark: Any = el_arena_push()
emit_streaming_preamble(sigs, source)
el_arena_pop(preamble_mark)
// Detect whether there is a fn main() and whether there are top-level
// executable stmts (for library detection) from sigs.
let has_el_main: Bool = false
let ns: Int = native_list_len(sigs)
let si: Int = 0
while si < ns {
let sig = native_list_get(sigs, si)
let sk2: String = sig["kind"]
if str_eq(sk2, "fn") {
let fn_name_chk: String = sig["name"]
if str_eq(fn_name_chk, "main") { let has_el_main = true }
}
let si = si + 1
}
// Collect top-level let names for seeding main()'s declared set.
let toplevel_let_names: [String] = native_list_empty()
let si = 0
while si < ns {
let sig = native_list_get(sigs, si)
let sk2: String = sig["kind"]
if str_eq(sk2, "toplevel_let") {
let tname: String = sig["name"]
let toplevel_let_names = native_list_append(toplevel_let_names, tname)
}
let si = si + 1
}
// In test mode: collect test function names for harness main().
let test_is_mode: Bool = false
let tmode_str: String = state_get("__test_mode")
if str_eq(tmode_str, "1") { let test_is_mode = true }
let test_names: [String] = native_list_empty()
let test_c_names: [String] = native_list_empty()
// Emit test harness preamble (counters, fail printer) when in test mode.
if test_is_mode {
emit_line("#include <stdio.h>")
emit_blank()
emit_line("static int __el_pass = 0, __el_fail = 0;")
emit_line("static const char *__el_cur_test = \"(none)\";")
emit_line("static void __el_test_fail(const char *test, const char *msg) {")
emit_line(" fprintf(stderr, \"FAIL %-40s %s\\n\", test, msg);")
emit_line("}")
emit_blank()
}
// Streaming parse-emit loop.
// For each parsed stmt:
// - FnDef (not main): emit immediately via cg_fn, release AST
// - Others: accumulate only fn-main body and top-level executable stmts
// (these are small in count relative to fn bodies)
let pos: Int = 0
let el_main_body: [Map<String, Any>] = native_list_empty()
let toplevel_exec_stmts: [Map<String, Any>] = native_list_empty()
let has_toplevel_exec: Bool = false
let stream_running: Bool = true
while stream_running {
if pos >= total_tokens {
let stream_running = false
} else {
let k: String = tok_kind(tokens, pos)
if str_eq(k, "Eof") {
let stream_running = false
} else {
if str_eq(k, "Test") {
if test_is_mode {
// Compile test "name" { ... } block into a static void __el_test_NAME() function.
let p: Int = pos + 1
let test_name: String = "unnamed"
if str_eq(tok_kind(tokens, p), "Str") {
let test_name = tok_value(tokens, p)
let p = p + 1
}
let fn_c_name: String = "__el_test_" + sanitize_test_name(test_name)
let test_names = native_list_append(test_names, test_name)
let test_c_names = native_list_append(test_c_names, fn_c_name)
// Emit the test function header.
emit_line("static void " + fn_c_name + "(void) {")
emit_line(" __el_cur_test = \"" + c_escape(test_name) + "\";")
// Skip the opening LBrace and parse body statements.
if str_eq(tok_kind(tokens, p), "LBrace") { let p = p + 1 }
let body_decl: [String] = native_list_empty()
let body_done: Bool = false
while !body_done {
let bk: String = tok_kind(tokens, p)
if str_eq(bk, "RBrace") {
let body_done = true
} else {
if str_eq(bk, "Eof") {
let body_done = true
} else {
let br = parse_one(tokens, p)
let bstmt = br["node"]
let np: Int = br["pos"]
el_release(br)
if np > p {
let body_arena: Any = el_arena_push()
let body_decl = cg_stmt(bstmt, " ", body_decl)
el_arena_pop(body_arena)
el_release(bstmt)
let p = np
} else {
let p = p + 1
}
}
}
}
// Skip past closing RBrace.
if str_eq(tok_kind(tokens, p), "RBrace") { let p = p + 1 }
el_release(body_decl)
emit_line("}")
emit_blank()
let pos = p
} else {
// Non-test mode: skip test blocks entirely to avoid OOM.
// Without this skip, the body `{ ... }` would be parsed as a Map
// literal, building a huge AST with O(n²) string allocation.
let p: Int = pos + 1
let k_name: String = tok_kind(tokens, p)
if str_eq(k_name, "Str") { let p = p + 1 }
let k_body: String = tok_kind(tokens, p)
if str_eq(k_body, "LBrace") { let p = skip_to_rbrace(tokens, p) }
let pos = p
}
} else {
let r = parse_one(tokens, pos)
let stmt = r["node"]
let new_pos: Int = r["pos"]
el_release(r)
// Guard against infinite loops
if new_pos <= pos {
el_release(stmt)
let pos = pos + 1
} else {
let sk: String = stmt["stmt"]
if str_eq(sk, "FnDef") {
let fn_name2: String = stmt["name"]
if str_eq(fn_name2, "main") {
// Capture main() body for later
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
}
el_release(stmt)
} else {
// Emit immediately this is the JIT core
// Arena scope: free all intermediate strings (str_concat,
// int_to_str, cg_expr fragments) after each function.
let fn_arena_mark: Any = el_arena_push()
cg_fn(stmt)
el_release(stmt)
el_arena_pop(fn_arena_mark)
}
} else {
if is_top_level_decl(stmt) {
// Import, TypeDef, EnumDef, CgiBlock, ServiceBlock, ExternFn
// These are no-ops in codegen (forward decls already emitted)
el_release(stmt)
} else {
if str_eq(sk, "Let") {
// Top-level let: file-scope slot already declared.
// Keep for main() init these are few and small.
let toplevel_exec_stmts = native_list_append(toplevel_exec_stmts, stmt)
let has_toplevel_exec = true
} else {
// Executable top-level stmt (rare)
let toplevel_exec_stmts = native_list_append(toplevel_exec_stmts, stmt)
let has_toplevel_exec = true
}
}
}
let pos = new_pos
}
}
}
}
}
// Tokens fully consumed by the streaming loop release now to free peak heap.
el_release(tokens)
if test_is_mode {
// Test mode: emit test harness main() that calls each collected test function.
// Discard El's main body and top-level exec stmts (not needed in test harness).
el_release(el_main_body)
el_release(toplevel_exec_stmts)
el_release(toplevel_let_names)
el_release(sigs)
let test_arena_mark: Any = el_arena_push()
emit_line("int main(int _argc, char **_argv) {")
emit_line(" el_runtime_init_args(_argc, _argv);")
let ti: Int = 0
let tn: Int = native_list_len(test_c_names)
while ti < tn {
let tc_name: String = native_list_get(test_c_names, ti)
emit_line(" " + tc_name + "();")
let ti = ti + 1
}
emit_line(" printf(\"%d passed, %d failed\\n\", __el_pass, __el_fail);")
emit_line(" return __el_fail;")
emit_line("}")
el_arena_pop(test_arena_mark)
el_release(test_names)
el_release(test_c_names)
return ""
}
// Release test tracking lists (empty in non-test mode).
el_release(test_names)
el_release(test_c_names)
// Library detection: no fn main and no top-level executable stmts
let is_library: Bool = false
if !has_el_main {
if !has_toplevel_exec {
let is_library = true
}
}
if is_library { return "" }
// Emit main() wrap in arena scope to free intermediate strings.
let main_arena_mark: Any = el_arena_push()
let kind2: String = state_get("__program_kind")
emit_line("int main(int _argc, char** _argv) {")
emit_line(" el_runtime_init_args(_argc, _argv);")
// cgi init if needed
let ns2: Int = native_list_len(sigs)
let si2: Int = 0
while si2 < ns2 {
let sig2 = native_list_get(sigs, si2)
let sk3: String = sig2["kind"]
if str_eq(sk3, "cgi_block") {
// We need the full cgi_block data it was parsed by scan_fn_sigs
// but scan only stored the name. For cgi_init we need dharma_id etc.
// Since cgi blocks are rare and small, they end up in toplevel_exec_stmts.
// Find the CgiBlock in toplevel_exec_stmts.
let tes_n: Int = native_list_len(toplevel_exec_stmts)
let tes_i: Int = 0
while tes_i < tes_n {
let tes = native_list_get(toplevel_exec_stmts, tes_i)
let tes_k: String = tes["stmt"]
if str_eq(tes_k, "CgiBlock") {
let cname2: String = tes["name"]
let cdid2: String = tes["dharma_id"]
let cprin2: String = tes["principal"]
let cnet2: String = tes["network"]
let ceng2: String = tes["engram"]
let has_did2: Bool = tes["has_dharma_id"]
let has_prin2: Bool = tes["has_principal"]
let has_net2: Bool = tes["has_network"]
let has_eng2: Bool = tes["has_engram"]
let arg_name2: String = "EL_STR(" + c_str_lit(cname2) + ")"
let arg_did2: String = cgi_arg(cdid2, has_did2)
let arg_prin2: String = cgi_arg(cprin2, has_prin2)
let arg_net2: String = cgi_arg(cnet2, has_net2)
let arg_eng2: String = cgi_arg(ceng2, has_eng2)
emit_line(" el_cgi_init(" + arg_name2 + ", " + arg_did2 + ", " + arg_prin2 + ", " + arg_net2 + ", " + arg_eng2 + ");")
}
let tes_i = tes_i + 1
}
}
let si2 = si2 + 1
}
// sigs fully consumed release to free peak heap.
el_release(sigs)
// Seed declared set with top-level let names
let main_decl2: [String] = native_list_empty()
let tln: Int = native_list_len(toplevel_let_names)
let tli: Int = 0
while tli < tln {
let main_decl2 = native_list_append(main_decl2, native_list_get(toplevel_let_names, tli))
let tli = tli + 1
}
// toplevel_let_names fully consumed release to free peak heap.
el_release(toplevel_let_names)
// Emit top-level executable stmts (lets and others) into main()
// Per-statement arena scope mirrors el_main_body: frees intermediate strings
// (str_concat fragments from cg_expr) after each statement, preventing O(n²)
// accumulation when many stmts are present (e.g. from unrecognized constructs).
let tes_n2: Int = native_list_len(toplevel_exec_stmts)
let tes_i2: Int = 0
while tes_i2 < tes_n2 {
let tes2 = native_list_get(toplevel_exec_stmts, tes_i2)
let tes_k2: String = tes2["stmt"]
if !str_eq(tes_k2, "CgiBlock") {
if !str_eq(tes_k2, "ServiceBlock") {
let tes_mark: Any = el_arena_push()
let main_decl2 = cg_stmt(tes2, " ", main_decl2)
el_arena_pop(tes_mark)
}
}
let tes_i2 = tes_i2 + 1
}
// toplevel_exec_stmts fully consumed release to free peak heap.
el_release(toplevel_exec_stmts)
// Emit fn main() body per-statement arena scope frees intermediate strings.
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 stmt_mark: Any = el_arena_push()
let main_decl2 = cg_stmt(mstmt, " ", main_decl2)
el_arena_pop(stmt_mark)
let mi = mi + 1
}
// el_main_body and main_decl2 fully consumed release to free peak heap.
el_release(el_main_body)
el_release(main_decl2)
emit_line(" return 0;")
emit_line("}")
emit_blank()
emit_cap_violations()
emit_arity_violations()
emit_time_violations()
el_arena_pop(main_arena_mark)
""
}