From a14c6b0b0f432c522d99559834e717d48c097b9a Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Tue, 24 Feb 2026 15:31:03 +0100 Subject: [PATCH 1/5] Interpreter: fix compound assign globals and eval STRING package context - BytecodeCompiler.handleCompoundAssignment: strip sigil before calling NameNormalizer for both LOAD_GLOBAL_SCALAR and STORE_GLOBAL_SCALAR. Previously "$main::n" was passed directly, causing a different global key than the canonical "main::n" used by plain assignment. - BytecodeCompiler.visit(BlockNode): sync ScopedSymbolTable scope with block scope so getCurrentPackage() is correct at each eval() call site inside the block (package Foo {} changes don't leak out). - CompileOperator: bake call-site package into EVAL_STRING opcode as pkgIdx, using ScopedSymbolTable.getCurrentPackage() at compile time. - SlowOpcodeHandler: read pkgIdx from EVAL_STRING opcode and pass it to EvalStringHandler as callSitePackage. - EvalStringHandler: use callSitePackage instead of currentCode.compilePackage so eval("__PACKAGE__") returns the correct package at each call site. - InterpretedCode: update disassembler to consume pkgIdx in EVAL_STRING. - CompileAssignment: use Perl-compatible error message for chop/chomp non-lvalue assignment ("Can't modify chop in scalar assignment"). Fixes comp/package_block.t (4/5, matching JVM baseline), op/chop.t (148/148). --- .../backend/bytecode/BytecodeCompiler.java | 19 ++++++++++++++----- .../backend/bytecode/CompileAssignment.java | 8 +++++++- .../backend/bytecode/CompileOperator.java | 9 ++++++++- .../backend/bytecode/EvalStringHandler.java | 14 ++++++-------- .../backend/bytecode/InterpretedCode.java | 4 +++- .../backend/bytecode/SlowOpcodeHandler.java | 10 +++++++--- 6 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 3533fd10f..574f88446 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -42,7 +42,6 @@ public class BytecodeCompiler implements Visitor { // Stack to save/restore register state when entering/exiting scopes private final Stack savedNextRegister = new Stack<>(); private final Stack savedBaseRegister = new Stack<>(); - // Loop label stack for last/next/redo control flow // Each entry tracks loop boundaries and optional label private final Stack loopStack = new Stack<>(); @@ -708,6 +707,10 @@ public void visit(BlockNode node) { && localOp.operator.equals("local"); enterScope(); + // Sync symbolTable package scope with block scope so that getCurrentPackage() + // at each eval() call site inside this block reflects the correct package. + // e.g. after "package Foo {}" closes, outer eval() call sites see "main" again. + int stScopeIdx = symbolTable.enterScope(); // Visit each statement in the block int numStatements = node.elements.size(); @@ -749,7 +752,8 @@ public void visit(BlockNode node) { emitReg(lastResultReg); } - // Exit scope restores register state + // Exit scope restores register state and symbolTable package + symbolTable.exitScope(stScopeIdx); exitScope(); // Set lastResultReg to the outer register (or -1 if VOID context) @@ -1243,7 +1247,10 @@ void handleCompoundAssignment(BinaryOperatorNode node) { // Global variable - need to load it first isGlobal = true; targetReg = allocateRegister(); - String normalizedName = NameNormalizer.normalizeVariableName(varName, getCurrentPackage()); + // Strip sigil before normalizing ("$x" -> "x", "$main::n" -> "main::n") + // matching CompileAssignment.java's pattern for STORE_GLOBAL_SCALAR + String bareVarName = varName.substring(1); + String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage()); int nameIdx = addToStringPool(normalizedName); emit(Opcodes.LOAD_GLOBAL_SCALAR); emitReg(targetReg); @@ -1303,7 +1310,7 @@ void handleCompoundAssignment(BinaryOperatorNode node) { emitReg(targetReg); emitReg(valueReg); - // If it's a global variable, store it back + // If it's a global variable, store the result back if (isGlobal) { OperatorNode leftOp = (OperatorNode) node.left; String varName = "$" + ((IdentifierNode) leftOp.operand).name; @@ -1313,7 +1320,9 @@ void handleCompoundAssignment(BinaryOperatorNode node) { throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name"); } - String normalizedName = NameNormalizer.normalizeVariableName(varName, getCurrentPackage()); + // Strip sigil before normalizing — same as CompileAssignment.java + String bareVarName = varName.substring(1); + String normalizedName = NameNormalizer.normalizeVariableName(bareVarName, getCurrentPackage()); int nameIdx = addToStringPool(normalizedName); emit(Opcodes.STORE_GLOBAL_SCALAR); emit(nameIdx); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 372da7562..e11f90bcd 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -986,7 +986,13 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.throwCompilerException("Assignment to unsupported array dereference"); } } else { - bytecodeCompiler.throwCompilerException("Assignment to unsupported operator: " + leftOp.operator); + // Match Perl's error message for non-lvalue operators (e.g. chop, chomp) + String op = leftOp.operator; + if (op.equals("chop") || op.equals("chomp")) { + bytecodeCompiler.throwCompilerException("Can't modify " + op + " in scalar assignment"); + } else { + bytecodeCompiler.throwCompilerException("Can't modify " + op + " in assignment"); + } } } else if (node.left instanceof IdentifierNode) { String varName = ((IdentifierNode) node.left).name; diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index c419ee711..96c50ce03 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -835,10 +835,17 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode // Allocate register for result int rd = bytecodeCompiler.allocateRegister(); - // Emit direct opcode EVAL_STRING + // Bake the call-site package into the opcode at compile time. + // ScopedSymbolTable.getCurrentPackage() reflects the packageStack at this + // exact node — e.g. "Foo" inside "package Foo { }", "main" outside. + // This is the interpreter equivalent of the JVM evalTag's ctx.symbolTable. + // Format: EVAL_STRING rd stringReg pkgIdx + int pkgIdx = bytecodeCompiler.addToStringPool( + bytecodeCompiler.getCurrentPackage()); bytecodeCompiler.emitWithToken(Opcodes.EVAL_STRING, node.getIndex()); bytecodeCompiler.emitReg(rd); bytecodeCompiler.emitReg(stringReg); + bytecodeCompiler.emit(pkgIdx); bytecodeCompiler.lastResultReg = rd; } else { diff --git a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java index 13db3320e..352bbdb3b 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java @@ -49,7 +49,8 @@ public static RuntimeScalar evalString(String perlCode, InterpretedCode currentCode, RuntimeBase[] registers, String sourceName, - int sourceLine) { + int sourceLine, + String callSitePackage) { try { // Step 1: Clear $@ at start of eval GlobalVariable.getGlobalVariable("main::@").set(""); @@ -76,13 +77,10 @@ public static RuntimeScalar evalString(String perlCode, symbolTable.warningFlagsStack.push((java.util.BitSet) currentCode.warningFlags.clone()); } - // Inherit the compile-time package from the calling code, matching what - // evalStringHelper (JVM path) does via capturedSymbolTable.snapShot(). - // Using the compile-time package (not InterpreterState.currentPackage which is - // the runtime package) ensures bare names like *named resolve to FOO3::named - // when the eval call site is inside "package FOO3". - String compilePackage = (currentCode != null) ? currentCode.compilePackage : "main"; - symbolTable.setCurrentPackage(compilePackage, false); + // Use the call-site package baked into the EVAL_STRING opcode at compile time. + // Each eval() call site emits its own pkgIdx from ScopedSymbolTable.getCurrentPackage() + // at that exact point — equivalent to the JVM evalTag's ctx.symbolTable package. + symbolTable.setCurrentPackage(callSitePackage, false); ErrorMessageUtil errorUtil = new ErrorMessageUtil(sourceName, tokens); EmitterContext ctx = new EmitterContext( diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index d3d5499f0..a8a963cd7 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -1236,7 +1236,9 @@ public String disassemble() { case Opcodes.EVAL_STRING: rd = bytecode[pc++]; rs = bytecode[pc++]; - sb.append("EVAL_STRING r").append(rd).append(" = eval(r").append(rs).append(")\n"); + int evalPkg = bytecode[pc++]; + sb.append("EVAL_STRING r").append(rd).append(" = eval(r").append(rs) + .append(", pkg=").append(stringPool[evalPkg]).append(")\n"); break; case Opcodes.SELECT_OP: rd = bytecode[pc++]; diff --git a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java index 09eb9aac0..d77c53efd 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -240,6 +240,9 @@ public static int executeEvalString( int rd = bytecode[pc++]; int stringReg = bytecode[pc++]; + // Read call-site package baked in by CompileOperator — interpreter equivalent + // of the JVM evalTag's ctx.symbolTable.getCurrentPackage(). + String callSitePackage = code.stringPool[bytecode[pc++]]; // Get the code string - handle both RuntimeScalar and RuntimeList (from string interpolation) RuntimeBase codeValue = registers[stringReg]; @@ -255,10 +258,11 @@ public static int executeEvalString( // Call EvalStringHandler to parse, compile, and execute RuntimeScalar result = EvalStringHandler.evalString( perlCode, - code, // Current InterpretedCode for context - registers, // Current registers for variable access + code, // Current InterpretedCode for context + registers, // Current registers for variable access code.sourceName, - code.sourceLine + code.sourceLine, + callSitePackage // Call-site package baked in at compile time ); registers[rd] = result; From fdddcda1bd59f0b0b2ed12eaef043cdf36018ef5 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Tue, 24 Feb 2026 16:21:55 +0100 Subject: [PATCH 2/5] Interpreter: fix NPE from bare block in non-VOID context (comp/parser.t 36,38,40) When { q,bar, } is parsed as For3Node(isSimpleBlock=true), the body executes but the block itself produces no value (lastResultReg = -1). In a non-VOID context the enclosing BlockNode allocates outerResultReg but never initialises it, leaving a null register slot that causes ARRAY_SIZE and similar ops to NPE at runtime. Fix: emit LOAD_UNDEF into a fresh register in the isSimpleBlock path when currentCallContext != VOID, so callers always get a defined result. The VOID path still sets lastResultReg = -1 as before (no allocation). Fixes comp/parser.t tests 36, 38, 40 under JPERL_EVAL_USE_INTERPRETER=1. --- .../backend/bytecode/BytecodeCompiler.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 574f88446..b7be57c54 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -3446,8 +3446,18 @@ public void visit(For1Node node) { globalLoopVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage()); } - // Step 1: Evaluate list in list context - node.list.accept(this); + // Step 1: Evaluate list in list context (guard against null from malformed parse) + // A null list can occur when the parser misinterprets e.g. "{ q,bar, }" as a for-loop. + // Emit an empty list so all subsequent iterator opcodes operate on valid registers. + if (node.list == null) { + int emptyReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emitReg(emptyReg); + emit(0); // zero elements + lastResultReg = emptyReg; + } else { + node.list.accept(this); + } int listReg = lastResultReg; // Step 2: Create iterator from the list @@ -3582,7 +3592,19 @@ public void visit(For3Node node) { if (node.body != null) { node.body.accept(this); } - lastResultReg = -1; // Block returns empty + // Bare blocks produce no value; set lastResultReg to -1. + // If we are in a non-VOID context, the enclosing BlockNode's + // outerResultReg will be left uninitialized — emit LOAD_UNDEF + // here so that any downstream opcode that reads the register + // (e.g. ARRAY_SIZE from a scalar() wrapper) sees a defined value. + if (currentCallContext != RuntimeContextType.VOID) { + int rd = allocateRegister(); + emit(Opcodes.LOAD_UNDEF); + emitReg(rd); + lastResultReg = rd; + } else { + lastResultReg = -1; + } } finally { // Exit scope to clean up lexical variables exitScope(); From f88c800f06fc61f4a9d9f650c7eee45a2ef45ec4 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Tue, 24 Feb 2026 16:31:42 +0100 Subject: [PATCH 3/5] Interpreter: fix op/postfixderef.t tests 27-29,59,63 --- .../backend/bytecode/BytecodeInterpreter.java | 12 ++++-------- .../backend/bytecode/CompileAssignment.java | 19 +++++++++++++++++++ .../runtime/runtimetypes/RuntimeScalar.java | 7 ++++++- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 21bc64746..09cbf71b7 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1381,15 +1381,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; - // Only dereference if it's a RuntimeScalar with REFERENCE type if (value instanceof RuntimeScalar) { - RuntimeScalar scalar = (RuntimeScalar) value; - if (scalar.type == RuntimeScalarType.REFERENCE) { - registers[rd] = scalar.scalarDeref(); - } else { - // Non-reference scalar, just copy - registers[rd] = value; - } + // scalarDeref() handles REFERENCE (returns target), + // GLOBREFERENCE (throws "Not a SCALAR reference"), + // UNDEF (autovivifies), STRING (symref or strict error). + registers[rd] = ((RuntimeScalar) value).scalarDeref(); } else { // RuntimeList or other types, pass through registers[rd] = value; diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index e11f90bcd..749c841e1 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -938,6 +938,25 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(globReg); bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = globReg; + } else if (leftOp.operator.equals("*") && leftOp.getAnnotation("postfixDeref") != null) { + // Postfix-deref glob lvalue: 'name'->** = value + // Evaluate the operand expression to get the glob name as a scalar, + // then load that glob dynamically and store the RHS into it. + leftOp.operand.accept(bytecodeCompiler); + int nameScalarReg = bytecodeCompiler.lastResultReg; + + int globReg = bytecodeCompiler.allocateRegister(); + int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); + bytecodeCompiler.emitWithToken(Opcodes.LOAD_GLOB_DYNAMIC, node.getIndex()); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emitReg(nameScalarReg); + bytecodeCompiler.emit(pkgIdx); + + bytecodeCompiler.emit(Opcodes.STORE_GLOB); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = globReg; } else if (leftOp.operator.equals("pos")) { // pos($var) = value - lvalue assignment to regex position diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index e3f201f33..9288010f2 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -1108,10 +1108,15 @@ public RuntimeScalar scalarDerefNonStrict(String packageName) { return switch (type) { case REFERENCE -> (RuntimeScalar) value; case TIED_SCALAR -> tiedFetch().scalarDerefNonStrict(packageName); - default -> { + case GLOBREFERENCE -> + // GLOBREFERENCE (like *STDOUT{IO}) is not a scalar reference even without strict refs + throw new PerlCompilerException("Not a SCALAR reference"); + case STRING, BYTE_STRING, UNDEF -> { String varName = NameNormalizer.normalizeVariableName(this.toString(), packageName); yield GlobalVariable.getGlobalVariable(varName); } + default -> + throw new PerlCompilerException("Not a SCALAR reference"); }; } From ac1aea4711415358272c2f26cc03a6df6f3066a5 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Tue, 24 Feb 2026 16:45:04 +0100 Subject: [PATCH 4/5] Interpreter: fix DEREF strict/non-strict split at compile time --- .../backend/bytecode/BytecodeCompiler.java | 16 ++++++++++++---- .../backend/bytecode/BytecodeInterpreter.java | 12 ++++++++---- .../runtime/runtimetypes/RuntimeScalar.java | 7 +------ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index b7be57c54..70c5978ed 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -2550,11 +2550,19 @@ void compileVariableReference(OperatorNode node, String op) { node.operand.accept(this); int refReg = lastResultReg; - // Dereference the result + // Dereference the result — pick strict or non-strict opcode at compile time int rd = allocateRegister(); - emitWithToken(Opcodes.DEREF, node.getIndex()); - emitReg(rd); - emitReg(refReg); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex()); + emitReg(rd); + emitReg(refReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex()); + emitReg(rd); + emitReg(refReg); + emit(pkgIdx); + } lastResultReg = rd; } else { diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 09cbf71b7..21bc64746 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1381,11 +1381,15 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; + // Only dereference if it's a RuntimeScalar with REFERENCE type if (value instanceof RuntimeScalar) { - // scalarDeref() handles REFERENCE (returns target), - // GLOBREFERENCE (throws "Not a SCALAR reference"), - // UNDEF (autovivifies), STRING (symref or strict error). - registers[rd] = ((RuntimeScalar) value).scalarDeref(); + RuntimeScalar scalar = (RuntimeScalar) value; + if (scalar.type == RuntimeScalarType.REFERENCE) { + registers[rd] = scalar.scalarDeref(); + } else { + // Non-reference scalar, just copy + registers[rd] = value; + } } else { // RuntimeList or other types, pass through registers[rd] = value; diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index 9288010f2..e3f201f33 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -1108,15 +1108,10 @@ public RuntimeScalar scalarDerefNonStrict(String packageName) { return switch (type) { case REFERENCE -> (RuntimeScalar) value; case TIED_SCALAR -> tiedFetch().scalarDerefNonStrict(packageName); - case GLOBREFERENCE -> - // GLOBREFERENCE (like *STDOUT{IO}) is not a scalar reference even without strict refs - throw new PerlCompilerException("Not a SCALAR reference"); - case STRING, BYTE_STRING, UNDEF -> { + default -> { String varName = NameNormalizer.normalizeVariableName(this.toString(), packageName); yield GlobalVariable.getGlobalVariable(varName); } - default -> - throw new PerlCompilerException("Not a SCALAR reference"); }; } From 4f927aacc32b4022b0bba2bb73a7237163d31994 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Tue, 24 Feb 2026 17:03:58 +0100 Subject: [PATCH 5/5] Interpreter: fix MATCH_REGEX cast and local *glob assignment --- .../backend/bytecode/CompileAssignment.java | 27 +++++++++++++++++++ .../bytecode/OpcodeHandlerExtended.java | 8 +++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 749c841e1..172e34fbf 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -464,6 +464,33 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.lastResultReg = arrayReg; return; + } else if (sigilOp.operator.equals("*") && sigilOp.operand instanceof IdentifierNode) { + // Handle local *glob = value + String globName = ((IdentifierNode) sigilOp.operand).name; + String normalizedName = NameNormalizer.normalizeVariableName(globName, bytecodeCompiler.getCurrentPackage()); + int nameIdx = bytecodeCompiler.addToStringPool(normalizedName); + + // Compile RHS first + node.right.accept(bytecodeCompiler); + int valueReg = bytecodeCompiler.lastResultReg; + + // Load the glob + int globReg = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(Opcodes.LOAD_GLOB); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emit(nameIdx); + + // Push glob to local variable stack for restoration on scope exit + bytecodeCompiler.emit(Opcodes.PUSH_LOCAL_VARIABLE); + bytecodeCompiler.emitReg(globReg); + + // Store the RHS value into the glob + bytecodeCompiler.emit(Opcodes.STORE_GLOB); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emitReg(valueReg); + + bytecodeCompiler.lastResultReg = globReg; + return; } else if (sigilOp.operator.equals("%") && sigilOp.operand instanceof IdentifierNode) { // Handle local %hash = value String varName = "%" + ((IdentifierNode) sigilOp.operand).name; diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index 5399c907a..4601b0a25 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -735,8 +735,8 @@ public static int executeMatchRegex(int[] bytecode, int pc, RuntimeBase[] regist int regexReg = bytecode[pc++]; int ctx = bytecode[pc++]; registers[rd] = RuntimeRegex.matchRegex( - (RuntimeScalar) registers[regexReg], // quotedRegex first - (RuntimeScalar) registers[stringReg], // string second + registers[regexReg].scalar(), // quotedRegex first + registers[stringReg].scalar(), // string second ctx ); return pc; @@ -752,8 +752,8 @@ public static int executeMatchRegexNot(int[] bytecode, int pc, RuntimeBase[] reg int regexReg = bytecode[pc++]; int ctx = bytecode[pc++]; RuntimeBase matchResult = RuntimeRegex.matchRegex( - (RuntimeScalar) registers[regexReg], // quotedRegex first - (RuntimeScalar) registers[stringReg], // string second + registers[regexReg].scalar(), // quotedRegex first + registers[stringReg].scalar(), // string second ctx ); // Negate the boolean result