diff --git a/src/main/java/org/perlonjava/codegen/EmitVariable.java b/src/main/java/org/perlonjava/codegen/EmitVariable.java index aeabae379..d19ca3368 100644 --- a/src/main/java/org/perlonjava/codegen/EmitVariable.java +++ b/src/main/java/org/perlonjava/codegen/EmitVariable.java @@ -654,6 +654,22 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { return; } else if (node.operand instanceof OperatorNode sigilNode) { // [my our] followed by [$ @ %] String sigil = sigilNode.operator; + + // Handle my \\$x - reference to a declared reference + if (sigil.equals("\\") && node.annotations != null && + Boolean.TRUE.equals(node.annotations.get("isDeclaredReference"))) { + // This is my \\$x which means: create a declared reference and then take a reference to it + // The operand is \$x, so we need to emit the declared reference creation + // and then take a reference to it + + // First, emit the declared reference variable (the inner part) + sigilNode.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + + // The variable is now on the stack, and we're in an assignment context + // The assignment operator will handle storing the reference + return; + } + if ("$@%".contains(sigil)) { Node identifierNode = sigilNode.operand; if (identifierNode instanceof IdentifierNode) { // my $a @@ -762,20 +778,25 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { // Create and fetch a global variable fetchGlobalVariable(emitterVisitor.ctx, true, sigil, name, node.getIndex()); } - // For declared references in non-void context, we need different handling + // Store the variable in a JVM local variable + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, varIndex); + + // For declared references in non-void context, return a reference to the variable if (isDeclaredReference && emitterVisitor.ctx.contextType != RuntimeContextType.VOID) { - // Duplicate the variable for storage - emitterVisitor.ctx.mv.visitInsn(Opcodes.DUP); - // Store in a JVM local variable - emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, varIndex); - // The original is still on the stack for the assignment + // Load the variable back from the local variable slot + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, varIndex); + // Create a reference to it + emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "org/perlonjava/runtime/RuntimeBase", + "createReference", + "()Lorg/perlonjava/runtime/RuntimeScalar;", + false); } else { // Normal handling for non-declared references if (emitterVisitor.ctx.contextType != RuntimeContextType.VOID) { - emitterVisitor.ctx.mv.visitInsn(Opcodes.DUP); + // Load the variable back for the return value + emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, varIndex); } - // Store in a JVM local variable - emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, varIndex); } if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR && !sigil.equals("$")) { diff --git a/src/main/java/org/perlonjava/parser/OperatorParser.java b/src/main/java/org/perlonjava/parser/OperatorParser.java index c5436cbd2..eebb6ec47 100644 --- a/src/main/java/org/perlonjava/parser/OperatorParser.java +++ b/src/main/java/org/perlonjava/parser/OperatorParser.java @@ -753,6 +753,37 @@ static OperatorNode parseGoto(Parser parser, int currentIndex) { } static OperatorNode parseLocal(Parser parser, LexerToken token, int currentIndex) { + // Check if this is a declared reference (local \$x, local \@array, etc.) + boolean isDeclaredReference = false; + if (peek(parser).type == LexerTokenType.OPERATOR && peek(parser).text.equals("\\")) { + isDeclaredReference = true; + + // Check if declared_refs feature is enabled + if (!parser.ctx.symbolTable.isFeatureCategoryEnabled("declared_refs")) { + throw new PerlCompilerException( + currentIndex, + "The experimental declared_refs feature is not enabled", + parser.ctx.errorUtil + ); + } + + // Emit experimental warning if warnings are enabled + if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::declared_refs")) { + // Use WarnDie.warn to respect $SIG{__WARN__} handler + try { + WarnDie.warn( + new RuntimeScalar("Declaring references is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.errorMessage(currentIndex, "")) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println(parser.ctx.errorUtil.errorMessage(currentIndex, "Declaring references is experimental")); + } + } + + TokenUtils.consume(parser, LexerTokenType.OPERATOR, "\\"); + } + Node operand; // Handle 'local' keyword as a unary operator with an operand if (peek(parser).text.equals("(")) { @@ -760,7 +791,54 @@ static OperatorNode parseLocal(Parser parser, LexerToken token, int currentIndex } else { operand = parser.parseExpression(parser.getPrecedence("++")); } - return new OperatorNode(token.text, operand, currentIndex); + + // Check for declared references inside parentheses: local(\$x) + if (operand instanceof ListNode listNode) { + for (Node element : listNode.elements) { + if (element instanceof OperatorNode operandNode) { + // Check if this element is a reference operator (backslash) + // This handles cases like local(\$x) where the backslash is inside the parentheses + if (operandNode.operator.equals("\\") && operandNode.operand instanceof OperatorNode) { + // This is a declared reference inside parentheses: local(\$x), local(\@arr), local(\%hash) + + // Check if declared_refs feature is enabled + if (!parser.ctx.symbolTable.isFeatureCategoryEnabled("declared_refs")) { + throw new PerlCompilerException( + operandNode.tokenIndex, + "The experimental declared_refs feature is not enabled", + parser.ctx.errorUtil + ); + } + + // Emit experimental warning if warnings are enabled + if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::declared_refs")) { + // Use WarnDie.warn to respect $SIG{__WARN__} handler + try { + WarnDie.warn( + new RuntimeScalar("Declaring references is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "")) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "Declaring references is experimental")); + } + } + + // Mark the nodes as declared references + operandNode.setAnnotation("isDeclaredReference", true); + if (operandNode.operand instanceof OperatorNode varNode) { + varNode.setAnnotation("isDeclaredReference", true); + } + } + } + } + } + + OperatorNode localNode = new OperatorNode(token.text, operand, currentIndex); + if (isDeclaredReference) { + localNode.setAnnotation("isDeclaredReference", true); + } + return localNode; } static OperatorNode parseReverse(Parser parser, LexerToken token, int currentIndex) {