Bug fixes for parsing associativity
This fixes the most common cases for #19 (there may be other edge cases I haven't considered yet) This also fixes incorrect precedence for `<=>`. It has the same precedence as `==` - https://secure.php.net/manual/en/language.operators.precedence.php has correct information for the `<=>` operator.
This commit is contained in:
Родитель
a266248840
Коммит
c64dbb20af
|
@ -1844,13 +1844,13 @@ class Parser {
|
|||
TokenKind::LessThanGreaterThanToken => [17, Associativity::None],
|
||||
TokenKind::EqualsEqualsEqualsToken => [17, Associativity::None],
|
||||
TokenKind::ExclamationEqualsEqualsToken => [17, Associativity::None],
|
||||
TokenKind::LessThanEqualsGreaterThanToken => [17, Associativity::None],
|
||||
|
||||
// relational-expression (X)
|
||||
TokenKind::LessThanToken => [18, Associativity::None],
|
||||
TokenKind::GreaterThanToken => [18, Associativity::None],
|
||||
TokenKind::LessThanEqualsToken => [18, Associativity::None],
|
||||
TokenKind::GreaterThanEqualsToken => [18, Associativity::None],
|
||||
TokenKind::LessThanEqualsGreaterThanToken => [18, Associativity::None],
|
||||
|
||||
// shift-expression (L)
|
||||
TokenKind::LessThanLessThanToken => [19, Associativity::Left],
|
||||
|
@ -1883,8 +1883,33 @@ class Parser {
|
|||
return self::UNKNOWN_PRECEDENCE_AND_ASSOCIATIVITY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Do not use outside this class, this may be changed or removed.
|
||||
*/
|
||||
const KNOWN_ASSIGNMENT_TOKEN_SET = [
|
||||
TokenKind::AsteriskAsteriskEqualsToken => true,
|
||||
TokenKind::AsteriskEqualsToken => true,
|
||||
TokenKind::SlashEqualsToken => true,
|
||||
TokenKind::PercentEqualsToken => true,
|
||||
TokenKind::PlusEqualsToken => true,
|
||||
TokenKind::MinusEqualsToken => true,
|
||||
TokenKind::DotEqualsToken => true,
|
||||
TokenKind::LessThanLessThanEqualsToken => true,
|
||||
TokenKind::GreaterThanGreaterThanEqualsToken => true,
|
||||
TokenKind::AmpersandEqualsToken => true,
|
||||
TokenKind::CaretEqualsToken => true,
|
||||
TokenKind::BarEqualsToken => true,
|
||||
// InstanceOf has other remaining issues, but this heuristic is an improvement for many common cases such as `$x && $y = $z`
|
||||
];
|
||||
|
||||
private function makeBinaryExpression($leftOperand, $operatorToken, $byRefToken, $rightOperand, $parentNode) {
|
||||
$assignmentExpression = $operatorToken->kind === TokenKind::EqualsToken;
|
||||
if ($assignmentExpression || \array_key_exists($operatorToken->kind, self::KNOWN_ASSIGNMENT_TOKEN_SET)) {
|
||||
if ($leftOperand instanceof BinaryExpression && !\array_key_exists($leftOperand->operator->kind, self::KNOWN_ASSIGNMENT_TOKEN_SET)) {
|
||||
// Handle cases without parenthesis, such as $x ** $y === $z, as $x ** ($y === $z)
|
||||
return $this->shiftBinaryOperands($leftOperand, $operatorToken, $byRefToken, $rightOperand, $parentNode);
|
||||
}
|
||||
}
|
||||
$binaryExpression = $assignmentExpression ? new AssignmentExpression() : new BinaryExpression();
|
||||
$binaryExpression->parent = $parentNode;
|
||||
$leftOperand->parent = $binaryExpression;
|
||||
|
@ -1898,6 +1923,25 @@ class Parser {
|
|||
return $binaryExpression;
|
||||
}
|
||||
|
||||
private function shiftBinaryOperands(BinaryExpression $leftOperand, $operatorToken, $byRefToken, $rightOperand, $parentNode) {
|
||||
$inner = $this->makeBinaryExpression(
|
||||
$leftOperand->rightOperand,
|
||||
$operatorToken,
|
||||
$byRefToken,
|
||||
$rightOperand,
|
||||
$parentNode
|
||||
);
|
||||
$outer = $this->makeBinaryExpression(
|
||||
$leftOperand->leftOperand,
|
||||
$leftOperand->operator,
|
||||
null,
|
||||
$inner,
|
||||
$parentNode
|
||||
);
|
||||
$inner->parent = $outer;
|
||||
return $outer;
|
||||
}
|
||||
|
||||
private function parseDoStatement($parentNode) {
|
||||
$doStatement = new DoStatement();
|
||||
$doStatement->parent = $parentNode;
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
$x && $y = $z;
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "AmpersandAmpersandToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"rightOperand": {
|
||||
"AssignmentExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "EqualsToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"byRef": null,
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
$x = $y || $z;
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"AssignmentExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "EqualsToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"byRef": null,
|
||||
"rightOperand": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "BarBarToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
$x && $y += $z;
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "AmpersandAmpersandToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"rightOperand": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "PlusEqualsToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
$x += $y && $z;
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "PlusEqualsToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"rightOperand": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "AmpersandAmpersandToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
$x < $a <=> $b;
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "LessThanToken",
|
||||
"textLength": 1
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "LessThanEqualsGreaterThanToken",
|
||||
"textLength": 3
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
// this is a syntax error
|
||||
$x == $y <=> $z;
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "';' expected.",
|
||||
"start": 40,
|
||||
"length": 0
|
||||
},
|
||||
{
|
||||
"kind": 0,
|
||||
"message": "Unexpected '<=>'",
|
||||
"start": 41,
|
||||
"length": 3
|
||||
}
|
||||
]
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"SourceFileNode": {
|
||||
"statementList": [
|
||||
{
|
||||
"InlineHtml": {
|
||||
"scriptSectionEndTag": null,
|
||||
"text": null,
|
||||
"scriptSectionStartTag": {
|
||||
"kind": "ScriptSectionStartTag",
|
||||
"textLength": 6
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"BinaryExpression": {
|
||||
"leftOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"kind": "EqualsEqualsToken",
|
||||
"textLength": 2
|
||||
},
|
||||
"rightOperand": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"error": "MissingToken",
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": "SkippedToken",
|
||||
"kind": "LessThanEqualsGreaterThanToken",
|
||||
"textLength": 3
|
||||
},
|
||||
{
|
||||
"ExpressionStatement": {
|
||||
"expression": {
|
||||
"Variable": {
|
||||
"dollar": null,
|
||||
"name": {
|
||||
"kind": "VariableName",
|
||||
"textLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"semicolon": {
|
||||
"kind": "SemicolonToken",
|
||||
"textLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"endOfFileToken": {
|
||||
"kind": "EndOfFileToken",
|
||||
"textLength": 0
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче