Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions src/main/java/org/perlonjava/codegen/EmitVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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("$")) {
Expand Down
80 changes: 79 additions & 1 deletion src/main/java/org/perlonjava/parser/OperatorParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -753,14 +753,92 @@ 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("(")) {
operand = ParsePrimary.parsePrimary(parser);
} 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) {
Expand Down