Skip to content

Commit 23d9c17

Browse files
committed
Add support for nullsafe operator
1 parent 31be7b4 commit 23d9c17

File tree

15 files changed

+877
-583
lines changed

15 files changed

+877
-583
lines changed

grammar/php7.y

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,8 @@ callable_variable:
943943
| function_call { $$ = $1; }
944944
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
945945
{ $$ = Expr\MethodCall[$1, $3, $4]; }
946+
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
947+
{ $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
946948
;
947949

948950
optional_plain_variable:
@@ -955,6 +957,8 @@ variable:
955957
| static_member { $$ = $1; }
956958
| array_object_dereferencable T_OBJECT_OPERATOR property_name
957959
{ $$ = Expr\PropertyFetch[$1, $3]; }
960+
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name
961+
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
958962
;
959963

960964
simple_variable:
@@ -979,6 +983,7 @@ new_variable:
979983
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
980984
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
981985
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
986+
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
982987
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
983988
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
984989
| new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
@@ -1048,6 +1053,7 @@ encaps_var:
10481053
plain_variable { $$ = $1; }
10491054
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
10501055
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
1056+
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
10511057
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
10521058
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
10531059
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'

grammar/tokens.y

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
%token T_EXTENDS
8686
%token T_IMPLEMENTS
8787
%token T_OBJECT_OPERATOR
88+
%token T_NULLSAFE_OBJECT_OPERATOR
8889
%token T_DOUBLE_ARROW
8990
%token T_LIST
9091
%token T_ARRAY

lib/PhpParser/Lexer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,9 @@ private function defineCompatibilityTokens() {
428428
if (!defined('T_MATCH')) {
429429
\define('T_MATCH', -7);
430430
}
431+
if (!defined('T_NULLSAFE_OBJECT_OPERATOR')) {
432+
\define('T_NULLSAFE_OBJECT_OPERATOR', -8);
433+
}
431434
}
432435

433436
/**
@@ -481,6 +484,7 @@ protected function createTokenMap() : array {
481484
$tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED;
482485
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
483486
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
487+
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
484488

485489
return $tokenMap;
486490
}

lib/PhpParser/Lexer/Emulative.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
99
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
1010
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
11+
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
1112
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
1213
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface;
1314
use PhpParser\Parser\Tokens;
@@ -49,6 +50,7 @@ public function __construct(array $options = [])
4950
$this->tokenEmulators[] = new MatchTokenEmulator();
5051
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator();
5152
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator();
53+
$this->tokenEmulators[] = new NullsafeTokenEmulator();
5254
}
5355

5456
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PhpParser\Lexer\TokenEmulator;
4+
5+
use PhpParser\Lexer\Emulative;
6+
7+
final class NullsafeTokenEmulator implements TokenEmulatorInterface
8+
{
9+
public function getPhpVersion(): string
10+
{
11+
return Emulative::PHP_8_0;
12+
}
13+
14+
public function isEmulationNeeded(string $code): bool
15+
{
16+
return strpos($code, '?->') !== false;
17+
}
18+
19+
public function emulate(string $code, array $tokens): array
20+
{
21+
// We need to manually iterate and manage a count because we'll change
22+
// the tokens array on the way
23+
$line = 1;
24+
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
25+
if (isset($tokens[$i + 1])) {
26+
if ($tokens[$i] === '?' && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
27+
array_splice($tokens, $i, 2, [
28+
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
29+
]);
30+
$c--;
31+
continue;
32+
}
33+
}
34+
if (\is_array($tokens[$i])) {
35+
$line += substr_count($tokens[$i][1], "\n");
36+
}
37+
}
38+
39+
return $tokens;
40+
}
41+
42+
public function reverseEmulate(string $code, array $tokens): array
43+
{
44+
// ?-> was not valid code previously, don't bother.
45+
return $tokens;
46+
}
47+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PhpParser\Node\Expr;
4+
5+
use PhpParser\Node\Arg;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Identifier;
8+
9+
class NullsafeMethodCall extends Expr
10+
{
11+
/** @var Expr Variable holding object */
12+
public $var;
13+
/** @var Identifier|Expr Method name */
14+
public $name;
15+
/** @var Arg[] Arguments */
16+
public $args;
17+
18+
/**
19+
* Constructs a nullsafe method call node.
20+
*
21+
* @param Expr $var Variable holding object
22+
* @param string|Identifier|Expr $name Method name
23+
* @param Arg[] $args Arguments
24+
* @param array $attributes Additional attributes
25+
*/
26+
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
27+
$this->attributes = $attributes;
28+
$this->var = $var;
29+
$this->name = \is_string($name) ? new Identifier($name) : $name;
30+
$this->args = $args;
31+
}
32+
33+
public function getSubNodeNames() : array {
34+
return ['var', 'name', 'args'];
35+
}
36+
37+
public function getType() : string {
38+
return 'Expr_NullsafeMethodCall';
39+
}
40+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PhpParser\Node\Expr;
4+
5+
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Identifier;
7+
8+
class NullsafePropertyFetch extends Expr
9+
{
10+
/** @var Expr Variable holding object */
11+
public $var;
12+
/** @var Identifier|Expr Property name */
13+
public $name;
14+
15+
/**
16+
* Constructs a nullsafe property fetch node.
17+
*
18+
* @param Expr $var Variable holding object
19+
* @param string|Identifier|Expr $name Property name
20+
* @param array $attributes Additional attributes
21+
*/
22+
public function __construct(Expr $var, $name, array $attributes = []) {
23+
$this->attributes = $attributes;
24+
$this->var = $var;
25+
$this->name = \is_string($name) ? new Identifier($name) : $name;
26+
}
27+
28+
public function getSubNodeNames() : array {
29+
return ['var', 'name'];
30+
}
31+
32+
public function getType() : string {
33+
return 'Expr_NullsafePropertyFetch';
34+
}
35+
}

