Summary
Python class-level constants (e.g., ParserStateFlags.PST_EOFTOKEN = 4096) are lowered to Ruby module-level local variables (e.g., parserstateflags_pst_eoftoken = 4096). These are invisible inside class method bodies in Ruby, causing NameError: undefined local variable at runtime.
Reproduction
Source Python:
class ParserStateFlags:
PST_EOFTOKEN = 4096
class Lexer:
def _read_word_internal(self):
if self._parser_state & ParserStateFlags.PST_EOFTOKEN != 0:
...
Transpiled Ruby:
parserstateflags_pst_eoftoken = 4096 # module-level local variable
class Lexer
def _read_word_internal
if self._parser_state & parserstateflags_pst_eoftoken != 0 # NameError!
Expected Ruby:
PARSERSTATEFLAGS_PST_EOFTOKEN = 4096 # Ruby constant (uppercase)
class Lexer
def _read_word_internal
if self._parser_state & PARSERSTATEFLAGS_PST_EOFTOKEN != 0 # works
Impact
Fatal crash — the Parable Ruby backend can't parse anything. 80 module-level variables affected, 45+ references from inside class methods.
Affected variable families: parserstateflags_*, tokentype_*, dolbracestate_*, word_ctx_*, matchedpairflags_*.
Root cause
In Python, module-level variables are accessible from any scope. In Ruby, local variables are scoped to their enclosing block — class bodies create new scopes. The Ruby backend needs to emit these as Ruby constants (uppercase names) to make them accessible inside class methods.
Discovered via ldayton/Parable#413.
Summary
Python class-level constants (e.g.,
ParserStateFlags.PST_EOFTOKEN = 4096) are lowered to Ruby module-level local variables (e.g.,parserstateflags_pst_eoftoken = 4096). These are invisible inside class method bodies in Ruby, causingNameError: undefined local variableat runtime.Reproduction
Source Python:
Transpiled Ruby:
Expected Ruby:
Impact
Fatal crash — the Parable Ruby backend can't parse anything. 80 module-level variables affected, 45+ references from inside class methods.
Affected variable families:
parserstateflags_*,tokentype_*,dolbracestate_*,word_ctx_*,matchedpairflags_*.Root cause
In Python, module-level variables are accessible from any scope. In Ruby, local variables are scoped to their enclosing block — class bodies create new scopes. The Ruby backend needs to emit these as Ruby constants (uppercase names) to make them accessible inside class methods.
Discovered via ldayton/Parable#413.