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:
Tyson Andre 2018-12-04 21:20:36 -05:00
Родитель a266248840
Коммит c64dbb20af
19 изменённых файлов: 511 добавлений и 1 удалений

Просмотреть файл

@ -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
}
}
}