Baseline: Ludic compiler + toolchain, Phase 1 syntax fixes complete

Self-hosted compiler (selfhost/*.ludic), runtime, examples, editor tooling,
and docs. Phase 1 of the syntax-redesign cohesion pass has landed:
edge-system fix, signature-query, when-alias, and the documentation truth-pass.
Suite green (14/14), C-free bootstrap fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-27 15:15:35 +03:00
commit 985f9ad8f2
418 changed files with 39065 additions and 0 deletions

View file

@ -0,0 +1,31 @@
{
"comment": "Editor mechanics that are not highlighting and not the language server: comments, bracket pairs, auto-closing, indentation triggers. VS Code reads this file directly; the JetBrains plugin implements the same rules natively; other editors can transcribe it.",
"comments": {
"lineComment": "#"
},
"brackets": [["{", "}"], ["[", "]"], ["(", ")"]],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"", "notIn": ["string", "comment"] }
],
"surroundingPairs": [["{", "}"], ["[", "]"], ["(", ")"], ["\"", "\""]],
"indentationRules": {
"increaseIndentPattern": "^((?!//).)*(\\{[^}\"']*|\\([^)\"']*|\\[[^\\]\"']*)$",
"decreaseIndentPattern": "^\\s*[}\\])].*$"
},
"onEnterRules": [
{
"beforeText": "^\\s*#.*$",
"action": { "indent": "none", "appendText": "# " }
}
],
"wordPattern": "[A-Za-z_][A-Za-z0-9_]*",
"folding": {
"markers": {
"start": "^\\s*#\\s*region\\b",
"end": "^\\s*#\\s*endregion\\b"
}
}
}

View file

@ -0,0 +1,27 @@
{
"scopeName": "markdown.ludic.codeblock",
"comment": "Injects Ludic highlighting into ```ludic fences in Markdown. Any TextMate-based editor (VS Code, Sublime, JetBrains' TextMate bundles, Zed) picks this up; JetBrains' native Markdown support gets the same thing for free from the registered `Ludic` language ID, and ludic-lsp will also serve semantic tokens and diagnostics for the fence bodies in a .md file.",
"injectionSelector": "L:text.html.markdown",
"patterns": [{ "include": "#ludic-code-block" }],
"repository": {
"ludic-code-block": {
"begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ludic)((\\s+|:|,|\\{|\\?)[^`~]*)?)$",
"name": "markup.fenced_code.block.markdown",
"end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$",
"beginCaptures": {
"3": { "name": "punctuation.definition.markdown" },
"4": { "name": "fenced_code.block.language.markdown" },
"5": { "name": "fenced_code.block.language.attributes.markdown" }
},
"endCaptures": { "3": { "name": "punctuation.definition.markdown" } },
"patterns": [
{
"begin": "(^|\\G)(\\s*)(.*)",
"while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)",
"contentName": "meta.embedded.block.ludic",
"patterns": [{ "include": "source.ludic" }]
}
]
}
}
}

View file

@ -0,0 +1,241 @@
{
"$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": "'(\\\\.|[^'\\\\])'"
}
]
},
"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(game|module)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "keyword.control.unit.ludic" },
"2": { "name": "entity.name.type.unit.ludic" }
}
},
{
"match": "\\b(component)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.type.component.ludic" },
"2": { "name": "entity.name.type.component.ludic" }
}
},
{
"match": "\\b(archetype)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "storage.type.archetype.ludic" },
"2": { "name": "entity.name.type.archetype.ludic" }
}
},
{
"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_]*)",
"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_]*)",
"captures": {
"1": { "name": "storage.modifier.edge.ludic" },
"2": { "name": "storage.type.system.ludic" },
"3": { "name": "entity.name.function.system.ludic" }
}
},
{
"match": "\\b(export\\s+|pure\\s+)?(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" }
}
},
{
"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(enter)\\s+([A-Za-z_][A-Za-z0-9_]*)",
"captures": {
"1": { "name": "keyword.control.enter.ludic" },
"2": { "name": "entity.name.type.scene.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|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|export|pure|extern|start|edge)\\b" },
{ "name": "storage.type.ludic", "match": "\\b(game|module|component|archetype|ui|scene|layer|const|var|let|fn|system|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" }
]
},
"widget": {
"patterns": [
{
"comment": "widget types, recognised by the 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": "widget props are written tight (id=Root); an assignment is written x = 1",
"match": "\\b([a-z][A-Za-z0-9_]*)(=)(?!=)",
"captures": {
"1": { "name": "variable.parameter.widget.ludic" },
"2": { "name": "keyword.operator.assignment.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|fx|flr|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|load_png|load_sprites|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|setreg|self|save|load|status|print_int|quit)\\b(?=\\s*\\()"
},
{
"comment": "compiler intrinsics — these lower straight to libc or the OS",
"name": "support.function.intrinsic.ludic",
"match": "\\b(mem_alloc|mem_realloc|mem_free|mem_copy|mem_set|peek8|poke8|peek32|poke32|peekp|pokep|peekf|pokef|ptr_add|ptr_null|ptr_is_null|as_fixed|as_int|file_open|file_read|file_write|file_seek|file_tell|file_close|read_byte|write_byte|print_str|str_len|shl|shr|band|bor|bxor|bnot|os_exit|os_time|os_argc|os_arg|file_stderr|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.comparison.ludic", "match": "==|!=|<=|>=|<|>" },
{ "name": "keyword.operator.assignment.ludic", "match": "\\+=|-=|\\*=|/=|=" },
{ "name": "keyword.operator.arithmetic.ludic", "match": "[+\\-*/%]" }
]
}
}
}