fix looks_like_string for empty strings and UTF-8, add cross-module includes in codegen

This commit is contained in:
Will Anderson
2026-05-03 00:27:20 -05:00
parent 3d71db4958
commit d63379103b
4 changed files with 207 additions and 100 deletions
Vendored Executable
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+7 -2
View File
@@ -2939,8 +2939,13 @@ static int looks_like_string(el_val_t v) {
const unsigned char* s = (const unsigned char*)p; const unsigned char* s = (const unsigned char*)p;
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
unsigned char c = s[i]; unsigned char c = s[i];
if (c == '\0') return i > 0; /* terminated string */ if (c == '\0') return 1; /* terminated string (empty string is still a valid string) */
if (c < 0x09 || (c > 0x0d && c < 0x20) || c >= 0x7f) return 0; /* Reject C0 control chars (non-whitespace), allow UTF-8 high bytes.
* 0x09-0x0d = tab/newline/cr/vt/ff (whitespace, OK)
* 0x20-0x7e = printable ASCII (OK)
* 0x7f = DEL (reject)
* 0x80-0xff = UTF-8 continuation/lead bytes (OK for multi-byte chars) */
if (c < 0x09 || (c > 0x0d && c < 0x20) || c == 0x7f) return 0;
} }
return 1; /* 16+ printable bytes — call it a string */ return 1; /* 16+ printable bytes — call it a string */
} }
+200 -98
View File
@@ -1,4 +1,4 @@
// codegen.el El compiler C source code generator // codegen.el - El compiler C source code generator
// //
// Input: list of AST statement maps (from parser.el) // Input: list of AST statement maps (from parser.el)
// Output: C source printed to stdout (streamed, one line at a time) // Output: C source printed to stdout (streamed, one line at a time)
@@ -7,37 +7,90 @@
// Functions map directly to C functions; top-level statements become main(). // Functions map directly to C functions; top-level statements become main().
// //
// Entry point: fn codegen(stmts: [Map<String, Any>], source: String) -> String // Entry point: fn codegen(stmts: [Map<String, Any>], source: String) -> String
// Returns "" output goes to stdout via println(). // Returns "" - output goes to stdout via println().
// //
// Streaming output avoids O(n²) string concatenation: each emitted line is // Streaming output avoids O(n-) string concatenation: each emitted line is
// printed immediately rather than appended to a growing string. // printed immediately rather than appended to a growing string.
// String helpers // -- String helpers ------------------------------------------------------------
// Escape a C string literal (double-quotes and backslashes). // 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 { fn c_escape(s: String) -> String {
let chars: [String] = native_string_chars(s) // Use index-based byte scanning via str_char_code(s, i) and str_char_at(s, i).
let total: Int = native_list_len(chars) // This avoids native_string_chars + str_join, which corrupts high-byte (>= 0x80)
// characters because list_join's looks_like_string heuristic rejects strings
// whose first byte is >= 0x7F and emits them as decimal pointer values instead.
//
// 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 parts: [String] = native_list_empty()
let i = 0 let i: Int = 0
let prev_was_hex_escape: Bool = false
while i < total { while i < total {
let ch: String = native_list_get(chars, i) let bval: Int = str_char_code(s, i)
if ch == "\"" { // If the previous token was a \xNN escape and the current byte is a
// hex digit, insert an empty string literal ("") to break the escape.
if prev_was_hex_escape {
if is_hex_digit_byte(bval) {
let parts = native_list_append(parts, "\"\"")
}
}
let prev_was_hex_escape = false
if bval == 34 {
// 34 = '"'
let parts = native_list_append(parts, "\\\"") let parts = native_list_append(parts, "\\\"")
} else { } else {
if ch == "\\" { if bval == 92 {
// 92 = '\\'
let parts = native_list_append(parts, "\\\\") let parts = native_list_append(parts, "\\\\")
} else { } else {
if ch == "\n" { if bval == 10 {
// 10 = '\n'
let parts = native_list_append(parts, "\\n") let parts = native_list_append(parts, "\\n")
} else { } else {
if ch == "\r" { if bval == 13 {
// 13 = '\r'
let parts = native_list_append(parts, "\\r") let parts = native_list_append(parts, "\\r")
} else { } else {
if ch == "\t" { if bval == 9 {
// 9 = '\t'
let parts = native_list_append(parts, "\\t") let parts = native_list_append(parts, "\\t")
} else { } else {
let parts = native_list_append(parts, ch) if bval >= 128 {
// Escape non-ASCII bytes (>= 0x80) as \xNN so
// Clang does not misinterpret multi-byte UTF-8
// sequences in C string literals.
let parts = native_list_append(parts, "\\x" + byte_to_hex2(bval))
let prev_was_hex_escape = true
} else {
let parts = native_list_append(parts, str_char_at(s, i))
}
} }
} }
} }
@@ -52,7 +105,7 @@ fn c_str_lit(s: String) -> String {
"\"" + c_escape(s) + "\"" "\"" + c_escape(s) + "\""
} }
// Type mapping // -- Type mapping --------------------------------------------------------------
fn el_type_to_c(type_str: String) -> String { fn el_type_to_c(type_str: String) -> String {
if type_str == "String" { return "const char*" } if type_str == "String" { return "const char*" }
@@ -64,7 +117,7 @@ fn el_type_to_c(type_str: String) -> String {
"void*" "void*"
} }
// Code emission // -- Code emission -------------------------------------------------------------
// //
// emit_line/emit_blank stream output directly via println. // emit_line/emit_blank stream output directly via println.
// This avoids building a large string in memory. // This avoids building a large string in memory.
@@ -77,7 +130,7 @@ fn emit_blank() -> Void {
println("") println("")
} }
// Operator helpers // -- Operator helpers ----------------------------------------------------------
fn binop_to_c(op: String) -> String { fn binop_to_c(op: String) -> String {
if op == "Plus" { return "+" } if op == "Plus" { return "+" }
@@ -95,11 +148,11 @@ fn binop_to_c(op: String) -> String {
op op
} }
// Expression codegen // -- Expression codegen --------------------------------------------------------
// //
// cg_expr returns a C expression string (not a statement). // cg_expr returns a C expression string (not a statement).
// duration_unit_nanos multiplier from a postfix-literal unit name to // duration_unit_nanos - multiplier from a postfix-literal unit name to
// nanoseconds. Singular and plural forms collapse to the same multiplier; // nanoseconds. Singular and plural forms collapse to the same multiplier;
// the parser already restricted `unit` to the set is_duration_unit accepts. // the parser already restricted `unit` to the set is_duration_unit accepts.
// Returns the multiplier as a decimal string suitable for splicing into // Returns the multiplier as a decimal string suitable for splicing into
@@ -130,7 +183,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
return v return v
} }
// DurationLit postfix-literal time value (e.g. 30.seconds, 1.hour). // DurationLit - postfix-literal time value (e.g. 30.seconds, 1.hour).
// Lowered to a literal int64 nanosecond count, wrapped in the runtime // Lowered to a literal int64 nanosecond count, wrapped in the runtime
// entry point so the intent is explicit at the C level. The arithmetic // entry point so the intent is explicit at the C level. The arithmetic
// is fully constant-folded by any optimising C compiler. // is fully constant-folded by any optimising C compiler.
@@ -144,7 +197,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
if kind == "Float" { if kind == "Float" {
// Wrap Float literals in el_from_float() so the bit pattern is // Wrap Float literals in el_from_float() so the bit pattern is
// preserved through the el_val_t (int64) slot. Without this, // preserved through the el_val_t (int64) slot. Without this,
// implicit doubleint64 conversion in C truncates `0.8` to `0` // implicit double->int64 conversion in C truncates `0.8` to `0`
// when passed to a builtin that expects el_val_t. // when passed to a builtin that expects el_val_t.
let v: String = expr["value"] let v: String = expr["value"]
return "el_from_float(" + v + ")" return "el_from_float(" + v + ")"
@@ -191,12 +244,12 @@ fn cg_expr(expr: Map<String, Any>) -> String {
let left_kind: String = left["expr"] let left_kind: String = left["expr"]
let right_kind: String = right["expr"] let right_kind: String = right["expr"]
// String/equality fast-path: skip O(N²) temporal traversals // -- String/equality fast-path: skip O(N-) temporal traversals --------
// The 10 temporal predicates below each recurse into the left subtree: // 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 // 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). // 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 // When either operand is a bare Str literal the result is always concat
// or str_eq no temporal dispatch is possible. Exit immediately. // or str_eq - no temporal dispatch is possible. Exit immediately.
if str_eq(op, "Plus") { if str_eq(op, "Plus") {
if str_eq(left_kind, "Str") { return "el_str_concat(" + left_c + ", " + right_c + ")" } 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(right_kind, "Str") { return "el_str_concat(" + left_c + ", " + right_c + ")" }
@@ -210,7 +263,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
if str_eq(right_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) // -- Temporal-type dispatch (Instant + Duration first-class) --------
// Run BEFORE the int / string / generic paths so typed temporal // Run BEFORE the int / string / generic paths so typed temporal
// operands route through the runtime wrappers and invalid combos // operands route through the runtime wrappers and invalid combos
// become #error directives rather than silently falling through to // become #error directives rather than silently falling through to
@@ -396,7 +449,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
if right_is_dur { return "el_duration_ne(" + left_c + ", " + right_c + ")" } if right_is_dur { return "el_duration_ne(" + left_c + ", " + right_c + ")" }
} }
} }
// Fall through let the existing path handle anything we // Fall through - let the existing path handle anything we
// didn't explicitly cover (typically string-concat with a // didn't explicitly cover (typically string-concat with a
// typed temporal value, e.g. for debug prints, which works // typed temporal value, e.g. for debug prints, which works
// because both share the int64 slot). // because both share the int64 slot).
@@ -415,7 +468,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
// builtin, or BinOp arithmetic over Ints) participates in // builtin, or BinOp arithmetic over Ints) participates in
// arithmetic, not string concat. Recursion into BinOp lets // arithmetic, not string concat. Recursion into BinOp lets
// `a + b + c` (chained Int adds) and `acc * 16 + d` route to // `a + b + c` (chained Int adds) and `acc * 16 + d` route to
// arithmetic instead of falling to el_str_concat both sides // arithmetic instead of falling to el_str_concat - both sides
// are Int so the outer `+` is too. // are Int so the outer `+` is too.
if is_int_expr(left) { if is_int_expr(left) {
if is_int_expr(right) { if is_int_expr(right) {
@@ -436,7 +489,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
return "(" + left_c + " " + op_c + " " + right_c + ")" return "(" + left_c + " " + op_c + " " + right_c + ")"
} }
// Otherwise: BinOp(+) with a Call/Ident side without int-typed // Otherwise: BinOp(+) with a Call/Ident side without int-typed
// evidence fall back to string concat (the historical default). // evidence - fall back to string concat (the historical default).
if left_kind == "Call" { if left_kind == "Call" {
return "el_str_concat(" + left_c + ", " + right_c + ")" return "el_str_concat(" + left_c + ", " + right_c + ")"
} }
@@ -468,7 +521,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
// identifiers tracked in __int_names (typed Int via `let x: Int = ...`). // identifiers tracked in __int_names (typed Int via `let x: Int = ...`).
// Without the int-name check, `seen == idx` between two Int locals // Without the int-name check, `seen == idx` between two Int locals
// miscompiles to str_eq(seen, idx), strcmp'ing what are integer values // miscompiles to str_eq(seen, idx), strcmp'ing what are integer values
// dressed as char* segfault on the first non-printable byte. // dressed as char* - segfault on the first non-printable byte.
if op == "EqEq" { if op == "EqEq" {
if left_kind == "Int" { if left_kind == "Int" {
return "(" + left_c + " == " + right_c + ")" return "(" + left_c + " == " + right_c + ")"
@@ -602,17 +655,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
// violations to be emitted as #error directives at the // violations to be emitted as #error directives at the
// top of the generated C, so cc fails with a clear msg. // top of the generated C, so cc fails with a clear msg.
cap_check_call(fn_name) cap_check_call(fn_name)
// Arity check against the builtin table refuse, with a clear // Arity check against the builtin table - refuse, with a clear
// El-source message, when a known builtin gets the wrong arg // El-source message, when a known builtin gets the wrong arg
// count (e.g. `http_serve(port)` instead of `http_serve(port, // count (e.g. `http_serve(port)` instead of `http_serve(port,
// handler)`). User-defined fns and variadic builtins pass // handler)`). User-defined fns and variadic builtins pass
// through (builtin_arity returns -1). // through (builtin_arity returns -1).
arity_check_call(fn_name, arity) arity_check_call(fn_name, arity)
// sleep(Duration) Phase 1 of the typed-time work. When the // sleep(Duration) - Phase 1 of the typed-time work. When the
// single arg is provably a Duration we lower to el_sleep_duration // single arg is provably a Duration we lower to el_sleep_duration
// so the runtime sees nanos directly. Existing sleep() callers // so the runtime sees nanos directly. Existing sleep() callers
// that pass an Int still emit `sleep(<int>)`, which falls through // that pass an Int still emit `sleep(<int>)`, which falls through
// to the no-such-symbol path those call sites must migrate to // to the no-such-symbol path - those call sites must migrate to
// a typed Duration. Acceptable: the spec marks them out for an // a typed Duration. Acceptable: the spec marks them out for an
// audit pass during Phase 1. // audit pass during Phase 1.
if str_eq(fn_name, "sleep") { if str_eq(fn_name, "sleep") {
@@ -623,6 +676,20 @@ fn cg_expr(expr: Map<String, Any>) -> String {
} }
} }
} }
// 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 + ")" return fn_name + "(" + args_c + ")"
} }
@@ -656,8 +723,8 @@ fn cg_expr(expr: Map<String, Any>) -> String {
// El programs use `t["field"]` for map access and `arr[i]` for // El programs use `t["field"]` for map access and `arr[i]` for
// list access. The parser emits the same Index node for both. // list access. The parser emits the same Index node for both.
// Dispatch at codegen time on the index expression kind: string- // Dispatch at codegen time on the index expression kind: string-
// literal index map field access (`el_get_field`); anything // literal index -> map field access (`el_get_field`); anything
// else list element access (`el_list_get`). // else -> list element access (`el_list_get`).
let obj = expr["object"] let obj = expr["object"]
let idx = expr["index"] let idx = expr["index"]
let obj_c: String = cg_expr(obj) let obj_c: String = cg_expr(obj)
@@ -691,7 +758,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
let n: Int = native_list_len(pairs) let n: Int = native_list_len(pairs)
// Empty literal: `el_map_new(0, )` is malformed C (trailing comma in // Empty literal: `el_map_new(0, )` is malformed C (trailing comma in
// a varargs call). Emit `el_map_new(0)` directly so empty-map // a varargs call). Emit `el_map_new(0)` directly so empty-map
// shadowing inside for/while/if bodies `let acc: Map = {}` // shadowing inside for/while/if bodies - `let acc: Map = {}` -
// doesn't fail downstream cc with parse errors. // doesn't fail downstream cc with parse errors.
if n == 0 { return "el_map_new(0)" } if n == 0 { return "el_map_new(0)" }
let items_parts: [String] = native_list_empty() let items_parts: [String] = native_list_empty()
@@ -723,7 +790,7 @@ fn cg_expr(expr: Map<String, Any>) -> String {
"EL_NULL" "EL_NULL"
} }
// Match codegen // -- Match codegen -------------------------------------------------------------
// //
// Lower a match expression to a GCC/Clang statement-expression. // Lower a match expression to a GCC/Clang statement-expression.
// A unique label suffix is allocated per match via state_set("__match_counter"). // A unique label suffix is allocated per match via state_set("__match_counter").
@@ -747,7 +814,7 @@ fn cg_match(expr: Map<String, Any>) -> String {
let subj_var: String = "_match_subj_" + id let subj_var: String = "_match_subj_" + id
let result_var: String = "_match_result_" + id let result_var: String = "_match_result_" + id
let done_label: String = "_match_done_" + id let done_label: String = "_match_done_" + id
// Accumulate arm fragments into a list to avoid O(n²) string growth. // Accumulate arm fragments into a list to avoid O(n-) string growth.
let parts: [String] = native_list_empty() 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 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 n: Int = native_list_len(arms)
@@ -781,7 +848,7 @@ fn cg_match(expr: Map<String, Any>) -> String {
} }
let parts = native_list_append(parts, "if (" + subj_var + " == " + bv + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } ") let parts = native_list_append(parts, "if (" + subj_var + " == " + bv + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
} else { } else {
// unknown pattern wildcard // unknown pattern -> wildcard
let parts = native_list_append(parts, "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } ") let parts = native_list_append(parts, "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
} }
} }
@@ -794,7 +861,7 @@ fn cg_match(expr: Map<String, Any>) -> String {
str_join(parts, "") str_join(parts, "")
} }
// If-as-expression codegen // -- If-as-expression codegen -------------------------------------------------
// //
// Lower `if cond { thenBody } else { elseBody }` used in expression position // 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 // (e.g. `let x = if a { b } else { c }`) to a GCC/Clang statement-expression
@@ -822,7 +889,7 @@ fn next_if_id() -> String {
// result var stays at its initial 0. // result var stays at its initial 0.
fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String { fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
let n: Int = native_list_len(stmts) let n: Int = native_list_len(stmts)
// Collect statement fragments into a list to avoid O(n²) string growth. // Collect statement fragments into a list to avoid O(n-) string growth.
let parts: [String] = native_list_empty() let parts: [String] = native_list_empty()
let i = 0 let i = 0
while i < n { while i < n {
@@ -851,7 +918,7 @@ fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
} }
} else { } else {
if str_eq(sk, "Assign") { if str_eq(sk, "Assign") {
// Real reassignment in an expression-position arm // Real reassignment in an expression-position arm -
// emit the store; the arm's "value" stays whatever // emit the store; the arm's "value" stays whatever
// result_var was last set to, which is the El // result_var was last set to, which is the El
// semantics (assignment is a statement, not a value). // semantics (assignment is a statement, not a value).
@@ -889,7 +956,7 @@ fn cg_if_expr(expr: Map<String, Any>) -> String {
out out
} }
// Variable scope tracking // -- Variable scope tracking ---------------------------------------------------
// //
// El allows `let x = expr` to both declare and reassign x in the same scope. // 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. // C doesn't allow redeclaring the same name in the same block.
@@ -908,7 +975,7 @@ fn list_contains(lst: [String], s: String) -> Bool {
false false
} }
// Statement codegen // -- Statement codegen ---------------------------------------------------------
// //
// cg_stmt emits C lines via println. declared is a list of already-declared // 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. // variable names in the current C scope; returns updated declared list.
@@ -957,7 +1024,7 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
if str_eq(ltype, "Zone") { if str_eq(ltype, "Zone") {
add_zone_name(name) add_zone_name(name)
} }
// Inference from RHS duration literals and known-typed calls // Inference from RHS - duration literals and known-typed calls
// propagate even when the let is unannotated. // propagate even when the let is unannotated.
if is_instant_expr(val) { if is_instant_expr(val) {
add_instant_name(name) add_instant_name(name)
@@ -1012,7 +1079,7 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
} }
// Bare reassignment: `name = expr`. Always emits a plain C assignment // Bare reassignment: `name = expr`. Always emits a plain C assignment
// (no `el_val_t` prefix) by construction the parser only produces // (no `el_val_t` prefix) - by construction the parser only produces
// Assign for an existing identifier. If the name happens NOT to be in // 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 // `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 // block) the emit still resolves at C level because the variable lives
@@ -1047,7 +1114,7 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
let cond_c: String = cg_expr(cond) let cond_c: String = cg_expr(cond)
let cond_c = strip_outer_parens(cond_c) let cond_c = strip_outer_parens(cond_c)
emit_line(indent + "while (" + cond_c + ") {") emit_line(indent + "while (" + cond_c + ") {")
// Body lives in its own C block clone so let-bindings inside the // 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 // 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). // a sibling scope's `let x` emit assignment on an undeclared name).
cg_stmts(body, indent + " ", native_list_clone(declared)) cg_stmts(body, indent + " ", native_list_clone(declared))
@@ -1114,7 +1181,7 @@ fn cg_if_stmt(expr: Map<String, Any>, indent: String, declared: [String]) -> Voi
let cond_c: String = cg_expr(cond) let cond_c: String = cg_expr(cond)
let cond_c = strip_outer_parens(cond_c) let cond_c = strip_outer_parens(cond_c)
emit_line(indent + "if (" + cond_c + ") {") emit_line(indent + "if (" + cond_c + ") {")
// Each branch gets its own clone of `declared` variables let-bound // 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 // 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 // leak back to the parent (or to the sibling branch) through shared
// list mutation. Cheap shallow copy; the entries (variable name strings) // list mutation. Cheap shallow copy; the entries (variable name strings)
@@ -1166,7 +1233,7 @@ fn cg_stmts(stmts: [Map<String, Any>], indent: String, declared: [String]) -> [S
decl decl
} }
// Function declaration codegen // -- Function declaration codegen -----------------------------------------------
fn param_decl(param: Map<String, Any>, idx: Int) -> String { fn param_decl(param: Map<String, Any>, idx: Int) -> String {
let name: String = param["name"] let name: String = param["name"]
@@ -1235,7 +1302,7 @@ fn is_int_name(name: String) -> Bool {
// Same shape as is_int_name, for Instant- and Duration-typed bindings. // Same shape as is_int_name, for Instant- and Duration-typed bindings.
// Used by the BinOp/comparison codegen to dispatch arithmetic through the // Used by the BinOp/comparison codegen to dispatch arithmetic through the
// typed runtime wrappers (el_instant_add_dur, el_duration_lt, ) and to // typed runtime wrappers (el_instant_add_dur, el_duration_lt, -) and to
// surface mismatches (Instant + Instant, Duration + Int) as #error // surface mismatches (Instant + Instant, Duration + Int) as #error
// directives at the top of the generated C. // directives at the top of the generated C.
fn is_instant_name(name: String) -> Bool { fn is_instant_name(name: String) -> Bool {
@@ -1297,7 +1364,7 @@ fn is_int_call(call_expr: Map<String, Any>) -> Bool {
} }
// Builtins that return an Instant. Used by is_instant_expr and the BinOp // 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 // dispatch - `now() + 5.seconds` types as Instant only because we can see
// that now() is an Instant-returning Call. // that now() is an Instant-returning Call.
fn is_instant_call(call_expr: Map<String, Any>) -> Bool { fn is_instant_call(call_expr: Map<String, Any>) -> Bool {
let func = call_expr["func"] let func = call_expr["func"]
@@ -1333,7 +1400,7 @@ fn is_duration_call(call_expr: Map<String, Any>) -> Bool {
return false return false
} }
// Phase 1.5 Calendar / CalendarTime / Rhythm / LocalDate / LocalTime / // Phase 1.5 - Calendar / CalendarTime / Rhythm / LocalDate / LocalTime /
// LocalDateTime / Zone are first-class boxed types. Each has its own name // LocalDateTime / Zone are first-class boxed types. Each has its own name
// set in process state, populated from typed `let` bindings and parameter // set in process state, populated from typed `let` bindings and parameter
// annotations. The BinOp dispatcher consults these to forbid mismatched // annotations. The BinOp dispatcher consults these to forbid mismatched
@@ -1521,7 +1588,7 @@ fn is_zone_expr(expr: Map<String, Any>) -> Bool {
// Recursive type predicates for Instant / Duration. Mirror is_int_expr. // Recursive type predicates for Instant / Duration. Mirror is_int_expr.
// is_instant_expr / is_duration_expr return true only when the expression // is_instant_expr / is_duration_expr return true only when the expression
// is provably of that type at codegen time. Anything ambiguous returns // is provably of that type at codegen time. Anything ambiguous returns
// false the BinOp dispatcher then leaves the expression on the // false - the BinOp dispatcher then leaves the expression on the
// untyped-int path, which is the safest fallback because at the runtime // untyped-int path, which is the safest fallback because at the runtime
// level all three types share the int64 slot. // level all three types share the int64 slot.
fn is_instant_expr(expr: Map<String, Any>) -> Bool { fn is_instant_expr(expr: Map<String, Any>) -> Bool {
@@ -1536,8 +1603,8 @@ fn is_instant_expr(expr: Map<String, Any>) -> Bool {
if str_eq(k, "BinOp") { if str_eq(k, "BinOp") {
let op: String = expr["op"] let op: String = expr["op"]
if str_eq(op, "Plus") { if str_eq(op, "Plus") {
// Instant + Duration Instant // Instant + Duration -> Instant
// Duration + Instant Instant // Duration + Instant -> Instant
if is_instant_expr(expr["left"]) { if is_instant_expr(expr["left"]) {
if is_duration_expr(expr["right"]) { return true } if is_duration_expr(expr["right"]) { return true }
} }
@@ -1547,7 +1614,7 @@ fn is_instant_expr(expr: Map<String, Any>) -> Bool {
return false return false
} }
if str_eq(op, "Minus") { if str_eq(op, "Minus") {
// Instant - Duration Instant // Instant - Duration -> Instant
if is_instant_expr(expr["left"]) { if is_instant_expr(expr["left"]) {
if is_duration_expr(expr["right"]) { return true } if is_duration_expr(expr["right"]) { return true }
} }
@@ -1574,15 +1641,15 @@ fn is_duration_expr(expr: Map<String, Any>) -> Bool {
if str_eq(k, "BinOp") { if str_eq(k, "BinOp") {
let op: String = expr["op"] let op: String = expr["op"]
if str_eq(op, "Plus") { if str_eq(op, "Plus") {
// Duration + Duration Duration // Duration + Duration -> Duration
if is_duration_expr(expr["left"]) { if is_duration_expr(expr["left"]) {
if is_duration_expr(expr["right"]) { return true } if is_duration_expr(expr["right"]) { return true }
} }
return false return false
} }
if str_eq(op, "Minus") { if str_eq(op, "Minus") {
// Duration - Duration Duration // Duration - Duration -> Duration
// Instant - Instant Duration (caught here, not in is_instant_expr) // Instant - Instant -> Duration (caught here, not in is_instant_expr)
if is_duration_expr(expr["left"]) { if is_duration_expr(expr["left"]) {
if is_duration_expr(expr["right"]) { return true } if is_duration_expr(expr["right"]) { return true }
} }
@@ -1592,8 +1659,8 @@ fn is_duration_expr(expr: Map<String, Any>) -> Bool {
return false return false
} }
if str_eq(op, "Star") { if str_eq(op, "Star") {
// Duration * Int Duration // Duration * Int -> Duration
// Int * Duration Duration // Int * Duration -> Duration
if is_duration_expr(expr["left"]) { if is_duration_expr(expr["left"]) {
if is_int_expr(expr["right"]) { return true } if is_int_expr(expr["right"]) { return true }
} }
@@ -1603,7 +1670,7 @@ fn is_duration_expr(expr: Map<String, Any>) -> Bool {
return false return false
} }
if str_eq(op, "Slash") { if str_eq(op, "Slash") {
// Duration / Int Duration // Duration / Int -> Duration
if is_duration_expr(expr["left"]) { if is_duration_expr(expr["left"]) {
if is_int_expr(expr["right"]) { return true } if is_int_expr(expr["right"]) { return true }
} }
@@ -1634,13 +1701,13 @@ fn time_record_violation(kind: String, detail: String) -> Bool {
// the outer dispatch only checks the immediate kind, not the inner. // the outer dispatch only checks the immediate kind, not the inner.
// //
// Rules: // Rules:
// Int literal Int // Int literal -> Int
// Ident in __int_names Int // Ident in __int_names -> Int
// Call to known-Int builtin Int // Call to known-Int builtin -> Int
// Neg of Int Int // Neg of Int -> Int
// BinOp arithmetic of two Ints Int (Plus, Minus, Star, Slash, Percent) // BinOp arithmetic of two Ints -> Int (Plus, Minus, Star, Slash, Percent)
// BinOp comparison/logical Int (yields 0/1; safe to treat as Int) // BinOp comparison/logical -> Int (yields 0/1; safe to treat as Int)
// anything else not provably Int // anything else -> not provably Int
fn is_int_expr(expr: Map<String, Any>) -> Bool { fn is_int_expr(expr: Map<String, Any>) -> Bool {
let k: String = expr["expr"] let k: String = expr["expr"]
if str_eq(k, "Int") { return true } if str_eq(k, "Int") { return true }
@@ -1659,7 +1726,7 @@ fn is_int_expr(expr: Map<String, Any>) -> Bool {
} }
if str_eq(k, "BinOp") { if str_eq(k, "BinOp") {
let op: String = expr["op"] let op: String = expr["op"]
// Comparisons and logicals always yield 0/1 safe Int. // Comparisons and logicals always yield 0/1 - safe Int.
if str_eq(op, "EqEq") { return true } if str_eq(op, "EqEq") { return true }
if str_eq(op, "NotEq") { return true } if str_eq(op, "NotEq") { return true }
if str_eq(op, "Lt") { return true } if str_eq(op, "Lt") { return true }
@@ -1668,7 +1735,7 @@ fn is_int_expr(expr: Map<String, Any>) -> Bool {
if str_eq(op, "GtEq") { return true } if str_eq(op, "GtEq") { return true }
if str_eq(op, "And") { return true } if str_eq(op, "And") { return true }
if str_eq(op, "Or") { return true } if str_eq(op, "Or") { return true }
// Arithmetic propagates: Int op Int Int. // Arithmetic propagates: Int op Int -> Int.
if str_eq(op, "Plus") { if str_eq(op, "Plus") {
if is_int_expr(expr["left"]) { if is_int_expr(expr["left"]) {
if is_int_expr(expr["right"]) { return true } if is_int_expr(expr["right"]) { return true }
@@ -1698,7 +1765,7 @@ fn is_int_expr(expr: Map<String, Any>) -> Bool {
return false return false
} }
// Capability-kind enforcement // -- Capability-kind enforcement ----------------------------------------------
// //
// A program's top-level block (cgi / service / none) determines which // A program's top-level block (cgi / service / none) determines which
// runtime primitives it may call. The compiler records violations in // runtime primitives it may call. The compiler records violations in
@@ -1707,11 +1774,11 @@ fn is_int_expr(expr: Map<String, Any>) -> Bool {
// downstream cc step fails with a clear message. // downstream cc step fails with a clear message.
// //
// Capability tiers: // Capability tiers:
// "cgi" full self-formation. All primitives. // "cgi" - full self-formation. All primitives.
// "service" bounded. Cannot call self-formation primitives: // "service" - bounded. Cannot call self-formation primitives:
// llm_call_agentic, llm_register_tool, dharma_emit, // llm_call_agentic, llm_register_tool, dharma_emit,
// dharma_field. Single-turn LLM calls are allowed. // dharma_field. Single-turn LLM calls are allowed.
// "utility" default. No DHARMA, no LLM. Pure compute + I/O. // "utility" - default. No DHARMA, no LLM. Pure compute + I/O.
// //
// The compiler-level rule is structural: the binary either CAN or CANNOT // 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. // emit the call. There is no runtime check, no opt-in, no override.
@@ -1726,7 +1793,7 @@ fn cap_record_violation(kind: String, fn_name: String) -> Bool {
return true return true
} }
// Self-formation primitives the cut between CGI and service. A program // Self-formation primitives - the cut between CGI and service. A program
// that emits these calls IS structurally a CGI; we forbid them everywhere // that emits these calls IS structurally a CGI; we forbid them everywhere
// else. // else.
fn is_self_formation_call(fn_name: String) -> Bool { fn is_self_formation_call(fn_name: String) -> Bool {
@@ -1737,7 +1804,7 @@ fn is_self_formation_call(fn_name: String) -> Bool {
return false return false
} }
// Any DHARMA primitive utilities have zero network presence. // Any DHARMA primitive - utilities have zero network presence.
fn is_dharma_call(fn_name: String) -> Bool { fn is_dharma_call(fn_name: String) -> Bool {
if str_eq(fn_name, "dharma_connect") { return true } if str_eq(fn_name, "dharma_connect") { return true }
if str_eq(fn_name, "dharma_send") { return true } if str_eq(fn_name, "dharma_send") { return true }
@@ -1750,7 +1817,7 @@ fn is_dharma_call(fn_name: String) -> Bool {
return false return false
} }
// Any LLM primitive utilities have no LLM access at all. // Any LLM primitive - utilities have no LLM access at all.
fn is_llm_call(fn_name: String) -> Bool { fn is_llm_call(fn_name: String) -> Bool {
if str_eq(fn_name, "llm_call") { return true } 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_system") { return true }
@@ -1800,14 +1867,14 @@ fn emit_cap_violations() -> Void {
if colon > 0 { if colon > 0 {
let kind: String = str_slice(entry, 0, colon) let kind: String = str_slice(entry, 0, colon)
let fn_name: String = str_slice(entry, colon + 1, str_len(entry)) 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)\"") 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 let i = i + next_comma + 1
} }
} }
// Surface temporal-type violations as #error directives. The cg_expr BinOp // Surface temporal-type violations as #error directives. The cg_expr BinOp
// dispatcher records each violation (Instant + Instant, Duration + Int, ) // dispatcher records each violation (Instant + Instant, Duration + Int, -)
// as a CSV entry "kind:detail" via time_record_violation. Each entry maps // 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- // to a single #error so downstream cc fails the build with a clear El-
// source-level message before the bogus C even links. // source-level message before the bogus C even links.
@@ -1830,7 +1897,7 @@ fn emit_time_violations() -> Void {
} }
} }
// Builtin arity table // -- Builtin arity table -------------------------------------------------------
// //
// El programs sometimes call runtime builtins with the wrong number of // El programs sometimes call runtime builtins with the wrong number of
// arguments (e.g. `http_serve(port)` instead of `http_serve(port, handler)`). // arguments (e.g. `http_serve(port)` instead of `http_serve(port, handler)`).
@@ -1840,7 +1907,7 @@ fn emit_time_violations() -> Void {
// //
// Strategy: a small static table mirrors el_runtime.h. Variadic builtins // Strategy: a small static table mirrors el_runtime.h. Variadic builtins
// (el_list_new, el_map_new, args) and unknown identifiers (user fns, // (el_list_new, el_map_new, args) and unknown identifiers (user fns,
// dynamic dispatch) return -1 no check. A mismatch records a violation // dynamic dispatch) return -1 -> no check. A mismatch records a violation
// in process state, which emit_arity_violations() turns into #error // in process state, which emit_arity_violations() turns into #error
// directives at the top of the generated C. // directives at the top of the generated C.
fn builtin_arity(name: String) -> Int { fn builtin_arity(name: String) -> Int {
@@ -2044,7 +2111,7 @@ fn builtin_arity(name: String) -> Int {
if str_eq(name, "get") { return 2 } if str_eq(name, "get") { return 2 }
if str_eq(name, "map_get") { return 2 } if str_eq(name, "map_get") { return 2 }
if str_eq(name, "map_set") { return 3 } if str_eq(name, "map_set") { return 3 }
// -1 sentinel: variadic / unknown / user-defined no check. // -1 sentinel: variadic / unknown / user-defined -> no check.
return -1 return -1
} }
@@ -2242,7 +2309,7 @@ fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
fn cg_fn(stmt: Map<String, Any>) -> Void { fn cg_fn(stmt: Map<String, Any>) -> Void {
let fn_name: String = stmt["name"] let fn_name: String = stmt["name"]
// Skip El's `fn main()` C provides its own main() for top-level stmts // 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. // and a duplicate `el_val_t main(void)` would collide with it.
if fn_name == "main" { return } if fn_name == "main" { return }
let params = stmt["params"] let params = stmt["params"]
@@ -2274,8 +2341,8 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
} }
// Lift the final bare expression into an explicit return so implicit // Lift the final bare expression into an explicit return so implicit
// returns ("fn lex(s) { ... tokens }") actually return their value. // returns ("fn lex(s) { ... tokens }") actually return their value.
// Void-returning functions skip this wrapping `println(x)` in // Void-returning functions skip this - wrapping `println(x)` in
// `return ` is a C type error. // `return -` is a C type error.
let body_xformed = body let body_xformed = body
if !str_eq(ret_type, "Void") { if !str_eq(ret_type, "Void") {
let body_xformed = transform_implicit_return(body) let body_xformed = transform_implicit_return(body)
@@ -2286,7 +2353,7 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
emit_blank() emit_blank()
} }
// Top-level codegen // -- Top-level codegen ---------------------------------------------------------
fn is_fndef(stmt: Map<String, Any>) -> Bool { fn is_fndef(stmt: Map<String, Any>) -> Bool {
let kind: String = stmt["stmt"] let kind: String = stmt["stmt"]
@@ -2312,7 +2379,7 @@ fn cgi_arg(value: String, has_value: Bool) -> String {
return "EL_NULL" return "EL_NULL"
} }
// VBD role enforcement // -- VBD role enforcement ------------------------------------------------------
// //
// Scan a function body for direct calls to DHARMA-restricted builtins // Scan a function body for direct calls to DHARMA-restricted builtins
// (dharma_emit, dharma_field). These may only appear inside @manager fns. // (dharma_emit, dharma_field). These may only appear inside @manager fns.
@@ -2445,16 +2512,16 @@ fn vbd_has_restricted_call(stmts: [Map<String, Any>]) -> Bool {
false false
} }
// Entry point // -- Entry point ----------------------------------------------------------------
fn codegen(stmts: [Map<String, Any>], source: String) -> String { fn codegen(stmts: [Map<String, Any>], source: String) -> String {
// Detect cgi/service blocks: at most one declarative top-level block. // Detect cgi/service blocks: at most one declarative top-level block.
// The block determines the program's CAPABILITY KIND: // The block determines the program's CAPABILITY KIND:
// "cgi" full self-formation. Calls all primitives. // "cgi" - full self-formation. Calls all primitives.
// "service" bounded. Cannot call self-formation primitives // "service" - bounded. Cannot call self-formation primitives
// (llm_call_agentic, llm_register_tool, dharma_emit, // (llm_call_agentic, llm_register_tool, dharma_emit,
// dharma_field, mindlink-creation). // dharma_field, mindlink-creation).
// "utility" default; no DHARMA membership, no LLM, no agentic. // "utility" - default; no DHARMA membership, no LLM, no agentic.
// Codegen enforces this with #error directives at every restricted // Codegen enforces this with #error directives at every restricted
// call site. The capability boundary is structural: a binary either // call site. The capability boundary is structural: a binary either
// CAN or CANNOT do a thing, and the compiler decides at emission time. // CAN or CANNOT do a thing, and the compiler decides at emission time.
@@ -2489,7 +2556,7 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
} }
if cgi_count >= 1 { if cgi_count >= 1 {
if svc_count >= 1 { if svc_count >= 1 {
emit_line("#error \"El: program declares both cgi and service blocks (mutually exclusive pick one)\"") 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 // Stash the program kind so cg_expr's Call branch can enforce
@@ -2509,9 +2576,44 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
emit_line("#include <stdint.h>") emit_line("#include <stdint.h>")
emit_line("#include <stdlib.h>") emit_line("#include <stdlib.h>")
emit_line("#include \"el_runtime.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() emit_blank()
// Forward declarations (skip `main` C provides its own) // Forward declarations (skip `main` - C provides its own)
let n: Int = native_list_len(stmts) let n: Int = native_list_len(stmts)
let i = 0 let i = 0
while i < n { while i < n {
@@ -2535,7 +2637,7 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
} }
emit_blank() emit_blank()
// Top-level `let` bindings file-scope storage. El programs use // Top-level `let` bindings -> file-scope storage. El programs use
// top-level `let GREETING = "..."` as module constants that any // top-level `let GREETING = "..."` as module constants that any
// function below should be able to read. Without this pass, a top- // function below should be able to read. Without this pass, a top-
// level Let only declares the name inside main()'s scope and any // level Let only declares the name inside main()'s scope and any
@@ -2683,7 +2785,7 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
let main_decl = cg_stmt(stmt, " ", main_decl) let main_decl = cg_stmt(stmt, " ", main_decl)
} }
} }
// Release AST node after final use each stmt is fully processed // Release AST node after final use - each stmt is fully processed
// by this point (forward decls, fn defs, top-level lets, and now // by this point (forward decls, fn defs, top-level lets, and now
// the main-body pass are all done). Releasing here prevents the // the main-body pass are all done). Releasing here prevents the
// accumulated AST from exhausting memory on large source files. // accumulated AST from exhausting memory on large source files.
@@ -2706,16 +2808,16 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
// Emit any accumulated capability-violation #error directives. cc // Emit any accumulated capability-violation #error directives. cc
// will fail on the first one and surface the message; placement at // will fail on the first one and surface the message; placement at
// the bottom is fine preprocessor errors halt the build wherever // the bottom is fine - preprocessor errors halt the build wherever
// they appear. // they appear.
emit_cap_violations() emit_cap_violations()
// Same for builtin-arity violations: cc halts on the first #error, // Same for builtin-arity violations: cc halts on the first #error,
// so a misuse of a known builtin (wrong arg count) fails the build // so a misuse of a known builtin (wrong arg count) fails the build
// with a clear message naming the builtin and its expected arity. // with a clear message naming the builtin and its expected arity.
emit_arity_violations() emit_arity_violations()
// Temporal-type violations (Instant + Instant, Duration + Int, ). // Temporal-type violations (Instant + Instant, Duration + Int, -).
emit_time_violations() emit_time_violations()
// Return empty string output was streamed via println // Return empty string - output was streamed via println
"" ""
} }