diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index d8b15e446..50516e7c0 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -707,6 +707,18 @@ public void visit(BlockNode node) { && node.elements.get(0) instanceof OperatorNode localOp && localOp.operator.equals("local"); + // If the first statement is a scoped package (package Foo { }), + // save the DynamicVariableManager level before the block body so PUSH_PACKAGE is restored. + int scopedPackageLevelReg = -1; + if (!node.elements.isEmpty() + && node.elements.get(0) instanceof OperatorNode firstOp + && (firstOp.operator.equals("package") || firstOp.operator.equals("class")) + && Boolean.TRUE.equals(firstOp.getAnnotation("isScoped"))) { + scopedPackageLevelReg = allocateRegister(); + emit(Opcodes.GET_LOCAL_LEVEL); + emitReg(scopedPackageLevelReg); + } + enterScope(); // Visit each statement in the block @@ -752,6 +764,13 @@ public void visit(BlockNode node) { // Exit scope restores register state exitScope(); + // Restore DynamicVariableManager level after scoped package block + // (undoes PUSH_PACKAGE emitted by the package operator inside the block) + if (scopedPackageLevelReg >= 0) { + emit(Opcodes.POP_LOCAL_LEVEL); + emitReg(scopedPackageLevelReg); + } + // Set lastResultReg to the outer register (or -1 if VOID context) lastResultReg = outerResultReg; } @@ -1244,7 +1263,9 @@ 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 (varName is "$x", need "x" for normalize) + String normalizedName = NameNormalizer.normalizeVariableName( + varName.substring(1), getCurrentPackage()); int nameIdx = addToStringPool(normalizedName); emit(Opcodes.LOAD_GLOBAL_SCALAR); emitReg(targetReg); @@ -1313,12 +1334,8 @@ void handleCompoundAssignment(BinaryOperatorNode node) { if (shouldBlockGlobalUnderStrictVars(varName)) { throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name"); } - - String normalizedName = NameNormalizer.normalizeVariableName(varName, getCurrentPackage()); - int nameIdx = addToStringPool(normalizedName); - emit(Opcodes.STORE_GLOBAL_SCALAR); - emit(nameIdx); - emitReg(targetReg); + // LOAD_GLOBAL_SCALAR loaded the live object; the compound-assign opcode + // already mutated it in-place via .set(), so no STORE_GLOBAL_SCALAR needed. } // The result is stored in targetReg @@ -2464,6 +2481,19 @@ void compileVariableDeclaration(OperatorNode node, String op) { lastResultReg = resultReg; return; } + // local *GLOB - localize a typeglob + if (node.operand instanceof OperatorNode sigilOp2 + && sigilOp2.operator.equals("*") + && sigilOp2.operand instanceof IdentifierNode idNode2) { + String globalName = NameNormalizer.normalizeVariableName(idNode2.name, getCurrentPackage()); + int nameIdx = addToStringPool(globalName); + int rd = allocateRegister(); + emit(Opcodes.LOCAL_GLOB); + emitReg(rd); + emit(nameIdx); + lastResultReg = rd; + return; + } throwCompilerException("Unsupported local operand: " + node.operand.getClass().getSimpleName()); } throwCompilerException("Unsupported variable declaration operator: " + op); diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index f0b90bb9a..0ab7c2261 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -2083,6 +2083,37 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c break; } + case Opcodes.FLIP_FLOP: { + // Flip-flop operator: rd = ScalarFlipFlopOperator.evaluate(id, left, right) + int rd = bytecode[pc++]; + int flipFlopId = bytecode[pc++]; + int rs1 = bytecode[pc++]; + int rs2 = bytecode[pc++]; + registers[rd] = ScalarFlipFlopOperator.evaluate( + flipFlopId, + ((RuntimeBase) registers[rs1]).scalar(), + ((RuntimeBase) registers[rs2]).scalar()); + break; + } + + case Opcodes.LOCAL_GLOB: { + // Localize a typeglob: save state, return glob + int rd = bytecode[pc++]; + int nameIdx = bytecode[pc++]; + String name = code.stringPool[nameIdx]; + RuntimeGlob glob = GlobalVariable.getGlobalIO(name); + DynamicVariableManager.pushLocalVariable(glob); + registers[rd] = glob; + break; + } + + case Opcodes.GET_LOCAL_LEVEL: { + // Save DynamicVariableManager local level into register rd + int rd = bytecode[pc++]; + registers[rd] = new RuntimeScalar(DynamicVariableManager.getLocalLevel()); + break; + } + case Opcodes.POP_PACKAGE: // Scoped package block exit — restore handled by POP_LOCAL_LEVEL. break; diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index ea24bdb7f..14aa3f819 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -21,7 +21,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Check if LHS is a scalar assignment (my $x = ... or our $x = ...) if (node.left instanceof OperatorNode) { OperatorNode leftOp = (OperatorNode) node.left; - if ((leftOp.operator.equals("my") || leftOp.operator.equals("our")) && leftOp.operand instanceof OperatorNode) { + if ((leftOp.operator.equals("my") || leftOp.operator.equals("state") || leftOp.operator.equals("our")) && leftOp.operand instanceof OperatorNode) { OperatorNode sigilOp = (OperatorNode) leftOp.operand; if (sigilOp.operator.equals("$")) { // Scalar assignment: use SCALAR context for RHS @@ -41,8 +41,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, // Special case: my $x = value if (node.left instanceof OperatorNode) { OperatorNode leftOp = (OperatorNode) node.left; - if (leftOp.operator.equals("my")) { - // Extract variable name from "my" operand + if (leftOp.operator.equals("my") || leftOp.operator.equals("state")) { + // Extract variable name from "my"/"state" operand Node myOperand = leftOp.operand; // Handle my $x (where $x is OperatorNode("$", IdentifierNode("x"))) diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java index 2bc247a80..f06559c5f 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java @@ -419,6 +419,20 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(rs1); bytecodeCompiler.emitReg(rs2); } + case "..." -> { + // Flip-flop operator (.. and ...) - per-call-site state via unique ID + // Note: numeric range (..) is handled earlier in visitBinaryOperator for list context; + // this case handles scalar-context flip-flop. + int flipFlopId = org.perlonjava.runtime.operators.ScalarFlipFlopOperator.currentId++; + org.perlonjava.runtime.operators.ScalarFlipFlopOperator op = + new org.perlonjava.runtime.operators.ScalarFlipFlopOperator(operator.equals("...")); + org.perlonjava.runtime.operators.ScalarFlipFlopOperator.flipFlops.putIfAbsent(flipFlopId, op); + bytecodeCompiler.emit(Opcodes.FLIP_FLOP); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emit(flipFlopId); + bytecodeCompiler.emitReg(rs1); + bytecodeCompiler.emitReg(rs2); + } default -> bytecodeCompiler.throwCompilerException("Unsupported operator: " + operator, tokenIndex); } diff --git a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java index 065258fa4..5980ef615 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java @@ -78,12 +78,11 @@ 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"; + // Use the runtime package at the eval call site. + // InterpreterState.currentPackage tracks runtime package changes from SET_PACKAGE/ + // PUSH_PACKAGE opcodes, so it correctly reflects the package active when eval is called. + // This matches Perl's behaviour: eval("__PACKAGE__") returns the package at call site. + String compilePackage = InterpreterState.currentPackage.get().toString(); symbolTable.setCurrentPackage(compilePackage, false); ErrorMessageUtil errorUtil = new ErrorMessageUtil(sourceName, tokens); diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index 8f2b41f9a..bab2e0d5d 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -1440,6 +1440,20 @@ public String disassemble() { break; // GENERATED_DISASM_END + case Opcodes.FLIP_FLOP: { + int ffRd = bytecode[pc++]; + int ffId = bytecode[pc++]; + int ffRs1 = bytecode[pc++]; + int ffRs2 = bytecode[pc++]; + sb.append("FLIP_FLOP r").append(ffRd).append(" = flipFlop(").append(ffId).append(", r").append(ffRs1).append(", r").append(ffRs2).append(")\n"); + break; + } + case Opcodes.LOCAL_GLOB: + sb.append("LOCAL_GLOB r").append(bytecode[pc++]).append(" = pushLocalVariable(glob '").append(stringPool[bytecode[pc++]]).append("')\n"); + break; + case Opcodes.GET_LOCAL_LEVEL: + sb.append("GET_LOCAL_LEVEL r").append(bytecode[pc++]).append("\n"); + break; case Opcodes.SET_PACKAGE: sb.append("SET_PACKAGE '").append(stringPool[bytecode[pc++]]).append("'\n"); break; diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index 46d83a45d..2937aace0 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -639,6 +639,16 @@ public class Opcodes { * Format: STORE_GLOB globReg valueReg */ public static final short STORE_GLOB = 164; + /** Localize a typeglob: rd = DynamicVariableManager.pushLocalVariable(LOAD_GLOB(nameIdx)) + * Saves current glob state and returns the glob for potential assignment. + * Format: LOCAL_GLOB rd nameIdx */ + public static final short LOCAL_GLOB = 342; + + /** Flip-flop operator: rd = ScalarFlipFlopOperator.evaluate(flipFlopId, rs1, rs2) + * flipFlopId is a unique per-call-site int constant. + * Format: FLIP_FLOP rd flipFlopId rs1 rs2 isExclusive */ + public static final short FLIP_FLOP = 343; + /** Open file: rd = IOOperator.open(ctx, args...) * Format: OPEN rd ctx argsReg */ public static final short OPEN = 165; @@ -998,6 +1008,11 @@ public class Opcodes { * Format: POP_LOCAL_LEVEL rs */ public static final short POP_LOCAL_LEVEL = 303; + /** Save current DynamicVariableManager local level into register rd. + * Used to bracket scoped package blocks so local pushes (PUSH_PACKAGE etc) are restored. + * Format: GET_LOCAL_LEVEL rd */ + public static final short GET_LOCAL_LEVEL = 341; + /** Superinstruction: foreach loop step for a global loop variable (e.g. $_). * Combines: hasNext check, next() into varReg, aliasGlobalVariable(name, varReg), conditional exit. * If iterator has next: varReg = next(), aliasGlobalVariable(name, varReg), fall through. diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java index d9032a951..7d0339d84 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java @@ -879,6 +879,11 @@ public static BlockNode parseOptionalPackageBlock(Parser parser, IdentifierNode parser.isInClassBlock = wasInClassBlock; } + // Mark as scoped so BytecodeCompiler emits PUSH_PACKAGE (not SET_PACKAGE) + // and BlockNode.visit() brackets the block with GET_LOCAL_LEVEL/POP_LOCAL_LEVEL + // to restore the runtime package after the block exits. + packageNode.setAnnotation("isScoped", Boolean.TRUE); + // Insert packageNode as first statement in block block.elements.addFirst(packageNode);