Skip to content

Commit

Permalink
Add support for nullsafe operator
Browse files Browse the repository at this point in the history
  • Loading branch information
nikic committed Aug 9, 2020
1 parent 31be7b4 commit 23d9c17
Show file tree
Hide file tree
Showing 15 changed files with 877 additions and 583 deletions.
6 changes: 6 additions & 0 deletions grammar/php7.y
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,8 @@ callable_variable:
| function_call { $$ = $1; }
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
{ $$ = Expr\MethodCall[$1, $3, $4]; }
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
{ $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
;

optional_plain_variable:
Expand All @@ -955,6 +957,8 @@ variable:
| static_member { $$ = $1; }
| array_object_dereferencable T_OBJECT_OPERATOR property_name
{ $$ = Expr\PropertyFetch[$1, $3]; }
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
;

simple_variable:
Expand All @@ -979,6 +983,7 @@ new_variable:
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
| new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
Expand Down Expand Up @@ -1048,6 +1053,7 @@ encaps_var:
plain_variable { $$ = $1; }
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
Expand Down
1 change: 1 addition & 0 deletions grammar/tokens.y
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
%token T_EXTENDS
%token T_IMPLEMENTS
%token T_OBJECT_OPERATOR
%token T_NULLSAFE_OBJECT_OPERATOR
%token T_DOUBLE_ARROW
%token T_LIST
%token T_ARRAY
Expand Down
4 changes: 4 additions & 0 deletions lib/PhpParser/Lexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,9 @@ private function defineCompatibilityTokens() {
if (!defined('T_MATCH')) {
\define('T_MATCH', -7);
}
if (!defined('T_NULLSAFE_OBJECT_OPERATOR')) {
\define('T_NULLSAFE_OBJECT_OPERATOR', -8);
}
}

/**
Expand Down Expand Up @@ -481,6 +484,7 @@ protected function createTokenMap() : array {
$tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED;
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;

return $tokenMap;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/PhpParser/Lexer/Emulative.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface;
use PhpParser\Parser\Tokens;
Expand Down Expand Up @@ -49,6 +50,7 @@ public function __construct(array $options = [])
$this->tokenEmulators[] = new MatchTokenEmulator();
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator();
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator();
$this->tokenEmulators[] = new NullsafeTokenEmulator();
}

public function startLexing(string $code, ErrorHandler $errorHandler = null) {
Expand Down
47 changes: 47 additions & 0 deletions lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);

namespace PhpParser\Lexer\TokenEmulator;

use PhpParser\Lexer\Emulative;

final class NullsafeTokenEmulator implements TokenEmulatorInterface
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}

public function isEmulationNeeded(string $code): bool
{
return strpos($code, '?->') !== false;
}

public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if (isset($tokens[$i + 1])) {
if ($tokens[$i] === '?' && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
array_splice($tokens, $i, 2, [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
]);
$c--;
continue;
}
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}

return $tokens;
}

public function reverseEmulate(string $code, array $tokens): array
{
// ?-> was not valid code previously, don't bother.
return $tokens;
}
}
40 changes: 40 additions & 0 deletions lib/PhpParser/Node/Expr/NullsafeMethodCall.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php declare(strict_types=1);

namespace PhpParser\Node\Expr;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;

class NullsafeMethodCall extends Expr
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
public $args;

/**
* Constructs a nullsafe method call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args;
}

public function getSubNodeNames() : array {
return ['var', 'name', 'args'];
}

public function getType() : string {
return 'Expr_NullsafeMethodCall';
}
}
35 changes: 35 additions & 0 deletions lib/PhpParser/Node/Expr/NullsafePropertyFetch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types=1);

namespace PhpParser\Node\Expr;

use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;

class NullsafePropertyFetch extends Expr
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Property name */
public $name;

/**
* Constructs a nullsafe property fetch node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Property name
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}

public function getSubNodeNames() : array {
return ['var', 'name'];
}

public function getType() : string {
return 'Expr_NullsafePropertyFetch';
}
}
66 changes: 34 additions & 32 deletions lib/PhpParser/Parser/Php5.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
*/
class Php5 extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = 390;
protected $tokenToSymbolMapSize = 391;
protected $actionTableSize = 1061;
protected $gotoTableSize = 580;

protected $invalidSymbol = 163;
protected $invalidSymbol = 164;
protected $errorSymbol = 1;
protected $defaultAction = -32766;
protected $unexpectedTokenRule = 32767;
Expand Down Expand Up @@ -192,36 +192,37 @@ class Php5 extends \PhpParser\ParserAbstract
"'$'",
"'`'",
"']'",
"'\"'"
"'\"'",
"T_NULLSAFE_OBJECT_OPERATOR"
);

protected $tokenToSymbol = array(
0, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 54, 162, 163, 159, 53, 36, 163,
157, 158, 51, 48, 7, 49, 50, 52, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 30, 154,
42, 15, 44, 29, 66, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 68, 163, 161, 35, 163, 160, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 155, 34, 156, 56, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
163, 163, 163, 163, 163, 163, 1, 2, 3, 4,
0, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 54, 162, 164, 159, 53, 36, 164,
157, 158, 51, 48, 7, 49, 50, 52, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 30, 154,
42, 15, 44, 29, 66, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 68, 164, 161, 35, 164, 160, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 155, 34, 156, 56, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 1, 2, 3, 4,
5, 6, 8, 9, 10, 11, 12, 13, 14, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 31, 32, 33, 37, 38, 39, 40, 41,
Expand All @@ -232,9 +233,10 @@ class Php5 extends \PhpParser\ParserAbstract
94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
144, 145, 146, 147, 148, 149, 150, 151, 152, 153
124, 125, 126, 127, 128, 129, 130, 131, 163, 132,
133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
143, 144, 145, 146, 147, 148, 149, 150, 151, 152,
153
);

protected $action = array(
Expand Down
Loading

0 comments on commit 23d9c17

Please sign in to comment.