diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 3533fd10f..70c5978ed 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); @@ -2541,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 { @@ -3437,8 +3454,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 @@ -3573,7 +3600,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(); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 372da7562..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; @@ -938,6 +965,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 @@ -986,7 +1032,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/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 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;