`ludic syntax [--json] [-o FILE]` prints what `ludicc --emit-syntax` does (a line per entry, or the JSON). `ludic-dev syntax` writes, between "ludic-dev syntax: begin" / "end" lines, the keyword, type, phase and attribute tables of ludic_syntax.h, LudicVocabulary's sets (JetBrains), ludic-mode.el's lists and the language server's word tests (is_keyword_word and the rest; is_contextual_word is every word the parser does not reserve, and every declaring or modifying one), and every TextMate pattern marked "comment": "ludic-dev syntax: <group>" (shared and the VS Code copy). The grammars gain module uses port bind action reducer dispatch registry def open component prop view alias friend unsafe numbers of as from mut system; import and extern colour as declarations; the phase clause knows Overlay; the bitwise pattern matches | and ^ on their own again. `ludic-dev syntax --check` - and check-vocabulary, whose old parser comparison it replaces, and the regression suite (syntax_cases, one line per file) - fails when a written list is behind, when a grammar lacks a keyword, type or phase, when docs/language has no page for a keyword, type, phase or attribute, or when the parser (a scan of selfhost/frontend: is_id / text == words, a == / ann == attributes) tests a word or reads an attribute vocab.ludic lacks, or the reverse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
250 lines
11 KiB
JSON
250 lines
11 KiB
JSON
{
|
|
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
|
"name": "Ludic",
|
|
"scopeName": "source.ludic",
|
|
"fileTypes": ["ludic"],
|
|
"comment": "Static grammar for Ludic. It is the fallback: when ludic-lsp is running, its semantic tokens refine everything here with real name resolution. The keyword, type and phase alternations are written by `ludic-dev syntax` from the compiler's own vocabulary (`ludicc --emit-syntax`): every pattern marked \"ludic-dev syntax: <group>\".",
|
|
"patterns": [
|
|
{ "include": "#comment" },
|
|
{ "include": "#declaration" },
|
|
{ "include": "#query" },
|
|
{ "include": "#string" },
|
|
{ "include": "#number" },
|
|
{ "include": "#annotation" },
|
|
{ "include": "#keyword" },
|
|
{ "include": "#widget" },
|
|
{ "include": "#builtin" },
|
|
{ "include": "#member" },
|
|
{ "include": "#call" },
|
|
{ "include": "#typename" },
|
|
{ "include": "#operator" }
|
|
],
|
|
"repository": {
|
|
"comment": {
|
|
"match": "#.*$",
|
|
"name": "comment.line.number-sign.ludic"
|
|
},
|
|
"string": {
|
|
"patterns": [
|
|
{
|
|
"name": "string.quoted.double.ludic",
|
|
"begin": "\"",
|
|
"end": "\"",
|
|
"beginCaptures": { "0": { "name": "punctuation.definition.string.begin.ludic" } },
|
|
"endCaptures": { "0": { "name": "punctuation.definition.string.end.ludic" } },
|
|
"patterns": [{ "name": "constant.character.escape.ludic", "match": "\\\\." }]
|
|
},
|
|
{
|
|
"name": "string.quoted.single.ludic",
|
|
"match": "'(\\\\.|[^'\\\\])'"
|
|
},
|
|
{
|
|
"name": "string.interpolated.ludic",
|
|
"begin": "`",
|
|
"end": "`",
|
|
"beginCaptures": { "0": { "name": "punctuation.definition.string.begin.ludic" } },
|
|
"endCaptures": { "0": { "name": "punctuation.definition.string.end.ludic" } },
|
|
"patterns": [
|
|
{ "name": "meta.embedded.ludic", "begin": "\\{", "end": "\\}", "patterns": [{ "include": "$self" }] },
|
|
{ "name": "constant.character.escape.ludic", "match": "\\\\." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"number": {
|
|
"patterns": [
|
|
{ "name": "constant.numeric.hex.ludic", "match": "\\b0[xX][0-9a-fA-F]+\\b" },
|
|
{ "name": "constant.numeric.fixed.ludic", "match": "\\b[0-9]+\\.[0-9]+\\b" },
|
|
{ "name": "constant.numeric.integer.ludic", "match": "\\b[0-9]+\\b" }
|
|
]
|
|
},
|
|
"annotation": {
|
|
"name": "entity.name.function.decorator.ludic",
|
|
"match": "@[A-Za-z_][A-Za-z0-9_]*"
|
|
},
|
|
"declaration": {
|
|
"patterns": [
|
|
{
|
|
"match": "\\b(program)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "keyword.control.unit.ludic" },
|
|
"2": { "name": "entity.name.type.unit.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(property)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "storage.type.component.ludic" },
|
|
"2": { "name": "entity.name.type.component.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(model)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "storage.type.archetype.ludic" },
|
|
"2": { "name": "entity.name.type.archetype.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"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(handler)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "storage.type.system.ludic" },
|
|
"2": { "name": "entity.name.function.system.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(extern\\s+)?(function)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "storage.modifier.extern.ludic" },
|
|
"2": { "name": "storage.type.function.ludic" },
|
|
"3": { "name": "entity.name.function.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(const|var|let)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "storage.type.ludic" },
|
|
"2": { "name": "variable.other.definition.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(state)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "keyword.control.state.ludic" },
|
|
"2": { "name": "entity.name.constant.state.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(import)\\s+(?=\")",
|
|
"captures": { "1": { "name": "keyword.control.import.ludic" } }
|
|
},
|
|
{
|
|
"comment": "ludic-dev syntax: phase-clause", "match": "\\b(phase)\\s+(Start|Input|FixedUpdate|Update|LateUpdate|Render|Overlay)\\b",
|
|
"captures": {
|
|
"1": { "name": "keyword.other.clause.ludic" },
|
|
"2": { "name": "constant.language.phase.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(become)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "keyword.control.become.ludic" },
|
|
"2": { "name": "entity.name.type.scene.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(scene|prefab|event)\\s+(cancellable\\s+)?([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "storage.type.ludic" },
|
|
"2": { "name": "keyword.other.clause.ludic" },
|
|
"3": { "name": "entity.name.type.ludic" }
|
|
}
|
|
},
|
|
{
|
|
"match": "\\b(spawn)\\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "keyword.control.spawn.ludic" },
|
|
"2": { "name": "entity.name.type.archetype.ludic" }
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"query": {
|
|
"comment": "Inside a query's brackets a bare word names a component and {Word} is a filter that does not bind.",
|
|
"begin": "\\b(query)\\s*(\\[)",
|
|
"beginCaptures": {
|
|
"1": { "name": "keyword.other.query.ludic" },
|
|
"2": { "name": "punctuation.section.brackets.begin.ludic" }
|
|
},
|
|
"end": "(\\])",
|
|
"endCaptures": { "1": { "name": "punctuation.section.brackets.end.ludic" } },
|
|
"patterns": [
|
|
{
|
|
"match": "(\\{)\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*(\\})",
|
|
"captures": {
|
|
"1": { "name": "punctuation.section.braces.begin.ludic" },
|
|
"2": { "name": "entity.name.type.tag.ludic" },
|
|
"3": { "name": "punctuation.section.braces.end.ludic" }
|
|
}
|
|
},
|
|
{ "name": "entity.name.type.component.ludic", "match": "\\b[A-Za-z_][A-Za-z0-9_]*\\b" },
|
|
{ "name": "punctuation.separator.ludic", "match": "," }
|
|
]
|
|
},
|
|
"keyword": {
|
|
"patterns": [
|
|
{ "name": "keyword.control.ludic", "comment": "ludic-dev syntax: statement", "match": "\\b(let|if|else|while|for|in|where|match|machine|become|return|break|continue|new|spawn|despawn|enable|disable|attach|detach|emit|cancel|dispatch|try)\\b" },
|
|
{ "name": "keyword.operator.logical.ludic", "comment": "ludic-dev syntax: operator", "match": "\\b(and|or|not)\\b" },
|
|
{ "name": "keyword.other.clause.ludic", "comment": "ludic-dev syntax: modifier", "match": "\\b(export|internal|friend|uses|open|of|as|from|numbers|unsafe|mut|phase|query|on|cancellable|public|layer|start|shows|lasts|then|loads|system)\\b" },
|
|
{ "name": "storage.type.ludic", "comment": "ludic-dev syntax: declaration", "match": "\\b(program|module|import|property|state|event|action|reducer|enum|model|prefab|scene|handler|ui|namespace|alias|port|bind|registry|def|component|view|prop|const|var|function|fn|extern|entry|test)\\b" },
|
|
{ "name": "support.type.primitive.ludic", "comment": "ludic-dev syntax: types", "match": "\\b(int|long|float|double|fixed|bool|byte|string|pointer|void|entity|countdown|words|floats|doubles|fixeds|pointers|Vector|IVec2|Rect)\\b" },
|
|
{ "name": "constant.language.boolean.ludic", "comment": "ludic-dev syntax: constant", "match": "\\b(true|false|null)\\b" },
|
|
{ "name": "constant.language.phase.ludic", "comment": "ludic-dev syntax: phases", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render|Overlay)\\b" }
|
|
]
|
|
},
|
|
"widget": {
|
|
"patterns": [
|
|
{
|
|
"comment": "widget types, recognised by the `key: value` props or the child block that follow them",
|
|
"name": "support.class.widget.ludic",
|
|
"match": "\\b(panel|col|row|label|button|image|spacer)\\b(?=\\s*(\\{|[a-z][A-Za-z0-9_]*\\s*:|$))"
|
|
},
|
|
{
|
|
"comment": "a widget prop or a named argument: `id: Root`, `x: 10`",
|
|
"match": "\\b([a-z][A-Za-z0-9_]*)(:)(?=\\s)",
|
|
"captures": {
|
|
"1": { "name": "variable.parameter.ludic" },
|
|
"2": { "name": "punctuation.separator.ludic" }
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"builtin": {
|
|
"patterns": [
|
|
{
|
|
"comment": "the runtime surface — every name here resolves to rt_<name> in runtime/native",
|
|
"name": "support.function.builtin.ludic",
|
|
"match": "\\b(min|max|abs|clamp|seed|rng_range|rng_chance|fixed|floor|map_size|map_row|tile|clear|present|fill_rect|frame_rect|put_px|text|text_int|font_load|text_ttf|text_w|text_h|image_load|draw_image|draw_image_scaled|draw_9slice|png_load|sprites_load|draw_sprite|draw_sprite_scaled|ui_build|ui_open|ui_tick|ui_render|ui_clicked|ui_set_text|ui_set_int|ui_focus|ui_focused|ui_visible|key|reg|set_reg|self|save|load|status|print|string|quit|panic|assert|ok|err|is_ok|is_err|some|none|is_some|is_none|unwrap_or)\\b(?=\\s*\\()"
|
|
},
|
|
{
|
|
"comment": "compiler intrinsics — these lower straight to libc or the OS",
|
|
"name": "support.function.intrinsic.ludic",
|
|
"match": "\\b(bytes|words|resize|free|fill|offset|as_fixed|as_int|file_open|file_read|file_write|file_seek|file_tell|file_close|read_char|exit|arg_count|arg|file_stderr|file_stdout|is_windowed|game_title|win_open|win_poll|win_present|win_running|win_close)\\b(?=\\s*\\()"
|
|
}
|
|
]
|
|
},
|
|
"member": {
|
|
"match": "(\\.)([A-Za-z_][A-Za-z0-9_]*)",
|
|
"captures": {
|
|
"1": { "name": "punctuation.accessor.ludic" },
|
|
"2": { "name": "variable.other.property.ludic" }
|
|
}
|
|
},
|
|
"call": {
|
|
"match": "\\b([A-Za-z_][A-Za-z0-9_]*)\\s*(?=\\()",
|
|
"name": "entity.name.function.call.ludic"
|
|
},
|
|
"typename": {
|
|
"comment": "components, archetypes, scenes and UI handles are Capitalised by convention",
|
|
"name": "entity.name.type.ludic",
|
|
"match": "\\b[A-Z][A-Za-z0-9_]*\\b"
|
|
},
|
|
"operator": {
|
|
"patterns": [
|
|
{ "name": "keyword.operator.arrow.ludic", "match": "->|=>" },
|
|
{ "name": "keyword.operator.range.ludic", "match": "\\.\\." },
|
|
{ "name": "keyword.operator.bitwise.ludic", "match": "<<|>>|&|\\||\\^|~" },
|
|
{ "name": "keyword.operator.comparison.ludic", "match": "==|!=|<=|>=|<|>" },
|
|
{ "name": "keyword.operator.assignment.ludic", "match": "\\+=|-=|\\*=|/=|=" },
|
|
{ "name": "keyword.operator.arithmetic.ludic", "match": "[+\\-*/%]" }
|
|
]
|
|
}
|
|
}
|
|
}
|