lib/PhpParser/Parser/Php5.php

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
*/
1818
class Php5 extends \PhpParser\ParserAbstract
1919
{
20-
protected $tokenToSymbolMapSize = 390;
20+
protected $tokenToSymbolMapSize = 391;
2121
protected $actionTableSize = 1061;
2222
protected $gotoTableSize = 580;
2323

24-
protected $invalidSymbol = 163;
24+
protected $invalidSymbol = 164;
2525
protected $errorSymbol = 1;
2626
protected $defaultAction = -32766;
2727
protected $unexpectedTokenRule = 32767;
@@ -192,36 +192,37 @@ class Php5 extends \PhpParser\ParserAbstract
192192
"'$'",
193193
"'`'",
194194
"']'",
195-
"'\"'"
195+
"'\"'",
196+
"T_NULLSAFE_OBJECT_OPERATOR"
196197
);
197198

198199
protected $tokenToSymbol = array(
199-
0, 163, 163, 163, 163, 163, 163, 163, 163, 163,
200-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
201-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
202-
163, 163, 163, 54, 162, 163, 159, 53, 36, 163,
203-
157, 158, 51, 48, 7, 49, 50, 52, 163, 163,
204-
163, 163, 163, 163, 163, 163, 163, 163, 30, 154,
205-
42, 15, 44, 29, 66, 163, 163, 163, 163, 163,
206-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
207-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
208-
163, 68, 163, 161, 35, 163, 160, 163, 163, 163,
209-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
210-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
211-
163, 163, 163, 155, 34, 156, 56, 163, 163, 163,
212-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
213-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
214-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
215-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
216-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
217-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
218-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
219-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
220-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
221-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
222-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
223-
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
224-
163, 163, 163, 163, 163, 163, 1, 2, 3, 4,
200+
0, 164, 164, 164, 164, 164, 164, 164, 164, 164,
201+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
202+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
203+
164, 164, 164, 54, 162, 164, 159, 53, 36, 164,
204+
157, 158, 51, 48, 7, 49, 50, 52, 164, 164,
205+
164, 164, 164, 164, 164, 164, 164, 164, 30, 154,
206+
42, 15, 44, 29, 66, 164, 164, 164, 164, 164,
207+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
208+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
209+
164, 68, 164, 161, 35, 164, 160, 164, 164, 164,
210+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
211+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
212+
164, 164, 164, 155, 34, 156, 56, 164, 164, 164,
213+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
214+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
215+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
216+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
217+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
218+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
219+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
220+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
221+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
222+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
223+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
224+
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
225+
164, 164, 164, 164, 164, 164, 1, 2, 3, 4,
225226
5, 6, 8, 9, 10, 11, 12, 13, 14, 16,
226227
17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
227228
27, 28, 31, 32, 33, 37, 38, 39, 40, 41,
@@ -232,9 +233,10 @@ class Php5 extends \PhpParser\ParserAbstract
232233
94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
233234
104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
234235
114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
235-
124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
236-
134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
237-
144, 145, 146, 147, 148, 149, 150, 151, 152, 153
236+
124, 125, 126, 127, 128, 129, 130, 131, 163, 132,
237+
133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
238+
143, 144, 145, 146, 147, 148, 149, 150, 151, 152,
239+
153
238240
);
239241

240242
protected $action = array(

0 commit comments

Comments
 (0)