v0.2: Pico-8 Lua dialect grammar and language

Reorganize into grammars/<name>/ subdirs ( Zed's [grammars.*] supports
a `path` field, so both grammars ship from this repo without a sibling-
repo split ). Vendor tree-sitter-lua as the fork base for tree-sitter-
pico8-lua; upstream MIT license preserved at grammars/pico-8-lua/
UPSTREAM-LICENSE.md.

Dialect features added: != as ~= alias, \ integer divide, ^^ binary xor,
>>> / <<> / >>< shifts and rotates, compound-assignment statements,
memory peek prefixes @ % $ (% coexists with binary modulo), single-line
`if (cond) stmt [else stmt]` and `while (cond) stmt`, statement-level
print shorthand ?, and `#include path` directives. Identifier rule no
longer accepts ! ? @ $ ( upstream did ).

Pico-8 Lua language ( languages/pico-8-lua/, suffix .p8lua ) ships
highlights with the full ~110 PICO-8 builtins as @function.builtin.
The cart injection now hands __lua__ bodies to pico-8-lua, so .p8 carts
and bare .p8lua files share the dialect-aware grammar. Examples updated
to exercise the dialect end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 12:50:41 -07:00
parent 04a92fc16e
commit 39d77a8cae
32 changed files with 32503 additions and 120 deletions
+4 -10
View File
@@ -1,11 +1,5 @@
; Inject Lua syntax highlighting into the __lua__ section.
;
; NOTE: This injects Zed's built-in Lua grammar, which does not
; understand Pico-8 dialect extensions ( ?, +=, !=, single-line
; `if (cond) stmt`, binary literals, memory peek prefix operators,
; etc. ). Code that uses those forms will produce parse errors
; locally, with degraded highlighting in those regions only — the
; rest of the file will still render correctly. A future Pico-8
; Lua grammar fork will replace this; see README for status.
; Hand the body of the __lua__ section to the Pico-8 Lua grammar so the
; dialect-aware parser parses it (compound-assignment statements, the `?`
; print shorthand, single-line `if (cond) stmt`, peek prefixes, etc.).
((lua_content) @injection.content
(#set! injection.language "lua"))
(#set! injection.language "pico-8-lua"))
+4
View File
@@ -0,0 +1,4 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)
+16
View File
@@ -0,0 +1,16 @@
name = "Pico-8 Lua"
grammar = "pico8_lua"
path_suffixes = ["p8lua"]
line_comments = ["-- "]
block_comment = ["--[[", "]]"]
hard_tabs = false
tab_size = 1
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "[[", end = "]]", close = true, newline = true },
]
+215
View File
@@ -0,0 +1,215 @@
; --- Keywords ---
"return" @keyword.return
[
"goto"
"in"
"local"
] @keyword
(label_statement) @label
(break_statement) @keyword
(do_statement
["do" "end"] @keyword)
(while_statement
["while" "do" "end"] @keyword.repeat)
(shorthand_while_statement
"while" @keyword.repeat)
(repeat_statement
["repeat" "until"] @keyword.repeat)
(if_statement
["if" "elseif" "else" "then" "end"] @keyword.conditional)
(elseif_statement
["elseif" "then" "end"] @keyword.conditional)
(else_statement
["else" "end"] @keyword.conditional)
(shorthand_if_statement
["if" "else"] @keyword.conditional)
(for_statement
["for" "do" "end"] @keyword.repeat)
(function_declaration
["function" "end"] @keyword.function)
(function_definition
["function" "end"] @keyword.function)
; --- PICO-8 dialect: print shorthand and #include ---
(print_shorthand_statement
directive: "?" @function.builtin)
(include_statement
directive: _ @keyword.directive)
(include_statement
path: (include_path) @string.special.path)
; --- Operators ---
(binary_expression
operator: _ @operator)
(unary_expression
operator: _ @operator)
(compound_assignment_statement
operator: _ @operator)
"=" @operator
[
"and"
"not"
"or"
] @keyword.operator
; --- Punctuation ---
[
";"
":"
","
"."
] @punctuation.delimiter
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
; --- Variables and constants ---
(identifier) @variable
(variable_list
(attribute
"<" @punctuation.bracket
(identifier) @attribute
">" @punctuation.bracket))
((identifier) @constant
(#match? @constant "^[A-Z][A-Z_0-9]*$"))
(vararg_expression) @constant
(nil) @constant.builtin
[
(false)
(true)
] @boolean
; PICO-8 callback hooks — recognized by name regardless of definition site.
((identifier) @function.builtin
(#any-of? @function.builtin
"_init" "_update" "_update60" "_draw"))
; --- Tables ---
(field
name: (identifier) @property)
(dot_index_expression
field: (identifier) @property)
(table_constructor
["{" "}"] @constructor)
; --- Functions ---
(parameters
(identifier) @variable.parameter)
(function_declaration
name: [
(identifier) @function
(dot_index_expression
field: (identifier) @function)
])
(function_declaration
name: (method_index_expression
method: (identifier) @function.method))
(assignment_statement
(variable_list
.
name: [
(identifier) @function
(dot_index_expression
field: (identifier) @function)
])
(expression_list
.
value: (function_definition)))
(table_constructor
(field
name: (identifier) @function
value: (function_definition)))
(function_call
name: [
(identifier) @function.call
(dot_index_expression
field: (identifier) @function.call)
(method_index_expression
method: (identifier) @function.method.call)
])
; --- PICO-8 builtin globals ---
(function_call
(identifier) @function.builtin
(#any-of? @function.builtin
; system
"load" "save" "ls" "run" "stop" "assert" "reset" "info" "flip" "printh"
"time" "t" "stat" "extcmd" "holdframe" "_set_fps"
; graphics
"clip" "pset" "pget" "sget" "sset" "fget" "fset" "print" "cursor"
"color" "cls" "camera" "circ" "circfill" "oval" "ovalfill" "line"
"rect" "rectfill" "rrect" "rrectfill" "pal" "palt" "spr" "sspr" "fillp"
; tables
"add" "del" "deli" "count" "all" "foreach" "pairs" "ipairs" "next"
; input
"btn" "btnp"
; audio
"sfx" "music"
; map
"mget" "mset" "map" "tline"
; memory
"peek" "poke" "peek2" "poke2" "peek4" "poke4" "memcpy" "reload"
"cstore" "memset"
; math
"max" "min" "mid" "flr" "ceil" "cos" "sin" "atan2" "sqrt" "abs"
"rnd" "srand"
; bitwise (named forms — operator forms covered by @operator)
"band" "bor" "bxor" "bnot" "shl" "shr" "lshr" "rotl" "rotr"
; custom menu
"menuitem"
; strings / conversion
"tostr" "tonum" "chr" "ord" "sub" "split" "type"
; cart data
"cartdata" "dget" "dset"
; metatables
"setmetatable" "getmetatable" "rawset" "rawget" "rawequal" "rawlen"
; coroutines
"cocreate" "coresume" "costatus" "yield"
; error
"error" "pcall" "xpcall"))
; --- Misc ---
(comment) @comment
(hash_bang_line) @comment.documentation
(number) @number
(string) @string
(escape_sequence) @string.escape
+20
View File
@@ -0,0 +1,20 @@
; Indent inside any block-bearing construct.
(do_statement) @indent
(while_statement) @indent
(repeat_statement) @indent
(for_statement) @indent
(if_statement) @indent
(elseif_statement) @indent
(else_statement) @indent
(function_declaration) @indent
(function_definition) @indent
(table_constructor) @indent
(parenthesized_expression) @indent
(arguments) @indent
; The closing keywords/brackets sit at the parent's indent level.
"end" @end
"until" @end
"}" @end
")" @end
"]" @end
+2
View File
@@ -0,0 +1,2 @@
; ( reserved for future use — e.g. injecting hex blob content into peek/poke
; string literals, or shader code in spr() comments )
+12
View File
@@ -0,0 +1,12 @@
; Top-level functions appear in the outline.
(function_declaration
"function" @context
name: (_) @name
parameters: (parameters) @context.extra) @item
; Local functions: local function foo()
(declaration
(function_declaration
"function" @context
name: (identifier) @name
parameters: (parameters) @context.extra)) @item