Add support for null coalesce operator (??) [PHP 7]

Added as Expr\BinaryOp\Coalesce.
This commit is contained in:
Nikita Popov 2015-03-12 22:45:38 +01:00
parent dc28449d81
commit 01f4be6b4d
10 changed files with 1128 additions and 1024 deletions

View File

@ -10,6 +10,7 @@
%right T_YIELD %right T_YIELD
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL %left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL
%left '?' ':' %left '?' ':'
%right T_COALESCE
%left T_BOOLEAN_OR %left T_BOOLEAN_OR
%left T_BOOLEAN_AND %left T_BOOLEAN_AND
%left '|' %left '|'
@ -590,6 +591,7 @@ expr:
| '(' new_expr ')' { $$ = $2; } | '(' new_expr ')' { $$ = $2; }
| expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; } | expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; }
| expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; } | expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; }
| expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }
| T_ISSET '(' variables_list ')' { $$ = Expr\Isset_[$3]; } | T_ISSET '(' variables_list ')' { $$ = Expr\Isset_[$3]; }
| T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; } | T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; }
| T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; } | T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; }

View File

@ -15,7 +15,9 @@ class Emulative extends \PhpParser\Lexer
const T_ELLIPSIS = 1001; const T_ELLIPSIS = 1001;
const T_POW = 1002; const T_POW = 1002;
const T_POW_EQUAL = 1003; const T_POW_EQUAL = 1003;
const T_COALESCE = 1004;
const PHP_7_0 = '7.0.0dev';
const PHP_5_6 = '5.6.0rc1'; const PHP_5_6 = '5.6.0rc1';
const PHP_5_5 = '5.5.0beta1'; const PHP_5_5 = '5.5.0beta1';
const PHP_5_4 = '5.4.0beta1'; const PHP_5_4 = '5.4.0beta1';
@ -45,11 +47,17 @@ class Emulative extends \PhpParser\Lexer
$this->newKeywords += $newKeywords; $this->newKeywords += $newKeywords;
} }
if (version_compare(PHP_VERSION, self::PHP_5_6, '<')) { if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
$this->tokenMap[self::T_ELLIPSIS] = Parser::T_ELLIPSIS; return;
$this->tokenMap[self::T_POW] = Parser::T_POW;
$this->tokenMap[self::T_POW_EQUAL] = Parser::T_POW_EQUAL;
} }
$this->tokenMap[self::T_COALESCE] = Parser::T_COALESCE;
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
return;
}
$this->tokenMap[self::T_ELLIPSIS] = Parser::T_ELLIPSIS;
$this->tokenMap[self::T_POW] = Parser::T_POW;
$this->tokenMap[self::T_POW_EQUAL] = Parser::T_POW_EQUAL;
} }
public function startLexing($code) { public function startLexing($code) {
@ -75,6 +83,12 @@ class Emulative extends \PhpParser\Lexer
* inside a string, i.e. a place where they don't have a special meaning). * inside a string, i.e. a place where they don't have a special meaning).
*/ */
protected function preprocessCode($code) { protected function preprocessCode($code) {
if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
return $code;
}
$code = str_replace('??', '~__EMU__COALESCE__~', $code);
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) { if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
return $code; return $code;
} }
@ -125,6 +139,10 @@ class Emulative extends \PhpParser\Lexer
$replace = array( $replace = array(
array(self::T_POW_EQUAL, '**=', $this->tokens[$i + 1][2]) array(self::T_POW_EQUAL, '**=', $this->tokens[$i + 1][2])
); );
} else if ('COALESCE' === $matches[1]) {
$replace = array(
array(self::T_COALESCE, '??', $this->tokens[$i + 1][2])
);
} else { } else {
// just ignore all other __EMU__ sequences // just ignore all other __EMU__ sequences
continue; continue;
@ -159,6 +177,8 @@ class Emulative extends \PhpParser\Lexer
return '**'; return '**';
} else if ('POWEQUAL' === $matches[1]) { } else if ('POWEQUAL' === $matches[1]) {
return '**='; return '**=';
} else if ('COALESCE' === $matches[1]) {
return '??';
} else { } else {
return $matches[0]; return $matches[0];
} }

View File

@ -0,0 +1,9 @@
<?php
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Coalesce extends BinaryOp
{
}

