Skip to content

Comments

Interpreter: fix package BLOCK scoping and compound assignment on globals#228

Merged
fglock merged 11 commits intomasterfrom
fix-interpreter-strictures
Feb 24, 2026
Merged

Interpreter: fix package BLOCK scoping and compound assignment on globals#228
fglock merged 11 commits intomasterfrom
fix-interpreter-strictures

Conversation

@fglock
Copy link
Owner

@fglock fglock commented Feb 24, 2026

Four interpreter-mode fixes for eval STRING correctness.

Fixes

  1. Compound assignment sigil bug (BytecodeCompiler): varName in compound assignment path included the sigil ($main::c) but normalizeVariableName expects bare name (main::c). LOAD/STORE_GLOBAL_SCALAR was using wrong global slot.

  2. Package BLOCK runtime scoping (BytecodeCompiler + CompileOperator): Scoped package Foo { } blocks now emit PUSH_PACKAGE/POP_PACKAGE to save/restore InterpreterState.currentPackage. Added nextPackageIsScoped flag since parser never sets the isScoped annotation.

  3. eval STRING inherits runtime package (EvalStringHandler): Use InterpreterState.currentPackage (runtime) instead of currentCode.compilePackage (compile-time) so eval STRING inside a package BLOCK sees the correct package for PACKAGE.

  4. Bare block value propagation (BytecodeCompiler For3Node): isSimpleBlock path discarded body result register. Now allocates outer register and copies result before exitScope(), matching JVM EmitStatement behaviour.

Results (JPERL_EVAL_USE_INTERPRETER=1 vs without)

Test JVM Before After
comp/package_block.t 4/5 0/5 4/5
comp/parser.t 63/193 58/193 61/193

Remaining gaps (tests 45, 53 in parser.t, test 4 in package_block.t) are pre-existing missing features.

…bals

Five fixes:

1. BytecodeCompiler: strip sigil before normalizeVariableName in compound
   assignment path. varName was "$main::c" but normalize expects "main::c".
   This caused LOAD/STORE_GLOBAL_SCALAR to use wrong global slot.

2. BytecodeCompiler/CompileOperator: emit PUSH_PACKAGE for scoped package
   blocks (package Foo { }) so InterpreterState.currentPackage is saved.
   Use nextPackageIsScoped flag set by visit(BlockNode). Emit POP_PACKAGE
   at block exit to restore the runtime package via DynamicVariableManager.

3. EvalStringHandler: use InterpreterState.currentPackage (runtime) instead
   of currentCode.compilePackage (compile-time) so that eval STRING inside
   a package BLOCK sees the correct package for __PACKAGE__.

4. EvalStringHandler: substitute undef for null captured registers instead
   of capturing null — null registers crash when the eval reads them as
   the eval string operand.

5. BytecodeCompiler visit(BlockNode): when the last statement produces no
   result (lastResultReg < 0, e.g. a bare block with void semantics), emit
   LOAD_UNDEF into the outer result register instead of leaving it null.
   A null register causes NPE when RETURN reads it.

Results (JPERL_EVAL_USE_INTERPRETER=1 vs without):
- comp/package_block.t: 0/5 -> 4/5 (test 4 is pre-existing)
- comp/parser.t: 58/193 -> 61/193 (tests 36, 38, 40 fixed)
- op/signatures.t: no regression (446/908 in both modes)
@fglock fglock force-pushed the fix-interpreter-strictures branch from 62f0927 to 8f708dd Compare February 24, 2026 10:12
…normalizeVariableName

$main::result .= "..." was silently failing because the sigil was passed
to normalizeVariableName, producing "$main::result" (with sigil) as the
store key instead of "main::result" (without sigil).
When consume() expects } or ] but finds EOF or wrong token,
emit "Missing right curly or square bracket" to match Perl's
error text. Fixes comp/package_block.t test 4.
SlowOpcodeHandler.executeListSliceFrom was reading startIndex as two
16-bit halves but emitInt writes a single int slot. This caused register
index overflow (e.g. "Index 303 out of bounds") when list assignment
to mixed scalar+array targets was compiled inside eval STRING with
captured variables in a for loop.

