Skip to content

Latest commit

 

History

History
148 lines (123 loc) · 9.17 KB

File metadata and controls

148 lines (123 loc) · 9.17 KB

Ren'Py Parser Support Matrix

This document classifies every Ren'Py syntax construct the parser encounters, and describes how the parser handles it. The parser is intentionally conservative: it would rather emit an ImportWarning and preserve the source verbatim as an UnsupportedStatement than silently misinterpret the construct.

Classification legend

  • Supported — parsed into a typed IR statement and consumed by the importer. Source locations are preserved.
  • Partially supported — parsed into a typed IR statement but with known limitations documented below. The importer uses what it can.
  • Observed but opaque — recognized as a block boundary (so the parser does not misinterpret nested content) but the body is kept verbatim as UnsupportedStatement. The user sees a warning.
  • Unsupported with warning — not recognized at all; the line is recorded as UnsupportedStatement with a reason, and an ImportWarning is emitted. The parser continues.

Statement-level constructs

Construct Form Classification Notes
label label name: / label name(args): Supported Body is recursively parsed.
jump jump target Supported Edge of kind flow, transfer jump.
jump ... if jump target if cond Partially supported Non-standard Ren'Py syntax; tolerated. Condition is preserved on the edge.
call call target / call target(args) Supported Edge of kind flow, transfer call.
call ... if call target if cond Partially supported Non-standard; tolerated.
return return Supported In a sandbox event, becomes a hidden return edge to the inferred hub.
menu menu: / menu name: / menu("arg"): Supported Each choice becomes an edge of kind flow, transfer choice, with choiceText.
menu choice "text": / 'text': / "text" if cond: Supported Condition preserved on the edge.
if / elif / else if cond: / elif cond: / else: Supported Branches parsed recursively; conditions captured as ConditionExpression.
default default var = value Supported Creates a GameVariable with defaultValue. Type inferred from literal.
define Character define id = Character("Name", color="...") Supported Creates a Character. Only the name and color keyword args are extracted; other kwargs are ignored.
define (non-Character) define X = 5 Observed but opaque Treated as unsupported; the right-hand side is not interpreted.
define gui.* / define config.* define gui.text_size = 28 Observed but opaque Proven non-story configuration; filtered out of normal diagnostics and reported as one informational count.
$ assignment $ var op value (op ∈ =, +=, -=, *=, /=) Supported Recorded as VariableMutation on the enclosing node.
$ (other expression) $ foo(bar) Unsupported with warning Recorded as unsupported; importer ignores.
python: block python: followed by indented block Observed but opaque The block is captured as a single UnsupportedStatement; its body is not interpreted. Variables referenced inside are NOT indexed.
init python: block init python: / init -1 python: Observed but opaque Same as python:.
screen block screen name(args): Observed but opaque The entire screen body is captured as one UnsupportedStatement.
transform block transform name: Observed but opaque Same.
image statement image name = ... / image name: Unsupported with warning Line recorded as unsupported.
layeredimage block layeredimage name: Observed but opaque Same.
scene scene bg with fade Supported (visual) Recognized as a VisualStatement. The image expression maps to a BackgroundBlock for imported scene documents. Note: the architectural scene node kind is inferred separately from label-name heuristics.
show / hide show sprite at position Supported (visual) Recognized as a VisualStatement; no scene block is produced.
with with fade Supported (visual) Recognized as a VisualStatement; no scene block.
play / queue / stop play music "track.mp3" Supported (visual) play music/stop music/play sound map to MusicBlock/SoundBlock; other channels are ignored.
voice voice "audio/line.ogg" Supported (visual) Recognized as a VisualStatement; no scene block.
pause pause / pause 2.0 Supported (visual) Recognized as a VisualStatement; no scene block.
window / centered / vcentered / nvl centered "text" Supported (visual) centered "text" / vcentered "text" map to NarrationBlock; the rest are ignored.
narrator dialogue "text" Supported Recorded as DialogueStatement with no character. Does not produce a graph node; only used for character co-occurrence.
character dialogue id "text" Supported Recorded as DialogueStatement with character = id. Links the character to the enclosing node.
Custom user-defined statement mystatement arg1 arg2 Unsupported with warning Line recorded as unsupported with reason = "mystatement".
Comment # ... Supported (stripped) Inline # is stripped from line text; full-line comments are skipped.
Blank line (empty) Supported (skipped) No-op.

Indentation and block boundaries

The parser uses indentation to determine block boundaries, matching Ren'Py's own behavior. Tabs are expanded to the next tab stop (width 4) for indentation calculation; mixing tabs and spaces inside the same block is not supported and produces a warning.

A line ending with : is treated as a block header. The parser then recurses into the indented body.

String literal handling

Double-quoted ("...") and single-quoted ('...') string literals are supported in:

  • menu choice text (including if cond forms);
  • character dialogue (narrator and character-prefixed);
  • Character("Name") first argument;
  • color="..." keyword argument.

Escaped quotes inside strings (\") are tolerated. The parser also strips the generator's hidden {#sa_<hex>} choice disambiguation tags on re-import (both 8-hex and 16-hex suffixes) so repeated identical choices re-import cleanly. Triple-quoted strings and f-strings are not supported.

Condition parsing

The condition parser (condition-parser.ts) extracts:

  • the raw condition text (preserved verbatim);
  • the set of variable names referenced (used to populate variableReads and to drive variable focus mode).

It does not evaluate conditions. It tokenizes Python identifiers, skips string literals, and excludes Python keywords (True, False, None, and, or, not, in, is, for, while, return, ...). Anything else inside a condition (function calls, attribute access, list/dict indexing, comprehensions) is preserved as raw text and the variable-name extraction is best-effort.

What the parser deliberately does NOT do

  • It does not resolve Ren'Py auto-callables or screen actions.
  • It does not interpret python: blocks; variables mutated inside python: blocks are NOT reflected in the variable index. This is a known limitation — users who do significant state mutation inside python: blocks will see an incomplete variable influence view.
  • It does not follow call screen or menu with a screen argument.
  • It does not interpret transform or image definitions.
  • It does not parse string interpolation [variable] inside dialogue; such references are not added to variableReads. This is a known limitation.
  • It never executes Python.

Fixtures

The shipped fixtures in src/lib/fixtures/index.ts exercise the parser:

  1. linear-vn — covers label, jump, menu, if/elif/else, default, define Character, $ assignment, character dialogue, narrator dialogue, and return.
  2. sandbox-vn — covers call/return cycles, gated events with if conditions on call, hub labels that jump to themselves, and multi-file projects.
  3. malformed-vn — covers missing colons, duplicate labels, empty default = 0, python: and screen: blocks, and a broken define statement. The parser must survive all of these and produce warnings instead of throwing.

Adding support for a new construct

  1. Add a new variant to ParsedStatement in parser-types.ts.
  2. Add a regex and a branch in block-parser.ts parseStatement().
  3. Decide whether the importer needs to consume the new statement. If yes, extend build-game-model.ts (or extract-scene-documents.ts). If no, leave it as UnsupportedStatement.
  4. Add a fixture under src/lib/fixtures/index.ts that exercises the new construct.
  5. Update this matrix.
  6. Run bun run typecheck and bun run test to verify nothing broke.

Testing philosophy

The parser is tested by behavior, not by IR shape: fixtures are parsed, and the resulting GameProject/scene documents are asserted to contain the expected nodes, edges, variable references, and blocks. The intermediate ParsedStatement IR is not asserted against in tests — it is an implementation detail that may change as long as the importer still produces the same Game Model.