File diff suppressed because it is too large Load Diff

View File

@ -262,6 +262,10 @@ class Standard extends PrettyPrinterAbstract
return $this->pInfixOp('Expr_BinaryOp_SmallerOrEqual', $node->left, ' <= ', $node->right); return $this->pInfixOp('Expr_BinaryOp_SmallerOrEqual', $node->left, ' <= ', $node->right);
} }
public function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) {
return $this->pInfixOp('Expr_BinaryOp_Coalesce', $node->left, ' ?? ', $node->right);
}
public function pExpr_Instanceof(Expr\Instanceof_ $node) { public function pExpr_Instanceof(Expr\Instanceof_ $node) {
return $this->pInfixOp('Expr_Instanceof', $node->expr, ' instanceof ', $node->class); return $this->pInfixOp('Expr_Instanceof', $node->expr, ' instanceof ', $node->class);
} }

View File

@ -9,65 +9,66 @@ abstract class PrettyPrinterAbstract
{ {
protected $precedenceMap = array( protected $precedenceMap = array(
// [precedence, associativity] where for the latter -1 is %left, 0 is %nonassoc and 1 is %right // [precedence, associativity] where for the latter -1 is %left, 0 is %nonassoc and 1 is %right
'Expr_BinaryOp_Pow' => array( 0, 1), 'Expr_BinaryOp_Pow' => array( 0, 1),
'Expr_BitwiseNot' => array( 1, 1), 'Expr_BitwiseNot' => array( 10, 1),
'Expr_PreInc' => array( 1, 1), 'Expr_PreInc' => array( 10, 1),
'Expr_PreDec' => array( 1, 1), 'Expr_PreDec' => array( 10, 1),
'Expr_PostInc' => array( 1, -1), 'Expr_PostInc' => array( 10, -1),
'Expr_PostDec' => array( 1, -1), 'Expr_PostDec' => array( 10, -1),
'Expr_UnaryPlus' => array( 1, 1), 'Expr_UnaryPlus' => array( 10, 1),
'Expr_UnaryMinus' => array( 1, 1), 'Expr_UnaryMinus' => array( 10, 1),
'Expr_Cast_Int' => array( 1, 1), 'Expr_Cast_Int' => array( 10, 1),
'Expr_Cast_Double' => array( 1, 1), 'Expr_Cast_Double' => array( 10, 1),
'Expr_Cast_String' => array( 1, 1), 'Expr_Cast_String' => array( 10, 1),
'Expr_Cast_Array' => array( 1, 1), 'Expr_Cast_Array' => array( 10, 1),
'Expr_Cast_Object' => array( 1, 1), 'Expr_Cast_Object' => array( 10, 1),
'Expr_Cast_Bool' => array( 1, 1), 'Expr_Cast_Bool' => array( 10, 1),
'Expr_Cast_Unset' => array( 1, 1), 'Expr_Cast_Unset' => array( 10, 1),
'Expr_ErrorSuppress' => array( 1, 1), 'Expr_ErrorSuppress' => array( 10, 1),
'Expr_Instanceof' => array( 2, 0), 'Expr_Instanceof' => array( 20, 0),
'Expr_BooleanNot' => array( 3, 1), 'Expr_BooleanNot' => array( 30, 1),
'Expr_BinaryOp_Mul' => array( 4, -1), 'Expr_BinaryOp_Mul' => array( 40, -1),
'Expr_BinaryOp_Div' => array( 4, -1), 'Expr_BinaryOp_Div' => array( 40, -1),
'Expr_BinaryOp_Mod' => array( 4, -1), 'Expr_BinaryOp_Mod' => array( 40, -1),
'Expr_BinaryOp_Plus' => array( 5, -1), 'Expr_BinaryOp_Plus' => array( 50, -1),
'Expr_BinaryOp_Minus' => array( 5, -1), 'Expr_BinaryOp_Minus' => array( 50, -1),
'Expr_BinaryOp_Concat' => array( 5, -1), 'Expr_BinaryOp_Concat' => array( 50, -1),
'Expr_BinaryOp_ShiftLeft' => array( 6, -1), 'Expr_BinaryOp_ShiftLeft' => array( 60, -1),
'Expr_BinaryOp_ShiftRight' => array( 6, -1), 'Expr_BinaryOp_ShiftRight' => array( 60, -1),
'Expr_BinaryOp_Smaller' => array( 7, 0), 'Expr_BinaryOp_Smaller' => array( 70, 0),
'Expr_BinaryOp_SmallerOrEqual' => array( 7, 0), 'Expr_BinaryOp_SmallerOrEqual' => array( 70, 0),
'Expr_BinaryOp_Greater' => array( 7, 0), 'Expr_BinaryOp_Greater' => array( 70, 0),
'Expr_BinaryOp_GreaterOrEqual' => array( 7, 0), 'Expr_BinaryOp_GreaterOrEqual' => array( 70, 0),
'Expr_BinaryOp_Equal' => array( 8, 0), 'Expr_BinaryOp_Equal' => array( 80, 0),
'Expr_BinaryOp_NotEqual' => array( 8, 0), 'Expr_BinaryOp_NotEqual' => array( 80, 0),
'Expr_BinaryOp_Identical' => array( 8, 0), 'Expr_BinaryOp_Identical' => array( 80, 0),
'Expr_BinaryOp_NotIdentical' => array( 8, 0), 'Expr_BinaryOp_NotIdentical' => array( 80, 0),
'Expr_BinaryOp_BitwiseAnd' => array( 9, -1), 'Expr_BinaryOp_BitwiseAnd' => array( 90, -1),
'Expr_BinaryOp_BitwiseXor' => array(10, -1), 'Expr_BinaryOp_BitwiseXor' => array(100, -1),
'Expr_BinaryOp_BitwiseOr' => array(11, -1), 'Expr_BinaryOp_BitwiseOr' => array(110, -1),
'Expr_BinaryOp_BooleanAnd' => array(12, -1), 'Expr_BinaryOp_BooleanAnd' => array(120, -1),
'Expr_BinaryOp_BooleanOr' => array(13, -1), 'Expr_BinaryOp_BooleanOr' => array(130, -1),
'Expr_Ternary' => array(14, -1), 'Expr_BinaryOp_Coalesce' => array(140, 1),
'Expr_Ternary' => array(150, -1),
// parser uses %left for assignments, but they really behave as %right // parser uses %left for assignments, but they really behave as %right
'Expr_Assign' => array(15, 1), 'Expr_Assign' => array(160, 1),
'Expr_AssignRef' => array(15, 1), 'Expr_AssignRef' => array(160, 1),
'Expr_AssignOp_Plus' => array(15, 1), 'Expr_AssignOp_Plus' => array(160, 1),
'Expr_AssignOp_Minus' => array(15, 1), 'Expr_AssignOp_Minus' => array(160, 1),
'Expr_AssignOp_Mul' => array(15, 1), 'Expr_AssignOp_Mul' => array(160, 1),
'Expr_AssignOp_Div' => array(15, 1), 'Expr_AssignOp_Div' => array(160, 1),
'Expr_AssignOp_Concat' => array(15, 1), 'Expr_AssignOp_Concat' => array(160, 1),
'Expr_AssignOp_Mod' => array(15, 1), 'Expr_AssignOp_Mod' => array(160, 1),
'Expr_AssignOp_BitwiseAnd' => array(15, 1), 'Expr_AssignOp_BitwiseAnd' => array(160, 1),
'Expr_AssignOp_BitwiseOr' => array(15, 1), 'Expr_AssignOp_BitwiseOr' => array(160, 1),
'Expr_AssignOp_BitwiseXor' => array(15, 1), 'Expr_AssignOp_BitwiseXor' => array(160, 1),
'Expr_AssignOp_ShiftLeft' => array(15, 1), 'Expr_AssignOp_ShiftLeft' => array(160, 1),
'Expr_AssignOp_ShiftRight' => array(15, 1), 'Expr_AssignOp_ShiftRight' => array(160, 1),
'Expr_AssignOp_Pow' => array(15, 1), 'Expr_AssignOp_Pow' => array(160, 1),
'Expr_BinaryOp_LogicalAnd' => array(16, -1), 'Expr_BinaryOp_LogicalAnd' => array(170, -1),
'Expr_BinaryOp_LogicalXor' => array(17, -1), 'Expr_BinaryOp_LogicalXor' => array(180, -1),
'Expr_BinaryOp_LogicalOr' => array(18, -1), 'Expr_BinaryOp_LogicalOr' => array(190, -1),
'Expr_Include' => array(19, -1), 'Expr_Include' => array(200, -1),
); );
protected $noIndentToken; protected $noIndentToken;
@ -263,4 +264,4 @@ abstract class PrettyPrinterAbstract
return $result; return $result;
} }
} }

