diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 1a4d69ca6..442ed885e 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -3413,9 +3413,9 @@ void compileVariableDeclaration(OperatorNode node, String op) { lastResultReg = rd; return; } - // local $hash{key} or local $array[index] - localize a hash/array element - if (node.operand instanceof BinaryOperatorNode binOp - && (binOp.operator.equals("{") || binOp.operator.equals("["))) { + // General fallback for any lvalue expression (matches JVM backend behavior) + // Handles: local $hash{key}, local $array[index], local $obj->method->{key}, etc. + if (node.operand instanceof BinaryOperatorNode binOp) { compileNode(binOp, -1, RuntimeContextType.SCALAR); int elemReg = lastResultReg; emit(Opcodes.PUSH_LOCAL_VARIABLE); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 90bfd0060..afaeb4f22 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -14,8 +14,10 @@ public class CompileAssignment { private static boolean handleLocalAssignment(BytecodeCompiler bc, BinaryOperatorNode node, OperatorNode leftOp, int rhsContext) { if (!leftOp.operator.equals("local")) return false; Node localOperand = leftOp.operand; - if (localOperand instanceof BinaryOperatorNode hashAccess && hashAccess.operator.equals("{")) { - bc.compileNode(hashAccess, -1, rhsContext); + // General fallback for any BinaryOperatorNode lvalue (matches JVM backend behavior) + // Handles: local $hash{key} = v, local $array[i] = v, local $obj->method->{key} = v, etc. + if (localOperand instanceof BinaryOperatorNode binOp) { + bc.compileNode(binOp, -1, rhsContext); int elemReg = bc.lastResultReg; bc.emit(Opcodes.PUSH_LOCAL_VARIABLE); bc.emitReg(elemReg); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java index ba36471a8..5ad9d28a7 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ExceptionFormatter.java @@ -56,7 +56,13 @@ private static ArrayList> formatThrowable(Throwable t) { // level; consuming them in order gives the correct nested call stack. var interpreterFrames = InterpreterState.getStack(); var interpreterPcs = InterpreterState.getPcStack(); + // Start at index 0 - caller() will skip this (the current function) int interpreterFrameIndex = 0; + + // Track whether we've added a frame for the current Perl call level. + // Multiple execute() frames can occur for the same call level (for internal ops). + // InterpretedCode.apply marks the END of a call level, so we reset after seeing it. + boolean addedFrameForCurrentLevel = false; for (var element : t.getStackTrace()) { if (element.getClassName().equals("org.perlonjava.frontend.parser.StatementParser") && @@ -73,12 +79,18 @@ private static ArrayList> formatThrowable(Throwable t) { lastFileName = callerInfo.filename() != null ? callerInfo.filename() : ""; callerStackIndex++; } + } else if (element.getClassName().equals("org.perlonjava.backend.bytecode.InterpretedCode") && + element.getMethodName().equals("apply")) { + // InterpretedCode.apply marks the END of a Perl call level. + // After this, the next execute frame starts a new call level. + if (addedFrameForCurrentLevel) { + interpreterFrameIndex++; + addedFrameForCurrentLevel = false; + } } else if (element.getClassName().equals("org.perlonjava.backend.bytecode.BytecodeInterpreter") && element.getMethodName().equals("execute")) { - // Consume the next interpreter frame in order. - // Using current() always returned the same topmost frame; consuming - // in order correctly maps each JVM execute() frame to its Perl level. - if (interpreterFrameIndex < interpreterFrames.size()) { + // Only add an entry for the current Perl call level once + if (!addedFrameForCurrentLevel && interpreterFrameIndex < interpreterFrames.size()) { var frame = interpreterFrames.get(interpreterFrameIndex); if (frame != null && frame.code() != null) { // For the innermost frame (index 0), use the runtime current package @@ -87,8 +99,6 @@ private static ArrayList> formatThrowable(Throwable t) { String pkg = (interpreterFrameIndex == 0) ? InterpreterState.currentPackage.get().toString() : frame.packageName(); - int currentInterpreterFrameIndex = interpreterFrameIndex; - interpreterFrameIndex++; String subName = frame.subroutineName(); if (subName != null && !subName.isEmpty() && !subName.contains("::")) { @@ -99,9 +109,9 @@ private static ArrayList> formatThrowable(Throwable t) { entry.add(pkg); String filename = frame.code().sourceName; String line = String.valueOf(frame.code().sourceLine); - if (currentInterpreterFrameIndex < interpreterPcs.size()) { + if (interpreterFrameIndex < interpreterPcs.size()) { Integer tokenIndex = null; - int pc = interpreterPcs.get(currentInterpreterFrameIndex); + int pc = interpreterPcs.get(interpreterFrameIndex); if (frame.code().pcToTokenIndex != null && !frame.code().pcToTokenIndex.isEmpty()) { var entryPc = frame.code().pcToTokenIndex.floorEntry(pc); if (entryPc != null) { @@ -119,6 +129,7 @@ private static ArrayList> formatThrowable(Throwable t) { entry.add(subName); stackTrace.add(entry); lastFileName = filename != null ? filename : ""; + addedFrameForCurrentLevel = true; } } } else if (element.getClassName().contains("org.perlonjava.anon") ||