Skip to content

Commit b75a84c

Browse files
committed
Fix double dereference syntax 49314arrayref[index] and 49314hashref{key}
- Added AST transformation in ParseInfix.java to convert 49314var[index] to ->[index] - Added AST transformation in ParseInfix.java to convert 49314var{key} to ->{key} - These syntaxes are equivalent in Perl but were not working in PerlOnJava Impact: - Unblocks ~354 tests in t/op/index.t that use 49314test[0] pattern - Fixes array and hash double dereference throughout the codebase Test: - Added comprehensive test file dereference_syntax.t with 7 subtests - All tests pass with both standard Perl and PerlOnJava - Tests cover basic dereference, assignment, edge cases, and loop patterns
1 parent 7597cb3 commit b75a84c

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,22 @@ public static Node parseInfixOperation(Parser parser, Node left, int precedence)
179179
case "{":
180180
// Handle hash subscripts
181181
right = new HashLiteralNode(parseHashSubscript(parser), parser.tokenIndex);
182+
// Check if left is $$var and transform to $var->{...}
183+
if (left instanceof OperatorNode leftOp && leftOp.operator.equals("$")
184+
&& leftOp.operand instanceof OperatorNode innerOp && innerOp.operator.equals("$")) {
185+
// Transform $$var{...} to $var->{...}
186+
return new BinaryOperatorNode("->", innerOp, right, parser.tokenIndex);
187+
}
182188
return new BinaryOperatorNode(token.text, left, right, parser.tokenIndex);
183189
case "[":
184190
// Handle array subscripts
185191
right = new ArrayLiteralNode(parseArraySubscript(parser), parser.tokenIndex);
192+
// Check if left is $$var and transform to $var->[...]
193+
if (left instanceof OperatorNode leftOp && leftOp.operator.equals("$")
194+
&& leftOp.operand instanceof OperatorNode innerOp && innerOp.operator.equals("$")) {
195+
// Transform $$var[...] to $var->[...]
196+
return new BinaryOperatorNode("->", innerOp, right, parser.tokenIndex);
197+
}
186198
return new BinaryOperatorNode(token.text, left, right, parser.tokenIndex);
187199
case "--":
188200
case "++":
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env perl
2+
use strict;
3+
use warnings;
4+
use Test::More;
5+
6+
# Test double dereference syntax: $$arrayref[index] and $$hashref{key}
7+
# These should be equivalent to $arrayref->[index] and $hashref->{key}
8+
9+
subtest 'Basic array double dereference' => sub {
10+
plan tests => 6;
11+
12+
my @array = ('first', 'second', 'third');
13+
my $ref = \@array;
14+
15+
# Test $$ref[index] syntax
16+
is($$ref[0], 'first', '$$ref[0] returns first element');
17+
is($$ref[1], 'second', '$$ref[1] returns second element');
18+
is($$ref[2], 'third', '$$ref[2] returns third element');
19+
20+
# Verify equivalence with arrow syntax
21+
is($$ref[0], $ref->[0], '$$ref[0] equals $ref->[0]');
22+
is($$ref[1], $ref->[1], '$$ref[1] equals $ref->[1]');
23+
is($$ref[2], $ref->[2], '$$ref[2] equals $ref->[2]');
24+
};
25+
26+
subtest 'Basic hash double dereference' => sub {
27+
plan tests => 6;
28+
29+
my %hash = (
30+
key1 => 'value1',
31+
key2 => 'value2',
32+
key3 => 'value3',
33+
);
34+
my $ref = \%hash;
35+
36+
# Test $$ref{key} syntax
37+
is($$ref{key1}, 'value1', '$$ref{key1} returns value1');
38+
is($$ref{key2}, 'value2', '$$ref{key2} returns value2');
39+
is($$ref{key3}, 'value3', '$$ref{key3} returns value3');
40+
41+
# Verify equivalence with arrow syntax
42+
is($$ref{key1}, $ref->{key1}, '$$ref{key1} equals $ref->{key1}');
43+
is($$ref{key2}, $ref->{key2}, '$$ref{key2} equals $ref->{key2}');
44+
is($$ref{key3}, $ref->{key3}, '$$ref{key3} equals $ref->{key3}');
45+
};
46+
47+
subtest 'Array of arrays (AoA) double dereference' => sub {
48+
plan tests => 4;
49+
50+
my @tests = (
51+
["foo", "bar", "baz"],
52+
["one", "two", "three"],
53+
["alpha", "beta", "gamma"],
54+
);
55+
56+
foreach my $test (@tests) {
57+
# This is the pattern used in t/op/index.t that was failing
58+
my $first = $$test[0];
59+
ok(defined $first, "$$test[0] is defined");
60+
}
61+
62+
# Specific test case from index.t
63+
my $test = ["foo", 1, 2];
64+
is($$test[0], 'foo', '$$test[0] from array ref returns correct value');
65+
};
66+
67+
subtest 'Hash of hashes (HoH) double dereference' => sub {
68+
plan tests => 3;
69+
70+
my %data = (
71+
user1 => { name => 'Alice', age => 30 },
72+
user2 => { name => 'Bob', age => 25 },
73+
);
74+
75+
my $user1_ref = $data{user1};
76+
is($$user1_ref{name}, 'Alice', '$$hashref{key} for nested hash');
77+
is($$user1_ref{age}, 30, '$$hashref{key} for numeric value');
78+
79+
my $user2_ref = $data{user2};
80+
is($$user2_ref{name}, 'Bob', '$$hashref{key} for different hash');
81+
};
82+
83+
subtest 'Assignment through double dereference' => sub {
84+
plan tests => 4;
85+
86+
# Array assignment
87+
my @array = (1, 2, 3);
88+
my $aref = \@array;
89+
$$aref[1] = 42;
90+
is($array[1], 42, 'Assignment through $$ref[index] modifies array');
91+
is($$aref[1], 42, 'Reading back through $$ref[index] works');
92+
93+
# Hash assignment
94+
my %hash = (key => 'old');
95+
my $href = \%hash;
96+
$$href{key} = 'new';
97+
is($hash{key}, 'new', 'Assignment through $$ref{key} modifies hash');
98+
is($$href{key}, 'new', 'Reading back through $$ref{key} works');
99+
};
100+
101+
subtest 'Edge cases and special scenarios' => sub {
102+
plan tests => 5;
103+
104+
# Undefined index
105+
my @array = (1, 2, 3);
106+
my $aref = \@array;
107+
is($$aref[10], undef, '$$ref[out_of_bounds] returns undef');
108+
109+
# Non-existent hash key
110+
my %hash = (key => 'value');
111+
my $href = \%hash;
112+
is($$href{nonexistent}, undef, '$$ref{nonexistent_key} returns undef');
113+
114+
# Empty array
115+
my @empty = ();
116+
my $empty_ref = \@empty;
117+
is($$empty_ref[0], undef, '$$ref[0] on empty array returns undef');
118+
119+
# Empty hash
120+
my %empty_hash = ();
121+
my $empty_href = \%empty_hash;
122+
is($$empty_href{key}, undef, '$$ref{key} on empty hash returns undef');
123+
124+
# Numeric hash key
125+
my %num_hash = (42 => 'answer');
126+
my $num_href = \%num_hash;
127+
is($$num_href{42}, 'answer', '$$ref{numeric_key} works');
128+
};
129+
130+
subtest 'Mixed references in loops' => sub {
131+
plan tests => 6;
132+
133+
# Pattern similar to op/index.t
134+
my @test_data = (
135+
["", -1, -1, -1],
136+
["foo", -1, -1, -1],
137+
["x", 0, -1, -1],
138+
);
139+
140+
my $i = 0;
141+
foreach my $test (@test_data) {
142+
my $str = $$test[0];
143+
ok(defined $str || $str eq '', 'String extracted from $$test[0]');
144+
my $expected = ($i == 2) ? 0 : -1; # Third test has 0, others have -1
145+
my $val = $$test[1];
146+
is($val, $expected, "Numeric value extracted from $$test[1] is $expected");
147+
$i++;
148+
}
149+
};
150+
151+
done_testing();

0 commit comments

Comments
 (0)