diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index dc33f2dfa..f1502d53a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -938,7 +938,8 @@ void handleArrayKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) { // Compile indices into a list List pairRegs = new ArrayList<>(); for (Node indexElement : indicesNode.elements) { - indexElement.accept(this); + // Compile index in SCALAR context to ensure RuntimeScalar + compileNode(indexElement, -1, RuntimeContextType.SCALAR); int indexReg = lastResultReg; int valueReg = allocateRegister(); @@ -1118,7 +1119,8 @@ void handleArrayElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { // Handle single element access: $array[0] if (indexNode.elements.size() == 1) { Node indexExpr = indexNode.elements.get(0); - indexExpr.accept(this); + // Compile index in SCALAR context to ensure RuntimeScalar + compileNode(indexExpr, -1, RuntimeContextType.SCALAR); int indexReg = lastResultReg; // Emit ARRAY_GET opcode @@ -1197,6 +1199,28 @@ void handleArraySlice(BinaryOperatorNode node, OperatorNode leftOp) { throwCompilerException("Array slice requires array or array reference"); return; } + } else if (leftOp.operand instanceof BlockNode blockNode) { + // Array dereference slice with block: @{$arrayref}[indices] + // Compile the block to get the array reference + int savedContext = currentCallContext; + currentCallContext = RuntimeContextType.SCALAR; + blockNode.accept(this); + currentCallContext = savedContext; + int refReg = lastResultReg; + + // Dereference to get the array + arrayReg = allocateRegister(); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + emitReg(arrayReg); + emitReg(refReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); + emitReg(arrayReg); + emitReg(refReg); + emit(pkgIdx); + } } else { throwCompilerException("Array slice requires identifier or reference"); return; @@ -1305,8 +1329,8 @@ void handleHashElementAccess(BinaryOperatorNode node, OperatorNode leftOp) { lastResultReg = rd; } else { - // Expression key - compile it - keyExpr.accept(this); + // Expression key - compile it in SCALAR context to ensure we get RuntimeScalar + compileNode(keyExpr, -1, RuntimeContextType.SCALAR); int keyReg = lastResultReg; // Emit HASH_GET opcode @@ -1374,6 +1398,28 @@ void handleHashSlice(BinaryOperatorNode node, OperatorNode leftOp) { emitReg(scalarRefReg); emit(pkgIdx); } + } else if (leftOp.operand instanceof BlockNode blockNode) { + // Hash dereference slice with block: @{$hashref}{keys} + // Compile the block to get the hash reference + int savedContext = currentCallContext; + currentCallContext = RuntimeContextType.SCALAR; + blockNode.accept(this); + currentCallContext = savedContext; + int refReg = lastResultReg; + + // Dereference to get the hash + hashReg = allocateRegister(); + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + emitReg(hashReg); + emitReg(refReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); + emitReg(hashReg); + emitReg(refReg); + emit(pkgIdx); + } } else { throwCompilerException("Hash slice requires hash variable or reference"); return; @@ -1401,7 +1447,7 @@ void handleHashSlice(BinaryOperatorNode node, OperatorNode leftOp) { emit(keyIdx); keyRegs.add(keyReg); } else { - // Expression key + // Expression key - use default context to allow arrays to expand keyElement.accept(this); keyRegs.add(lastResultReg); } @@ -1495,6 +1541,7 @@ void handleHashKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) { emit(keyIdx); keyRegs.add(keyReg); } else { + // Expression key - use default context to allow arrays to expand keyElement.accept(this); keyRegs.add(lastResultReg); } @@ -1644,7 +1691,8 @@ void handleGeneralArrayAccess(BinaryOperatorNode node) { // Handle single element access if (indexNode.elements.size() == 1) { Node indexExpr = indexNode.elements.get(0); - indexExpr.accept(this); + // Compile index in SCALAR context to ensure RuntimeScalar + compileNode(indexExpr, -1, RuntimeContextType.SCALAR); int indexReg = lastResultReg; // The base might be either: @@ -1714,8 +1762,8 @@ void handleGeneralHashAccess(BinaryOperatorNode node) { emitReg(keyReg); emit(keyIdx); } else { - // Expression key - compile it - keyExpr.accept(this); + // Expression key - compile it in SCALAR context to ensure RuntimeScalar + compileNode(keyExpr, -1, RuntimeContextType.SCALAR); keyReg = lastResultReg; } @@ -1805,7 +1853,7 @@ void handlePushUnshift(BinaryOperatorNode node) { } else if (leftOp.operator.equals("@") && !(leftOp.operand instanceof IdentifierNode)) { // Array dereference: @$arrayref or @{expr} // Evaluate the operand expression to get the reference, then deref - leftOp.operand.accept(this); + compileNode(leftOp.operand, -1, RuntimeContextType.SCALAR); int refReg = lastResultReg; // Dereference to get the array @@ -3519,8 +3567,8 @@ void compileVariableReference(OperatorNode node, String op) { emitReg(rd); emit(nameIdx); lastResultReg = rd; - } else if (node.operand instanceof OperatorNode) { - node.operand.accept(this); + } else if (node.operand instanceof OperatorNode || node.operand instanceof BlockNode) { + compileNode(node.operand, -1, RuntimeContextType.SCALAR); int refReg = lastResultReg; int rd = allocateOutputRegister(); int pkgIdx = addToStringPool(getCurrentPackage()); @@ -3537,7 +3585,7 @@ void compileVariableReference(OperatorNode node, String op) { throwCompilerException("Unsupported * operand: " + node.operand.getClass().getSimpleName()); } } else if (op.equals("&")) { - // Code reference: &subname + // Code reference: &subname or &{expr} // Gets a reference to a named subroutine if (node.operand instanceof IdentifierNode idNode) { String subName = idNode.name; @@ -3555,6 +3603,21 @@ void compileVariableReference(OperatorNode node, String op) { emitReg(rd); emit(nameIdx); + lastResultReg = rd; + } else if (node.operand instanceof BlockNode || node.operand instanceof OperatorNode) { + // Dynamic code reference: &{$name} or &$name + // Compile the expression to get the name/value, then dereference as code + compileNode(node.operand, -1, RuntimeContextType.SCALAR); + int valueReg = lastResultReg; + + // Use CODE_DEREF_NONSTRICT to look up the code reference + int rd = allocateOutputRegister(); + int pkgIdx = addToStringPool(getCurrentPackage()); + emit(Opcodes.CODE_DEREF_NONSTRICT); + emitReg(rd); + emitReg(valueReg); + emit(pkgIdx); + lastResultReg = rd; } else { throwCompilerException("Unsupported & operand: " + node.operand.getClass().getSimpleName()); diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index d0f3eb0f4..e51501eda 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -794,6 +794,16 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c RuntimeScalar codeRef = (codeRefBase instanceof RuntimeScalar) ? (RuntimeScalar) codeRefBase : codeRefBase.scalar(); + + // Dereference symbolic code references using current package + // This matches the JVM backend's call to codeDerefNonStrict() + // Only call for STRING/BYTE_STRING types (symbolic references) + // For CODE, REFERENCE, etc. let RuntimeCode.apply() handle errors + if (codeRef.type == RuntimeScalarType.STRING || codeRef.type == RuntimeScalarType.BYTE_STRING) { + String currentPkg = InterpreterState.currentPackage.get().toString(); + codeRef = codeRef.codeDerefNonStrict(currentPkg); + } + RuntimeBase argsBase = registers[argsReg]; RuntimeArray callArgs; @@ -1380,7 +1390,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c case Opcodes.EVAL_STRING, Opcodes.SELECT_OP, Opcodes.LOAD_GLOB, Opcodes.SLEEP_OP, Opcodes.ALARM_OP, Opcodes.DEREF_GLOB, Opcodes.DEREF_GLOB_NONSTRICT, Opcodes.LOAD_GLOB_DYNAMIC, Opcodes.DEREF_SCALAR_STRICT, - Opcodes.DEREF_SCALAR_NONSTRICT -> { + Opcodes.DEREF_SCALAR_NONSTRICT, Opcodes.CODE_DEREF_NONSTRICT -> { pc = executeSpecialIO(opcode, bytecode, pc, registers, code); } @@ -2095,6 +2105,9 @@ private static int executeSpecialIO(int opcode, int[] bytecode, int pc, case Opcodes.DEREF_SCALAR_NONSTRICT -> { return SlowOpcodeHandler.executeDerefScalarNonStrict(bytecode, pc, registers, code); } + case Opcodes.CODE_DEREF_NONSTRICT -> { + return SlowOpcodeHandler.executeCodeDerefNonStrict(bytecode, pc, registers, code); + } default -> throw new RuntimeException("Unknown special I/O opcode: " + opcode); } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 01f68d4ba..0d9e25c8c 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -889,13 +889,15 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = valueReg; - } else if (leftOp.operator.equals("@") && leftOp.operand instanceof OperatorNode derefOp) { - // Array dereference assignment: @$r = ... - // The operand should be a scalar variable containing an array reference + } else if (leftOp.operator.equals("@") && (leftOp.operand instanceof OperatorNode || leftOp.operand instanceof BlockNode)) { + // Array dereference assignment: @$r = ... or @{expr} = ... + // The operand should evaluate to an array reference - if (derefOp.operator.equals("$")) { - // Compile the scalar to get the array reference - bytecodeCompiler.compileNode(derefOp, -1, rhsContext); + boolean isSimpleScalarDeref = leftOp.operand instanceof OperatorNode derefOp && derefOp.operator.equals("$"); + + if (isSimpleScalarDeref || leftOp.operand instanceof BlockNode) { + // Compile the operand to get the array reference + bytecodeCompiler.compileNode(leftOp.operand, -1, RuntimeContextType.SCALAR); int scalarRefReg = bytecodeCompiler.lastResultReg; // Dereference to get the actual array @@ -936,6 +938,41 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(arrayReg); bytecodeCompiler.emitReg(valueReg); bytecodeCompiler.lastResultReg = valueReg; + } else if (leftOp.operator.equals("%") && (leftOp.operand instanceof OperatorNode || leftOp.operand instanceof BlockNode)) { + // Hash dereference assignment: %$r = ... or %{expr} = ... + // The operand should evaluate to a hash reference + + boolean isSimpleScalarDeref = leftOp.operand instanceof OperatorNode derefOp && derefOp.operator.equals("$"); + + if (isSimpleScalarDeref || leftOp.operand instanceof BlockNode) { + // Compile the operand to get the hash reference + bytecodeCompiler.compileNode(leftOp.operand, -1, RuntimeContextType.SCALAR); + int scalarRefReg = bytecodeCompiler.lastResultReg; + + // Dereference to get the actual hash + int hashReg = bytecodeCompiler.allocateRegister(); + if (bytecodeCompiler.isStrictRefsEnabled()) { + bytecodeCompiler.emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + bytecodeCompiler.emitReg(hashReg); + bytecodeCompiler.emitReg(scalarRefReg); + } else { + int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); + bytecodeCompiler.emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex()); + bytecodeCompiler.emitReg(hashReg); + bytecodeCompiler.emitReg(scalarRefReg); + bytecodeCompiler.emit(pkgIdx); + } + + // Assign the value to the dereferenced hash + bytecodeCompiler.emit(Opcodes.HASH_SET_FROM_LIST); + bytecodeCompiler.emitReg(hashReg); + bytecodeCompiler.emitReg(valueReg); + + // In list context, return the hash flattened; in other contexts return the hash + bytecodeCompiler.lastResultReg = hashReg; + } else { + bytecodeCompiler.throwCompilerException("Assignment to unsupported hash dereference"); + } } else { if (leftOp.operator.equals("chop") || leftOp.operator.equals("chomp")) { bytecodeCompiler.throwCompilerException("Can't modify " + leftOp.operator + " in scalar assignment"); @@ -1211,8 +1248,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emit(keyIdx); keyRegs.add(keyReg); } else { - // Expression key - bytecodeCompiler.compileNode(keyElement, -1, rhsContext); + // Expression key - use default context to allow arrays to expand + keyElement.accept(bytecodeCompiler); keyRegs.add(bytecodeCompiler.lastResultReg); } } @@ -1330,8 +1367,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(keyReg); bytecodeCompiler.emit(keyIdx); } else { - // Expression key: $hash{$var} or $hash{func()} - bytecodeCompiler.compileNode(keyElement, -1, rhsContext); + // Expression key: $hash{$var} or $hash{func()} - must be compiled in SCALAR context + bytecodeCompiler.compileNode(keyElement, -1, RuntimeContextType.SCALAR); keyReg = bytecodeCompiler.lastResultReg; } @@ -1381,7 +1418,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(keyReg); bytecodeCompiler.emit(keyIdx); } else { - bytecodeCompiler.compileNode(keyElement, -1, rhsContext); + // Expression key - must be compiled in SCALAR context + bytecodeCompiler.compileNode(keyElement, -1, RuntimeContextType.SCALAR); keyReg = bytecodeCompiler.lastResultReg; } } else { @@ -1657,6 +1695,26 @@ static int resolveArrayForDollarHash(BytecodeCompiler bytecodeCompiler, Operator bytecodeCompiler.emit(pkgIdx); } return arrayReg; + } else if (dollarHashOp.operand instanceof BlockNode blockNode) { + // $#{BLOCK} = value - evaluate block to get array reference + int savedContext = bytecodeCompiler.currentCallContext; + bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR; + blockNode.accept(bytecodeCompiler); + bytecodeCompiler.currentCallContext = savedContext; + int refReg = bytecodeCompiler.lastResultReg; + int arrayReg = bytecodeCompiler.allocateRegister(); + if (bytecodeCompiler.isStrictRefsEnabled()) { + bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY, dollarHashOp.getIndex()); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(refReg); + } else { + int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage()); + bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, dollarHashOp.getIndex()); + bytecodeCompiler.emitReg(arrayReg); + bytecodeCompiler.emitReg(refReg); + bytecodeCompiler.emit(pkgIdx); + } + return arrayReg; } bytecodeCompiler.throwCompilerException("$# assignment requires array variable"); return -1; diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java b/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java index 9ca1fd39c..b426400e6 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java @@ -69,7 +69,8 @@ private static void visitExistsArrow(BytecodeCompiler bc, OperatorNode node, Bin bc.throwCompilerException("Array index required for exists"); return; } - indexNode.elements.get(0).accept(bc); + // Compile index in SCALAR context + bc.compileNode(indexNode.elements.get(0), -1, RuntimeContextType.SCALAR); int indexReg = bc.lastResultReg; int rd = bc.allocateOutputRegister(); bc.emit(Opcodes.ARRAY_EXISTS); @@ -157,7 +158,8 @@ private static void visitDeleteHashSlice(BytecodeCompiler bc, OperatorNode node, bc.emit(keyIdx); keyRegs.add(keyReg); } else { - keyElement.accept(bc); + // Compile key in SCALAR context + bc.compileNode(keyElement, -1, RuntimeContextType.SCALAR); keyRegs.add(bc.lastResultReg); } } @@ -207,7 +209,8 @@ private static void visitDeleteArrow(BytecodeCompiler bc, OperatorNode node, Bin bc.throwCompilerException("Array index required for delete"); return; } - indexNode.elements.get(0).accept(bc); + // Compile index in SCALAR context + bc.compileNode(indexNode.elements.get(0), -1, RuntimeContextType.SCALAR); int indexReg = bc.lastResultReg; int rd = bc.allocateOutputRegister(); bc.emit(Opcodes.ARRAY_DELETE); @@ -301,11 +304,13 @@ private static int compileHashKey(BytecodeCompiler bc, Node keySpec) { bc.emit(keyIdx); return keyReg; } else { - keyElement.accept(bc); + // Compile in scalar context to ensure we get a RuntimeScalar + bc.compileNode(keyElement, -1, RuntimeContextType.SCALAR); return bc.lastResultReg; } } else { - keySpec.accept(bc); + // Compile in scalar context to ensure we get a RuntimeScalar + bc.compileNode(keySpec, -1, RuntimeContextType.SCALAR); return bc.lastResultReg; } } @@ -345,7 +350,8 @@ private static int compileArrayIndex(BytecodeCompiler bc, BinaryOperatorNode arr bc.throwCompilerException("Array exists/delete requires index"); return -1; } - indexNode.elements.get(0).accept(bc); + // Compile in scalar context to ensure we get a RuntimeScalar + bc.compileNode(indexNode.elements.get(0), -1, RuntimeContextType.SCALAR); return bc.lastResultReg; } } diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index b9d9e40ea..e541db5e8 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -55,7 +55,8 @@ private static int compileArrayIndex(BytecodeCompiler bc, BinaryOperatorNode arr bc.throwCompilerException("Array exists/delete requires index"); return -1; } - indexNode.elements.get(0).accept(bc); + // Compile index in SCALAR context + bc.compileNode(indexNode.elements.get(0), -1, RuntimeContextType.SCALAR); return bc.lastResultReg; } @@ -152,8 +153,8 @@ private static int resolveArrayOperand(BytecodeCompiler bc, OperatorNode node, S bc.emit(nameIdx); return arrayReg; } - } else if (arrayOp.operand instanceof OperatorNode) { - arrayOp.operand.accept(bc); + } else if (arrayOp.operand instanceof OperatorNode || arrayOp.operand instanceof BlockNode) { + bc.compileNode(arrayOp.operand, -1, RuntimeContextType.SCALAR); int refReg = bc.lastResultReg; int arrayReg = bc.allocateRegister(); if (bc.isStrictRefsEnabled()) { @@ -1166,6 +1167,16 @@ private static void visitArrayLastIndex(BytecodeCompiler bc, OperatorNode node) int nameIdx = bc.addToStringPool(NameNormalizer.normalizeVariableName(((IdentifierNode) node.operand).name, bc.getCurrentPackage())); bc.emit(Opcodes.LOAD_GLOBAL_ARRAY); bc.emitReg(arrayReg); bc.emit(nameIdx); } + } else if (node.operand instanceof BlockNode blockNode) { + // $#{BLOCK} - evaluate block to get array reference, then get last index + int savedContext = bc.currentCallContext; + bc.currentCallContext = RuntimeContextType.SCALAR; + blockNode.accept(bc); + bc.currentCallContext = savedContext; + int refReg = bc.lastResultReg; + arrayReg = bc.allocateRegister(); + if (bc.isStrictRefsEnabled()) { bc.emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); bc.emitReg(arrayReg); bc.emitReg(refReg); } + else { int pkgIdx = bc.addToStringPool(bc.getCurrentPackage()); bc.emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex()); bc.emitReg(arrayReg); bc.emitReg(refReg); bc.emit(pkgIdx); } } else bc.throwCompilerException("$# requires array variable"); int sizeReg = bc.allocateRegister(); bc.emit(Opcodes.ARRAY_SIZE); bc.emitReg(sizeReg); bc.emitReg(arrayReg); int oneReg = bc.allocateRegister(); bc.emit(Opcodes.LOAD_INT); bc.emitReg(oneReg); bc.emitInt(1); @@ -1201,12 +1212,22 @@ private static void visitTransliterate(BytecodeCompiler bc, OperatorNode node) { if (!(node.operand instanceof ListNode)) bc.throwCompilerException("tr operator requires list operand"); ListNode list = (ListNode) node.operand; if (list.elements.size() < 3) bc.throwCompilerException("tr operator requires search, replace, and modifiers"); - list.elements.get(0).accept(bc); int searchReg = bc.lastResultReg; - list.elements.get(1).accept(bc); int replaceReg = bc.lastResultReg; - list.elements.get(2).accept(bc); int modifiersReg = bc.lastResultReg; + // Compile all elements in SCALAR context (matches JVM backend) + bc.compileNode(list.elements.get(0), -1, RuntimeContextType.SCALAR); int searchReg = bc.lastResultReg; + bc.compileNode(list.elements.get(1), -1, RuntimeContextType.SCALAR); int replaceReg = bc.lastResultReg; + bc.compileNode(list.elements.get(2), -1, RuntimeContextType.SCALAR); int modifiersReg = bc.lastResultReg; int targetReg; - if (list.elements.size() > 3 && list.elements.get(3) != null) { list.elements.get(3).accept(bc); targetReg = bc.lastResultReg; } - else { targetReg = bc.allocateRegister(); int nameIdx = bc.addToStringPool(NameNormalizer.normalizeVariableName("_", bc.getCurrentPackage())); bc.emit(Opcodes.LOAD_GLOBAL_SCALAR); bc.emitReg(targetReg); bc.emit(nameIdx); } + if (list.elements.size() > 3 && list.elements.get(3) != null) { + // Target like ($y = $x) must be compiled in SCALAR context to get the scalar lvalue, not a list + bc.compileNode(list.elements.get(3), -1, RuntimeContextType.SCALAR); + targetReg = bc.lastResultReg; + } else { + targetReg = bc.allocateRegister(); + int nameIdx = bc.addToStringPool(NameNormalizer.normalizeVariableName("_", bc.getCurrentPackage())); + bc.emit(Opcodes.LOAD_GLOBAL_SCALAR); + bc.emitReg(targetReg); + bc.emit(nameIdx); + } int rd = bc.allocateOutputRegister(); bc.emit(Opcodes.TR_TRANSLITERATE); bc.emitReg(rd); bc.emitReg(searchReg); bc.emitReg(replaceReg); bc.emitReg(modifiersReg); bc.emitReg(targetReg); bc.emitInt(bc.currentCallContext); bc.lastResultReg = rd; diff --git a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java index f190853aa..59919dc89 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java @@ -1256,6 +1256,12 @@ public static String disassemble(InterpretedCode interpretedCode) { nameIdx = interpretedCode.bytecode[pc++]; sb.append("DEREF_SCALAR_NONSTRICT r").append(rd).append(" = ${r").append(rs).append("} pkg=").append(interpretedCode.stringPool[nameIdx]).append("\n"); break; + case Opcodes.CODE_DEREF_NONSTRICT: + rd = interpretedCode.bytecode[pc++]; + rs = interpretedCode.bytecode[pc++]; + nameIdx = interpretedCode.bytecode[pc++]; + sb.append("CODE_DEREF_NONSTRICT r").append(rd).append(" = &{r").append(rs).append("} pkg=").append(interpretedCode.stringPool[nameIdx]).append("\n"); + break; case Opcodes.RETRIEVE_BEGIN_SCALAR: rd = interpretedCode.bytecode[pc++]; nameIdx = interpretedCode.bytecode[pc++]; diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index 073ef588f..0a9b9e28d 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -1822,6 +1822,13 @@ public class Opcodes { */ public static final short QUOTE_REGEX_O = 374; + /** + * Code dereference (non-strict): rd = value.codeDerefNonStrict(package) + * For &{$name} - looks up code reference from symbolic name using current package. + * Format: CODE_DEREF_NONSTRICT rd value_reg package_string_idx + */ + public static final short CODE_DEREF_NONSTRICT = 375; + private Opcodes() { } // Utility class - no instantiation } diff --git a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java index a4af9a1c7..1d4a00074 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -1175,4 +1175,26 @@ public static int executeGlobSlotGet( return pc; } + + /** + * CODE_DEREF_NONSTRICT: rd = value.codeDerefNonStrict(package) + * For &{$name} - looks up code reference from symbolic name using specified package. + * Format: [CODE_DEREF_NONSTRICT] [rd] [value_reg] [package_string_idx] + */ + public static int executeCodeDerefNonStrict(int[] bytecode, int pc, + RuntimeBase[] registers, InterpretedCode code) { + int rd = bytecode[pc++]; + int valueReg = bytecode[pc++]; + int pkgIdx = bytecode[pc++]; + + RuntimeBase valueBase = registers[valueReg]; + RuntimeScalar value = (valueBase instanceof RuntimeScalar) + ? (RuntimeScalar) valueBase + : valueBase.scalar(); + + String pkg = code.stringPool[pkgIdx]; + registers[rd] = value.codeDerefNonStrict(pkg); + + return pc; + } } diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java index 4fc823467..4ba4e991f 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java @@ -626,14 +626,16 @@ yield new For3Node(label, TokenUtils.consume(parser); Node modifierExpression = parser.parseExpression(0); parseStatementTerminator(parser); - yield new BinaryOperatorNode("&&", modifierExpression, expression, parser.tokenIndex); + // Handle "my $x = EXPR if COND" - must declare variable even when condition is false + yield handleStatementModifierWithMy(expression, modifierExpression, "&&", parser.tokenIndex); } case "unless" -> { TokenUtils.consume(parser); Node modifierExpression = parser.parseExpression(0); parseStatementTerminator(parser); - yield new BinaryOperatorNode("||", modifierExpression, expression, parser.tokenIndex); + // Handle "my $x = EXPR unless COND" - must declare variable even when condition is true + yield handleStatementModifierWithMy(expression, modifierExpression, "||", parser.tokenIndex); } case "for", "foreach" -> { @@ -874,4 +876,47 @@ public static void parseStatementTerminator(Parser parser) { consume(parser); } } + + /** + * Handle statement modifiers (if/unless) with my declarations. + * For "my $x = EXPR if COND", the variable must be declared even when condition is false. + * Uses comma operator to declare variable in current scope: (my $x, COND && ($x = EXPR)) + * This avoids creating a new scope (which BlockNode would do). + * + * @param expression The main expression (e.g., my $x = shift) + * @param modifierExpression The condition expression + * @param operator "&&" for if, "||" for unless + * @param tokenIndex Token index for error reporting + * @return Transformed AST node + */ + private static Node handleStatementModifierWithMy(Node expression, Node modifierExpression, + String operator, int tokenIndex) { + // Check if expression is an assignment with 'my' on the left side + if (expression instanceof BinaryOperatorNode assignNode && assignNode.operator.equals("=")) { + Node left = assignNode.left; + // Check if left side is a 'my' declaration + if (left instanceof OperatorNode myNode && myNode.operator.equals("my")) { + // Transform: my $x = EXPR if COND + // Into: (my $x, COND && ($x = EXPR)) + // The comma operator evaluates both in the current scope (no new scope created) + // This ensures $x is declared even when condition is false + + // Extract the variable being declared + Node variable = myNode.operand; + + // Create the assignment without 'my': $x = EXPR + Node plainAssignment = new BinaryOperatorNode("=", variable, assignNode.right, tokenIndex); + + // Create the conditional: COND && ($x = EXPR) or COND || ($x = EXPR) + Node conditional = new BinaryOperatorNode(operator, modifierExpression, plainAssignment, tokenIndex); + + // Use comma operator: (my $x, conditional) + // ListNode in statement context acts as comma operator + return new ListNode(List.of(left, conditional), tokenIndex); + } + } + + // No 'my' declaration, use simple short-circuit + return new BinaryOperatorNode(operator, modifierExpression, expression, tokenIndex); + } }