Fix O(n²) string construction in codegen-js, lexer, parser, elb

Replace accumulate-by-concatenation loops with native_list_append + str_join.
Eliminates quadratic memory growth when processing large source files.
This is the v2 compiler state — what produced /tmp/elc-v2.
This commit is contained in:
Will Anderson
2026-05-02 22:35:49 -05:00
parent 2a211992d4
commit 3d71db4958
4 changed files with 84 additions and 75 deletions
+31 -48
View File
@@ -23,26 +23,26 @@
fn js_escape(s: String) -> String { fn js_escape(s: String) -> String {
let chars: [String] = native_string_chars(s) let chars: [String] = native_string_chars(s)
let total: Int = native_list_len(chars) let total: Int = native_list_len(chars)
let out = "" let parts: [String] = native_list_empty()
let i = 0 let i = 0
while i < total { while i < total {
let ch: String = native_list_get(chars, i) let ch: String = native_list_get(chars, i)
if ch == "\"" { if ch == "\"" {
let out = out + "\\\"" let parts = native_list_append(parts, "\\\"")
} else { } else {
if ch == "\\" { if ch == "\\" {
let out = out + "\\\\" let parts = native_list_append(parts, "\\\\")
} else { } else {
if ch == "\n" { if ch == "\n" {
let out = out + "\\n" let parts = native_list_append(parts, "\\n")
} else { } else {
if ch == "\r" { if ch == "\r" {
let out = out + "\\r" let parts = native_list_append(parts, "\\r")
} else { } else {
if ch == "\t" { if ch == "\t" {
let out = out + "\\t" let parts = native_list_append(parts, "\\t")
} else { } else {
let out = out + ch let parts = native_list_append(parts, ch)
} }
} }
} }
@@ -50,7 +50,7 @@ fn js_escape(s: String) -> String {
} }
let i = i + 1 let i = i + 1
} }
out str_join(parts, "")
} }
fn js_str_lit(s: String) -> String { fn js_str_lit(s: String) -> String {
@@ -365,17 +365,15 @@ fn js_cg_expr(expr: Map<String, Any>) -> String {
let arity: Int = native_list_len(args) let arity: Int = native_list_len(args)
let func_kind: String = func["expr"] let func_kind: String = func["expr"]
let args_c = "" let args_parts: [String] = native_list_empty()
let i = 0 let i = 0
while i < arity { while i < arity {
let arg = native_list_get(args, i) let arg = native_list_get(args, i)
let arg_c: String = js_cg_expr(arg) let arg_c: String = js_cg_expr(arg)
if i > 0 { let args_parts = native_list_append(args_parts, arg_c)
let args_c = args_c + ", "
}
let args_c = args_c + arg_c
let i = i + 1 let i = i + 1
} }
let args_c: String = str_join(args_parts, ", ")
if func_kind == "Ident" { if func_kind == "Ident" {
let fn_name: String = func["name"] let fn_name: String = func["name"]
@@ -426,38 +424,32 @@ fn js_cg_expr(expr: Map<String, Any>) -> String {
let elems = expr["elems"] let elems = expr["elems"]
let n: Int = native_list_len(elems) let n: Int = native_list_len(elems)
if n == 0 { return "[]" } if n == 0 { return "[]" }
let items = "" let items_parts: [String] = native_list_empty()
let i = 0 let i = 0
while i < n { while i < n {
let elem = native_list_get(elems, i) let elem = native_list_get(elems, i)
let elem_c: String = js_cg_expr(elem) let elem_c: String = js_cg_expr(elem)
if i > 0 { let items_parts = native_list_append(items_parts, elem_c)
let items = items + ", "
}
let items = items + elem_c
let i = i + 1 let i = i + 1
} }
return "[" + items + "]" return "[" + str_join(items_parts, ", ") + "]"
} }
if kind == "Map" { if kind == "Map" {
let pairs = expr["pairs"] let pairs = expr["pairs"]
let n: Int = native_list_len(pairs) let n: Int = native_list_len(pairs)
if n == 0 { return "{}" } if n == 0 { return "{}" }
let items = "" let items_parts: [String] = native_list_empty()
let i = 0 let i = 0
while i < n { while i < n {
let pair = native_list_get(pairs, i) let pair = native_list_get(pairs, i)
let key: String = pair["key"] let key: String = pair["key"]
let val = pair["value"] let val = pair["value"]
let val_c: String = js_cg_expr(val) let val_c: String = js_cg_expr(val)
if i > 0 { let items_parts = native_list_append(items_parts, js_str_lit(key) + ": " + val_c)
let items = items + ", "
}
let items = items + js_str_lit(key) + ": " + val_c
let i = i + 1 let i = i + 1
} }
return "{" + items + "}" return "{" + str_join(items_parts, ", ") + "}"
} }
if kind == "Try" { if kind == "Try" {
@@ -505,7 +497,8 @@ fn js_cg_match(expr: Map<String, Any>) -> String {
let subj_c: String = js_cg_expr(subject) let subj_c: String = js_cg_expr(subject)
let id: String = js_next_match_id() let id: String = js_next_match_id()
let subj_var: String = "_match_subj_" + id let subj_var: String = "_match_subj_" + id
let out: String = "((" + subj_var + ") => { " let parts: [String] = native_list_empty()
let parts = native_list_append(parts, "((" + subj_var + ") => { ")
let n: Int = native_list_len(arms) let n: Int = native_list_len(arms)
let i = 0 let i = 0
while i < n { while i < n {
@@ -515,28 +508,28 @@ fn js_cg_match(expr: Map<String, Any>) -> String {
let pkind: String = pat["pattern"] let pkind: String = pat["pattern"]
let body_c: String = js_cg_expr(body) let body_c: String = js_cg_expr(body)
if str_eq(pkind, "Wildcard") { if str_eq(pkind, "Wildcard") {
let out = out + "return (" + body_c + "); " let parts = native_list_append(parts, "return (" + body_c + "); ")
} else { } else {
if str_eq(pkind, "Binding") { if str_eq(pkind, "Binding") {
let bname: String = pat["name"] let bname: String = pat["name"]
let out = out + "{ const " + bname + " = " + subj_var + "; return (" + body_c + "); } " let parts = native_list_append(parts, "{ const " + bname + " = " + subj_var + "; return (" + body_c + "); } ")
} else { } else {
if str_eq(pkind, "LitInt") { if str_eq(pkind, "LitInt") {
let v: String = pat["value"] let v: String = pat["value"]
let out = out + "if (" + subj_var + " === " + v + ") return (" + body_c + "); " let parts = native_list_append(parts, "if (" + subj_var + " === " + v + ") return (" + body_c + "); ")
} else { } else {
if str_eq(pkind, "LitStr") { if str_eq(pkind, "LitStr") {
let v: String = pat["value"] let v: String = pat["value"]
let out = out + "if (str_eq(" + subj_var + ", " + js_str_lit(v) + ")) return (" + body_c + "); " let parts = native_list_append(parts, "if (str_eq(" + subj_var + ", " + js_str_lit(v) + ")) return (" + body_c + "); ")
} else { } else {
if str_eq(pkind, "LitBool") { if str_eq(pkind, "LitBool") {
let v: String = pat["value"] let v: String = pat["value"]
let bv = "false" let bv = "false"
if str_eq(v, "true") { let bv = "true" } if str_eq(v, "true") { let bv = "true" }
let out = out + "if (" + subj_var + " === " + bv + ") return (" + body_c + "); " let parts = native_list_append(parts, "if (" + subj_var + " === " + bv + ") return (" + body_c + "); ")
} else { } else {
// unknown pattern wildcard // unknown pattern wildcard
let out = out + "return (" + body_c + "); " let parts = native_list_append(parts, "return (" + body_c + "); ")
} }
} }
} }
@@ -544,8 +537,8 @@ fn js_cg_match(expr: Map<String, Any>) -> String {
} }
let i = i + 1 let i = i + 1
} }
let out = out + "return null; })(" + subj_c + ")" let parts = native_list_append(parts, "return null; })(" + subj_c + ")")
out str_join(parts, "")
} }
// Variable scope tracking // Variable scope tracking
@@ -696,14 +689,7 @@ fn js_strip_outer_parens(s: String) -> String {
let i = i + 1 let i = i + 1
} }
if balanced { if balanced {
let inner = "" return str_slice(s, 1, n - 1)
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
} }
} }
} }
@@ -759,18 +745,15 @@ fn js_cg_stmts(stmts: [Map<String, Any>], indent: String, declared: [String]) ->
fn js_params_str(params: [Map<String, Any>]) -> String { fn js_params_str(params: [Map<String, Any>]) -> String {
let n: Int = native_list_len(params) let n: Int = native_list_len(params)
if n == 0 { return "" } if n == 0 { return "" }
let out = "" let parts: [String] = native_list_empty()
let i = 0 let i = 0
while i < n { while i < n {
let param = native_list_get(params, i) let param = native_list_get(params, i)
let name: String = param["name"] let name: String = param["name"]
if i > 0 { let parts = native_list_append(parts, name)
let out = out + ", "
}
let out = out + name
let i = i + 1 let i = i + 1
} }
out str_join(parts, ", ")
} }
// Same implicit-return transform as the C backend. // Same implicit-return transform as the C backend.
+27 -26
View File
@@ -146,6 +146,7 @@ fn keyword_kind(word: String) -> String {
if word == "engine" { return "Engine" } if word == "engine" { return "Engine" }
if word == "accessor" { return "Accessor" } if word == "accessor" { return "Accessor" }
if word == "vessel" { return "Vessel" } if word == "vessel" { return "Vessel" }
if word == "extern" { return "Extern" }
"" ""
} }
@@ -156,7 +157,7 @@ fn keyword_kind(word: String) -> String {
// Returns { "text": ..., "pos": i } // Returns { "text": ..., "pos": i }
fn scan_digits(chars: [String], start: Int, total: Int) -> Map<String, Any> { fn scan_digits(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start let i = start
let text = "" let parts: [String] = native_list_empty()
let running = true let running = true
while running { while running {
if i >= total { if i >= total {
@@ -164,20 +165,20 @@ fn scan_digits(chars: [String], start: Int, total: Int) -> Map<String, Any> {
} else { } else {
let ch: String = native_list_get(chars, i) let ch: String = native_list_get(chars, i)
if lex_is_digit(ch) { if lex_is_digit(ch) {
let text = text + ch let parts = native_list_append(parts, ch)
let i = i + 1 let i = i + 1
} else { } else {
let running = false let running = false
} }
} }
} }
{ "text": text, "pos": i } { "text": str_join(parts, ""), "pos": i }
} }
// scan_ident advance i while chars[i] is alphanumeric or underscore // scan_ident advance i while chars[i] is alphanumeric or underscore
fn scan_ident(chars: [String], start: Int, total: Int) -> Map<String, Any> { fn scan_ident(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start let i = start
let text = "" let parts: [String] = native_list_empty()
let running = true let running = true
while running { while running {
if i >= total { if i >= total {
@@ -185,14 +186,14 @@ fn scan_ident(chars: [String], start: Int, total: Int) -> Map<String, Any> {
} else { } else {
let ch: String = native_list_get(chars, i) let ch: String = native_list_get(chars, i)
if is_alnum_or_underscore(ch) { if is_alnum_or_underscore(ch) {
let text = text + ch let parts = native_list_append(parts, ch)
let i = i + 1 let i = i + 1
} else { } else {
let running = false let running = false
} }
} }
} }
{ "text": text, "pos": i } { "text": str_join(parts, ""), "pos": i }
} }
// Code-bearing string detection + comment strip // Code-bearing string detection + comment strip
@@ -253,7 +254,7 @@ fn looks_like_code(s: String) -> Bool {
fn strip_code_comments(s: String) -> String { fn strip_code_comments(s: String) -> String {
let chars: [String] = native_string_chars(s) let chars: [String] = native_string_chars(s)
let total: Int = native_list_len(chars) let total: Int = native_list_len(chars)
let out = "" let out_parts: [String] = native_list_empty()
let i = 0 let i = 0
let in_squote = false let in_squote = false
let in_dquote = false let in_dquote = false
@@ -269,11 +270,11 @@ fn strip_code_comments(s: String) -> String {
if in_js_string { if in_js_string {
// Backslash escape: consume next char verbatim regardless of which. // Backslash escape: consume next char verbatim regardless of which.
if ch == "\\" { if ch == "\\" {
let out = out + ch let out_parts = native_list_append(out_parts, ch)
let next_i = i + 1 let next_i = i + 1
if next_i < total { if next_i < total {
let nc: String = native_list_get(chars, next_i) let nc: String = native_list_get(chars, next_i)
let out = out + nc let out_parts = native_list_append(out_parts, nc)
let prev = nc let prev = nc
let i = next_i + 1 let i = next_i + 1
} else { } else {
@@ -292,7 +293,7 @@ fn strip_code_comments(s: String) -> String {
} }
} }
} }
let out = out + ch let out_parts = native_list_append(out_parts, ch)
let prev = ch let prev = ch
let i = i + 1 let i = i + 1
} }
@@ -308,7 +309,7 @@ fn strip_code_comments(s: String) -> String {
if next_ch == "/" { if next_ch == "/" {
// URL guard: prev char ':' means this is "://", not a comment. // URL guard: prev char ':' means this is "://", not a comment.
if prev == ":" { if prev == ":" {
let out = out + ch let out_parts = native_list_append(out_parts, ch)
let prev = ch let prev = ch
let i = i + 1 let i = i + 1
} else { } else {
@@ -360,7 +361,7 @@ fn strip_code_comments(s: String) -> String {
} }
let prev = "" let prev = ""
} else { } else {
let out = out + ch let out_parts = native_list_append(out_parts, ch)
let prev = ch let prev = ch
let i = i + 1 let i = i + 1
} }
@@ -369,23 +370,23 @@ fn strip_code_comments(s: String) -> String {
// Open a JS string? // Open a JS string?
if ch == "'" { if ch == "'" {
let in_squote = true let in_squote = true
let out = out + ch let out_parts = native_list_append(out_parts, ch)
let prev = ch let prev = ch
let i = i + 1 let i = i + 1
} else { } else {
if ch == "\"" { if ch == "\"" {
let in_dquote = true let in_dquote = true
let out = out + ch let out_parts = native_list_append(out_parts, ch)
let prev = ch let prev = ch
let i = i + 1 let i = i + 1
} else { } else {
if ch == "`" { if ch == "`" {
let in_btick = true let in_btick = true
let out = out + ch let out_parts = native_list_append(out_parts, ch)
let prev = ch let prev = ch
let i = i + 1 let i = i + 1
} else { } else {
let out = out + ch let out_parts = native_list_append(out_parts, ch)
let prev = ch let prev = ch
let i = i + 1 let i = i + 1
} }
@@ -394,14 +395,14 @@ fn strip_code_comments(s: String) -> String {
} }
} }
} }
out str_join(out_parts, "")
} }
// scan_string scan a quoted string literal, handling \" escapes. // scan_string scan a quoted string literal, handling \" escapes.
// Starts AFTER the opening quote. Returns { "text": content, "pos": i_after_close } // Starts AFTER the opening quote. Returns { "text": content, "pos": i_after_close }
fn scan_string(chars: [String], start: Int, total: Int) -> Map<String, Any> { fn scan_string(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start let i = start
let text = "" let parts: [String] = native_list_empty()
let running = true let running = true
while running { while running {
if i >= total { if i >= total {
@@ -414,26 +415,26 @@ fn scan_string(chars: [String], start: Int, total: Int) -> Map<String, Any> {
if next_i < total { if next_i < total {
let next_ch: String = native_list_get(chars, next_i) let next_ch: String = native_list_get(chars, next_i)
if next_ch == "\"" { if next_ch == "\"" {
let text = text + "\"" let parts = native_list_append(parts, "\"")
let i = next_i + 1 let i = next_i + 1
} else { } else {
if next_ch == "n" { if next_ch == "n" {
let text = text + "\n" let parts = native_list_append(parts, "\n")
let i = next_i + 1 let i = next_i + 1
} else { } else {
if next_ch == "t" { if next_ch == "t" {
let text = text + "\t" let parts = native_list_append(parts, "\t")
let i = next_i + 1 let i = next_i + 1
} else { } else {
if next_ch == "r" { if next_ch == "r" {
let text = text + "\r" let parts = native_list_append(parts, "\r")
let i = next_i + 1 let i = next_i + 1
} else { } else {
if next_ch == "\\" { if next_ch == "\\" {
let text = text + "\\" let parts = native_list_append(parts, "\\")
let i = next_i + 1 let i = next_i + 1
} else { } else {
let text = text + next_ch let parts = native_list_append(parts, next_ch)
let i = next_i + 1 let i = next_i + 1
} }
} }
@@ -448,13 +449,13 @@ fn scan_string(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = i + 1 let i = i + 1
let running = false let running = false
} else { } else {
let text = text + ch let parts = native_list_append(parts, ch)
let i = i + 1 let i = i + 1
} }
} }
} }
} }
{ "text": text, "pos": i } { "text": str_join(parts, ""), "pos": i }
} }
// Main lexer // Main lexer
+23
View File
@@ -687,6 +687,29 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
return make_result({ "stmt": "Return", "value": val }, p) return make_result({ "stmt": "Return", "value": val }, p)
} }
// extern fn declaration (no body forward declaration for separate compilation)
if k == "Extern" {
let p = pos + 1
let k2: String = tok_kind(tokens, p)
if str_eq(k2, "Fn") {
let p = p + 1
let name: String = tok_value(tokens, p)
let p = p + 1
let r = parse_params(tokens, p)
let params = r["params"]
let p = r["pos"]
let ret_type = ""
let k3: String = tok_kind(tokens, p)
if str_eq(k3, "Arrow") {
let p = p + 1
let kt: String = tok_kind(tokens, p)
if str_eq(kt, "Ident") { let ret_type = tok_value(tokens, p) }
let p = skip_type(tokens, p)
}
return make_result({ "stmt": "ExternFn", "name": name, "params": params, "ret_type": ret_type }, p)
}
}
// fn definition // fn definition
if k == "Fn" { if k == "Fn" {
let p = pos + 1 let p = pos + 1
+3 -1
View File
@@ -234,7 +234,9 @@ fn compile_module(src_path: String, out_dir: String, elc_bin: String, dry_run: B
return true return true
} }
let cmd: String = elc_bin + " --emit-header " + src_path + " " + c_out // elc streams C to stdout (collect mode not yet implemented); use
// shell redirection so the output lands in the file, not the terminal.
let cmd: String = elc_bin + " --emit-header " + src_path + " > " + c_out + " 2>&1"
println(" compile " + src_path) println(" compile " + src_path)
if dry_run { return true } if dry_run { return true }