From 21b1533db4216066c0d194b74f82ee10d24c6351 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Wed, 25 Feb 2026 22:15:00 +0100 Subject: [PATCH 1/2] Fix postfix deref eval/interpreter regressions and JVM ->%[] --- .../backend/bytecode/BytecodeCompiler.java | 163 +++++++++++++++++- .../backend/bytecode/BytecodeInterpreter.java | 3 + .../backend/bytecode/CompileAssignment.java | 12 ++ .../bytecode/CompileBinaryOperator.java | 12 ++ .../backend/bytecode/InterpretedCode.java | 78 +++++++-- .../perlonjava/backend/bytecode/Opcodes.java | 5 + .../backend/bytecode/SlowOpcodeHandler.java | 42 +++-- .../perlonjava/backend/jvm/Dereference.java | 98 +++++++++++ .../frontend/parser/ParseInfix.java | 18 ++ .../runtime/runtimetypes/RuntimeScalar.java | 31 +++- 10 files changed, 430 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index 50516e7c0..d25693664 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -821,6 +821,78 @@ public void visit(NumberNode node) { lastResultReg = rd; } + void handleArrayKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) { + int arrayReg; + + // Index/value slice: %array[indices] returns alternating index/value pairs. + // Postfix ->%[...] is parsed into this form by the parser. + + if (leftOp.operand instanceof IdentifierNode) { + String varName = "@" + ((IdentifierNode) leftOp.operand).name; + if (hasVariable(varName)) { + arrayReg = getVariableRegister(varName); + } else { + arrayReg = allocateRegister(); + String globalArrayName = NameNormalizer.normalizeVariableName(((IdentifierNode) leftOp.operand).name, getCurrentPackage()); + int nameIdx = addToStringPool(globalArrayName); + emit(Opcodes.LOAD_GLOBAL_ARRAY); + emitReg(arrayReg); + emit(nameIdx); + } + } else { + // %$arrayref[...] / %{expr}[...] / $aref->%[...] / ['a'..'z']->%[...] + leftOp.operand.accept(this); + int scalarRefReg = lastResultReg; + arrayReg = allocateRegister(); + emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex()); + emitReg(arrayReg); + emitReg(scalarRefReg); + } + + if (!(node.right instanceof ArrayLiteralNode indicesNode)) { + throwCompilerException("Index/value slice requires ArrayLiteralNode"); + return; + } + if (indicesNode.elements.isEmpty()) { + throwCompilerException("Index/value slice requires at least one index"); + return; + } + + // Compile indices into a list + List pairRegs = new ArrayList<>(); + for (Node indexElement : indicesNode.elements) { + indexElement.accept(this); + int indexReg = lastResultReg; + + int valueReg = allocateRegister(); + emit(Opcodes.ARRAY_GET); + emitReg(valueReg); + emitReg(arrayReg); + emitReg(indexReg); + + pairRegs.add(indexReg); + pairRegs.add(valueReg); + } + + int listReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emitReg(listReg); + emit(pairRegs.size()); + for (int r : pairRegs) { + emitReg(r); + } + + if (currentCallContext == RuntimeContextType.SCALAR) { + int rd = allocateRegister(); + emit(Opcodes.LIST_TO_SCALAR); + emitReg(rd); + emitReg(listReg); + lastResultReg = rd; + } else { + lastResultReg = listReg; + } + } + @Override public void visit(StringNode node) { // Emit LOAD_STRING or LOAD_VSTRING depending on whether this is a v-string literal. @@ -1231,6 +1303,81 @@ void handleHashSlice(BinaryOperatorNode node, OperatorNode leftOp) { lastResultReg = rdSlice; } + void handleHashKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) { + int hashReg; + + // Key/value slice: %hash{keys} returns alternating key/value pairs. + // Postfix ->%{...} is parsed into this form by the parser. + + if (leftOp.operand instanceof IdentifierNode) { + // Direct key/value slice: %hash{keys} + String varName = ((IdentifierNode) leftOp.operand).name; + String hashVarName = "%" + varName; + + if (hasVariable(hashVarName)) { + hashReg = getVariableRegister(hashVarName); + } else { + hashReg = allocateRegister(); + String globalHashName = NameNormalizer.normalizeVariableName(varName, getCurrentPackage()); + int nameIdx = addToStringPool(globalHashName); + emit(Opcodes.LOAD_GLOBAL_HASH); + emitReg(hashReg); + emit(nameIdx); + } + } else { + // %$hashref{keys} / %{expr}{keys} / $hashref->%{keys} + // Compile operand to a scalar and then dereference as hash. + leftOp.operand.accept(this); + int scalarRefReg = lastResultReg; + hashReg = allocateRegister(); + emitWithToken(Opcodes.DEREF_HASH, node.getIndex()); + emitReg(hashReg); + emitReg(scalarRefReg); + } + + if (!(node.right instanceof HashLiteralNode)) { + throwCompilerException("Key/value slice requires HashLiteralNode"); + return; + } + + HashLiteralNode keysNode = (HashLiteralNode) node.right; + if (keysNode.elements.isEmpty()) { + throwCompilerException("Key/value slice requires at least one key"); + return; + } + + List keyRegs = new ArrayList<>(); + for (Node keyElement : keysNode.elements) { + if (keyElement instanceof IdentifierNode) { + String keyString = ((IdentifierNode) keyElement).name; + int keyReg = allocateRegister(); + int keyIdx = addToStringPool(keyString); + emit(Opcodes.LOAD_STRING); + emitReg(keyReg); + emit(keyIdx); + keyRegs.add(keyReg); + } else { + keyElement.accept(this); + keyRegs.add(lastResultReg); + } + } + + int keysListReg = allocateRegister(); + emit(Opcodes.CREATE_LIST); + emitReg(keysListReg); + emit(keyRegs.size()); + for (int keyReg : keyRegs) { + emitReg(keyReg); + } + + int rd = allocateRegister(); + emit(Opcodes.HASH_KEYVALUE_SLICE); + emitReg(rd); + emitReg(hashReg); + emitReg(keysListReg); + lastResultReg = rd; + } + /** * Handle compound assignment operators: +=, -=, *=, /=, %= * Example: $sum += $elem means $sum = $sum + $elem @@ -2573,11 +2720,19 @@ void compileVariableReference(OperatorNode node, String op) { int refReg = lastResultReg; // Dereference the result + // Use strict/non-strict variants to match JVM behavior and strict refs semantics. int rd = allocateRegister(); - emitWithToken(Opcodes.DEREF, node.getIndex()); - emitReg(rd); - emitReg(refReg); - + if (isStrictRefsEnabled()) { + emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex()); + emitReg(rd); + emitReg(refReg); + } else { + int pkgIdx = addToStringPool(getCurrentPackage()); + emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex()); + emitReg(rd); + emitReg(refReg); + 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 0ab7c2261..4b2b9a04a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1755,6 +1755,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c case Opcodes.HASH_SLICE: case Opcodes.HASH_SLICE_SET: case Opcodes.HASH_SLICE_DELETE: + case Opcodes.HASH_KEYVALUE_SLICE: case Opcodes.LIST_SLICE_FROM: pc = executeSliceOps(opcode, bytecode, pc, registers, code); break; @@ -3061,6 +3062,8 @@ private static int executeSliceOps(int opcode, int[] bytecode, int pc, return SlowOpcodeHandler.executeHashSliceSet(bytecode, pc, registers); case Opcodes.HASH_SLICE_DELETE: return SlowOpcodeHandler.executeHashSliceDelete(bytecode, pc, registers); + case Opcodes.HASH_KEYVALUE_SLICE: + return SlowOpcodeHandler.executeHashKeyValueSlice(bytecode, pc, registers); case Opcodes.LIST_SLICE_FROM: return SlowOpcodeHandler.executeListSliceFrom(bytecode, pc, registers); default: diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java index 14aa3f819..27f5a3608 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java @@ -944,6 +944,18 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler, bytecodeCompiler.emitReg(globReg); bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = globReg; + } else if (leftOp.operator.equals("*")) { + // Glob assignment where the glob comes from an expression, e.g. $ref->** = ... + // or 'name'->** = ... + // Compile the glob expression to obtain the RuntimeGlob, then store through it. + leftOp.accept(bytecodeCompiler); + int globReg = bytecodeCompiler.lastResultReg; + + bytecodeCompiler.emit(Opcodes.STORE_GLOB); + bytecodeCompiler.emitReg(globReg); + bytecodeCompiler.emitReg(valueReg); + bytecodeCompiler.lastResultReg = globReg; } else if (leftOp.operator.equals("pos")) { // pos($var) = value - lvalue assignment to regex position diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java index b3ef4b621..d9e55c0c5 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java @@ -306,6 +306,12 @@ else if (node.right instanceof BinaryOperatorNode) { return; } + // Index/value slice: %array[indices] (and postfix ->%[...] parses into this form) + if (leftOp.operator.equals("%")) { + bytecodeCompiler.handleArrayKeyValueSlice(node, leftOp); + return; + } + // Handle normal array element access: $array[index] if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode) { bytecodeCompiler.handleArrayElementAccess(node, leftOp); @@ -333,6 +339,12 @@ else if (node.right instanceof BinaryOperatorNode) { return; } + // Key/value slice: %hash{keys} (and postfix ->%{...} parses into this form) + if (leftOp.operator.equals("%")) { + bytecodeCompiler.handleHashKeyValueSlice(node, leftOp); + return; + } + // Handle normal hash element access: $hash{key} if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode) { bytecodeCompiler.handleHashElementAccess(node, leftOp); diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java index bab2e0d5d..7030e59ff 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java @@ -236,6 +236,8 @@ public String disassemble() { while (pc < bytecode.length) { int startPc = pc; int opcode = bytecode[pc++]; + int rs1; + int rs2; sb.append(String.format("%4d: ", startPc)); switch (opcode) { @@ -354,43 +356,94 @@ public String disassemble() { case Opcodes.LOAD_GLOBAL_SCALAR: rd = bytecode[pc++]; int nameIdx = bytecode[pc++]; - sb.append("LOAD_GLOBAL_SCALAR r").append(rd).append(" = $").append(stringPool[nameIdx]).append("\n"); + sb.append("LOAD_GLOBAL_SCALAR r").append(rd).append(" = $"); + if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) { + sb.append(stringPool[nameIdx]); + } else { + sb.append(""); + } + sb.append("\n"); break; case Opcodes.LOAD_GLOBAL_ARRAY: rd = bytecode[pc++]; nameIdx = bytecode[pc++]; - sb.append("LOAD_GLOBAL_ARRAY r").append(rd).append(" = @").append(stringPool[nameIdx]).append("\n"); + sb.append("LOAD_GLOBAL_ARRAY r").append(rd).append(" = @"); + if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) { + sb.append(stringPool[nameIdx]); + } else { + sb.append(""); + } + sb.append("\n"); break; case Opcodes.STORE_GLOBAL_ARRAY: nameIdx = bytecode[pc++]; int storeArraySrcReg = bytecode[pc++]; - sb.append("STORE_GLOBAL_ARRAY @").append(stringPool[nameIdx]).append(" = r").append(storeArraySrcReg).append("\n"); + sb.append("STORE_GLOBAL_ARRAY @"); + if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) { + sb.append(stringPool[nameIdx]); + } else { + sb.append(""); + } + sb.append(" = r").append(storeArraySrcReg).append("\n"); break; case Opcodes.LOAD_GLOBAL_HASH: rd = bytecode[pc++]; nameIdx = bytecode[pc++]; - sb.append("LOAD_GLOBAL_HASH r").append(rd).append(" = %").append(stringPool[nameIdx]).append("\n"); + sb.append("LOAD_GLOBAL_HASH r").append(rd).append(" = %"); + if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) { + sb.append(stringPool[nameIdx]); + } else { + sb.append(""); + } + sb.append("\n"); break; case Opcodes.STORE_GLOBAL_HASH: nameIdx = bytecode[pc++]; int storeHashSrcReg = bytecode[pc++]; - sb.append("STORE_GLOBAL_HASH %").append(stringPool[nameIdx]).append(" = r").append(storeHashSrcReg).append("\n"); + sb.append("STORE_GLOBAL_HASH %"); + if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) { + sb.append(stringPool[nameIdx]); + } else { + sb.append(""); + } + sb.append(" = r").append(storeHashSrcReg).append("\n"); break; case Opcodes.LOAD_GLOBAL_CODE: rd = bytecode[pc++]; nameIdx = bytecode[pc++]; - sb.append("LOAD_GLOBAL_CODE r").append(rd).append(" = &").append(stringPool[nameIdx]).append("\n"); + sb.append("LOAD_GLOBAL_CODE r").append(rd).append(" = &"); + if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) { + sb.append(stringPool[nameIdx]); + } else { + sb.append(""); + } + sb.append("\n"); break; case Opcodes.STORE_GLOBAL_SCALAR: nameIdx = bytecode[pc++]; int srcReg = bytecode[pc++]; - sb.append("STORE_GLOBAL_SCALAR $").append(stringPool[nameIdx]).append(" = r").append(srcReg).append("\n"); + sb.append("STORE_GLOBAL_SCALAR $"); + if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) { + sb.append(stringPool[nameIdx]); + } else { + sb.append(""); + } + sb.append(" = r").append(srcReg).append("\n"); + break; + case Opcodes.HASH_KEYVALUE_SLICE: { + rd = bytecode[pc++]; + int kvRs1 = bytecode[pc++]; // hash register + int kvRs2 = bytecode[pc++]; // keys list register + sb.append("HASH_KEYVALUE_SLICE r").append(rd) + .append(" = r").append(kvRs1) + .append(".getKeyValueSlice(r").append(kvRs2).append(")\n"); break; + } case Opcodes.ADD_SCALAR: rd = bytecode[pc++]; - int rs1 = bytecode[pc++]; - int rs2 = bytecode[pc++]; - sb.append("ADD_SCALAR r").append(rd).append(" = r").append(rs1).append(" + r").append(rs2).append("\n"); + int addRs1 = bytecode[pc++]; + int addRs2 = bytecode[pc++]; + sb.append("ADD_SCALAR r").append(rd).append(" = r").append(addRs1).append(" + r").append(addRs2).append("\n"); break; case Opcodes.SUB_SCALAR: rd = bytecode[pc++]; @@ -1287,6 +1340,11 @@ public String disassemble() { rs = bytecode[pc++]; sb.append("DEREF_ARRAY r").append(rd).append(" = @{r").append(rs).append("}\n"); break; + case Opcodes.DEREF_HASH: + rd = bytecode[pc++]; + rs = bytecode[pc++]; + sb.append("DEREF_HASH r").append(rd).append(" = %{r").append(rs).append("}\n"); + break; case Opcodes.RETRIEVE_BEGIN_SCALAR: rd = bytecode[pc++]; nameIdx = 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 2937aace0..02c9c7318 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -1131,5 +1131,10 @@ public class Opcodes { * Format: DO_FILE rd fileReg ctx */ public static final short DO_FILE = 340; + /** Hash key/value slice: rd = hash.getKeyValueSlice(keys_list) + * Perl: %hash{keys} returns alternating key/value pairs. + * Format: HASH_KEYVALUE_SLICE rd hashReg keysListReg */ + public static final short HASH_KEYVALUE_SLICE = 344; + 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 37c82cb41..e0584e432 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java @@ -75,6 +75,27 @@ public static int executeGetppid(int[] bytecode, int pc, RuntimeBase[] registers return pc; } + /** + * HASH_KEYVALUE_SLICE: rd = hash.getKeyValueSlice(keys_list) + * Format: [HASH_KEYVALUE_SLICE] [rd] [hashReg] [keysListReg] + * Effect: Returns alternating key/value pairs for the given keys. + */ + public static int executeHashKeyValueSlice( + int[] bytecode, + int pc, + RuntimeBase[] registers) { + + int rd = bytecode[pc++]; + int hashReg = bytecode[pc++]; + int keysListReg = bytecode[pc++]; + + RuntimeHash hash = (RuntimeHash) registers[hashReg]; + RuntimeList keysList = (RuntimeList) registers[keysListReg]; + + registers[rd] = hash.getKeyValueSlice(keysList); + return pc; + } + /** * SLOW_FORK: rd = fork() * Format: [SLOW_FORK] [rd] @@ -354,7 +375,7 @@ public static int executeDerefScalarStrict( int rd = bytecode[pc++]; int rs = bytecode[pc++]; - registers[rd] = ((RuntimeScalar) registers[rs]).scalarDeref(); + registers[rd] = registers[rs].scalar().scalarDeref(); return pc; } @@ -373,7 +394,7 @@ public static int executeDerefScalarNonStrict( int rs = bytecode[pc++]; int pkgIdx = bytecode[pc++]; String pkg = code.stringPool[pkgIdx]; - registers[rd] = ((RuntimeScalar) registers[rs]).scalarDerefNonStrict(pkg); + registers[rd] = registers[rs].scalar().scalarDerefNonStrict(pkg); return pc; } @@ -392,20 +413,9 @@ public static int executeDerefGlob( int rs = bytecode[pc++]; int nameIdx = bytecode[pc++]; // currentPackage (unused at runtime, consumed for alignment) - RuntimeBase val = registers[rs]; - - // PVIO case: *STDOUT{IO} returns RuntimeIO directly — wrap in a temporary glob - if (val instanceof RuntimeIO io) { - RuntimeGlob tmp = new RuntimeGlob("__ANON__"); - tmp.setIO(io); - registers[rd] = tmp; - return pc; - } - - // General case: use globDeref() — throws "Not a GLOB reference" for invalid refs. - // Matches JVM path (EmitVariable.java: globDeref()). - RuntimeScalar scalar = (RuntimeScalar) val; - registers[rd] = scalar.globDeref(); + // Delegate to RuntimeScalar.globDeref() (after scalar context coercion). + // This centralizes PVIO / GLOBREFERENCE handling and matches the JVM path. + registers[rd] = registers[rs].scalar().globDeref(); return pc; } diff --git a/src/main/java/org/perlonjava/backend/jvm/Dereference.java b/src/main/java/org/perlonjava/backend/jvm/Dereference.java index 004b9041b..830fffabc 100644 --- a/src/main/java/org/perlonjava/backend/jvm/Dereference.java +++ b/src/main/java/org/perlonjava/backend/jvm/Dereference.java @@ -190,6 +190,104 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper } return; } + + if (sigil.equals("%") && arrayOperation.equals("get")) { + /* $aref->%[1, 7, 3] + * BinaryOperatorNode: [ + * OperatorNode: % + * OperatorNode: $ + * IdentifierNode: aref + * ArrayLiteralNode: + * NumberNode: 1 + * NumberNode: 7 + * NumberNode: 3 + * + * Perl index/value slice: returns alternating index and value. + */ + + emitterVisitor.ctx.logDebug("visit(BinaryOperatorNode) %var[] "); + + // Evaluate base as scalar (array reference) + sigilNode.operand.accept(scalarVisitor); + + int baseSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot(); + boolean pooledBase = baseSlot >= 0; + if (!pooledBase) { + baseSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); + } + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, baseSlot); + + // Build list of alternating index/value pairs + emitterVisitor.ctx.mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeList"); + emitterVisitor.ctx.mv.visitInsn(Opcodes.DUP); + emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESPECIAL, + "org/perlonjava/runtime/runtimetypes/RuntimeList", + "", "()V", false); + + int outSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot(); + boolean pooledOut = outSlot >= 0; + if (!pooledOut) { + outSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); + } + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, outSlot); + + ArrayLiteralNode right = (ArrayLiteralNode) node.right; + int idxSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot(); + boolean pooledIdx = idxSlot >= 0; + if (!pooledIdx) { + idxSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); + } + + for (Node elem : right.elements) { + // Evaluate index scalar + elem.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, idxSlot); + + // out.add(index) + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, outSlot); + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, idxSlot); + emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/RuntimeList", + "add", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)V", false); + + // out.add(base.arrayDerefGet(index)) + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, outSlot); + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, baseSlot); + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, idxSlot); + emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/RuntimeScalar", + "arrayDerefGet", + "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", + false); + emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/RuntimeList", + "add", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)V", false); + } + + if (pooledIdx) { + emitterVisitor.ctx.javaClassInfo.releaseSpillSlot(); + } + + // Load result + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, outSlot); + + if (pooledOut) { + emitterVisitor.ctx.javaClassInfo.releaseSpillSlot(); + } + if (pooledBase) { + emitterVisitor.ctx.javaClassInfo.releaseSpillSlot(); + } + + // Context conversion + if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) { + emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/runtimetypes/RuntimeList", + "scalar", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); + } else if (emitterVisitor.ctx.contextType == RuntimeContextType.VOID) { + emitterVisitor.ctx.mv.visitInsn(Opcodes.POP); + } + return; + } } if (node.left instanceof ListNode list) { // ("a","b","c")[2] // transform to: ["a","b","c"]->[2] diff --git a/src/main/java/org/perlonjava/frontend/parser/ParseInfix.java b/src/main/java/org/perlonjava/frontend/parser/ParseInfix.java index 9e7d9d164..9a42ce7c4 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ParseInfix.java +++ b/src/main/java/org/perlonjava/frontend/parser/ParseInfix.java @@ -233,6 +233,24 @@ public static Node parseInfixOperation(Parser parser, Node left, int precedence) } else { throw new PerlCompilerException(parser.tokenIndex, "syntax error", parser.ctx.errorUtil); } + case "%": + // ->%{key-list} or ->%[index-list] + // Parse similar to ->@{}/->@[] but using % as the sigil. + TokenUtils.consume(parser); + right = ParsePrimary.parsePrimary(parser); + if (right instanceof HashLiteralNode) { + return new BinaryOperatorNode("{", + new OperatorNode("%", left, parser.tokenIndex), + right, + parser.tokenIndex); + } else if (right instanceof ArrayLiteralNode) { + return new BinaryOperatorNode("[", + new OperatorNode("%", left, parser.tokenIndex), + right, + parser.tokenIndex); + } else { + throw new PerlCompilerException(parser.tokenIndex, "syntax error", parser.ctx.errorUtil); + } case "&": // Handle lexical method calls: $obj->&priv() TokenUtils.consume(parser); // consume '&' diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index e3f201f33..bf054c56e 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -1269,7 +1269,15 @@ public RuntimeGlob globDeref() { return switch (type) { case UNDEF -> throw new PerlCompilerException("Can't use an undefined value as a GLOB reference"); - case GLOBREFERENCE -> (RuntimeGlob) value; + case GLOBREFERENCE -> { + // Some internal representations store PVIO as GLOBREFERENCE with a RuntimeIO value. + if (value instanceof RuntimeIO io) { + RuntimeGlob tmp = new RuntimeGlob("__ANON__"); + tmp.setIO(io); + yield tmp; + } + yield (RuntimeGlob) value; + } case GLOB -> { // PVIO (like *STDOUT{IO}) is stored as type GLOB with a RuntimeIO value. // Perl allows postfix glob deref (->**) of PVIO by creating a temporary glob @@ -1305,7 +1313,26 @@ public RuntimeGlob globDerefNonStrict(String packageName) { } return switch (type) { - case GLOB, GLOBREFERENCE -> (RuntimeGlob) value; + case GLOBREFERENCE -> { + // Some internal representations store PVIO as GLOBREFERENCE with a RuntimeIO value. + if (value instanceof RuntimeIO io) { + RuntimeGlob tmp = new RuntimeGlob("__ANON__"); + tmp.setIO(io); + yield tmp; + } + yield (RuntimeGlob) value; + } + case GLOB -> { + // PVIO (like *STDOUT{IO}) is stored as type GLOB with a RuntimeIO value. + // Perl allows postfix glob deref (->**) of PVIO by creating a temporary glob + // with the IO slot set to that handle. + if (value instanceof RuntimeIO io) { + RuntimeGlob tmp = new RuntimeGlob("__ANON__"); + tmp.setIO(io); + yield tmp; + } + yield (RuntimeGlob) value; + } default -> { String varName = NameNormalizer.normalizeVariableName(this.toString(), packageName); // Use the canonical glob object for this symbol name. From de91d5ed57f685bb00c24f07a811d71ecf9bffb1 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 26 Feb 2026 09:33:45 +0100 Subject: [PATCH 2/2] Checkpoint: declared_refs list reference semantics in eval-interpreter --- .../backend/bytecode/BytecodeCompiler.java | 278 ++++++++++++++---- .../backend/bytecode/BytecodeInterpreter.java | 32 +- .../frontend/parser/OperatorParser.java | 1 + .../runtime/runtimetypes/RuntimeList.java | 5 +- 4 files changed, 241 insertions(+), 75 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java index d25693664..edcf203c0 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java @@ -1818,10 +1818,20 @@ void compileVariableDeclaration(OperatorNode node, String op) { Boolean.TRUE.equals(node.annotations.get("isDeclaredReference")); if (isDeclaredReference) { - // This is my \\$x which means: create a declared reference and then take a reference to it - // The operand is \$x, so we recursively compile it - sigilOp.accept(this); - // The result is now in lastResultReg + // my \\$x means: declare a reference variable (my \$x) and then take a reference to that. + // Compile the inner declared-ref my in current context, then wrap with CREATE_REF. + OperatorNode innerMy = new OperatorNode(op, sigilOp.operand, node.getIndex()); + innerMy.setAnnotation("isDeclaredReference", true); + innerMy.accept(this); + int innerReg = lastResultReg; + + if (currentCallContext != RuntimeContextType.VOID && innerReg != -1) { + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(innerReg); + lastResultReg = refReg; + } return; } } @@ -1829,12 +1839,14 @@ void compileVariableDeclaration(OperatorNode node, String op) { // my ($x, $y, @rest) - list of variable declarations ListNode listNode = (ListNode) node.operand; List varRegs = new ArrayList<>(); + List wrapWithRef = new ArrayList<>(); // Check if this is a declared reference (my \($x, $y)) boolean isDeclaredReference = node.annotations != null && Boolean.TRUE.equals(node.annotations.get("isDeclaredReference")); // Track if we found any backslash operators inside the list (my (\($x, $y))) + // Note: backslash inside the list should only affect that element, not the whole list. boolean foundBackslashInList = false; for (Node element : listNode.elements) { @@ -1844,6 +1856,59 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Handle backslash operator (reference constructor): my (\$x) or my (\($x, $y)) if (sigil.equals("\\")) { + // Parser may represent element-level double-backslash (\\$i) as a single backslash + // node annotated with declaredReferenceOriginalSigil="\\". + Object elementOriginalSigilObj = sigilOp.annotations != null + ? sigilOp.annotations.get("declaredReferenceOriginalSigil") + : null; + if ("\\".equals(elementOriginalSigilObj) && sigilOp.getBooleanAnnotation("isDeclaredReference") + && sigilOp.operand instanceof OperatorNode varNode + && "$@%".contains(varNode.operator) + && varNode.operand instanceof IdentifierNode idNode) { + String baseName = idNode.name; + + // Declare the scalar reference variable $name + String scalarName = "$" + baseName; + int scalarReg = addVariable(scalarName, op); + emit(Opcodes.LOAD_UNDEF); + emitReg(scalarReg); + + // Allocate/initialize the underlying storage and create a reference to it + int declaredReg; + if ("$".equals(varNode.operator)) { + declaredReg = allocateRegister(); + emit(Opcodes.LOAD_UNDEF); + emitReg(declaredReg); + } else { + String declaredVarName = varNode.operator + baseName; + declaredReg = addVariable(declaredVarName, op); + if ("@".equals(varNode.operator)) { + emit(Opcodes.NEW_ARRAY); + emitReg(declaredReg); + } else { + emit(Opcodes.NEW_HASH); + emitReg(declaredReg); + } + } + + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(declaredReg); + emit(Opcodes.SET_SCALAR); + emitReg(scalarReg); + emitReg(refReg); + + // Return a reference to $name (ref-to-ref semantics) + int scalarRefReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(scalarRefReg); + emitReg(scalarReg); + varRegs.add(scalarRefReg); + wrapWithRef.add(false); + continue; + } + // Check if it's a double backslash first: my (\\$x) if (sigilOp.operand instanceof OperatorNode innerBackslash && innerBackslash.operator.equals("\\")) { @@ -1867,11 +1932,12 @@ void compileVariableDeclaration(OperatorNode node, String op) { emitReg(refReg); emitReg(lastResultReg); varRegs.add(refReg); + wrapWithRef.add(false); } continue; } - // Single backslash - mark that we need to create references later + // Single backslash - this element should become a reference foundBackslashInList = true; // Check if it's a nested list: my (\($d, $e)) @@ -1882,32 +1948,37 @@ void compileVariableDeclaration(OperatorNode node, String op) { "$@%".contains(nestedVarNode.operator)) { // Get the variable name if (nestedVarNode.operand instanceof IdentifierNode idNode) { - // For declared refs, variable is always scalar holding a ref - String varName = "$" + idNode.name; + // Initialize based on original sigil. + // Declared refs declare the underlying variable and return a reference to it. + // We keep the underlying variable with its original sigil ($/@/%) and later + // create references when building the return list. + String originalSigil = nestedVarNode.operator; + String declaredVarName = originalSigil + idNode.name; - // Declare the variable - int reg = addVariable(varName, op); + // Declare the underlying variable (not $-renamed) + int declaredReg = addVariable(declaredVarName, op); - // Initialize based on original sigil - String originalSigil = nestedVarNode.operator; switch (originalSigil) { case "$" -> { emit(Opcodes.LOAD_UNDEF); - emitReg(reg); + emitReg(declaredReg); } case "@" -> { - // Create an array ref emit(Opcodes.NEW_ARRAY); - emitReg(reg); + emitReg(declaredReg); } case "%" -> { - // Create a hash ref emit(Opcodes.NEW_HASH); - emitReg(reg); + emitReg(declaredReg); } } - varRegs.add(reg); + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(declaredReg); + varRegs.add(refReg); + wrapWithRef.add(false); } } } @@ -1915,32 +1986,33 @@ void compileVariableDeclaration(OperatorNode node, String op) { "$@%".contains(varNode.operator)) { // Single variable: my (\$x) or state (\$x) or my (\@x) or my (\%x) if (varNode.operand instanceof IdentifierNode idNode) { - // For declared refs, variable is always scalar holding a ref - String varName = "$" + idNode.name; + String originalSigil = varNode.operator; + String declaredVarName = originalSigil + idNode.name; - // Declare the variable - int reg = addVariable(varName, op); + // Declare the underlying variable (not $-renamed) + int reg = addVariable(declaredVarName, op); - // Initialize based on original sigil - String originalSigil = varNode.operator; switch (originalSigil) { case "$" -> { emit(Opcodes.LOAD_UNDEF); emitReg(reg); } case "@" -> { - // Create an array ref emit(Opcodes.NEW_ARRAY); emitReg(reg); } case "%" -> { - // Create a hash ref emit(Opcodes.NEW_HASH); emitReg(reg); } } - varRegs.add(reg); + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(reg); + varRegs.add(refReg); + wrapWithRef.add(false); } } continue; @@ -1949,6 +2021,64 @@ void compileVariableDeclaration(OperatorNode node, String op) { if (sigilOp.operand instanceof IdentifierNode) { String varName = sigil + ((IdentifierNode) sigilOp.operand).name; + // Parser may rewrite list elements like \$f/\@f/\%f into a scalar $f (dropping the backslash node). + // Preserve semantics here by initializing $f to a reference to the underlying storage and + // returning the reference value from the declaration expression. + boolean elementIsDeclaredReference = sigilOp.getBooleanAnnotation("isDeclaredReference"); + Object originalSigilObj = sigilOp.annotations != null ? sigilOp.annotations.get("declaredReferenceOriginalSigil") : null; + if (elementIsDeclaredReference && "$".equals(sigil) && ("$".equals(originalSigilObj) || "@".equals(originalSigilObj) || "%".equals(originalSigilObj))) { + String originalSigil = (String) originalSigilObj; + String baseName = ((IdentifierNode) sigilOp.operand).name; + String declaredVarName = originalSigil + baseName; + + // Declare the scalar variable $name + int scalarReg = addVariable(varName, op); + emit(Opcodes.LOAD_UNDEF); + emitReg(scalarReg); + + // Declare and initialize the underlying storage + int declaredReg; + if ("$".equals(originalSigil)) { + // For declared scalar refs: allocate a fresh scalar lvalue and store a reference to it in $name + declaredReg = allocateRegister(); + emit(Opcodes.LOAD_UNDEF); + emitReg(declaredReg); + } else { + declaredReg = addVariable(declaredVarName, op); + if ("@".equals(originalSigil)) { + emit(Opcodes.NEW_ARRAY); + emitReg(declaredReg); + } else { + emit(Opcodes.NEW_HASH); + emitReg(declaredReg); + } + } + + // Set $name to a reference to the underlying variable + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(declaredReg); + emit(Opcodes.SET_SCALAR); + emitReg(scalarReg); + emitReg(refReg); + + // Element-level declared refs: + // - \$f expects a REF value (\$f) + // - \@f/\%f expect the referent ARRAY/HASH ref value + if ("$".equals(originalSigil)) { + int scalarRefReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(scalarRefReg); + emitReg(scalarReg); + varRegs.add(scalarRefReg); + } else { + varRegs.add(refReg); + } + wrapWithRef.add(false); + continue; + } + // Check if this variable is captured by closures or is a state variable if (sigilOp.id != 0 || op.equals("state")) { // Variable is captured or is a state variable - use persistent storage @@ -1984,6 +2114,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { } varRegs.add(reg); + wrapWithRef.add(isDeclaredReference); } else { // Regular lexical variable (not captured, not state) int reg = addVariable(varName, op); @@ -2006,6 +2137,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { } varRegs.add(reg); + wrapWithRef.add(isDeclaredReference); } } else if (sigilOp.operand instanceof OperatorNode) { // Handle declared references with backslash: my (\$x) @@ -2024,35 +2156,45 @@ void compileVariableDeclaration(OperatorNode node, String op) { } } - // Return a list of the declared variables (or their references if isDeclaredReference) + // Return a list of the declared variables (or their references if isDeclaredReference). int resultReg = allocateRegister(); - if ((isDeclaredReference || foundBackslashInList) && currentCallContext != RuntimeContextType.VOID) { - // Create references to all variables first - List refRegs = new ArrayList<>(); - for (int varReg : varRegs) { - int refReg = allocateRegister(); - emit(Opcodes.CREATE_REF); - emitReg(refReg); - emitReg(varReg); - refRegs.add(refReg); + if (currentCallContext != RuntimeContextType.VOID) { + // Build the return list, optionally wrapping only selected elements with CREATE_REF. + List outRegs = new ArrayList<>(); + for (int i = 0; i < varRegs.size(); i++) { + int vReg = varRegs.get(i); + if (isDeclaredReference && i < wrapWithRef.size() && Boolean.TRUE.equals(wrapWithRef.get(i))) { + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(vReg); + outRegs.add(refReg); + } else { + outRegs.add(vReg); + } + } + + if (currentCallContext == RuntimeContextType.SCALAR) { + // In scalar context, declaration lists return the last element. + if (outRegs.isEmpty()) { + lastResultReg = -1; + return; + } + lastResultReg = outRegs.get(outRegs.size() - 1); + return; } - // Create a list of the references emit(Opcodes.CREATE_LIST); emitReg(resultReg); - emit(refRegs.size()); - for (int refReg : refRegs) { - emitReg(refReg); + emit(outRegs.size()); + for (int outReg : outRegs) { + emitReg(outReg); } } else { - // Regular list of variables emit(Opcodes.CREATE_LIST); emitReg(resultReg); - emit(varRegs.size()); - for (int varReg : varRegs) { - emitReg(varReg); - } + emit(0); } lastResultReg = resultReg; @@ -2156,6 +2298,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { Boolean.TRUE.equals(node.annotations.get("isDeclaredReference")); // Track if we found any backslash operators inside the list + // Note: backslash inside the list should only affect that element, not the whole list. boolean foundBackslashInList = false; for (Node element : listNode.elements) { @@ -2187,7 +2330,7 @@ void compileVariableDeclaration(OperatorNode node, String op) { continue; } - // Single backslash - mark that we need to create references later + // Single backslash - this element should become a reference foundBackslashInList = true; // Check if it's a nested list: our (\($d, $e)) @@ -2197,8 +2340,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { if (nestedElement instanceof OperatorNode nestedVarNode && "$@%".contains(nestedVarNode.operator)) { if (nestedVarNode.operand instanceof IdentifierNode idNode) { - // For declared refs, variable is always scalar holding a ref - String varName = "$" + idNode.name; + String originalSigil = nestedVarNode.operator; + String varName = originalSigil + idNode.name; // Declare and load the package variable int reg = addVariable(varName, "our"); @@ -2209,7 +2352,6 @@ void compileVariableDeclaration(OperatorNode node, String op) { int nameIdx = addToStringPool(globalVarName); // Load based on original sigil - String originalSigil = nestedVarNode.operator; switch (originalSigil) { case "$" -> { emit(Opcodes.LOAD_GLOBAL_SCALAR); @@ -2228,7 +2370,11 @@ void compileVariableDeclaration(OperatorNode node, String op) { } } - varRegs.add(reg); + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(reg); + varRegs.add(refReg); } } } @@ -2236,8 +2382,8 @@ void compileVariableDeclaration(OperatorNode node, String op) { "$@%".contains(varNode.operator)) { // Single variable: our (\$x) or our (\@x) or our (\%x) if (varNode.operand instanceof IdentifierNode idNode) { - // For declared refs, variable is always scalar holding a ref - String varName = "$" + idNode.name; + String originalSigil = varNode.operator; + String varName = originalSigil + idNode.name; // Declare and load the package variable int reg = addVariable(varName, "our"); @@ -2248,7 +2394,6 @@ void compileVariableDeclaration(OperatorNode node, String op) { int nameIdx = addToStringPool(globalVarName); // Load based on original sigil - String originalSigil = varNode.operator; switch (originalSigil) { case "$" -> { emit(Opcodes.LOAD_GLOBAL_SCALAR); @@ -2267,7 +2412,11 @@ void compileVariableDeclaration(OperatorNode node, String op) { } } - varRegs.add(reg); + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(reg); + varRegs.add(refReg); } } continue; @@ -2324,15 +2473,24 @@ void compileVariableDeclaration(OperatorNode node, String op) { // Return a list of the declared variables (or their references if isDeclaredReference) int resultReg = allocateRegister(); - if ((isDeclaredReference || foundBackslashInList) && currentCallContext != RuntimeContextType.VOID) { + if (currentCallContext != RuntimeContextType.VOID) { // Create references to all variables first List refRegs = new ArrayList<>(); for (int varReg : varRegs) { - int refReg = allocateRegister(); - emit(Opcodes.CREATE_REF); - emitReg(refReg); - emitReg(varReg); - refRegs.add(refReg); + if (isDeclaredReference) { + int refReg = allocateRegister(); + emit(Opcodes.CREATE_REF); + emitReg(refReg); + emitReg(varReg); + refRegs.add(refReg); + } else { + refRegs.add(varReg); + } + } + + if (currentCallContext == RuntimeContextType.SCALAR) { + lastResultReg = refRegs.isEmpty() ? -1 : refRegs.get(refRegs.size() - 1); + return; } // Create a list of the references diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 4b2b9a04a..6a4f4b4f7 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -1382,17 +1382,21 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c case Opcodes.CREATE_REF: { // Create reference: rd = rs.createReference() - // For multi-element lists, create references to each element + // For lists, create a list of references to each element int rd = bytecode[pc++]; int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; - // Special handling for RuntimeList - if (value instanceof RuntimeList list && list.elements.size() != 1) { - // Multi-element or empty list: create list of references - registers[rd] = list.createListReference(); + if (value == null) { + // Null value - return undef + registers[rd] = RuntimeScalarCache.scalarUndef; + } else if (value instanceof RuntimeList list) { + if (list.size() == 1) { + registers[rd] = list.getFirst().createReference(); + } else { + registers[rd] = list.createListReference(); + } } else { - // Single value or single-element list: create single reference registers[rd] = value.createReference(); } break; @@ -2282,13 +2286,17 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc, int rd = bytecode[pc++]; int rs = bytecode[pc++]; RuntimeBase value = registers[rs]; - - // Special handling for RuntimeList - if (value instanceof RuntimeList list && list.elements.size() != 1) { - // Multi-element or empty list: create list of references - registers[rd] = list.createListReference(); + if (value instanceof RuntimeList list) { + if (list.size() == 1) { + registers[rd] = list.getFirst().createReference(); + } else { + RuntimeList refs = new RuntimeList(); + for (RuntimeScalar element : list) { + refs.add(element.createReference()); + } + registers[rd] = refs; + } } else { - // Single value or single-element list: create single reference registers[rd] = value.createReference(); } return pc; diff --git a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java index 93c94fdd7..6a8a059de 100644 --- a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java @@ -342,6 +342,7 @@ static OperatorNode parseVariableDeclaration(Parser parser, String operator, int scalarVarNode = new OperatorNode("$", varNode.operand, varNode.tokenIndex); } scalarVarNode.setAnnotation("isDeclaredReference", true); + scalarVarNode.setAnnotation("declaredReferenceOriginalSigil", varNode.operator); addVariableToScope(parser.ctx, operator, scalarVarNode); // Also mark the original nodes varNode.setAnnotation("isDeclaredReference", true); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java index 654d8cfdd..9c020d76e 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeList.java @@ -370,9 +370,8 @@ public RuntimeScalar createReference() { public RuntimeList createListReference() { RuntimeList result = new RuntimeList(); List resultList = result.elements; - Iterator iterator = this.iterator(); - while (iterator.hasNext()) { - resultList.add(iterator.next().createReference()); + for (RuntimeBase element : this.elements) { + resultList.add(element.createReference()); } return result; }