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::LessThanGreaterThanToken => [17, Associativity::None],
|
||||||
TokenKind::EqualsEqualsEqualsToken => [17, Associativity::None],
|
TokenKind::EqualsEqualsEqualsToken => [17, Associativity::None],
|
||||||
TokenKind::ExclamationEqualsEqualsToken => [17, Associativity::None],
|
TokenKind::ExclamationEqualsEqualsToken => [17, Associativity::None],
|
||||||
|
TokenKind::LessThanEqualsGreaterThanToken => [17, Associativity::None],
|
||||||
|
|
||||||
// relational-expression (X)
|
// relational-expression (X)
|
||||||
TokenKind::LessThanToken => [18, Associativity::None],
|
TokenKind::LessThanToken => [18, Associativity::None],
|
||||||
TokenKind::GreaterThanToken => [18, Associativity::None],
|
TokenKind::GreaterThanToken => [18, Associativity::None],
|
||||||
TokenKind::LessThanEqualsToken => [18, Associativity::None],
|
TokenKind::LessThanEqualsToken => [18, Associativity::None],
|
||||||
TokenKind::GreaterThanEqualsToken => [18, Associativity::None],
|
TokenKind::GreaterThanEqualsToken => [18, Associativity::None],
|
||||||
TokenKind::LessThanEqualsGreaterThanToken => [18, Associativity::None],
|
|
||||||
|
|
||||||
// shift-expression (L)
|
// shift-expression (L)
|
||||||
TokenKind::LessThanLessThanToken => [19, Associativity::Left],
|
TokenKind::LessThanLessThanToken => [19, Associativity::Left],
|
||||||
|
@ -1883,8 +1883,33 @@ class Parser {
|
||||||
return self::UNKNOWN_PRECEDENCE_AND_ASSOCIATIVITY;
|
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) {
|
private function makeBinaryExpression($leftOperand, $operatorToken, $byRefToken, $rightOperand, $parentNode) {
|
||||||
$assignmentExpression = $operatorToken->kind === TokenKind::EqualsToken;
|
$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 = $assignmentExpression ? new AssignmentExpression() : new BinaryExpression();
|
||||||
$binaryExpression->parent = $parentNode;
|
$binaryExpression->parent = $parentNode;
|
||||||
$leftOperand->parent = $binaryExpression;
|
$leftOperand->parent = $binaryExpression;
|
||||||
|
@ -1898,6 +1923,25 @@ class Parser {
|
||||||
return $binaryExpression;
|
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) {
|
private function parseDoStatement($parentNode) {
|
||||||
$doStatement = new DoStatement();
|
$doStatement = new DoStatement();
|
||||||
$doStatement->parent = $parentNode;
|
$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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче