diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index eedb514b2..a1fa58687 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -493,12 +493,7 @@ 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 unqualified sub calls - // resolve in the correct package (not main) - if (ctx.symbolTable != null) { - symbolTable.setCurrentPackage(ctx.symbolTable.getCurrentPackage(), - ctx.symbolTable.currentPackageIsClass()); - } + } // If we have captured variables, allocate registers for them diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 4930bd3c7..ca9c24196 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1244,6 +1244,14 @@ 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: diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index 4374be4fe..bc387b96a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -554,21 +554,37 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode } } else if (op.equals("stat") || op.equals("lstat")) { // stat FILE or lstat FILE - int savedContext = bytecodeCompiler.currentCallContext; - bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; - try { - node.operand.accept(bytecodeCompiler); - int operandReg = bytecodeCompiler.lastResultReg; + boolean isUnderscoreOperand = (node.operand instanceof IdentifierNode) + && ((IdentifierNode) node.operand).name.equals("_"); - int rd = bytecodeCompiler.allocateRegister(); - bytecodeCompiler.emit(op.equals("stat") ? Opcodes.STAT : Opcodes.LSTAT); - bytecodeCompiler.emitReg(rd); - bytecodeCompiler.emitReg(operandReg); - bytecodeCompiler.emit(savedContext); // Pass calling context + if (isUnderscoreOperand) { + int savedContext = bytecodeCompiler.currentCallContext; + try { + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(op.equals("stat") ? Opcodes.STAT_LASTHANDLE : Opcodes.LSTAT_LASTHANDLE); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emit(savedContext); + bytecodeCompiler.lastResultReg = rd; + } finally { + bytecodeCompiler.currentCallContext = savedContext; + } + } else { + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; + try { + node.operand.accept(bytecodeCompiler); + int operandReg = bytecodeCompiler.lastResultReg; - bytecodeCompiler.lastResultReg = rd; - } finally { - bytecodeCompiler.currentCallContext = savedContext; + int rd = bytecodeCompiler.allocateRegister(); + bytecodeCompiler.emit(op.equals("stat") ? Opcodes.STAT : Opcodes.LSTAT); + bytecodeCompiler.emitReg(rd); + bytecodeCompiler.emitReg(operandReg); + bytecodeCompiler.emit(savedContext); + + bytecodeCompiler.lastResultReg = rd; + } finally { + bytecodeCompiler.currentCallContext = savedContext; + } } } else if (op.startsWith("-") && op.length() == 2) { // File test operators: -r, -w, -x, etc. diff --git a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java index 4849678ec..5b543dc6f 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java +++ b/src/main/java/org/perlonjava/backend/bytecode/OpcodeHandlerExtended.java @@ -513,6 +513,20 @@ public static int executeLstat(int[] bytecode, int pc, RuntimeBase[] registers) return pc; } + public static int executeStatLastHandle(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = Stat.statLastHandle(ctx); + return pc; + } + + public static int executeLstatLastHandle(int[] bytecode, int pc, RuntimeBase[] registers) { + int rd = bytecode[pc++]; + int ctx = bytecode[pc++]; + registers[rd] = Stat.lstatLastHandle(ctx); + return pc; + } + /** * Execute print operation. * Format: PRINT contentReg filehandleReg diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index 56fa0170b..315919082 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -1149,5 +1149,13 @@ public class Opcodes { /** Defined-or assignment: rd //= rs. Format: DEFINED_OR_ASSIGN rd rs */ public static final short DEFINED_OR_ASSIGN = 349; + /** stat _ (use cached stat buffer): rd = Stat.statLastHandle() + * Format: STAT_LASTHANDLE rd ctx */ + public static final short STAT_LASTHANDLE = 350; + + /** lstat _ (use cached stat buffer): rd = Stat.lstatLastHandle() + * Format: LSTAT_LASTHANDLE rd ctx */ + public static final short LSTAT_LASTHANDLE = 351; + private Opcodes() {} // Utility class - no instantiation } diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java index f8cde803e..c7b436290 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java @@ -864,24 +864,20 @@ static void handleStatOperator(EmitterVisitor emitterVisitor, OperatorNode node, if (node.operand instanceof IdentifierNode identNode && identNode.name.equals("_")) { - // stat _ or lstat _ - still use the old methods since they don't take args + // stat _ or lstat _ - use cached stat buffer with context + emitterVisitor.pushCallContext(); emitterVisitor.ctx.mv.visitMethodInsn( Opcodes.INVOKESTATIC, "org/perlonjava/runtime/operators/Stat", operator + "LastHandle", - "()Lorg/perlonjava/runtime/runtimetypes/RuntimeList;", + "(I)Lorg/perlonjava/runtime/runtimetypes/RuntimeBase;", false); - // Handle context - treat as list that needs conversion if (emitterVisitor.ctx.contextType == RuntimeContextType.VOID) { handleVoidContext(emitterVisitor); } else if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) { - // Convert with stat's special semantics - emitterVisitor.ctx.mv.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - "org/perlonjava/runtime/runtimetypes/RuntimeList", - "statScalar", - "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", - false); + emitterVisitor.ctx.mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeScalar"); + } else if (emitterVisitor.ctx.contextType == RuntimeContextType.LIST) { + emitterVisitor.ctx.mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeList"); } } else { // stat EXPR or lstat EXPR - use context-aware methods diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java index 6151612e7..c755d7ca9 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitterMethodCreator.java @@ -1619,6 +1619,12 @@ private static InterpretedCode compileToInterpreter( ctx.errorUtil ); + // Inherit package from the JVM compiler context so unqualified sub calls + // resolve in the correct package (not main) in the interpreter fallback. + if (ctx.symbolTable != null) { + compiler.setCompilePackage(ctx.symbolTable.getCurrentPackage()); + } + // Compile AST to interpreter bytecode (pass ctx for package context and closure detection) InterpretedCode code = compiler.compile(ast, ctx); diff --git a/src/main/java/org/perlonjava/frontend/parser/SignatureParser.java b/src/main/java/org/perlonjava/frontend/parser/SignatureParser.java index 409b12510..7f88d7934 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SignatureParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SignatureParser.java @@ -503,7 +503,7 @@ private Node generateTooFewArgsMessage() { new StringNode("Too few arguments for subroutine '" + fullName + "' (got ", parser.tokenIndex), argCount, parser.tokenIndex), - new StringNode("; expected at least ", parser.tokenIndex), + new StringNode(minParams == maxParams ? "; expected " : "; expected at least ", parser.tokenIndex), parser.tokenIndex), new NumberNode(Integer.toString(adjustedMin), parser.tokenIndex), parser.tokenIndex), @@ -539,7 +539,7 @@ private Node generateTooManyArgsMessage() { new StringNode("Too many arguments for subroutine '" + fullName + "' (got ", parser.tokenIndex), argCount, parser.tokenIndex), - new StringNode("; expected at most ", parser.tokenIndex), + new StringNode(minParams == maxParams ? "; expected " : "; expected at most ", parser.tokenIndex), parser.tokenIndex), new NumberNode(Integer.toString(adjustedMax), parser.tokenIndex), parser.tokenIndex), diff --git a/src/main/java/org/perlonjava/runtime/operators/Stat.java b/src/main/java/org/perlonjava/runtime/operators/Stat.java index 77b558879..8dea5f768 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Stat.java +++ b/src/main/java/org/perlonjava/runtime/operators/Stat.java @@ -21,6 +21,7 @@ import static org.perlonjava.runtime.operators.FileTestOperator.lastBasicAttr; import static org.perlonjava.runtime.operators.FileTestOperator.lastFileHandle; import static org.perlonjava.runtime.operators.FileTestOperator.lastPosixAttr; +import static org.perlonjava.runtime.operators.FileTestOperator.lastStatOk; import static org.perlonjava.runtime.operators.FileTestOperator.updateLastStat; import static org.perlonjava.runtime.runtimetypes.GlobalVariable.getGlobalVariable; import static org.perlonjava.runtime.runtimetypes.RuntimeIO.resolvePath; @@ -63,12 +64,38 @@ private static int getPermissionsOctal(BasicFileAttributes basicAttr, PosixFileA return permissions; } - public static RuntimeList statLastHandle() { - return stat(lastFileHandle); + public static RuntimeBase statLastHandle(int ctx) { + if (!lastStatOk) { + getGlobalVariable("main::!").set(9); + RuntimeList empty = new RuntimeList(); + if (ctx == RuntimeContextType.SCALAR) { + return empty.statScalar(); + } + return empty; + } + RuntimeList res = new RuntimeList(); + statInternal(res, lastBasicAttr, lastPosixAttr); + if (ctx == RuntimeContextType.SCALAR) { + return res.statScalar(); + } + return res; } - public static RuntimeList lstatLastHandle() { - return lstat(lastFileHandle); + public static RuntimeBase lstatLastHandle(int ctx) { + if (!lastStatOk) { + getGlobalVariable("main::!").set(9); + RuntimeList empty = new RuntimeList(); + if (ctx == RuntimeContextType.SCALAR) { + return empty.statScalar(); + } + return empty; + } + RuntimeList res = new RuntimeList(); + statInternal(res, lastBasicAttr, lastPosixAttr); + if (ctx == RuntimeContextType.SCALAR) { + return res.statScalar(); + } + return res; } /**