Add separate compilation: extern fn, --emit-header, elb build coordinator

This commit is contained in:
Will Anderson
2026-05-02 21:10:44 -05:00
parent 64e870c207
commit a084feb812
5 changed files with 600 additions and 62 deletions
+100 -55
View File
@@ -18,26 +18,26 @@
fn c_escape(s: String) -> String {
let chars: [String] = native_string_chars(s)
let total: Int = native_list_len(chars)
let out = ""
let parts: [String] = native_list_empty()
let i = 0
while i < total {
let ch: String = native_list_get(chars, i)
if ch == "\"" {
let out = out + "\\\""
let parts = native_list_append(parts, "\\\"")
} else {
if ch == "\\" {
let out = out + "\\\\"
let parts = native_list_append(parts, "\\\\")
} else {
if ch == "\n" {
let out = out + "\\n"
let parts = native_list_append(parts, "\\n")
} else {
if ch == "\r" {
let out = out + "\\r"
let parts = native_list_append(parts, "\\r")
} else {
if ch == "\t" {
let out = out + "\\t"
let parts = native_list_append(parts, "\\t")
} else {
let out = out + ch
let parts = native_list_append(parts, ch)
}
}
}
@@ -45,7 +45,7 @@ fn c_escape(s: String) -> String {
}
let i = i + 1
}
out
str_join(parts, "")
}
fn c_str_lit(s: String) -> String {
@@ -191,6 +191,25 @@ fn cg_expr(expr: Map<String, Any>) -> String {
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
@@ -565,17 +584,15 @@ fn cg_expr(expr: Map<String, Any>) -> String {
let arity: Int = native_list_len(args)
let func_kind: String = func["expr"]
let args_c = ""
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)
if i > 0 {
let args_c = args_c + ", "
}
let args_c = args_c + arg_c
let args_parts = native_list_append(args_parts, arg_c)
let i = i + 1
}
let args_c: String = str_join(args_parts, ", ")
if func_kind == "Ident" {
let fn_name: String = func["name"]
@@ -658,18 +675,15 @@ fn cg_expr(expr: Map<String, Any>) -> String {
// Empty literal: el_list_new(0, ) generates malformed C (trailing
// comma in a varargs call). Emit el_list_empty() directly.
if n == 0 { return "el_list_empty()" }
let items = ""
let 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)
if i > 0 {
let items = items + ", "
}
let items = items + elem_c
let items_parts = native_list_append(items_parts, elem_c)
let i = i + 1
}
return "el_list_new(" + native_int_to_str(n) + ", " + items + ")"
return "el_list_new(" + native_int_to_str(n) + ", " + str_join(items_parts, ", ") + ")"
}
if kind == "Map" {
@@ -680,20 +694,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
// shadowing inside for/while/if bodies `let acc: Map = {}`
// doesn't fail downstream cc with parse errors.
if n == 0 { return "el_map_new(0)" }
let items = ""
let 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)
if i > 0 {
let items = items + ", "
}
let items = items + c_str_lit(key) + ", " + val_c
let items_parts = native_list_append(items_parts, c_str_lit(key) + ", " + val_c)
let i = i + 1
}
return "el_map_new(" + native_int_to_str(n) + ", " + items + ")"
return "el_map_new(" + native_int_to_str(n) + ", " + str_join(items_parts, ", ") + ")"
}
if kind == "Try" {
@@ -736,7 +747,9 @@ fn cg_match(expr: Map<String, Any>) -> String {
let subj_var: String = "_match_subj_" + id
let result_var: String = "_match_result_" + id
let done_label: String = "_match_done_" + id
let out: String = "({ el_val_t " + subj_var + " = " + subj_c + "; el_val_t " + result_var + " = 0; "
// 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 {
@@ -746,19 +759,19 @@ fn cg_match(expr: Map<String, Any>) -> String {
let pkind: String = pat["pattern"]
let body_c: String = cg_expr(body)
if str_eq(pkind, "Wildcard") {
let out = out + "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
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 out = out + "{ el_val_t " + bname + " = " + subj_var + "; " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
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 out = out + "if (" + subj_var + " == " + v + ") { " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
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 out = out + "if (str_eq(" + subj_var + ", EL_STR(" + c_str_lit(v) + "))) { " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
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"]
@@ -766,10 +779,10 @@ fn cg_match(expr: Map<String, Any>) -> String {
if str_eq(v, "true") {
let bv = "1"
}
let out = out + "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 {
// unknown pattern wildcard
let out = out + "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } "
let parts = native_list_append(parts, "{ " + result_var + " = (" + body_c + "); goto " + done_label + "; } ")
}
}
}
@@ -777,8 +790,8 @@ fn cg_match(expr: Map<String, Any>) -> String {
}
let i = i + 1
}
let out = out + done_label + ":; " + result_var + "; })"
out
let parts = native_list_append(parts, done_label + ":; " + result_var + "; })")
str_join(parts, "")
}
// If-as-expression codegen
@@ -809,7 +822,8 @@ fn next_if_id() -> String {
// 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)
let out = ""
// 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)
@@ -820,20 +834,20 @@ fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
let name: String = s["name"]
let val = s["value"]
let val_c: String = cg_expr(val)
let out = out + "el_val_t " + name + " = " + val_c + "; "
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 out = out + result_var + " = (" + val_c + "); "
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 out = out + result_var + " = (" + val_c + "); "
let parts = native_list_append(parts, result_var + " = (" + val_c + "); ")
} else {
let out = out + "(void)(" + val_c + "); "
let parts = native_list_append(parts, "(void)(" + val_c + "); ")
}
} else {
if str_eq(sk, "Assign") {
@@ -844,7 +858,7 @@ fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
let aname: String = s["name"]
let aval = s["value"]
let aval_c: String = cg_expr(aval)
let out = out + aname + " = " + aval_c + "; "
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
@@ -855,7 +869,7 @@ fn cg_if_expr_arm(stmts: [Map<String, Any>], result_var: String) -> String {
}
let i = i + 1
}
out
str_join(parts, "")
}
fn cg_if_expr(expr: Map<String, Any>) -> String {
@@ -1053,6 +1067,7 @@ fn cg_stmt(stmt: Map<String, Any>, indent: String, declared: [String]) -> [Strin
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 }
declared
}
@@ -1084,14 +1099,7 @@ fn strip_outer_parens(s: String) -> String {
let i = i + 1
}
if balanced {
let inner = ""
let j = 1
while j < n - 1 {
let ch: String = native_list_get(chars, j)
let inner = inner + ch
let j = j + 1
}
return inner
return str_slice(s, 1, n - 1)
}
}
}
@@ -1168,18 +1176,15 @@ fn param_decl(param: Map<String, Any>, idx: Int) -> String {
fn params_to_c(params: [Map<String, Any>]) -> String {
let n: Int = native_list_len(params)
if n == 0 { return "void" }
let out = ""
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)
if i > 0 {
let out = out + ", "
}
let out = out + decl
let parts = native_list_append(parts, decl)
let i = i + 1
}
out
str_join(parts, ", ")
}
// Transform a function body so that an implicit-return final expression
@@ -2295,6 +2300,7 @@ fn is_top_level_decl(stmt: Map<String, Any>) -> Bool {
if kind == "EnumDef" { return true }
if kind == "Import" { return true }
if kind == "CgiBlock" { return true }
if kind == "ExternFn" { return true }
false
}
@@ -2519,6 +2525,12 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
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()
@@ -2557,6 +2569,36 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
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 {
@@ -2567,6 +2609,9 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
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.