6c975b1d50
The module question ended with a limit: textual inlining destroys file
provenance, so a duplicate-definition message could name the symbol but not the
files. Threading it exposed a bigger absence first.
TOKENS HAD NO POSITION AT ALL. A token was a flat (kind, value) pair, so NO
diagnostic in El could name a place -- every error named a symbol and never a
line. That is the prerequisite the module question was resting on.
THE CHAIN, end to end
lexer counts newlines; tok_append mints (kind, value, line)
parser stride 2 -> 3; tok_line added; FnDef carries its line
codegen records <fn> defines_at:<line>
resolve_imports publishes <file> spans <start> <end> for the combined source
checker maps a combined line back to file:line-within-that-file
duplicate definition: 'helper' is defined 2 times — El has no namespacing,
so imported modules share one global scope
/tmp/modtest/a.el:1
/tmp/modtest/b.el:1
PREDICTIONS AND RESULTS
P1 15 stride sites, encapsulated in tok_kind/tok_value TRUE, but see below
P2 adding a line field is mechanical TRUE
P3 the lexer must count newlines TRUE
P4 resolve_imports can record per-file line ranges TRUE
P5 the message can then name both files TRUE
P6 token memory grows TRUE, 25.0 -> 33.9 MB (+36%)
FOUR DEFECTS, EACH FOUND BY RUNNING AND NOT BY READING
1. interp_tokens_append_all walks the token list DIRECTLY with its own copy of
the stride. Gen1 built fine and gen2 emitted corrupt C, because the
compiler's own source uses string interpolation. My search missed it because
I grepped for the variable name `tokens`; it is called `dst`/`result`.
Searching by name instead of by shape -- third time today.
2. tok_count in test_compiler.el carried the stride too. I had scoped the search
to compiler sources and it had escaped into the tests.
3. Nested resolve_imports calls accumulated spans into shared state, so each
republished meaningless line ranges under the parent's name. Making the
buffer local fixed it; guarding the WRITE did not, which is what I tried
first.
4. The first working version reported b.el:3 -- the COMBINED line against a
filename that has no line 3. A file:line that does not match the file is
worse than no line at all.
105/105 native, 37/37 integration, fixpoint ok, compiler self-checks clean.
998 lines
42 KiB
EmacsLisp
998 lines
42 KiB
EmacsLisp
import "../../runtime/eltest.el"
|
|
// 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 {
|
|
// A token is (kind, value, line). This helper carried its own copy of the
|
|
// stride, so it escaped a search scoped to the compiler sources.
|
|
native_list_len(tokens) / 3
|
|
}
|
|
|
|
// ── 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-3-layout" {
|
|
// A token is (kind, value, line): token i has kind at 3*i, value at 3*i+1,
|
|
// line at 3*i+2. Before 2026-08-17 a token carried no position at all, so
|
|
// no diagnostic in El could name a place.
|
|
let tokens: [Any] = lex("fn foo")
|
|
let raw_len: Int = native_list_len(tokens)
|
|
assert raw_len == 9, "fn + foo + Eof = 3 tokens = 9 raw entries"
|
|
assert native_list_get(tokens, 0) == "Fn", "raw[0] is the kind"
|
|
assert native_list_get(tokens, 1) == "fn", "raw[1] is the value"
|
|
assert native_list_get(tokens, 2) == "1", "raw[2] is the line"
|
|
assert native_list_get(tokens, 3) == "Ident", "raw[3] is the next kind"
|
|
assert native_list_get(tokens, 5) == "1", "still line 1"
|
|
}
|
|
|
|
test "lexer-tracks-line-numbers" {
|
|
let tokens: [Any] = lex("fn a\nfn b\nfn c")
|
|
assert tok_line(tokens, 0) == "1", "first fn is on line 1"
|
|
assert tok_line(tokens, 2) == "2", "second fn is on line 2"
|
|
assert tok_line(tokens, 4) == "3", "third fn is on line 3"
|
|
}
|
|
|
|
|
|
// ── 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"
|
|
}
|
|
|
|
// ── Decorator seam: boundary-beat attribution ────────────────────────────────
|
|
//
|
|
// The beat carries the CONSTRUCT that caused it, not only the fn that beat.
|
|
// Without the second argument the graph accumulates boundary events with no
|
|
// way to attribute them to the decorator responsible, so no construct can ever
|
|
// be measured and "is this decorator earning its keep" stays an argument
|
|
// instead of a query.
|
|
|
|
|
|
|
|
test "decorator-undecorated-fn-has-no-beat" {
|
|
let src: String = "fn f() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert !str_contains(out, "engram_boundary_beat"), "an undecorated fn does not beat"
|
|
}
|
|
|
|
// ── Decorator seam: the twelve inert names ───────────────────────────────────
|
|
//
|
|
// PINS A KNOWN DEFECT. codegen calls fn_has_decorator for exactly three names
|
|
// (manager, accessor, route). Twelve others parse, attach as {name,args}, and
|
|
// compile to nothing — including four that look like protection:
|
|
// @authenticate (6 uses), @authorize (3), @rate_limit (3), @validate (2).
|
|
//
|
|
// This test asserts the CURRENT behaviour so that fixing it is a visible
|
|
// change rather than a silent one. When a pass wires or rejects these, this
|
|
// test flips and that flip is the proof.
|
|
|
|
test "decorator-authenticate-compiles-to-nothing" {
|
|
let src: String = "@authenticate\nfn f() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
let bare: String = compile_capture("fn f() -> Int { return 1 }")
|
|
assert str_eq(out, bare), "KNOWN DEFECT: @authenticate emits identical C to no decorator at all"
|
|
}
|
|
|
|
// ── Declared constructs ──────────────────────────────────────────────────────
|
|
//
|
|
// A construct declares its own meaning and codegen reads it. Adding a
|
|
// construct is a declaration in the program; it does not touch the compiler.
|
|
|
|
|
|
test "declared-construct-name-unknown-to-codegen" {
|
|
// The name is arbitrary. Nothing in the compiler mentions it.
|
|
let src: String = "@decorator(\"injects_at_entry\", \"engram_boundary_beat\")\nfn zzq_unlikely_name() {}\n@zzq_unlikely_name\nfn f() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "EL_STR(\"zzq_unlikely_name\")"), "an arbitrary construct name works"
|
|
}
|
|
|
|
test "undeclared-construct-still-injects-nothing" {
|
|
let src: String = "@nobody_declared_this\nfn f() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert !str_contains(out, "engram_boundary_beat"), "an undeclared construct injects nothing"
|
|
}
|
|
|
|
|
|
// ── Declared constructs: guards ──────────────────────────────────────────────
|
|
//
|
|
// A guard is an injection that may refuse. Non-zero return short-circuits the
|
|
// decorated fn. This is what @authenticate/@authorize/@rate_limit/@validate
|
|
// needed and never had — fourteen applications that read as protection and
|
|
// emitted no instruction.
|
|
|
|
|
|
|
|
|
|
test "undeclared-guard-emits-nothing" {
|
|
let src: String = "@not_a_declared_guard\nfn handler() -> Int { return 7 }"
|
|
let out: String = compile_capture(src)
|
|
assert !str_contains(out, "if (__g)"), "an undeclared construct guards nothing"
|
|
}
|
|
|
|
// ── Declared constructs: exit injection and composition ──────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
// ── Declared constructs: wraps and prohibitions ──────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
// ── Runtime seam ─────────────────────────────────────────────────────────────
|
|
//
|
|
// CONTROL for the finding that a crossing can be resolved at execution rather
|
|
// than at emission. Codegen emits one unconditional indirection per fn; which
|
|
// constructs apply is read from a table written after the binary exists.
|
|
|
|
test "seam-indirection-emitted-on-every-fn" {
|
|
let src: String = "fn a() -> Int { return 1 }\nfn b() -> Int { return 2 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "el_seam_run(EL_STR(\"a\"), 0, 0);"), "fn a carries the indirection"
|
|
assert str_contains(out, "el_seam_run(EL_STR(\"b\"), 0, 0);"), "fn b carries the indirection"
|
|
}
|
|
|
|
test "seam-emitted-without-any-decorator" {
|
|
// The point of the seam: source need not mention a construct at all.
|
|
let src: String = "fn undecorated() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "el_seam_run"), "an undecorated fn is still bindable at runtime"
|
|
assert !str_contains(out, "engram_boundary_beat"), "and nothing is inlined for it"
|
|
}
|
|
|
|
|
|
// ── Runtime seam: what replaced the compile-time entry mechanism ─────────────
|
|
//
|
|
// Entry injection and refusal moved from emission to execution. These assert
|
|
// the emitted shape; the BEHAVIOUR — that a construct declared after the build
|
|
// applies, refuses, composes, and that an unlinked target is skipped — is
|
|
// covered by tests/integration/seam_binding.sh, which needs a built binary and
|
|
// an environment and therefore cannot be a compile_capture test.
|
|
|
|
test "seam-replaces-inlined-entry-injection" {
|
|
let src: String = "@manager\nfn m() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "el_seam_run(EL_STR(\"m\")"), "the crossing goes through the seam"
|
|
assert !str_contains(out, "engram_boundary_beat(EL_STR(\"m\")"), "nothing is inlined at the crossing any more"
|
|
}
|
|
|
|
test "seam-entry-is-refusable" {
|
|
let src: String = "fn f() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "if (__s) return __s;"), "a bound construct can short-circuit the fn"
|
|
}
|
|
|
|
test "seam-is-emitted-for-undecorated-fns" {
|
|
let src: String = "fn plain() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "el_seam_run(EL_STR(\"plain\")"), "any fn is bindable later, decorated or not"
|
|
}
|
|
|
|
|
|
// ── Exit crossings resolve at runtime too ────────────────────────────────────
|
|
//
|
|
// The wrapper is now UNCONDITIONAL. It has to be: early returns must route
|
|
// through something for an exit construct to see them, and codegen cannot know
|
|
// which fns will be bound after the binary exists. Measured cost of always
|
|
// emitting it: 0.37s -> 0.38s across ten self-compiles.
|
|
|
|
test "every-fn-gets-a-body-helper-and-wrapper" {
|
|
let src: String = "fn plain(k: Int) -> Int { if k > 0 { return 1 } return 2 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "static el_val_t __el_body_plain"), "the body is a helper"
|
|
assert str_contains(out, "el_val_t plain(el_val_t k) {"), "the visible fn is a wrapper"
|
|
}
|
|
|
|
test "exit-crossing-goes-through-the-seam" {
|
|
let src: String = "fn f() -> Int { return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "__r = el_seam_run(EL_STR(\"f\"), 1, __r);"), "the exit crossing is resolved at execution and may replace the result"
|
|
}
|
|
|
|
test "early-returns-route-through-the-exit-seam" {
|
|
let src: String = "fn early(k: Int) -> Int { if k > 0 { return 99 } return 1 }"
|
|
let out: String = compile_capture(src)
|
|
let helper: Int = str_index_of(out, "__el_body_early")
|
|
let seam: Int = str_index_of(out, "el_seam_run(EL_STR(\"early\"), 1")
|
|
assert helper < seam, "the early return is inside the helper, so it passes through the exit seam"
|
|
}
|
|
|
|
|
|
// ── Invocation control resolves at runtime ───────────────────────────────────
|
|
//
|
|
// Every fn gets an env struct and a thunk, because codegen cannot know which
|
|
// fns a wrap construct will be bound to after the binary exists. That the bound
|
|
// construct can invoke the body zero or N times is behaviour, so it lives in
|
|
// tests/integration/seam_binding.sh.
|
|
|
|
test "every-fn-gets-a-closure" {
|
|
let src: String = "fn f(k: Int) -> Int { return k }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "struct __env_f { el_val_t k; };"), "captured environment"
|
|
assert str_contains(out, "static el_val_t __thunk_f(void* __v)"), "thunk over that environment"
|
|
assert str_contains(out, "el_seam_wrap(EL_STR(\"f\"), __thunk_f, &__env)"), "invocation goes through the seam"
|
|
}
|
|
|
|
test "zero-param-fn-emits-valid-c" {
|
|
// An empty struct is a GNU extension and an empty initialiser is C23.
|
|
let src: String = "fn noargs() -> Int { return 3 }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "struct __env_noargs { char __e0; };"), "zero-param env has a field"
|
|
assert !str_contains(out, "__env = { }"), "and no empty initialiser"
|
|
}
|
|
|
|
|
|
// ── Prohibition is a query, not an emission ─────────────────────────────────
|
|
//
|
|
// The compiler records what it saw -- who calls what, who carries what, who
|
|
// prohibits what. Whether that is legal is decided by tools/check/prohibitions.sh
|
|
// against the emitted relations, at build time. An emitter that also adjudicates
|
|
// has to contain every rule anyone will ever want.
|
|
|
|
test "compiler-no-longer-emits-prohibition-errors" {
|
|
let src: String = "@decorator(\"prohibits_outside\", \"raw_sql\")\nfn repository() {}\nfn sneaky() -> Int { raw_sql(\"DROP\") return 1 }"
|
|
let out: String = compile_capture(src)
|
|
assert !str_contains(out, "boundary violation"), "the emitter does not adjudicate"
|
|
}
|
|
|
|
// ── Int return types drive + dispatch ────────────────────────────────────────
|
|
//
|
|
// El has one type, so `a + b` must be dispatched from what the operands ARE.
|
|
// The 35 Int-returning builtins moved to signatures.rel; the dispatch stayed,
|
|
// because choosing between arithmetic and concatenation is emission.
|
|
|
|
test "int-returning-builtin-drives-arithmetic-dispatch" {
|
|
let src: String = "fn main() { let a = str_len(\"hello\") let b = str_len(\"hi\") let c = a + b println(int_to_str(c)) }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "(a + b)"), "Int + Int is arithmetic"
|
|
assert !str_contains(out, "el_str_concat(a, b)"), "and NOT concatenation"
|
|
}
|
|
|
|
test "string-plus-string-still-concatenates" {
|
|
let src: String = "fn main() { let s = \"a\" + \"b\" println(s) }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "el_str_concat"), "String + String still concatenates"
|
|
}
|
|
|
|
// ── Reserved words that reserved nothing ─────────────────────────────────────
|
|
//
|
|
// sealed, activate, seed, protocol and impl were keywords in the lexer and were
|
|
// consumed by no parser or codegen path. Each stole an identifier from users
|
|
// for nothing, and using one silently miscompiled: `let seed = 42` compiled
|
|
// clean and produced the wrong value with no diagnostic at any layer.
|
|
|
|
test "freed-identifiers-compile-as-identifiers" {
|
|
let src: String = "fn main() { let seed = 42 let impl = seed + 1 println(int_to_str(impl)) }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "el_val_t seed"), "seed is an identifier"
|
|
assert str_contains(out, "el_val_t impl"), "impl is an identifier"
|
|
assert str_contains(out, "(seed + 1)"), "and arithmetic on them dispatches correctly"
|
|
}
|
|
|
|
test "test-keyword-is-still-reserved" {
|
|
// `test` LOOKED inert by the same measure and is not: codegen consumes it
|
|
// for --test mode, 408 uses in the tree. Measuring only parser.el would
|
|
// have removed it.
|
|
let src: String = "fn main() { println(\"x\") }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "int main"), "the suite still compiles, which requires test to remain a keyword"
|
|
}
|
|
|
|
// ── A bare literal is a magnitude with no axis ───────────────────────────────
|
|
//
|
|
// Duration + Int was already refused because an Int carries no unit. Adding one
|
|
// to a POINT is worse: it moves the instant by an unspecified amount. The
|
|
// asymmetry had no justification; it was simply never written.
|
|
|
|
test "instant-plus-bare-int-is-refused" {
|
|
let src: String = "fn main() { let t: Instant = now() let u: Instant = t + 3 println(\"x\") }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "TIME_TYPE_ERROR: Instant + Int"), "3 of what?"
|
|
}
|
|
|
|
test "instant-plus-unit-suffix-is-allowed" {
|
|
// .hour supplies the axis, so the magnitude becomes a displacement.
|
|
let src: String = "fn main() { let t: Instant = now() let u: Instant = t + 1.hour println(\"x\") }"
|
|
let out: String = compile_capture(src)
|
|
assert str_contains(out, "el_instant_add_dur"), "a unit suffix makes it a Duration"
|
|
assert !str_contains(out, "TIME_TYPE_ERROR"), "and the addition is legal"
|
|
}
|