Skip to content

Commit 79be360

Browse files
committed
#16 - Add support for nested property-access
1 parent 935c54b commit 79be360

File tree

4 files changed

+148
-11
lines changed

4 files changed

+148
-11
lines changed

parser/parser.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,32 @@ static void xx_ret_let_assignment(zval *ret, char *type, zval *operator, xx_pars
670670
parser_add_int(ret, "char", state->active_char);
671671
}
672672

673+
// New helper supporting nested property access assignments where the base is an expression (e.g. this->arr->arr = 1)
674+
static void xx_ret_let_property_access_assignment(zval *ret, zval *operator, zval *left_expr, xx_parser_token *P, zval *expr, xx_scanner_state *state)
675+
{
676+
array_init(ret);
677+
678+
parser_add_str(ret, "assign-type", "property-access");
679+
if (operator) {
680+
parser_add_zval(ret, "operator", operator);
681+
}
682+
/* Store the left expression chain */
683+
parser_add_zval(ret, "left", left_expr);
684+
685+
if (P) {
686+
parser_add_str_free(ret, "property", P->token);
687+
efree(P);
688+
}
689+
690+
if (expr) {
691+
parser_add_zval(ret, "expr", expr);
692+
}
693+
694+
parser_add_str(ret, "file", state->active_file);
695+
parser_add_int(ret, "line", state->active_line);
696+
parser_add_int(ret, "char", state->active_char);
697+
}
698+
673699
static void xx_ret_if_statement(zval *ret, zval *expr, zval *statements, zval *elseif_statements, zval *else_statements, xx_scanner_state *state)
674700
{
675701
array_init(ret);

parser/zephir.lemon

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
%right BITWISE_NOT .
6666
%right PARENTHESES_CLOSE .
6767
%right SBRACKET_OPEN .
68-
%right ARROW .
68+
%left ARROW .
6969

7070
// The following text is included near the beginning of the C source
7171
// code file that implements the parser.
@@ -464,10 +464,6 @@ xx_class_definition(R) ::= xx_class_properties_definition(C) xx_class_consts_def
464464
xx_ret_class_definition(&R, &C, &M, &K, status->scanner_state);
465465
}
466466

467-
xx_class_definition(R) ::= xx_class_consts_definition(K) xx_class_properties_definition(C) xx_class_methods_definition(M) . {
468-
xx_ret_class_definition(&R, &C, &M, &K, status->scanner_state);
469-
}
470-
471467
xx_interface_definition(R) ::= xx_class_consts_definition(C) . {
472468
xx_ret_interface_definition(&R, NULL, &C, status->scanner_state);
473469
}
@@ -1401,6 +1397,10 @@ xx_for_statement(R) ::= FOR IDENTIFIER(K) COMMA IDENTIFIER(V) IN REVERSE xx_comm
14011397
xx_ret_for_statement(&R, &E, K, V, 1, &L, status->scanner_state);
14021398
}
14031399

1400+
xx_for_statement(R) ::= FOR IDENTIFIER(K) COMMA IDENTIFIER(V) IN REVERSE xx_common_expr(E) BRACKET_OPEN BRACKET_CLOSE . {
1401+
xx_ret_for_statement(&R, &E, K, V, 1, NULL, status->scanner_state);
1402+
}
1403+
14041404
xx_for_statement(R) ::= FOR PARENTHESES_OPEN IDENTIFIER(V) IN xx_common_expr(E) PARENTHESES_CLOSE BRACKET_OPEN xx_statement_list(L) BRACKET_CLOSE . {
14051405
xx_ret_for_statement(&R, &E, NULL, V, 0, &L, status->scanner_state);
14061406
}
@@ -1523,14 +1523,15 @@ xx_let_assignment(R) ::= IDENTIFIER(D) ARROW IDENTIFIER(I) SBRACKET_OPEN SBRACKE
15231523
}
15241524

15251525
/* y->x[z][] = {expr} */
1526-
xx_let_assignment(R) ::= IDENTIFIER(D) ARROW IDENTIFIER(I) xx_array_offset_list(X) xx_assignment_operator(O) xx_assign_expr(E) . {
1527-
xx_ret_let_assignment(&R, "object-property-array-index", &O, D, I, &X, &E, status->scanner_state);
1528-
}
1529-
15301526
xx_let_assignment(R) ::= IDENTIFIER(D) ARROW IDENTIFIER(I) xx_array_offset_list(X) SBRACKET_OPEN SBRACKET_CLOSE xx_assignment_operator(O) xx_assign_expr(E) . {
15311527
xx_ret_let_assignment(&R, "object-property-array-index-append", &O, D, I, &X, &E, status->scanner_state);
15321528
}
15331529

