Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
9 changes: 0 additions & 9 deletions dev/import-perl5/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -400,15 +400,6 @@ imports:
target: perl5_t/Term-Table
type: directory

# From CPAN distribution
- source: perl5/cpan/Time-Local/lib/Time/Local.pm
target: src/main/perl/lib/Time/Local.pm

# Tests for distribution
- source: perl5/cpan/Time-Local/t
target: perl5_t/Time-Local
type: directory

# Add more imports below as needed
# Example with minimal fields:
# - source: perl5/lib/SomeModule.pm
Expand Down
43 changes: 31 additions & 12 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.perlonjava.backend.bytecode;

import org.perlonjava.frontend.analysis.FindDeclarationVisitor;
import org.perlonjava.frontend.analysis.Visitor;
import org.perlonjava.backend.jvm.EmitterMethodCreator;
import org.perlonjava.backend.jvm.EmitterContext;
Expand Down Expand Up @@ -494,7 +493,18 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
// Use the calling context from EmitterContext for top-level expressions
// This is crucial for eval STRING to propagate context correctly
currentCallContext = ctx.contextType;

// Inherit package from the JVM compiler context so that eval STRING
// compiles unqualified names in the caller's package, not "main".
// This is compile-time package propagation — it sets the symbol table's
// current package so the parser/emitter qualify names correctly.
// Note: the *runtime* package (InterpreterState.currentPackage) is a
// separate concern used only by caller(); it must be scoped via
// DynamicVariableManager around eval execution to prevent leaking.
// See InterpreterState.currentPackage javadoc for details.
if (ctx.symbolTable != null) {
symbolTable.setCurrentPackage(ctx.symbolTable.getCurrentPackage(),
ctx.symbolTable.currentPackageIsClass());
}
}

// If we have captured variables, allocate registers for them
Expand Down Expand Up @@ -547,7 +557,7 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
}

// Build InterpretedCode
InterpretedCode result = new InterpretedCode(
return new InterpretedCode(
toShortArray(),
constants.toArray(),
stringPool.toArray(new String[0]),
Expand All @@ -563,9 +573,6 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
warningFlags, // Warning flags for eval STRING inheritance
symbolTable.getCurrentPackage() // Compile-time package for eval STRING name resolution
);
result.containsRegex = FindDeclarationVisitor.findOperator(node, "matchRegex") != null
|| FindDeclarationVisitor.findOperator(node, "replaceRegex") != null;
return result;
}

// =========================================================================
Expand Down Expand Up @@ -4107,12 +4114,6 @@ public void visit(For1Node node) {
variableScopes.peek().put(varName, varReg);
allDeclaredVariables.put(varName, varReg);
}
} else if (varOp.operator.equals("$") && varOp.operand instanceof IdentifierNode
&& globalLoopVarName == null) {
String varName = "$" + ((IdentifierNode) varOp.operand).name;
if (hasVariable(varName)) {
variableScopes.peek().put(varName, varReg);
}
}
}

