diff --git a/lang/el-compiler/src/lexer.el b/lang/el-compiler/src/lexer.el index a8620d3..a14f7e2 100644 --- a/lang/el-compiler/src/lexer.el +++ b/lang/el-compiler/src/lexer.el @@ -145,6 +145,22 @@ fn tok_append(tokens: [Any], kind: String, value: String) -> [Any] { // -- Keyword lookup ------------------------------------------------------------ +// keyword_kind — the language's reserved spellings. +// +// A grammar is a BASIS: `fn` means function-start because someone said so, and +// nothing derives it. But unlike the other tables moved out this session, this +// one stays code, and the SHOULD gate is why. The keyword set is closed by the +// language definition -- it does not leak the way an allowlist does -- and the +// lexer runs before the program is understood, so a program can never declare +// its own keywords. Externalising it would cost file I/O on every compile and +// buy nothing. +// +// Removed 2026-08-17: sealed, activate, seed, protocol, impl. Reserved in the +// lexer, consumed by no parser or codegen path, and each one stole an +// identifier from users for nothing. `test` LOOKED inert by the same measure +// and is not -- codegen consumes it at 4135 for --test mode, 408 uses in the +// tree. The first measurement checked only parser.el and would have broken all +// of them. fn keyword_kind(word: String) -> String { if word == "let" { return "Let" } if word == "fn" { return "Fn" } @@ -161,14 +177,9 @@ fn keyword_kind(word: String) -> String { if word == "from" { return "From" } if word == "as" { return "As" } if word == "with" { return "With" } - if word == "sealed" { return "Sealed" } - if word == "activate" { return "Activate" } if word == "where" { return "Where" } if word == "test" { return "Test" } - if word == "seed" { return "Seed" } if word == "assert" { return "Assert" } - if word == "protocol" { return "Protocol" } - if word == "impl" { return "Impl" } if word == "retry" { return "Retry" } if word == "times" { return "Times" } if word == "fallback" { return "Fallback" } diff --git a/lang/tests/native/test_compiler.el b/lang/tests/native/test_compiler.el index 999e0c3..177fb15 100644 --- a/lang/tests/native/test_compiler.el +++ b/lang/tests/native/test_compiler.el @@ -943,3 +943,27 @@ test "string-plus-string-still-concatenates" { 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" +}