View File

@ -96,6 +96,9 @@ class EmulativeTest extends LexerTest
array('**=', array( array('**=', array(
array(Parser::T_POW_EQUAL, '**='), array(Parser::T_POW_EQUAL, '**='),
)), )),
array('??', array(
array(Parser::T_COALESCE, '??'),
)),
array('0b1010110', array( array('0b1010110', array(
array(Parser::T_LNUMBER, '0b1010110'), array(Parser::T_LNUMBER, '0b1010110'),
)), )),

View File

@ -9,6 +9,12 @@ $a ?: $c;
// precedence // precedence
$a ? $b : $c ? $d : $e; $a ? $b : $c ? $d : $e;
$a ? $b : ($c ? $d : $e); $a ? $b : ($c ? $d : $e);
// null coalesce
$a ?? $b;
$a ?? $b ?? $c;
$a ?? $b ? $c : $d;
$a && $b ?? $c;
----- -----
array( array(
0: Expr_Ternary( 0: Expr_Ternary(
@ -69,4 +75,54 @@ array(
) )
) )
) )
) 4: Expr_BinaryOp_Coalesce(
left: Expr_Variable(
name: a
)
right: Expr_Variable(
name: b
)
)
5: Expr_BinaryOp_Coalesce(
left: Expr_Variable(
name: a
)
right: Expr_BinaryOp_Coalesce(
left: Expr_Variable(
name: b
)
right: Expr_Variable(
name: c
)
)
)
6: Expr_Ternary(
cond: Expr_BinaryOp_Coalesce(
left: Expr_Variable(
name: a
)
right: Expr_Variable(
name: b
)
)
if: Expr_Variable(
name: c
)
else: Expr_Variable(
name: d
)
)
7: Expr_BinaryOp_Coalesce(
left: Expr_BinaryOp_BooleanAnd(
left: Expr_Variable(
name: a
)
right: Expr_Variable(
name: b
)
)
right: Expr_Variable(
name: c
)
)
)

