Skip to content

Comments

Interpreter: fix package blocks, eval STRING context, and compound assignment#231

Merged
fglock merged 7 commits intomasterfrom
fix-interpreter-package-blocks
Feb 25, 2026
Merged

Interpreter: fix package blocks, eval STRING context, and compound assignment#231
fglock merged 7 commits intomasterfrom
fix-interpreter-package-blocks

Conversation

@fglock
Copy link
Owner

@fglock fglock commented Feb 24, 2026

Fixes interpreter-mode regressions.\n\n## Changes\n\n- BytecodeCompiler: Honor per-argument context annotations in ListNode; thread eval STRING context; fix PUSH_PACKAGE/POP_PACKAGE scoped package blocks\n- BytecodeInterpreter: Handle PUSH_PACKAGE/POP_PACKAGE opcodes\n- EvalStringHandler: Use runtime package and propagate caller context\n- SlowOpcodeHandler: Pass callContext to evalString\n- CompileOperator: Emit callContext with EVAL_STRING opcode\n- CompileAssignment: Add lvalue context check matching JVM behavior\n- InterpretedCode: Package scope support\n- StatementParser: Package block parsing fix\n- RuntimeCode: Dynamic variable management improvements\n\n## Tests improved\n- comp/package_block.t: 0/5 → 4/5\n- comp/parser.t: improvements\n- op/postfixderef.t: improvements

…s are present

When JPERL_EVAL_USE_INTERPRETER=1, eval STRING was returning undef instead
of the last expression's value in two cases:

1. eval '1; END { }' returned undef because OperatorNode("undef") placeholder
   for the END block overwrote lastResultReg after the real last expression.

2. INIT { eval 'END { }; 1' or die } returned undef because the eval body
   was compiled in VOID context, discarding the result register entirely.

Fixes:
- CompileOperator: OperatorNode("undef") with no operand in VOID context
  is a no-op, not LOAD_UNDEF (END/BEGIN/INIT/CHECK/UNITCHECK placeholders)
- BytecodeCompiler.visit(BlockNode): pre-scan to find last non-placeholder
  statement; freeze lastResultReg after visiting it so trailing placeholders
  cannot clobber it
- BytecodeCompiler.compile(): eval STRING body uses at least SCALAR context
  so the result register is always allocated (never VOID)
… scope snapshot

When JPERL_EVAL_USE_INTERPRETER=1, bare names inside eval STRING (e.g.
*named{CODE}) were resolving to main:: instead of the correct package
at the eval call site (e.g. FOO3::named inside package FOO3 { }).

Root cause: BytecodeCompiler.symbolTable defaulted to 'main' and was
not being updated with the compile-time package/pragma state.

Fix mirrors the JVM compiler's approach exactly:
- CompileOperator: at EVAL_STRING emit time, snapshot the scope manager
  (BytecodeCompiler.symbolTable.snapShot()) and store it as an
  EmitterContext in RuntimeCode.evalContext under a unique evalTag,
  exactly as EmitEval/evalTag does for JVM-compiled eval STRING.
  The evalTag is baked into the EVAL_STRING opcode (4th field).
- SlowOpcodeHandler: read evalTag from opcode, pass to EvalStringHandler.
- EvalStringHandler: retrieve saved EmitterContext from evalContext,
  use its symbolTable snapshot directly as the compile-time scope.
- BytecodeCompiler.compile(): sync package+pragmas from ctx.symbolTable
  so BytecodeCompiler resolves bare names in the eval body correctly.
- RuntimeCode.evalStringWithInterpreter: use capturedSymbolTable
  (compile-time scope from EmitEval) instead of RuntimeCode.getCurrentPackage()
  which uses caller() and cannot see scoped package blocks.

Results: op/stash.t, comp/package_block.t, comp/parser.t now all show
identical pass counts between JVM and interpreter modes.
@fglock fglock merged commit e047677 into master Feb 25, 2026
2 checks passed
@fglock fglock deleted the fix-interpreter-package-blocks branch February 25, 2026 09:16
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.

1 participant