Expand Down Expand Up @@ -4208,6 +4209,17 @@ public void visit(For3Node node) {
if (currentCallContext != RuntimeContextType.VOID) {
outerResultReg = allocateRegister();
}

int labelIdx = -1;
int exitPcPlaceholder = -1;
if (node.labelName != null) {
labelIdx = addToStringPool(node.labelName);
emit(Opcodes.PUSH_LABELED_BLOCK);
emit(labelIdx);
exitPcPlaceholder = bytecode.size();
emitInt(0);
}

enterScope();
try {
// Just execute the body once, no loop
Expand All @@ -4224,6 +4236,13 @@ public void visit(For3Node node) {
// Exit scope to clean up lexical variables
exitScope();
}

if (node.labelName != null) {
emit(Opcodes.POP_LABELED_BLOCK);
int exitPc = bytecode.size();
patchJump(exitPcPlaceholder, exitPc);
}

lastResultReg = outerResultReg;
return;
}
Expand Down
108 changes: 76 additions & 32 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
String frameSubName = subroutineName != null ? subroutineName : (code.subName != null ? code.subName : "(eval)");
InterpreterState.push(code, framePackageName, frameSubName);

int regexLocalLevel = -1;
if (code.containsRegex) {
regexLocalLevel = DynamicVariableManager.getLocalLevel();
RuntimeRegexState.pushLocal();
}

// Pure register file (NOT stack-based - matches compiler for control flow correctness)
RuntimeBase[] registers = new RuntimeBase[code.maxRegisters];

Expand All @@ -75,6 +69,15 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
// When exception occurs, pop from stack and jump to catch PC
java.util.Stack<Integer> evalCatchStack = new java.util.Stack<>();

// Labeled block stack for non-local last/next/redo handling.
// When a function call returns a RuntimeControlFlowList, we check this stack
// to see if the label matches an enclosing labeled block.
java.util.Stack<int[]> labeledBlockStack = new java.util.Stack<>();
// Each entry is [labelStringPoolIdx, exitPc]

try {
outer:
while (true) {
try {
// Main dispatch loop - JVM JIT optimizes switch to tableswitch (O(1) jump)
while (pc < bytecode.length) {
Expand All @@ -94,16 +97,14 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;

case Opcodes.RETURN: {
// Return from subroutine: return rd
int retReg = bytecode[pc++];
RuntimeBase retVal = registers[retReg];

if (retVal == null) {
return new RuntimeList();
}
RuntimeList retList = retVal.getList();
if (code.containsRegex) {
RuntimeList.resolveMatchProxies(retList);
}
return retList;
return retVal.getList();
}

case Opcodes.GOTO: {
Expand Down Expand Up @@ -989,8 +990,25 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

// Check for control flow (last/next/redo/goto/tail-call)
if (result.isNonLocalGoto()) {
// Propagate control flow up the call stack
return result;
RuntimeControlFlowList flow = (RuntimeControlFlowList) result;
// Check labeled block stack for a matching label
boolean handled = false;
for (int i = labeledBlockStack.size() - 1; i >= 0; i--) {
int[] entry = labeledBlockStack.get(i);
String blockLabel = code.stringPool[entry[0]];
if (flow.matchesLabel(blockLabel)) {
// Pop entries down to and including the match
while (labeledBlockStack.size() > i) {
labeledBlockStack.pop();
}
pc = entry[1]; // jump to block exit
handled = true;
break;
}
}
if (!handled) {
return result;
}
}
break;
}
Expand Down Expand Up @@ -1034,8 +1052,23 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

// Check for control flow (last/next/redo/goto/tail-call)
if (result.isNonLocalGoto()) {
// Propagate control flow up the call stack
return result;
RuntimeControlFlowList flow = (RuntimeControlFlowList) result;
boolean handled = false;
for (int i = labeledBlockStack.size() - 1; i >= 0; i--) {
int[] entry = labeledBlockStack.get(i);
String blockLabel = code.stringPool[entry[0]];
if (flow.matchesLabel(blockLabel)) {
while (labeledBlockStack.size() > i) {
labeledBlockStack.pop();
}
pc = entry[1];
handled = true;
break;
}
}
if (!handled) {
return result;
}
}
break;
}
Expand Down Expand Up @@ -1252,14 +1285,6 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
pc = OpcodeHandlerExtended.executeLstat(bytecode, pc, registers);
break;

case Opcodes.STAT_LASTHANDLE:
pc = OpcodeHandlerExtended.executeStatLastHandle(bytecode, pc, registers);
break;

case Opcodes.LSTAT_LASTHANDLE:
pc = OpcodeHandlerExtended.executeLstatLastHandle(bytecode, pc, registers);
break;

// File test operations (opcodes 190-216) - delegated to handler
case Opcodes.FILETEST_R:
case Opcodes.FILETEST_W:
Expand Down Expand Up @@ -1526,6 +1551,25 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;
}

// =================================================================
// LABELED BLOCK SUPPORT
// =================================================================

case Opcodes.PUSH_LABELED_BLOCK: {
int labelIdx = bytecode[pc++];
int exitPc = readInt(bytecode, pc);
pc += 1;
labeledBlockStack.push(new int[]{labelIdx, exitPc});
break;
}

case Opcodes.POP_LABELED_BLOCK: {
if (!labeledBlockStack.isEmpty()) {
labeledBlockStack.pop();
}
break;
}

// =================================================================
// LIST OPERATIONS
// =================================================================
Expand Down Expand Up @@ -2196,9 +2240,10 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
// Special handling for ClassCastException to show which opcode is failing
// Check if we're inside an eval block first
if (!evalCatchStack.isEmpty()) {
evalCatchStack.pop();
int catchPc = evalCatchStack.pop();
WarnDie.catchEval(e);
return new RuntimeList();
pc = catchPc;
continue outer;
}

// Not in eval - show detailed error with bytecode context
Expand All @@ -2224,14 +2269,13 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
// Check if we're inside an eval block
if (!evalCatchStack.isEmpty()) {
// Inside eval block - catch the exception
evalCatchStack.pop(); // Pop the catch handler
int catchPc = evalCatchStack.pop(); // Pop the catch handler

// Call WarnDie.catchEval() to set $@
WarnDie.catchEval(e);

// Eval block failed - return empty list
// (The result will be undef in scalar context, empty in list context)
return new RuntimeList();
pc = catchPc;
continue outer;
}

// Not in eval block - propagate exception
Expand All @@ -2252,10 +2296,10 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
// Wrap other exceptions with interpreter context including bytecode context
String errorMessage = formatInterpreterError(code, pc, e);
throw new RuntimeException(errorMessage, e);
}
} // end outer while
} finally {
if (regexLocalLevel >= 0) {
DynamicVariableManager.popToLocalLevel(regexLocalLevel);
}
// Always pop the interpreter state
InterpreterState.pop();
}
}
Expand Down
Loading