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.
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user