1530+
/* {expr}->x = {expr} (nested property access) */
1531+
xx_let_assignment(R) ::= xx_common_expr(V) ARROW IDENTIFIER(I) xx_assignment_operator(O) xx_assign_expr(E) . {
1532+
xx_ret_let_property_access_assignment(&R, &O, &V, I, &E, status->scanner_state);
1533+
}
1534+
15341535
/* y::x = {expr} */
15351536
xx_let_assignment(R) ::= IDENTIFIER(D) DOUBLECOLON IDENTIFIER(I) xx_assignment_operator(O) xx_assign_expr(E) . {
15361537
xx_ret_let_assignment(&R, "static-property", &O, D, I, NULL, &E, status->scanner_state);
@@ -1994,11 +1995,11 @@ xx_common_expr(R) ::= xx_common_expr(O1) EXCLUSIVE_RANGE xx_common_expr(O2) . {
19941995
}
19951996

19961997
/* y = fetch x, z[k] */
1997-
xx_fetch_expr(R) ::= FETCH IDENTIFIER(O1) COMMA xx_common_expr(O2) . {
1998+
xx_fetch_expr(R) ::= FETCH IDENTIFIER(O1) COMMA xx_common_expr(E2) . {
19981999
{
19992000
zval identifier;
20002001
xx_ret_literal(&identifier, XX_T_IDENTIFIER, O1, status->scanner_state);
2001-
xx_ret_expr(&R, "fetch", &identifier, &O2, NULL, status->scanner_state);
2002+
xx_ret_expr(&R, "fetch", &identifier, &E2, NULL, status->scanner_state);
20022003
}
20032004
}
20042005

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
--TEST--
2+
Deep nested property access in let assignment (this->a->b->c = 42)
3+
--SKIPIF--
4+
<?php include(__DIR__ . '/../skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
$code = <<<'ZEP'
8+
namespace Debug;
9+
10+
class Chain
11+
{
12+
public a;
13+
public b;
14+
public c;
15+
16+
public function make()
17+
{
18+
let this->a = new Chain();
19+
let this->a->b = new Chain();
20+
let this->a->b->c = 42;
21+
}
22+
}
23+
ZEP;
24+
25+
$ir = zephir_parse_file($code, '(eval code)');
26+
$class = $ir[1];
27+
$methods = $class['definition']['methods'];
28+
$make = null;
29+
foreach ($methods as $m) {
30+
if ($m['name'] === 'make') { $make = $m; break; }
31+
}
32+
if (!$make) { echo "MISSING_METHOD\n"; return; }
33+
$statements = $make['statements'];
34+
$lets = [];
35+
foreach ($statements as $st) { if ($st['type'] === 'let') { $lets[] = $st; } }
36+
if (count($lets) !== 3) { echo "WRONG_LET_COUNT\n"; return; }
37+
$a1 = $lets[0]['assignments'][0];
38+
$a2 = $lets[1]['assignments'][0];
39+
$a3 = $lets[2]['assignments'][0];
40+
// Validate assign types
41+
if ($a1['assign-type'] !== 'object-property' || $a1['property'] !== 'a') { echo "FIRST_FAIL\n"; return; }
42+
if ($a2['assign-type'] !== 'property-access' || $a2['property'] !== 'b') { echo "SECOND_FAIL\n"; return; }
43+
if ($a3['assign-type'] !== 'property-access' || $a3['property'] !== 'c') { echo "THIRD_FAIL\n"; return; }
44+
// Check left chain forms
45+
if ($a2['left']['type'] !== 'property-access') { echo "CHAIN2_FAIL\n"; return; }
46+
if ($a3['left']['type'] !== 'property-access') { echo "CHAIN3_FAIL\n"; return; }
47+
// Ensure deepest chain left-left structure ends with identifier 'a'
48+
$left = $a3['left'];
49+
// Walk back one level: left = ( (this->a->b) ) -> retrieve its left
50+
$l2 = $left['left'];
51+
$l3 = $l2['left']; // Should be identifier 'this'
52+
if ($l2['right']['value'] !== 'b') { echo "B_PROP_MISSING\n"; return; }
53+
if ($l3['right']['value'] !== 'a') { echo "A_PROP_MISSING\n"; return; }
54+
echo "OK\n";
55+
?>
56+
--EXPECT--
57+
OK
58+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
Nested property access in let assignment (this->arr->arr = 1)
3+
--SKIPIF--
4+
<?php include(__DIR__ . '/../skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
$code = <<<'ZEP'
8+
namespace Debug;
9+
10+
class ZephirDebug
11+
{
12+
public arr;
13+
14+
public static function test()
15+
{
16+
let this->arr = new ZephirDebug();
17+
let this->arr->arr = 1;
18+
}
19+
}
20+
ZEP;
21+
22+
try {
23+
$ir = zephir_parse_file($code, '(eval code)');
24+
// Find the class definition and its method to assert assignments exist
25+
$class = $ir[1];
26+
$methods = $class['definition']['methods'];
27+
$testMethod = null;
28+
foreach ($methods as $m) {
29+
if ($m['name'] === 'test') { $testMethod = $m; break; }
30+
}
31+
if (!$testMethod) {
32+
echo "MISSING_METHOD\n"; exit; }
33+
$statements = $testMethod['statements'];
34+
$lets = [];
35+
foreach ($statements as $st) { if ($st['type'] === 'let') { $lets[] = $st; } }
36+
if (count($lets) !== 2) { echo "WRONG_LET_COUNT\n"; exit; }
37+
$a1 = $lets[0]['assignments'][0];
38+
$a2 = $lets[1]['assignments'][0];
39+
// First assignment should be a simple object property
40+
if ($a1['assign-type'] !== 'object-property' || $a1['property'] !== 'arr') { echo "FIRST_ASSIGN_FAIL\n"; exit; }
41+
// Second assignment should represent nested property access (current helper names it 'property-access')
42+
if ($a2['assign-type'] !== 'property-access' || $a2['property'] !== 'arr') { echo "SECOND_ASSIGN_FAIL\n"; exit; }
43+
// Ensure left side is a property-access expression chain
44+
if ($a2['left']['type'] !== 'property-access') { echo "LEFT_EXPR_FAIL\n"; exit; }
45+
echo "OK\n";
46+
} catch (Throwable $e) {
47+
echo 'EXCEPTION: ' . $e->getMessage() . "\n";
48+
}
49+
?>
50+
--EXPECT--
51+
OK
52+

0 commit comments

Comments
 (0)