Skip to content

Parse remaining GRIT_POWER grammar extensions (INTABLE, TABINIT, SAFENAV, CCOMMENT) - #1

Merged
solareon merged 6 commits into
Red40-Development:fivem-luafrom
Entytaiment25:grit-power-parser-extensions
Aug 29, 2026
Merged

solareon merged 6 commits into
Red40-Development:fivem-luafrom
Entytaiment25:grit-power-parser-extensions

Conversation

@Entytaiment25

Copy link
Copy Markdown

Summary

Fills in the FiveM/GRIT-Lua syntax extensions this fork's parser/lexer didn't yet recognize, checked directly against citizenfx/lua at luaglm-548 (CMakeLists.txt GRIT_POWER_* options + lparser.c/llex.c), not just from memory of the feature names.

Each commit is one feature, self-contained:

  • destructuring locals (local a, b in t) and destructuring reassignment (a, b in t) — the two grammar positions GRIT_POWER_INTABLE covers (localstat and restassign in the reference parser). Both desugar at parse time into the equivalent field-access assignment (a, b = t.a, t.b), so the rest of the checker treats them like ordinary assignments with zero changes elsewhere. For the reassignment form, each lhs target's own name/field selects the key read back out of t (x.y reads t.y, x[1] reads t[1]); targets without a literal name or index (x[y]) are rejected, matching the same restriction getassignkey enforces in the reference implementation.
  • table constructor set sugar (GRIT_POWER_TABINIT) — .name = val as an alternative to name =/[expr] =, and either dot or bracket form may omit = expr, defaulting the value to true.
  • safe navigation on calls/method calls (GRIT_POWER_SAFENAV) — ?. and ?[ already worked; this adds ?:, ?(, ?{, ?"...". The lexer already falls back to a bare "?" token for anything other than ?./?[ (confirmed by reading lex_qm, not assumed), so this is one generic suffix_handlers["?"] that delegates to whichever plain handler follows, restricted to the funcargs/method token set so a stray t??.b still correctly errors (caught by a second review pass, see commit history).
  • C-style block comments (GRIT_POWER_CCOMMENT) — /* ... */, reusing the existing "long_comment" token so the parser needs no changes; treated exactly like --[[ ]] for line tracking, and like --[[ ]] it doesn't feed inline luacheck: ignore directives (only -- short comments do that today).

Deliberately not included: GRIT_POWER_EACH/BLOB/WOW/CHRONO/READLINE_HISTORY/DEFER_OLD are runtime/stdlib features (a __iter metamethod, C API additions, timers, REPL history, a library function), not grammar — nothing for a static parser to recognize. GRIT_POWER_NAMESPACE_SEL ships OFF in this repo's own CMakeLists.txt/makefile, so it isn't part of the actual reference build. GRIT_POWER_COMPOUND (+= etc.) turned out to already work end-to-end (compound_operators + the OpSet node), so there's no commit for it.

Known limitation (disclosed, not fixed)

All three in/desugar-style features share one artifact: if the right-hand base expression is itself undefined, you get one warning per destructured name/target, at the same line:column, since the desugar references one physical source occurrence multiple times. E.g. local a, b in undefinedTbl reports accessing undefined variable 'undefinedTbl' twice at the identical position, rather than the hand-written form's two distinct columns. Happy to add dedup if you'd rather have it, but didn't want to build a hidden-temp-local AST shape to chase a cosmetic duplicate without checking first.

Test plan

  • Every construct verified against actual parser/checker output (not just "doesn't throw" — luacheck.check_strings reports syntax errors as report items rather than exceptions, so this required checking for code 011 explicitly, not pcall alone)
  • Spec tests added per feature in spec/parser_spec.lua / spec/lexer_spec.lua, matching existing style and asserting real captured offsets, not guessed ones
  • Regression sweep after each commit against all prior forms (old table-constructor syntax, plain division/idiv, existing ?./?[, existing comments, etc.)
  • Full busted suite — not run locally (no lua/luarocks/busted in this environment, only luajit), relying on this repo's own CI to run it; flagging explicitly rather than silently omitting

Matches GRIT_POWER_INTABLE's local-declaration form: `local a, b in t`
desugars at parse time into `local a, b = t.a, t.b`, so the rest of the
checker treats it identically to a normal local assignment.

Note: if `t` itself is undefined, this reports one warning per
destructured name at the same position, since the desugar references
the single physical occurrence of `t` once per field.
Matches GRIT_POWER_INTABLE's non-local form: `a, b in t` desugars into
`a, b = t.a, t.b`. Each lhs target's own name/field selects the key read
back out of t: `a` reads t.a, `x.y` or `x[1]` reads t.y or t[1]. Targets
without a literal name or index (e.g. `x[y]`) are rejected, matching the
same restriction the reference grammar enforces at the assignment-key step.

Same disclosed caveat as the local form: if t itself is undefined, it
warns once per destructured name at the same position.
Matches GRIT_POWER_TABINIT: a table-constructor field can now be written
as `.name` (alternative to `name =`/`[expr] =`) and either dot or
bracket form may omit `= expr`, in which case the value defaults to
`true` (the synthesized True node borrows the key's own source range).
`{ .a = 1, .b, [2] = 3, [4] }` now parses like
`{ a = 1, b = true, [2] = 3, [4] = true }`.
Matches the rest of GRIT_POWER_SAFENAV: `?.` and `?[` were already
supported, but `?:` (safe method call) and `?(`/`?{`/`?"..."` (safe
funcargs) were not. The lexer already falls back to a bare "?" token for
anything other than `?.`/`?[` (it only special-cases those two), so this
adds one generic suffix_handlers["?"] that delegates to whichever plain
handler follows: safe navigation only changes runtime short-circuiting,
not the AST shape, so `t?:m()` parses to the same Invoke node as
`t:m()`. Chains like `t?.a?.b?:c()` fall out for free since the suffix
loop already re-enters on each iteration.
Matches GRIT_POWER_CCOMMENT: /* ... */ is now recognized after '/' in
lex_div, alongside the existing '/' and '//' cases. It reuses the
existing "long_comment" token type, so the parser needs no changes:
it's treated exactly like a "--[[ ]]" long comment for line-ending and
line-count tracking. Like "--[[ ]]" today, its contents are not
captured and it does not feed inline "luacheck: ignore" directives,
only "--" short comments do that.

Closer detection tracks the previous byte (mirroring the reference
lexer's last == '*' && current == '/' check), so a lone '*' followed by
non-'/' or a stray leading '/' (e.g. "/*/ ... */") doesn't close early.
suffix_handlers["?"] delegated to whatever handler matched the next
token, which included "?." and "?[" themselves, so "t??.b" and
"t??[1]" silently parsed as valid (delegating straight into those
combined-token handlers) even though the reference grammar has no
second '?' to consume there, only "?:" and "?("/"?{"/"?\"...\"" are
valid after a bare '?'. Restrict the delegation to that exact set.
@Entytaiment25

Copy link
Copy Markdown
Author

*Claude Code Assistance

@solareon
solareon merged commit f113528 into Red40-Development:fivem-lua Aug 29, 2026
0 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants