Phase 5: anchor the editor vocabulary to the compiler

check-vocabulary.py now extracts the keywords selfhost/parse*.ludic actually
dispatches on and requires the header's declaration/clause keywords to be a
subset (LUDIC_KW_RESERVED exempts documented-but-unimplemented scene/layer/on/
start, itself checked so an implemented reserved word must be promoted). Closes
the highlighted-but-unparsed loop against the real compiler; verified it catches
an injected bogus keyword.

Reconciled the drift it exposed across ludic_syntax.h, JetBrains, TextMate, and
emacs: dropped scene/layer/on/start (->RESERVED), the never-implemented
needs/uses/requires/ensures/invariant/effects clauses, and the retired
edge/export/pure prefixes; added enum/main. @annotations highlight generically.
Tooling-only (no seed change): test.sh 14/14, check-vocabulary + test-tools 28/0.

Phases 1-5 complete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-27 16:35:46 +03:00
parent f87964c5f7
commit 9585405dc0
8 changed files with 426 additions and 64 deletions

View file

@ -11,12 +11,11 @@
;;; Code:
(defconst ludic--declaration-keywords
'("game" "module" "import" "component" "struct" "archetype" "enum" "ui" "scene" "layer"
"const" "var" "fn" "extern" "system" "start" "on"))
'("game" "module" "import" "component" "struct" "archetype" "enum" "ui"
"const" "var" "fn" "extern" "system" "main"))
(defconst ludic--clause-keywords
'("phase" "query" "reads" "writes" "needs" "uses"
"requires" "ensures" "invariant" "effects"))
'("phase" "query" "reads" "writes"))
(defconst ludic--statement-keywords
'("let" "return" "if" "else" "when" "while" "for" "in" "spawn" "despawn"

View file

@ -41,12 +41,11 @@ object LudicTokens {
*/
object LudicVocabulary {
val DECL = setOf(
"game", "module", "import", "component", "struct", "archetype", "enum", "ui", "scene", "layer",
"const", "var", "fn", "extern", "system", "start", "on"
"game", "module", "import", "component", "struct", "archetype", "enum", "ui",
"const", "var", "fn", "extern", "system", "main"
)
val CLAUSE = setOf(
"phase", "query", "reads", "writes", "needs", "uses",
"requires", "ensures", "invariant", "effects"
"phase", "query", "reads", "writes"
)
val STMT = setOf(
"let", "return", "if", "else", "when", "while", "for", "in", "spawn", "despawn",

View file

@ -75,35 +75,25 @@
}
},
{
"match": "\\b(scene)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\s+start)?",
"captures": {
"1": { "name": "storage.type.scene.ludic" },
"2": { "name": "entity.name.type.scene.ludic" },
"3": { "name": "keyword.other.start.ludic" }
}
},
{
"match": "\\b(layer|ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"match": "\\b(ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.type.ludic" },
"2": { "name": "entity.name.type.ludic" }
}
},
{
"match": "\\b(edge\\s+)?(system)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"match": "\\b(system)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.modifier.edge.ludic" },
"2": { "name": "storage.type.system.ludic" },
"3": { "name": "entity.name.function.system.ludic" }
"1": { "name": "storage.type.system.ludic" },
"2": { "name": "entity.name.function.system.ludic" }
}
},
{
"match": "\\b(export\\s+|pure\\s+)?(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"match": "\\b(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.modifier.ludic" },
"2": { "name": "storage.modifier.extern.ludic" },
"3": { "name": "storage.type.function.ludic" },
"4": { "name": "entity.name.function.ludic" }
"1": { "name": "storage.modifier.extern.ludic" },
"2": { "name": "storage.type.function.ludic" },
"3": { "name": "entity.name.function.ludic" }
}
},
{
@ -173,9 +163,9 @@
"patterns": [
{ "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|where|break|continue|new)\\b" },
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes|needs|uses|requires|ensures|invariant|effects|on)\\b" },
{ "name": "keyword.other.ludic", "match": "\\b(import|extern|start)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|struct|archetype|enum|ui|scene|layer|const|var|let|fn|system|state)\\b" },
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes)\\b" },
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|struct|archetype|enum|ui|const|var|let|fn|system|main|state)\\b" },
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" },
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }

View file

@ -75,35 +75,25 @@
}
},
{
"match": "\\b(scene)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\s+start)?",
"captures": {
"1": { "name": "storage.type.scene.ludic" },
"2": { "name": "entity.name.type.scene.ludic" },
"3": { "name": "keyword.other.start.ludic" }
}
},
{
"match": "\\b(layer|ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"match": "\\b(ui)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.type.ludic" },
"2": { "name": "entity.name.type.ludic" }
}
},
{
"match": "\\b(edge\\s+)?(system)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"match": "\\b(system)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.modifier.edge.ludic" },
"2": { "name": "storage.type.system.ludic" },
"3": { "name": "entity.name.function.system.ludic" }
"1": { "name": "storage.type.system.ludic" },
"2": { "name": "entity.name.function.system.ludic" }
}
},
{
"match": "\\b(export\\s+|pure\\s+)?(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"match": "\\b(extern\\s+)?(fn)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.modifier.ludic" },
"2": { "name": "storage.modifier.extern.ludic" },
"3": { "name": "storage.type.function.ludic" },
"4": { "name": "entity.name.function.ludic" }
"1": { "name": "storage.modifier.extern.ludic" },
"2": { "name": "storage.type.function.ludic" },
"3": { "name": "entity.name.function.ludic" }
}
},
{
@ -173,9 +163,9 @@
"patterns": [
{ "name": "keyword.control.ludic", "match": "\\b(if|else|when|while|for|in|match|machine|become|enter|return|spawn|despawn|where|break|continue|new)\\b" },
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes|needs|uses|requires|ensures|invariant|effects|on)\\b" },
{ "name": "keyword.other.ludic", "match": "\\b(import|extern|start)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|struct|archetype|enum|ui|scene|layer|const|var|let|fn|system|state)\\b" },
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|reads|writes)\\b" },
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|struct|archetype|enum|ui|const|var|let|fn|system|main|state)\\b" },
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|void)\\b" },
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\b" },
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }