diff --git a/el-compiler/src/codegen-js.el b/el-compiler/src/codegen-js.el index 62b4edb..b6c0b48 100644 --- a/el-compiler/src/codegen-js.el +++ b/el-compiler/src/codegen-js.el @@ -23,26 +23,26 @@ fn js_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) } } } @@ -50,7 +50,7 @@ fn js_escape(s: String) -> String { } let i = i + 1 } - out + str_join(parts, "") } fn js_str_lit(s: String) -> String { @@ -365,17 +365,15 @@ fn js_cg_expr(expr: Map) -> 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 = js_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"] @@ -426,38 +424,32 @@ fn js_cg_expr(expr: Map) -> String { let elems = expr["elems"] let n: Int = native_list_len(elems) if n == 0 { return "[]" } - 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 = js_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 "[" + items + "]" + return "[" + str_join(items_parts, ", ") + "]" } if kind == "Map" { let pairs = expr["pairs"] let n: Int = native_list_len(pairs) if n == 0 { return "{}" } - 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 = js_cg_expr(val) - if i > 0 { - let items = items + ", " - } - let items = items + js_str_lit(key) + ": " + val_c + let items_parts = native_list_append(items_parts, js_str_lit(key) + ": " + val_c) let i = i + 1 } - return "{" + items + "}" + return "{" + str_join(items_parts, ", ") + "}" } if kind == "Try" { @@ -505,7 +497,8 @@ fn js_cg_match(expr: Map) -> String { let subj_c: String = js_cg_expr(subject) let id: String = js_next_match_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 i = 0 while i < n { @@ -515,28 +508,28 @@ fn js_cg_match(expr: Map) -> String { let pkind: String = pat["pattern"] let body_c: String = js_cg_expr(body) if str_eq(pkind, "Wildcard") { - let out = out + "return (" + body_c + "); " + let parts = native_list_append(parts, "return (" + body_c + "); ") } else { if str_eq(pkind, "Binding") { 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 { if str_eq(pkind, "LitInt") { 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 { if str_eq(pkind, "LitStr") { 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 { if str_eq(pkind, "LitBool") { let v: String = pat["value"] let bv = "false" 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 { // 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 { } let i = i + 1 } - let out = out + "return null; })(" + subj_c + ")" - out + let parts = native_list_append(parts, "return null; })(" + subj_c + ")") + str_join(parts, "") } // ── Variable scope tracking ─────────────────────────────────────────────────── @@ -696,14 +689,7 @@ fn js_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) } } } @@ -759,18 +745,15 @@ fn js_cg_stmts(stmts: [Map], indent: String, declared: [String]) -> fn js_params_str(params: [Map]) -> String { let n: Int = native_list_len(params) if n == 0 { return "" } - let out = "" + let parts: [String] = native_list_empty() let i = 0 while i < n { let param = native_list_get(params, i) let name: String = param["name"] - if i > 0 { - let out = out + ", " - } - let out = out + name + let parts = native_list_append(parts, name) let i = i + 1 } - out + str_join(parts, ", ") } // Same implicit-return transform as the C backend. diff --git a/el-compiler/src/lexer.el b/el-compiler/src/lexer.el index 6a430a0..6649e91 100644 --- a/el-compiler/src/lexer.el +++ b/el-compiler/src/lexer.el @@ -146,6 +146,7 @@ fn keyword_kind(word: String) -> String { if word == "engine" { return "Engine" } if word == "accessor" { return "Accessor" } if word == "vessel" { return "Vessel" } + if word == "extern" { return "Extern" } "" } @@ -156,7 +157,7 @@ fn keyword_kind(word: String) -> String { // Returns { "text": ..., "pos": i } fn scan_digits(chars: [String], start: Int, total: Int) -> Map { let i = start - let text = "" + let parts: [String] = native_list_empty() let running = true while running { if i >= total { @@ -164,20 +165,20 @@ fn scan_digits(chars: [String], start: Int, total: Int) -> Map { } else { let ch: String = native_list_get(chars, i) if lex_is_digit(ch) { - let text = text + ch + let parts = native_list_append(parts, ch) let i = i + 1 } else { let running = false } } } - { "text": text, "pos": i } + { "text": str_join(parts, ""), "pos": i } } // scan_ident — advance i while chars[i] is alphanumeric or underscore fn scan_ident(chars: [String], start: Int, total: Int) -> Map { let i = start - let text = "" + let parts: [String] = native_list_empty() let running = true while running { if i >= total { @@ -185,14 +186,14 @@ fn scan_ident(chars: [String], start: Int, total: Int) -> Map { } else { let ch: String = native_list_get(chars, i) if is_alnum_or_underscore(ch) { - let text = text + ch + let parts = native_list_append(parts, ch) let i = i + 1 } else { let running = false } } } - { "text": text, "pos": i } + { "text": str_join(parts, ""), "pos": i } } // ── Code-bearing string detection + comment strip ──────────────────────────── @@ -253,7 +254,7 @@ fn looks_like_code(s: String) -> Bool { fn strip_code_comments(s: String) -> String { let chars: [String] = native_string_chars(s) let total: Int = native_list_len(chars) - let out = "" + let out_parts: [String] = native_list_empty() let i = 0 let in_squote = false let in_dquote = false @@ -269,11 +270,11 @@ fn strip_code_comments(s: String) -> String { if in_js_string { // Backslash escape: consume next char verbatim regardless of which. if ch == "\\" { - let out = out + ch + let out_parts = native_list_append(out_parts, ch) let next_i = i + 1 if next_i < total { 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 i = next_i + 1 } 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 i = i + 1 } @@ -308,7 +309,7 @@ fn strip_code_comments(s: String) -> String { if next_ch == "/" { // URL guard: prev char ':' means this is "://", not a comment. if prev == ":" { - let out = out + ch + let out_parts = native_list_append(out_parts, ch) let prev = ch let i = i + 1 } else { @@ -360,7 +361,7 @@ fn strip_code_comments(s: String) -> String { } let prev = "" } else { - let out = out + ch + let out_parts = native_list_append(out_parts, ch) let prev = ch let i = i + 1 } @@ -369,23 +370,23 @@ fn strip_code_comments(s: String) -> String { // Open a JS string? if ch == "'" { let in_squote = true - let out = out + ch + let out_parts = native_list_append(out_parts, ch) let prev = ch let i = i + 1 } else { if ch == "\"" { let in_dquote = true - let out = out + ch + let out_parts = native_list_append(out_parts, ch) let prev = ch let i = i + 1 } else { if ch == "`" { let in_btick = true - let out = out + ch + let out_parts = native_list_append(out_parts, ch) let prev = ch let i = i + 1 } else { - let out = out + ch + let out_parts = native_list_append(out_parts, ch) let prev = ch 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. // Starts AFTER the opening quote. Returns { "text": content, "pos": i_after_close } fn scan_string(chars: [String], start: Int, total: Int) -> Map { let i = start - let text = "" + let parts: [String] = native_list_empty() let running = true while running { if i >= total { @@ -414,26 +415,26 @@ fn scan_string(chars: [String], start: Int, total: Int) -> Map { if next_i < total { let next_ch: String = native_list_get(chars, next_i) if next_ch == "\"" { - let text = text + "\"" + let parts = native_list_append(parts, "\"") let i = next_i + 1 } else { if next_ch == "n" { - let text = text + "\n" + let parts = native_list_append(parts, "\n") let i = next_i + 1 } else { if next_ch == "t" { - let text = text + "\t" + let parts = native_list_append(parts, "\t") let i = next_i + 1 } else { if next_ch == "r" { - let text = text + "\r" + let parts = native_list_append(parts, "\r") let i = next_i + 1 } else { if next_ch == "\\" { - let text = text + "\\" + let parts = native_list_append(parts, "\\") let i = next_i + 1 } else { - let text = text + next_ch + let parts = native_list_append(parts, next_ch) let i = next_i + 1 } } @@ -448,13 +449,13 @@ fn scan_string(chars: [String], start: Int, total: Int) -> Map { let i = i + 1 let running = false } else { - let text = text + ch + let parts = native_list_append(parts, ch) let i = i + 1 } } } } - { "text": text, "pos": i } + { "text": str_join(parts, ""), "pos": i } } // ── Main lexer ──────────────────────────────────────────────────────────────── diff --git a/el-compiler/src/parser.el b/el-compiler/src/parser.el index 866fb86..e91a288 100644 --- a/el-compiler/src/parser.el +++ b/el-compiler/src/parser.el @@ -687,6 +687,29 @@ fn parse_stmt(tokens: [Map], pos: Int) -> Map { 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 if k == "Fn" { let p = pos + 1 diff --git a/elb.el b/elb.el index 40642d5..76851b8 100644 --- a/elb.el +++ b/elb.el @@ -234,7 +234,9 @@ fn compile_module(src_path: String, out_dir: String, elc_bin: String, dry_run: B 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) if dry_run { return true }