Add separate compilation: extern fn, --emit-header, elb build coordinator
This commit is contained in:
@@ -1853,6 +1853,29 @@ el_val_t fs_write_bytes(el_val_t pathv, el_val_t bytesv, el_val_t lengthv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// exec_command — run a shell command, return exit code (0 = success).
|
||||
// Used by elb and other El tooling to invoke subprocesses.
|
||||
el_val_t exec_command(el_val_t cmdv) {
|
||||
const char* cmd = EL_CSTR(cmdv);
|
||||
if (!cmd) return (el_val_t)(int64_t)-1;
|
||||
int ret = system(cmd);
|
||||
return (el_val_t)(int64_t)ret;
|
||||
}
|
||||
|
||||
// exec_capture — run a shell command, capture stdout, return as String.
|
||||
// Returns "" on failure.
|
||||
el_val_t exec_capture(el_val_t cmdv) {
|
||||
const char* cmd = EL_CSTR(cmdv);
|
||||
if (!cmd) return el_wrap_str(el_strdup(""));
|
||||
FILE* f = popen(cmd, "r");
|
||||
if (!f) return el_wrap_str(el_strdup(""));
|
||||
JsonBuf b; jb_init(&b);
|
||||
char buf[4096];
|
||||
while (fgets(buf, sizeof(buf), f)) jb_puts(&b, buf);
|
||||
pclose(f);
|
||||
return el_wrap_str(b.buf);
|
||||
}
|
||||
|
||||
el_val_t fs_list(el_val_t pathv) {
|
||||
const char* path = EL_CSTR(pathv);
|
||||
el_val_t lst = el_list_empty();
|
||||
|
||||
@@ -739,6 +739,10 @@ el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */
|
||||
/* See bottom of el_runtime.c for the implementation.
|
||||
* Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
|
||||
* No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
|
||||
/* ── Subprocess execution ────────────────────────────────────────────────── */
|
||||
el_val_t exec_command(el_val_t cmd); /* run shell command, return exit code */
|
||||
el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
||||
|
||||
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
||||
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
||||
el_val_t trace_span_start(el_val_t name);
|
||||
|
||||
+100
-55
@@ -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.
|
||||
|
||||
+106
-7
@@ -79,6 +79,74 @@ fn strip_flags(argv: [String]) -> [String] {
|
||||
return out
|
||||
}
|
||||
|
||||
// Detect --emit-header flag in argv.
|
||||
fn detect_emit_header(argv: [String]) -> Bool {
|
||||
let n: Int = native_list_len(argv)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let a: String = native_list_get(argv, i)
|
||||
if str_eq(a, "--emit-header") { return true }
|
||||
let i = i + 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Reconstruct an El type annotation string from a parsed type node.
|
||||
fn type_node_to_el(t: Map<String, Any>) -> String {
|
||||
let k: String = t["kind"]
|
||||
if str_eq(k, "Simple") { return t["name"] }
|
||||
if str_eq(k, "List") {
|
||||
let inner: String = type_node_to_el(t["inner"])
|
||||
return "[" + inner + "]"
|
||||
}
|
||||
if str_eq(k, "Map") {
|
||||
let kt: String = type_node_to_el(t["key"])
|
||||
let vt: String = type_node_to_el(t["val"])
|
||||
return "Map<" + kt + ", " + vt + ">"
|
||||
}
|
||||
"Any"
|
||||
}
|
||||
|
||||
// emit_header — write a .elh file from parsed statements.
|
||||
// Scans for FnDef nodes and emits 'extern fn' declarations.
|
||||
fn emit_header(stmts: [Map<String, Any>], hdr_path: String) -> Void {
|
||||
let n: Int = native_list_len(stmts)
|
||||
let i = 0
|
||||
let parts: [String] = native_list_empty()
|
||||
let parts = native_list_append(parts, "// auto-generated by elc --emit-header — do not edit\n")
|
||||
while i < n {
|
||||
let stmt = native_list_get(stmts, i)
|
||||
let kind: String = stmt["stmt"]
|
||||
if str_eq(kind, "FnDef") {
|
||||
let name: String = stmt["name"]
|
||||
if !str_eq(name, "main") {
|
||||
let params = stmt["params"]
|
||||
let ret_type: String = stmt["ret_type"]
|
||||
// build param list
|
||||
let np: Int = native_list_len(params)
|
||||
let pi = 0
|
||||
let param_parts: [String] = native_list_empty()
|
||||
while pi < np {
|
||||
let param = native_list_get(params, pi)
|
||||
let pname: String = param["name"]
|
||||
let ptype: String = param["type"]
|
||||
if str_eq(ptype, "") { let ptype = "Any" }
|
||||
let param_parts = native_list_append(param_parts, pname + ": " + ptype)
|
||||
let pi = pi + 1
|
||||
}
|
||||
let params_str: String = str_join(param_parts, ", ")
|
||||
let ret_str: String = ret_type
|
||||
if str_eq(ret_str, "") { let ret_str = "Any" }
|
||||
let sig: String = "extern fn " + name + "(" + params_str + ") -> " + ret_str
|
||||
let parts = native_list_append(parts, sig + "\n")
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
let content: String = str_join(parts, "")
|
||||
let ok: Bool = fs_write(hdr_path, content)
|
||||
}
|
||||
|
||||
// ── Import resolution ────────────────────────────────────────────────────────
|
||||
//
|
||||
// elc supports two forms of import:
|
||||
@@ -135,6 +203,9 @@ fn parse_import_line(trimmed: String, dir: String) -> String {
|
||||
// source text with every imported module's body inlined ahead of the entry
|
||||
// source, deduplicated by absolute path. Uses state_set to track which paths
|
||||
// have already been pulled in for this run.
|
||||
//
|
||||
// Accumulates chunks into lists and joins once at the end to avoid the O(n²)
|
||||
// memory growth caused by repeated `prefix = prefix + chunk` concatenation.
|
||||
fn resolve_imports(src_path: String) -> String {
|
||||
let seen_key: String = "__elc_imp__:" + src_path
|
||||
let already: String = state_get(seen_key)
|
||||
@@ -146,22 +217,36 @@ fn resolve_imports(src_path: String) -> String {
|
||||
let lines: [String] = str_split(source, "\n")
|
||||
let n: Int = native_list_len(lines)
|
||||
|
||||
// First pass: pull in every import body ahead of this file's body.
|
||||
let prefix: String = ""
|
||||
let body: String = ""
|
||||
// Collect chunks into lists — O(1) amortized per append.
|
||||
// Join once at the end — O(n) single pass.
|
||||
let prefix_chunks: [String] = native_list_empty()
|
||||
let body_chunks: [String] = native_list_empty()
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let line: String = native_list_get(lines, i)
|
||||
let trimmed: String = str_trim(line)
|
||||
let imp_path: String = parse_import_line(trimmed, dir)
|
||||
if !str_eq(imp_path, "") {
|
||||
let prefix = prefix + resolve_imports(imp_path)
|
||||
// Use pre-compiled header if available (separate compilation).
|
||||
// Only check .elh for imported files — never for the entry file itself.
|
||||
let imp_elh_path: String = str_slice(imp_path, 0, str_len(imp_path) - 3) + ".elh"
|
||||
let imp_elh: String = fs_read(imp_elh_path)
|
||||
if !str_eq(imp_elh, "") {
|
||||
// Header exists: mark the .el as seen (so it won't be re-inlined
|
||||
// if something else also imports it) and use the header text.
|
||||
let seen_imp_key: String = "__elc_imp__:" + imp_path
|
||||
state_set(seen_imp_key, "1")
|
||||
let prefix_chunks = native_list_append(prefix_chunks, imp_elh)
|
||||
} else {
|
||||
let imp_body: String = resolve_imports(imp_path)
|
||||
let prefix_chunks = native_list_append(prefix_chunks, imp_body)
|
||||
}
|
||||
} else {
|
||||
let body = body + line + "\n"
|
||||
let body_chunks = native_list_append(body_chunks, line + "\n")
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return prefix + body
|
||||
return str_join(prefix_chunks, "") + str_join(body_chunks, "")
|
||||
}
|
||||
|
||||
// main — CLI entry point.
|
||||
@@ -176,13 +261,27 @@ fn main() -> Void {
|
||||
// (Section 1.5 of the language spec). detect_target itself is fine
|
||||
// because the function-name position has no token-class restriction.
|
||||
let tgt: String = detect_target(argv)
|
||||
let do_emit_header: Bool = detect_emit_header(argv)
|
||||
let positional: [String] = strip_flags(argv)
|
||||
let argc: Int = native_list_len(positional)
|
||||
if argc < 1 {
|
||||
println("el-compiler: usage: elc [--target=c|js] <source.el> [<output>]")
|
||||
println("el-compiler: usage: elc [--target=c|js] [--emit-header] <source.el> [<output>]")
|
||||
exit(1)
|
||||
}
|
||||
let src_path: String = native_list_get(positional, 0)
|
||||
|
||||
// When --emit-header is requested, parse the source file directly
|
||||
// (without inlining imports) and write out a .elh file alongside the .c.
|
||||
if do_emit_header {
|
||||
let raw_source: String = fs_read(src_path)
|
||||
let hdr_tokens: [Map<String, Any>] = lex(raw_source)
|
||||
let hdr_stmts: [Map<String, Any>] = parse(hdr_tokens)
|
||||
el_release(hdr_tokens)
|
||||
let hdr_path: String = str_slice(src_path, 0, str_len(src_path) - 3) + ".elh"
|
||||
emit_header(hdr_stmts, hdr_path)
|
||||
el_release(hdr_stmts)
|
||||
}
|
||||
|
||||
let source: String = resolve_imports(src_path)
|
||||
let out: String = compile_dispatch(tgt, source)
|
||||
if argc >= 2 {
|
||||
|
||||
Reference in New Issue
Block a user