Skip to content

Commit ca88698

Browse files
authored
Merge pull request #40 from fglock/fix-test-failures
Fix declared references support for local and improve reference retur…
2 parents ab54eb8 + 19e6c58 commit ca88698

File tree

2 files changed

+109
-10
lines changed

2 files changed

+109
-10
lines changed

src/main/java/org/perlonjava/codegen/EmitVariable.java

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,22 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) {
654654
return;
655655
} else if (node.operand instanceof OperatorNode sigilNode) { // [my our] followed by [$ @ %]
656656
String sigil = sigilNode.operator;
657+
658+
// Handle my \\$x - reference to a declared reference
659+
if (sigil.equals("\\") && node.annotations != null &&
660+
Boolean.TRUE.equals(node.annotations.get("isDeclaredReference"))) {
661+
// This is my \\$x which means: create a declared reference and then take a reference to it
662+
// The operand is \$x, so we need to emit the declared reference creation
663+
// and then take a reference to it
664+
665+
// First, emit the declared reference variable (the inner part)
666+
sigilNode.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
667+
668+
// The variable is now on the stack, and we're in an assignment context
669+
// The assignment operator will handle storing the reference
670+
return;
671+
}
672+
657673
if ("$@%".contains(sigil)) {
658674
Node identifierNode = sigilNode.operand;
659675
if (identifierNode instanceof IdentifierNode) { // my $a
@@ -762,20 +778,25 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) {
762778
// Create and fetch a global variable
763779
fetchGlobalVariable(emitterVisitor.ctx, true, sigil, name, node.getIndex());
764780
}
765-
// For declared references in non-void context, we need different handling
781+
// Store the variable in a JVM local variable
782+
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, varIndex);
783+
784+
// For declared references in non-void context, return a reference to the variable
766785
if (isDeclaredReference && emitterVisitor.ctx.contextType != RuntimeContextType.VOID) {
767-
// Duplicate the variable for storage
768-
emitterVisitor.ctx.mv.visitInsn(Opcodes.DUP);
769-
// Store in a JVM local variable
770-
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, varIndex);
771-
// The original is still on the stack for the assignment
786+
// Load the variable back from the local variable slot
787+
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, varIndex);
788+
// Create a reference to it
789+
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
790+
"org/perlonjava/runtime/RuntimeBase",
791+
"createReference",
792+
"()Lorg/perlonjava/runtime/RuntimeScalar;",
793+
false);
772794
} else {
773795
// Normal handling for non-declared references
774796
if (emitterVisitor.ctx.contextType != RuntimeContextType.VOID) {
775-
emitterVisitor.ctx.mv.visitInsn(Opcodes.DUP);
797+
// Load the variable back for the return value
798+
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, varIndex);
776799
}
777-
// Store in a JVM local variable
778-
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, varIndex);
779800
}
780801

781802
if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR && !sigil.equals("$")) {

src/main/java/org/perlonjava/parser/OperatorParser.java

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,14 +753,92 @@ static OperatorNode parseGoto(Parser parser, int currentIndex) {
753753
}
754754

755755
static OperatorNode parseLocal(Parser parser, LexerToken token, int currentIndex) {
756+
// Check if this is a declared reference (local \$x, local \@array, etc.)
757+
boolean isDeclaredReference = false;
758+
if (peek(parser).type == LexerTokenType.OPERATOR && peek(parser).text.equals("\\")) {
759+
isDeclaredReference = true;
760+
761+
// Check if declared_refs feature is enabled
762+
if (!parser.ctx.symbolTable.isFeatureCategoryEnabled("declared_refs")) {
763+
throw new PerlCompilerException(
764+
currentIndex,
765+
"The experimental declared_refs feature is not enabled",
766+
parser.ctx.errorUtil
767+
);
768+
}
769+
770+
// Emit experimental warning if warnings are enabled
771+
if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::declared_refs")) {
772+
// Use WarnDie.warn to respect $SIG{__WARN__} handler
773+
try {
774+
WarnDie.warn(
775+
new RuntimeScalar("Declaring references is experimental"),
776+
new RuntimeScalar(parser.ctx.errorUtil.errorMessage(currentIndex, ""))
777+
);
778+
} catch (Exception e) {
779+
// If warning system isn't initialized yet, fall back to System.err
780+
System.err.println(parser.ctx.errorUtil.errorMessage(currentIndex, "Declaring references is experimental"));
781+
}
782+
}
783+
784+
TokenUtils.consume(parser, LexerTokenType.OPERATOR, "\\");
785+
}
786+
756787
Node operand;
757788
// Handle 'local' keyword as a unary operator with an operand
758789
if (peek(parser).text.equals("(")) {
759790
operand = ParsePrimary.parsePrimary(parser);
760791
} else {
761792
operand = parser.parseExpression(parser.getPrecedence("++"));
762793
}
763-
return new OperatorNode(token.text, operand, currentIndex);
794+
795+
// Check for declared references inside parentheses: local(\$x)
796+
if (operand instanceof ListNode listNode) {
797+
for (Node element : listNode.elements) {
798+
if (element instanceof OperatorNode operandNode) {
799+
// Check if this element is a reference operator (backslash)
800+
// This handles cases like local(\$x) where the backslash is inside the parentheses
801+
if (operandNode.operator.equals("\\") && operandNode.operand instanceof OperatorNode) {
802+
// This is a declared reference inside parentheses: local(\$x), local(\@arr), local(\%hash)
803+
804+
// Check if declared_refs feature is enabled
805+
if (!parser.ctx.symbolTable.isFeatureCategoryEnabled("declared_refs")) {
806+
throw new PerlCompilerException(
807+
operandNode.tokenIndex,
808+
"The experimental declared_refs feature is not enabled",
809+
parser.ctx.errorUtil
810+
);
811+
}
812+
813+
// Emit experimental warning if warnings are enabled
814+
if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::declared_refs")) {
815+
// Use WarnDie.warn to respect $SIG{__WARN__} handler
816+
try {
817+
WarnDie.warn(
818+
new RuntimeScalar("Declaring references is experimental"),
819+
new RuntimeScalar(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, ""))
820+
);
821+
} catch (Exception e) {
822+
// If warning system isn't initialized yet, fall back to System.err
823+
System.err.println(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "Declaring references is experimental"));
824+
}
825+
}
826+
827+
// Mark the nodes as declared references
828+
operandNode.setAnnotation("isDeclaredReference", true);
829+
if (operandNode.operand instanceof OperatorNode varNode) {
830+
varNode.setAnnotation("isDeclaredReference", true);
831+
}
832+
}
833+
}
834+
}
835+
}
836+
837+
OperatorNode localNode = new OperatorNode(token.text, operand, currentIndex);
838+
if (isDeclaredReference) {
839+
localNode.setAnnotation("isDeclaredReference", true);
840+
}
841+
return localNode;
764842
}
765843

766844
static OperatorNode parseReverse(Parser parser, LexerToken token, int currentIndex) {

0 commit comments

Comments
 (0)