728 lines
29 KiB
EmacsLisp
728 lines
29 KiB
EmacsLisp
// 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<String, Any>] = parse(tokens)
|
|
if native_list_len(stmts) == 0 { return "" }
|
|
let first: Map<String, Any> = native_list_get(stmts, 0)
|
|
first["stmt"]
|
|
}
|
|
|
|
fn get_first_stmt(src: String) -> Map<String, Any> {
|
|
let tokens: [Any] = lex(src)
|
|
let stmts: [Map<String, Any>] = 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<String, Any> = 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<String, Any> = 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<String, Any> = 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<String, Any> = native_list_get(params, 0)
|
|
let p1: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = 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<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let first_body: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = 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<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let while_node: Map<String, Any> = 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<String, Any> = 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<String, Any> = 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<String, Any> = 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<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let expr_stmt: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
assert native_list_len(stmts) == 2, "two fn declarations parsed"
|
|
let s0: Map<String, Any> = native_list_get(stmts, 0)
|
|
let s1: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let a: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let for_node: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let ret: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let ret: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let ret: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let ret: Map<String, Any> = 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<String, Any>] = parse(tokens)
|
|
let fn_node: Map<String, Any> = native_list_get(stmts, 0)
|
|
let body = fn_node["body"]
|
|
let ret: Map<String, Any> = 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"
|
|
}
|