Also fix InterpretedCode disassembler: EVAL_TRY and UNKNOWN opcode
now read/display correctly. Fix BytecodeInterpreter error message
to show full opcode value instead of truncating to 8 bits.

Fixes perf/benchmarks.t -10 regression under JPERL_EVAL_USE_INTERPRETER.
…on failure

PerlCompilerException(String message) calls RuntimeCode.caller() to get
file/line info. When thrown during interpreter eval STRING execution,
caller() may throw or return unexpected results, leaving errorMessage
empty/null and causing $@ to appear empty after eval catches the error.

Fix by wrapping caller() in try/catch — on failure, use bare message.
Also re-throw PerlCompilerException directly from BytecodeInterpreter
catch block (instead of wrapping in RuntimeException) so outer eval
handlers see the correct exception with a usable getMessage().
…on failure

PerlCompilerException(String message) calls RuntimeCode.caller() to get
file/line info. When thrown during interpreter eval STRING execution,
caller() may throw or return unexpected results, leaving errorMessage
empty/null and causing $@ to appear empty after eval catches the error.

Fix by wrapping caller() in try/catch — on failure, use bare message.
Also re-throw PerlCompilerException directly from BytecodeInterpreter
catch block (instead of wrapping in RuntimeException) so outer eval
handlers see the correct exception with a usable getMessage().
…eric check

OpcodeHandlerExtended.executeBitwiseAndBinary/OrBinary/XorBinary were
calling BitwiseOperators.bitwiseAndBinary/OrBinary/XorBinary directly,
bypassing the looksLikeNumber() check. This caused:
- String operands (e.g. "\xFF" & "\x{100}") to silently use numeric path
- Unicode codepoint error not thrown, $@ not set after eval STRING

Fix: call BitwiseOperators.bitwiseAnd/Or/Xor (the dispatchers) instead,
which check string vs numeric and route to bitwiseAndDot/OrDot/XorDot
for string operands.

Fixes op/bop.t 487/522 (was 480) under JPERL_EVAL_USE_INTERPRETER.
…eCompiler

String literal operands like 'curly'->@*, 'larry'->%*, 'name'->**
were failing with "Unsupported @ operand: StringNode". These are
symbolic references — treat the string as a global variable name
and emit LOAD_GLOBAL_ARRAY / LOAD_GLOBAL_HASH / LOAD_GLOB accordingly.

Fixes 2 extra tests in op/postfixderef.t under JPERL_EVAL_USE_INTERPRETER.
- Add DEREF_GLOB opcode (333) for $ref->** postfix glob dereference
- BytecodeCompiler: emit DEREF_GLOB for * with OperatorNode (e.g. $ref->**)
- BytecodeCompiler: emit LOAD_GLOB for * with StringNode (e.g. 'name'->**)
- SlowOpcodeHandler.executeDerefGlob: calls globDeref() matching JVM path
- BytecodeInterpreter: route DEREF_GLOB through executeSpecialIO

Also adds StringNode support for @/%/* symref deref in BytecodeCompiler
(already committed separately, included here for context).

Fixes op/postfixderef.t 80/128 (was 78) under JPERL_EVAL_USE_INTERPRETER.
- SlowOpcodeHandler.executeDerefGlob: handle RuntimeIO (PVIO) register
  value directly by wrapping in a temporary glob (matches globDeref()
  behavior for PVIO case)
- InterpretedCode disassembler: add DEREF_GLOB case (3 operands:
  rd, rs, pkgIdx) to avoid misaligned bytecode display
@fglock fglock merged commit a01997c into master Feb 24, 2026
2 checks passed
@fglock fglock deleted the fix-interpreter-strictures branch February 24, 2026 12:49
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