- ludic-fmt: `rows[i]`, `new []int`, `s[a..b]`, `emit(`, `~x`, list-literal
and query-tag braces stay tight; member calls hug their paren
(`Date.new(`, `Prefab.spawn(`); `<< >> & | ^ ~` are operators and
`&& ||` are not — mirrored in ludic_syntax.h, LudicLexer.kt and the
TextMate grammar. 149 of 274 tracked sources failed --check before.
- ludic-lsp: `initializationOptions.compilerDiagnostics` / `compilerPath`
are honoured — on save the document's compilation unit is compiled and
`file:line: error: msg` is published as a "ludicc" diagnostic (the
option had been documented but never implemented); the workspace scan
file is per process; bracket codes spelled as char literals
- Overlay added to LUDIC_PHASES, LudicTokens.kt, ludic-mode.el and the
grammar (the game uses it; check-vocabulary flagged the drift)
- editors: VS Code snippets/package.json/extension.js (`${workspaceFolder}`
and `~` expansion, PATH lookup, Apache-2.0, real repo URL), Neovim root
markers, Helix/Zed/Sublime bin/ paths, Emacs vocabulary, JetBrains
comments; tools/editors/README.md no longer describes C tooling
- .forgejo workflows clone `${{ github.server_url }}/${{ github.repository }}`
so a fork or mirror tests itself; the docs publish pushes the same way
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
251 lines
10 KiB
JSON
251 lines
10 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. Generated shapes are kept in sync with tools/ludic-tools/ludic_syntax.h.",
|
|
"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" } }
|
|
},
|
|
{
|
|
"match": "\\b(phase)\\s+(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\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", "match": "\\b(if|else|while|for|in|match|machine|become|return|spawn|despawn|enable|disable|attach|detach|emit|cancel|where|break|continue|new|try)\\b" },
|
|
{ "name": "keyword.operator.logical.ludic", "match": "\\b(and|or|not)\\b" },
|
|
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on|cancellable|public|layer|start|shows|lasts|loads|then|export|internal)\\b" },
|
|
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
|
|
{ "name": "storage.type.ludic", "match": "\\b(program|property|model|prefab|namespace|enum|ui|const|var|let|function|handler|entry|event|scene|state|test)\\b" },
|
|
{ "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|countdown|bool|entity|string|pointer|byte|words|fixeds|pointers|Vector|IVec2|Rect|void)\\b" },
|
|
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false|null)\\b" },
|
|
{ "name": "constant.language.phase.ludic", "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": "[+\\-*/%]" }
|
|
]
|
|
}
|
|
}
|
|
}
|