diff --git a/lang/tests/native/test_compiler.el b/lang/tests/native/test_compiler.el new file mode 100644 index 0000000..6798114 --- /dev/null +++ b/lang/tests/native/test_compiler.el @@ -0,0 +1,727 @@ +// tests/native/test_compiler.el — comprehensive tests for the El compiler pipeline. +// +// Tests the lexer (lexer.el), parser (parser.el), and codegen (codegen.el) +// through the compile() entry point in compiler.el. +// +// Compiled and run via the native test harness: +// elc --test tests/native/test_compiler.el > /tmp/el_compiler_tests.c +// gcc -O2 -I runtime /tmp/el_compiler_tests.c runtime/el_runtime.c -lcurl -lpthread -lm -o /tmp/el_compiler_tests +// /tmp/el_compiler_tests + +import "../../el-compiler/src/lexer.el" +import "../../el-compiler/src/parser.el" +import "../../el-compiler/src/codegen.el" +import "../../el-compiler/src/codegen-js.el" +import "../../el-compiler/src/compiler.el" + +// ── Lexer helpers ───────────────────────────────────────────────────────────── + +fn tok_count(tokens: [Any]) -> Int { + native_list_len(tokens) / 2 +} + +// ── Codegen helper: capture compile() stdout to a string ───────────────────── + +fn compile_capture(src: String) -> String { + let tmp: String = "/tmp/el_compiler_test_" + int_to_str(time_now()) + ".c" + stdout_to_file(tmp) + compile(src) + stdout_restore() + fs_read(tmp) +} + +// ── Lexer tests ─────────────────────────────────────────────────────────────── + +test "lex-empty" { + let tokens: [Any] = lex("") + assert tok_count(tokens) == 1, "empty source yields only Eof" + assert tok_kind(tokens, 0) == "Eof", "single token is Eof" +} + +test "lex-whitespace-stripped" { + let tokens: [Any] = lex(" \t\n\r ") + assert tok_count(tokens) == 1, "whitespace-only yields only Eof" +} + +test "lex-comment-stripped" { + let tokens: [Any] = lex("// this is a comment\n// another") + assert tok_count(tokens) == 1, "comments stripped — only Eof" +} + +test "lex-int-literals" { + let tokens: [Any] = lex("0 1 42 100 999") + assert tok_count(tokens) == 6, "five int literals + Eof" + assert tok_kind(tokens, 0) == "Int", "first is Int" + assert tok_value(tokens, 0) == "0", "value is 0" + assert tok_kind(tokens, 2) == "Int", "third is Int" + assert tok_value(tokens, 2) == "42", "value is 42" + assert tok_kind(tokens, 4) == "Int", "fifth is Int" + assert tok_value(tokens, 4) == "999", "value is 999" +} + +test "lex-float-literals" { + let tokens: [Any] = lex("3.14 0.0 1.5") + assert tok_count(tokens) == 4, "three float literals + Eof" + assert tok_kind(tokens, 0) == "Float", "first is Float" + assert tok_value(tokens, 0) == "3.14", "value is 3.14" + assert tok_kind(tokens, 1) == "Float", "second is Float" + assert tok_value(tokens, 1) == "0.0", "value is 0.0" +} + +test "lex-string-literals" { + let tokens: [Any] = lex("\"hello\" \"world\" \"\"") + assert tok_count(tokens) == 4, "three string literals + Eof" + assert tok_kind(tokens, 0) == "Str", "first is Str" + assert tok_value(tokens, 0) == "hello", "value is hello" + assert tok_kind(tokens, 2) == "Str", "third is Str" + assert tok_value(tokens, 2) == "", "empty string value is empty" +} + +test "lex-string-escape-newline" { + let tokens: [Any] = lex("\"hello\\nworld\"") + assert tok_count(tokens) == 2, "one Str token + Eof" + assert tok_kind(tokens, 0) == "Str", "is Str" + let val: String = tok_value(tokens, 0) + assert str_contains(val, "hello"), "value contains hello" + assert str_contains(val, "world"), "value contains world" + assert str_len(val) == 11, "hello + newline + world = 11 chars" +} + +test "lex-string-escape-tab" { + let tokens: [Any] = lex("\"a\\tb\"") + assert tok_count(tokens) == 2, "one Str + Eof" + let val: String = tok_value(tokens, 0) + assert str_len(val) == 3, "a + tab + b = 3 chars" +} + +test "lex-string-escape-backslash" { + let tokens: [Any] = lex("\"a\\\\b\"") + assert tok_count(tokens) == 2, "one Str + Eof" + let val: String = tok_value(tokens, 0) + assert str_len(val) == 3, "a + backslash + b = 3 chars" +} + +test "lex-bool-literals" { + let tokens: [Any] = lex("true false") + assert tok_count(tokens) == 3, "two Bool tokens + Eof" + assert tok_kind(tokens, 0) == "Bool", "first is Bool" + assert tok_value(tokens, 0) == "true", "first is true" + assert tok_kind(tokens, 1) == "Bool", "second is Bool" + assert tok_value(tokens, 1) == "false", "second is false" +} + +test "lex-identifier" { + let tokens: [Any] = lex("foo bar _under _123") + assert tok_count(tokens) == 5, "four idents + Eof" + assert tok_kind(tokens, 0) == "Ident", "foo is Ident" + assert tok_value(tokens, 0) == "foo", "value is foo" + assert tok_kind(tokens, 2) == "Ident", "underscore ident recognized" + assert tok_value(tokens, 2) == "_under", "value is _under" +} + +test "lex-keywords" { + let tokens: [Any] = lex("let fn if else while for return import type enum match") + assert tok_count(tokens) == 12, "eleven keywords + Eof" + assert tok_kind(tokens, 0) == "Let", "let keyword" + assert tok_kind(tokens, 1) == "Fn", "fn keyword" + assert tok_kind(tokens, 2) == "If", "if keyword" + assert tok_kind(tokens, 3) == "Else", "else keyword" + assert tok_kind(tokens, 4) == "While", "while keyword" + assert tok_kind(tokens, 5) == "For", "for keyword" + assert tok_kind(tokens, 6) == "Return", "return keyword" + assert tok_kind(tokens, 7) == "Import", "import keyword" + assert tok_kind(tokens, 8) == "Type", "type keyword" + assert tok_kind(tokens, 9) == "Enum", "enum keyword" + assert tok_kind(tokens, 10) == "Match", "match keyword" +} + +test "lex-more-keywords" { + let tokens: [Any] = lex("extern break continue") + assert tok_count(tokens) == 4, "three keywords + Eof" + assert tok_kind(tokens, 0) == "Extern", "extern keyword" + assert tok_kind(tokens, 1) == "Break", "break keyword" + assert tok_kind(tokens, 2) == "Continue", "continue keyword" +} + +test "lex-keyword-values" { + let tokens: [Any] = lex("let fn return") + assert tok_value(tokens, 0) == "let", "let value is let" + assert tok_value(tokens, 1) == "fn", "fn value is fn" + assert tok_value(tokens, 2) == "return", "return value is return" +} + +test "lex-arithmetic-operators" { + let tokens: [Any] = lex("+ - * / %") + assert tok_count(tokens) == 6, "five ops + Eof" + assert tok_kind(tokens, 0) == "Plus", "plus" + assert tok_kind(tokens, 1) == "Minus", "minus" + assert tok_kind(tokens, 2) == "Star", "star" + assert tok_kind(tokens, 3) == "Slash", "slash" + assert tok_kind(tokens, 4) == "Percent", "percent" +} + +test "lex-comparison-operators" { + let tokens: [Any] = lex("== != < > <= >=") + assert tok_count(tokens) == 7, "six ops + Eof" + assert tok_kind(tokens, 0) == "EqEq", "eqeq" + assert tok_value(tokens, 0) == "==", "eqeq value" + assert tok_kind(tokens, 1) == "NotEq", "noteq" + assert tok_kind(tokens, 2) == "Lt", "lt" + assert tok_kind(tokens, 3) == "Gt", "gt" + assert tok_kind(tokens, 4) == "LtEq", "lteq" + assert tok_kind(tokens, 5) == "GtEq", "gteq" +} + +test "lex-logical-operators" { + let tokens: [Any] = lex("&& || !") + assert tok_count(tokens) == 4, "three logical ops + Eof" + assert tok_kind(tokens, 0) == "And", "and" + assert tok_value(tokens, 0) == "&&", "and value" + assert tok_kind(tokens, 1) == "Or", "or" + assert tok_kind(tokens, 2) == "Not", "not" +} + +test "lex-arrow-tokens" { + let tokens: [Any] = lex("-> =>") + assert tok_count(tokens) == 3, "arrow + fat-arrow + Eof" + assert tok_kind(tokens, 0) == "Arrow", "thin arrow" + assert tok_value(tokens, 0) == "->", "arrow value" + assert tok_kind(tokens, 1) == "FatArrow", "fat arrow" +} + +test "lex-delimiters" { + let tokens: [Any] = lex("( ) [ ] { } , : ; .") + assert tok_count(tokens) == 11, "ten delimiters + Eof" + assert tok_kind(tokens, 0) == "LParen", "lparen" + assert tok_kind(tokens, 1) == "RParen", "rparen" + assert tok_kind(tokens, 2) == "LBracket", "lbracket" + assert tok_kind(tokens, 3) == "RBracket", "rbracket" + assert tok_kind(tokens, 4) == "LBrace", "lbrace" + assert tok_kind(tokens, 5) == "RBrace", "rbrace" + assert tok_kind(tokens, 6) == "Comma", "comma" + assert tok_kind(tokens, 7) == "Colon", "colon" + assert tok_kind(tokens, 8) == "Semicolon", "semicolon" + assert tok_kind(tokens, 9) == "Dot", "dot" +} + +test "lex-double-colon" { + let tokens: [Any] = lex("::") + assert tok_count(tokens) == 2, "colons + Eof" + assert tok_kind(tokens, 0) == "ColonColon", "double colon" + assert tok_value(tokens, 0) == "::", "double colon value" +} + +test "lex-dot-dot" { + let tokens: [Any] = lex(".. ..=") + assert tok_count(tokens) == 3, "two range tokens + Eof" + assert tok_kind(tokens, 0) == "DotDot", "dotdot" + assert tok_kind(tokens, 1) == "DotDotEq", "dotdoteq" +} + +test "lex-pipe-operators" { + let tokens: [Any] = lex("| || |>") + assert tok_count(tokens) == 4, "three pipe tokens + Eof" + assert tok_kind(tokens, 0) == "Pipe", "pipe" + assert tok_kind(tokens, 1) == "Or", "or" + assert tok_kind(tokens, 2) == "PipeOp", "pipe-op" +} + +test "lex-at-and-question" { + let tokens: [Any] = lex("@ ?") + assert tok_count(tokens) == 3, "at + question + Eof" + assert tok_kind(tokens, 0) == "At", "at sign" + assert tok_kind(tokens, 1) == "QuestionMark", "question mark" +} + +test "lex-eof-always-last" { + let t1: [Any] = lex("x") + let t2: [Any] = lex("let x = 1") + let t3: [Any] = lex("") + let n1: Int = tok_count(t1) + let n2: Int = tok_count(t2) + let n3: Int = tok_count(t3) + assert tok_kind(t1, n1 - 1) == "Eof", "eof last after single ident" + assert tok_kind(t2, n2 - 1) == "Eof", "eof last after let stmt" + assert tok_kind(t3, n3 - 1) == "Eof", "eof last after empty" +} + +test "lex-string-with-spaces" { + let tokens: [Any] = lex("\"hello world\"") + assert tok_count(tokens) == 2, "string with space: 1 Str + Eof" + assert tok_value(tokens, 0) == "hello world", "internal space preserved" +} + +test "lex-multiline-source" { + let src: String = "let x: Int = 1\nlet y: Int = 2\n" + let tokens: [Any] = lex(src) + assert tok_count(tokens) > 5, "multiline source produces multiple tokens" + assert tok_kind(tokens, 0) == "Let", "first token is Let" +} + +test "lex-flat-stride-2-layout" { + // Verify that the flat stride-2 layout: token i has kind at index 2*i, value at 2*i+1 + let tokens: [Any] = lex("fn foo") + // tokens[0] = "Fn", tokens[1] = "fn", tokens[2] = "Ident", tokens[3] = "foo", ... + let raw_len: Int = native_list_len(tokens) + assert raw_len == 6, "fn + foo + Eof = 3 tokens = 6 raw entries" + let kind0: String = native_list_get(tokens, 0) + let val0: String = native_list_get(tokens, 1) + let kind1: String = native_list_get(tokens, 2) + let val1: String = native_list_get(tokens, 3) + assert kind0 == "Fn", "raw[0] is Fn kind" + assert val0 == "fn", "raw[1] is fn value" + assert kind1 == "Ident", "raw[2] is Ident kind" + assert val1 == "foo", "raw[3] is foo value" +} + +// ── Parser tests ────────────────────────────────────────────────────────────── + +fn get_first_stmt_kind(src: String) -> String { + let tokens: [Any] = lex(src) + let stmts: [Map] = parse(tokens) + if native_list_len(stmts) == 0 { return "" } + let first: Map = native_list_get(stmts, 0) + first["stmt"] +} + +fn get_first_stmt(src: String) -> Map { + let tokens: [Any] = lex(src) + let stmts: [Map] = parse(tokens) + native_list_get(stmts, 0) +} + +test "parse-let-stmt" { + assert get_first_stmt_kind("let x: Int = 5") == "Let", "let int stmt" + assert get_first_stmt_kind("let s: String = \"hi\"") == "Let", "let string stmt" + assert get_first_stmt_kind("let b: Bool = true") == "Let", "let bool stmt" + let stmt: Map = get_first_stmt("let x: Int = 42") + let name: String = stmt["name"] + assert name == "x", "let name is x" +} + +test "parse-fn-decl" { + assert get_first_stmt_kind("fn foo() -> Void { }") == "FnDef", "fn declaration" + assert get_first_stmt_kind("fn bar(x: Int) -> Int { return x }") == "FnDef", "fn with param" + let stmt: Map = get_first_stmt("fn foo() -> Void { }") + let name: String = stmt["name"] + assert name == "foo", "fn name is foo" +} + +test "parse-fn-params" { + let stmt: Map = get_first_stmt("fn bar(x: Int, y: String) -> Int { return 0 }") + let params = stmt["params"] + let n: Int = native_list_len(params) + assert n == 2, "fn has 2 params" + let p0: Map = native_list_get(params, 0) + let p1: Map = native_list_get(params, 1) + assert p0["name"] == "x", "first param name x" + assert p1["name"] == "y", "second param name y" +} + +test "parse-return-stmt" { + let tokens: [Any] = lex("fn f() -> Int { return 42 }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let n: Int = native_list_len(body) + assert n > 0, "fn body non-empty" + let ret: Map = native_list_get(body, 0) + assert ret["stmt"] == "Return", "return stmt kind" +} + +test "parse-if-stmt" { + // In El, `if` is an expression. Standalone `if` in a fn body is wrapped + // as Expr stmt with value.expr == "If". + let tokens: [Any] = lex("fn f() -> Int { if x > 0 { return 1 } return 0 }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let first_body: Map = native_list_get(body, 0) + assert first_body["stmt"] == "Expr", "if stmt in fn body is Expr wrapper" + let val = first_body["value"] + assert val["expr"] == "If", "Expr wraps If expression" +} + +test "parse-if-else" { + let tokens: [Any] = lex("fn f() -> Int { if x > 0 { return 1 } else { return 0 } }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + // if-else is also an Expr stmt wrapping an If expression + let expr_stmt: Map = native_list_get(body, 0) + assert expr_stmt["stmt"] == "Expr", "if-else is Expr stmt" + let if_node = expr_stmt["value"] + assert if_node["expr"] == "If", "Expr wraps If expression" + let has_else: Bool = if_node["has_else"] + assert has_else, "if-else has else branch" +} + +test "parse-while-stmt" { + let tokens: [Any] = lex("fn f() -> Void { while i < 10 { i = i + 1 } }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let while_node: Map = native_list_get(body, 0) + assert while_node["stmt"] == "While", "while stmt kind" +} + +test "parse-import-stmt" { + assert get_first_stmt_kind("import \"some/module.el\"") == "Import", "import stmt" +} + +test "parse-extern-fn" { + assert get_first_stmt_kind("extern fn native_op(x: Int) -> Int") == "ExternFn", "extern fn" +} + +test "parse-let-int-value" { + let stmt: Map = get_first_stmt("let n: Int = 99") + let val = stmt["value"] + let val_kind: String = val["expr"] + assert val_kind == "Int", "let value is Int expr" + let v: String = val["value"] + assert v == "99", "int literal value 99" +} + +test "parse-let-string-value" { + let stmt: Map = get_first_stmt("let s: String = \"hello\"") + let val = stmt["value"] + let val_kind: String = val["expr"] + assert val_kind == "Str", "let value is Str expr" + let v: String = val["value"] + assert v == "hello", "string literal value hello" +} + +test "parse-let-bool-value" { + let stmt: Map = get_first_stmt("let b: Bool = true") + let val = stmt["value"] + let val_kind: String = val["expr"] + assert val_kind == "Bool", "let value is Bool expr" +} + +test "parse-binop-expr" { + let stmt: Map = get_first_stmt("let x: Int = 1 + 2") + let val = stmt["value"] + let val_kind: String = val["expr"] + assert val_kind == "BinOp", "let value is BinOp" + let op: String = val["op"] + assert op == "Plus", "binop is Plus" +} + +test "parse-call-expr" { + let tokens: [Any] = lex("fn f() -> Void { println(\"hi\") }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let expr_stmt: Map = native_list_get(body, 0) + assert expr_stmt["stmt"] == "Expr", "call is Expr stmt" + let val = expr_stmt["value"] + let val_kind: String = val["expr"] + assert val_kind == "Call", "expr is Call" +} + +test "parse-multiple-fns" { + let src: String = "fn a() -> Int { return 1 }\nfn b() -> Int { return 2 }" + let tokens: [Any] = lex(src) + let stmts: [Map] = parse(tokens) + assert native_list_len(stmts) == 2, "two fn declarations parsed" + let s0: Map = native_list_get(stmts, 0) + let s1: Map = native_list_get(stmts, 1) + assert s0["name"] == "a", "first fn name a" + assert s1["name"] == "b", "second fn name b" +} + +test "parse-assign-stmt" { + let tokens: [Any] = lex("fn f() -> Void { x = 42 }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let a: Map = native_list_get(body, 0) + assert a["stmt"] == "Assign", "assign stmt kind" + assert a["name"] == "x", "assign target x" +} + +test "parse-for-stmt" { + let tokens: [Any] = lex("fn f() -> Void { for x in items { println(x) } }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let for_node: Map = native_list_get(body, 0) + assert for_node["stmt"] == "For", "for stmt kind" + assert for_node["item"] == "x", "for item is x" +} + +test "parse-unary-not" { + let tokens: [Any] = lex("fn f() -> Bool { return !x }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let ret: Map = native_list_get(body, 0) + let val = ret["value"] + assert val["expr"] == "Not", "unary not is Not expr" +} + +test "parse-unary-neg" { + let tokens: [Any] = lex("fn f() -> Int { return -5 }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let ret: Map = native_list_get(body, 0) + let val = ret["value"] + assert val["expr"] == "Neg", "unary minus is Neg expr" +} + +test "parse-array-literal" { + let tokens: [Any] = lex("fn f() -> [Int] { return [1, 2, 3] }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let ret: Map = native_list_get(body, 0) + let val = ret["value"] + assert val["expr"] == "Array", "array literal is Array expr" + let elems = val["elems"] + assert native_list_len(elems) == 3, "array has 3 elements" +} + +test "parse-empty-array" { + let tokens: [Any] = lex("fn f() -> [Int] { return [] }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let ret: Map = native_list_get(body, 0) + let val = ret["value"] + assert val["expr"] == "Array", "empty array is Array expr" + let elems = val["elems"] + assert native_list_len(elems) == 0, "empty array has 0 elements" +} + +test "parse-index-expr" { + let tokens: [Any] = lex("fn f() -> Any { return arr[0] }") + let stmts: [Map] = parse(tokens) + let fn_node: Map = native_list_get(stmts, 0) + let body = fn_node["body"] + let ret: Map = native_list_get(body, 0) + let val = ret["value"] + assert val["expr"] == "Index", "array index is Index expr" +} + +// ── Codegen tests ───────────────────────────────────────────────────────────── + +test "codegen-includes" { + let out: String = compile_capture("fn main() -> Void { }") + assert str_contains(out, "#include"), "output has #include" + assert str_contains(out, "el_runtime.h"), "output includes el_runtime.h" +} + +test "codegen-int-main" { + let out: String = compile_capture("fn main() -> Void { }") + assert str_contains(out, "int main("), "output has int main()" +} + +test "codegen-runtime-init" { + let out: String = compile_capture("fn main() -> Void { }") + assert str_contains(out, "el_runtime_init_args("), "runtime init in main" +} + +test "codegen-void-function-signature" { + let out: String = compile_capture("fn f() -> Int { return 0 }") + assert str_contains(out, "f(void)"), "no-param fn uses void signature" +} + +test "codegen-function-with-params" { + let out: String = compile_capture("fn add(x: Int, y: Int) -> Int { return x + y }") + assert str_contains(out, "add("), "function add in output" + assert str_contains(out, "el_val_t x"), "param x in output" + assert str_contains(out, "el_val_t y"), "param y in output" +} + +test "codegen-int-literal" { + let out: String = compile_capture("fn answer() -> Int { return 42 }") + assert str_contains(out, "42"), "integer literal 42 in output" + assert str_contains(out, "return"), "return statement in output" +} + +test "codegen-string-literal" { + let out: String = compile_capture("fn greet() -> String { return \"hello\" }") + assert str_contains(out, "hello"), "string literal hello in output" +} + +test "codegen-if-statement" { + let src: String = "fn check(x: Int) -> Int { if x > 0 { return 1 } return 0 }" + let out: String = compile_capture(src) + assert str_contains(out, "if ("), "if statement in C output" +} + +test "codegen-if-else" { + let src: String = "fn check(x: Int) -> Int { if x > 0 { return 1 } else { return 0 } }" + let out: String = compile_capture(src) + assert str_contains(out, "if ("), "if in output" + assert str_contains(out, "} else {"), "else branch in output" +} + +test "codegen-while-loop" { + let src: String = "fn f() -> Int { let i: Int = 0 while i < 10 { i = i + 1 } return i }" + let out: String = compile_capture(src) + assert str_contains(out, "while ("), "while loop in C output" +} + +test "codegen-let-binding" { + let src: String = "fn f() -> Int { let n: Int = 5 return n }" + let out: String = compile_capture(src) + assert str_contains(out, "el_val_t n"), "let binding in output" +} + +test "codegen-function-call" { + let src: String = "fn f() -> Void { println(\"hi\") }" + let out: String = compile_capture(src) + assert str_contains(out, "println("), "function call in output" +} + +test "codegen-string-concat" { + let src: String = "fn f() -> String { let a: String = \"x\" let b: String = \"y\" return a + b }" + let out: String = compile_capture(src) + assert str_contains(out, "el_str_concat"), "string concat uses el_str_concat" +} + +test "codegen-int-arithmetic" { + let src: String = "fn f(x: Int, y: Int) -> Int { return x + y }" + let out: String = compile_capture(src) + assert !str_contains(out, "el_str_concat(x"), "int add does not use el_str_concat" +} + +test "codegen-comparison" { + let src: String = "fn f(x: Int) -> Bool { return x > 0 }" + let out: String = compile_capture(src) + assert str_contains(out, ">"), "comparison in output" +} + +test "codegen-string-equality" { + let src: String = "fn f(s: String) -> Bool { return s == \"hello\" }" + let out: String = compile_capture(src) + assert str_contains(out, "str_eq("), "string equality uses str_eq" +} + +test "codegen-logical-and" { + let src: String = "fn f(a: Bool, b: Bool) -> Bool { return a && b }" + let out: String = compile_capture(src) + assert str_contains(out, "&&"), "logical and in output" +} + +test "codegen-logical-or" { + let src: String = "fn f(a: Bool, b: Bool) -> Bool { return a || b }" + let out: String = compile_capture(src) + assert str_contains(out, "||"), "logical or in output" +} + +test "codegen-unary-not" { + let src: String = "fn f(b: Bool) -> Bool { return !b }" + let out: String = compile_capture(src) + assert str_contains(out, "!"), "unary not in output" +} + +test "codegen-string-escape-in-c" { + let src: String = "fn msg() -> String { return \"hello\\nworld\\t!\" }" + let out: String = compile_capture(src) + assert str_contains(out, "\\n"), "newline escape in C output" + assert str_contains(out, "\\t"), "tab escape in C output" +} + +test "codegen-many-functions" { + // Multiple functions — exercises streaming loop + per-function arena scoping + let src: String = "fn a() -> Int { return 1 }\nfn b() -> Int { return 2 }\nfn c() -> Int { return 3 }\nfn d() -> Int { return 4 }\nfn e() -> Int { return 5 }" + let out: String = compile_capture(src) + assert str_contains(out, "el_val_t a("), "function a in output" + assert str_contains(out, "el_val_t b("), "function b in output" + assert str_contains(out, "el_val_t c("), "function c in output" + assert str_contains(out, "el_val_t d("), "function d in output" + assert str_contains(out, "el_val_t e("), "function e in output" +} + +test "codegen-deep-expression" { + // Deeply nested arithmetic — exercises recursive cg_expr + per-statement arena + let src: String = "fn deep() -> Int { return 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 }" + let out: String = compile_capture(src) + assert str_contains(out, "return"), "deep expr: return present" + assert str_contains(out, "8"), "deep expr: literal 8 present" +} + +test "codegen-forward-declarations" { + // Functions should have forward declarations before definitions + let src: String = "fn b() -> Int { return a() }\nfn a() -> Int { return 1 }" + let out: String = compile_capture(src) + assert str_contains(out, "el_val_t a("), "function a in output" + assert str_contains(out, "el_val_t b("), "function b in output" +} + +test "codegen-for-loop" { + let src: String = "fn f() -> Void { let items: [Int] = native_list_empty() for item in items { println(item) } }" + let out: String = compile_capture(src) + assert str_contains(out, "for ("), "for loop in C output" + assert str_contains(out, "el_list_get("), "for loop uses el_list_get" +} + +test "codegen-extern-fn" { + let src: String = "extern fn my_native(x: Int) -> Int\nfn use_it() -> Int { return my_native(1) }" + let out: String = compile_capture(src) + assert str_contains(out, "my_native("), "extern fn referenced in output" +} + +test "codegen-nested-calls" { + let src: String = "fn f() -> String { return str_concat(int_to_str(42), \" ok\") }" + let out: String = compile_capture(src) + assert str_contains(out, "str_concat"), "nested calls: str_concat in output" + assert str_contains(out, "int_to_str"), "nested calls: int_to_str in output" +} + +// ── Self-host / smoke tests ─────────────────────────────────────────────────── + +test "compiler-minimal-program" { + let src: String = "fn main() -> Void { println(\"ok\") }" + let out: String = compile_capture(src) + assert str_contains(out, "#include"), "has #include" + assert str_contains(out, "int main("), "has int main()" + assert str_contains(out, "println("), "calls println" + assert str_contains(out, "el_runtime.h"), "links el_runtime.h" +} + +test "compiler-pure-library" { + // No fn main = library mode: codegen_streaming returns before emitting main() + let src: String = "fn helper(x: Int) -> Int { return x + 1 }" + let out: String = compile_capture(src) + assert !str_contains(out, "int main("), "library: no int main" + assert str_contains(out, "#include"), "library: has includes" + assert str_contains(out, "helper("), "library: helper function present" +} + +test "compiler-multiple-fns-with-main" { + let src: String = "fn greet(name: String) -> String { return \"Hello \" + name }\nfn main() -> Void { println(greet(\"world\")) }" + let out: String = compile_capture(src) + assert str_contains(out, "greet("), "greet in output" + assert str_contains(out, "int main("), "main in output" + assert str_contains(out, "println("), "println in output" +} + +test "compiler-let-in-main" { + let src: String = "fn main() -> Void { let x: Int = 42 println(int_to_str(x)) }" + let out: String = compile_capture(src) + assert str_contains(out, "el_val_t x"), "let binding x in output" + assert str_contains(out, "42"), "literal 42 in output" +} + +test "compiler-string-concat-chain" { + let src: String = "fn f() -> String { let a: String = \"x\" let b: String = \"y\" let c: String = \"z\" return a + b + c }" + let out: String = compile_capture(src) + assert str_contains(out, "el_str_concat"), "string chain uses el_str_concat" +} + +test "compiler-negative-literal" { + let src: String = "fn f() -> Int { return -42 }" + let out: String = compile_capture(src) + assert str_contains(out, "42"), "negative literal value in output" +} + +test "compiler-stdint-include" { + // The generated C should include stdint.h for int64_t + let src: String = "fn f() -> Int { return 0 }" + let out: String = compile_capture(src) + assert str_contains(out, "stdint.h"), "output includes stdint.h" +}