View File

@ -51,6 +51,7 @@ $a && $b;
$a || $b; $a || $b;
$a ? $b : $c; $a ? $b : $c;
$a ?: $c; $a ?: $c;
$a ?? $c;
$a = $b; $a = $b;
$a **= $b; $a **= $b;
@ -119,6 +120,7 @@ $a && $b;
$a || $b; $a || $b;
$a ? $b : $c; $a ? $b : $c;
$a ?: $c; $a ?: $c;
$a ?? $c;
$a = $b; $a = $b;
$a **= $b; $a **= $b;
$a *= $b; $a *= $b;

View File

@ -20,6 +20,11 @@ $a ? $b : $c ? $d : $e ? $f : $g;
$a ? $b : ($c ? $d : ($e ? $f : $g)); $a ? $b : ($c ? $d : ($e ? $f : $g));
$a ? $b ? $c : $d : $f; $a ? $b ? $c : $d : $f;
$a ?? $b ?? $c;
($a ?? $b) ?? $c;
$a ?? ($b ? $c : $d);
$a || ($b ?? $c);
(1 > 0) > (1 < 0); (1 > 0) > (1 < 0);
++$a + $b; ++$a + $b;
$a + $b++; $a + $b++;
@ -47,6 +52,10 @@ $a = $b = $c = $d = ($f and true);
$a ? $b : $c ? $d : $e ? $f : $g; $a ? $b : $c ? $d : $e ? $f : $g;
$a ? $b : ($c ? $d : ($e ? $f : $g)); $a ? $b : ($c ? $d : ($e ? $f : $g));
$a ? $b ? $c : $d : $f; $a ? $b ? $c : $d : $f;
$a ?? $b ?? $c;
($a ?? $b) ?? $c;
$a ?? ($b ? $c : $d);
$a || ($b ?? $c);
(1 > 0) > (1 < 0); (1 > 0) > (1 < 0);
++$a + $b; ++$a + $b;
$a + $b++; $a + $b++;