Archived
Add separate compilation: extern fn, --emit-header, elb build coordinator
This commit is contained in:
+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