Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down