Files
zed-p8/grammars/p8-cart/grammar.js
T
kistaro 39d77a8cae 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>
2026-05-01 12:50:41 -07:00

75 lines
2.7 KiB
JavaScript

/**
* tree-sitter grammar for the PICO-8 .p8 cartridge text format.
*
* The .p8 format is a flat text container divided into named sections
* delimited by lines of the form `__name__`. The first section is
* always `__lua__` and contains the cart's Lua source; the remaining
* sections (`__gfx__`, `__gff__`, `__label__`, `__map__`, `__sfx__`,
* `__music__`) hold hex-encoded asset data. The file begins with a
* fixed magic header line and a `version N` line.
*
* This grammar is intentionally minimal: it parses the section
* structure and exposes each section's body as a single named node
* so that injection queries (see languages/pico8-cart/injections.scm)
* can hand the contents off to other languages — most importantly
* Lua for the `__lua__` section.
*/
module.exports = grammar({
name: 'p8_cart',
// Whitespace is significant inside hex sections, so we don't skip it.
extras: $ => [],
rules: {
cartridge: $ => seq(
optional($.header),
optional($.version),
repeat($.section),
),
header: $ => /pico-8 cartridge \/\/[^\n]*\n/,
version: $ => /version[ \t]+\d+\n/,
section: $ => choice(
$.lua_section,
$.gfx_section,
$.gff_section,
$.label_section,
$.map_section,
$.sfx_section,
$.music_section,
$.unknown_section,
),
lua_section: $ => seq($.lua_marker, optional($.lua_content)),
gfx_section: $ => seq($.gfx_marker, optional($.body)),
gff_section: $ => seq($.gff_marker, optional($.body)),
label_section: $ => seq($.label_marker, optional($.body)),
map_section: $ => seq($.map_marker, optional($.body)),
sfx_section: $ => seq($.sfx_marker, optional($.body)),
music_section: $ => seq($.music_marker, optional($.body)),
unknown_section: $ => seq($.section_marker, optional($.body)),
lua_marker: $ => token(prec(2, '__lua__\n')),
gfx_marker: $ => token(prec(2, '__gfx__\n')),
gff_marker: $ => token(prec(2, '__gff__\n')),
label_marker: $ => token(prec(2, '__label__\n')),
map_marker: $ => token(prec(2, '__map__\n')),
sfx_marker: $ => token(prec(2, '__sfx__\n')),
music_marker: $ => token(prec(2, '__music__\n')),
section_marker: $ => token(prec(1, /__[a-z][a-z0-9_]*__\n/)),
lua_content: $ => repeat1($.line),
body: $ => repeat1($.line),
// A single physical line. The lexer prefers section markers over
// generic lines via the precedence above, so a line that happens
// to be exactly `__name__\n` will tokenize as a marker, not a line.
line: $ => choice(
token(prec(0, /[^\n]*\n/)),
token(prec(0, /[^\n]+/)), // final line with no trailing newline
),
},
});