From 0143cc458ac158e808e074acab417d4f706d89d8 Mon Sep 17 00:00:00 2001 From: bigmerge Date: Mon, 17 Aug 2026 09:43:58 -0500 Subject: [PATCH] ANSWER: is a grammar a convention, or a region? Both, at different layers -- and it is the same split as serialization: the convention is the BASIS, never the ACT. lexeme -> token `fn` means function-start because someone said so CONVENTION shape recognition given tokens, which construct is this REGION source -> structure parsing is transduction onto that basis GEOMETRY byte traversal something must read them in order IRREDUCIBLE Three things push the ACT toward region rather than convention: ambiguity (a * b needs context; a grammar resolves it with the lexer hack, a region by neighbourhood), error recovery (nearest-region is free), and precedence, which is ordering along an axis with a conventional parameter. AND THE SHOULD GATE SAYS NO TO THE OBVIOUS MOVE Every other table this session moved to data. This one stays code. 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 costs file I/O on every compile and buys nothing. Same verdict as is_digit in ASCII. WHAT WAS ACTUALLY WRONG: five of 46 keywords were consumed by no parser or codegen path. sealed, activate, seed, protocol, impl. Each stole an identifier from users for nothing. SECOND SILENT MISCOMPILATION OF THE DAY. Using one did not fail to parse: let seed = 42 let impl = seed + 1 compiled CLEAN -- zero cc errors -- and printed 0 instead of 44. No diagnostic at any layer. Fixed by removing the five. A DEFECT IN MY OWN MEASUREMENT, caught before it did damage: my first pass checked only parser.el and reported `test` as inert too. codegen consumes it at 4135 for --test mode, and the tree has 408 uses. Removing it would have broken every test in the suite. The measurement was re-run across all four consumers. 100/100 native + 2 new, 31/31 integration, fixpoint ok. --- lang/el-compiler/src/lexer.el | 21 ++++++++++++++++----- lang/tests/native/test_compiler.el | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) 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" +}