Skip to content

Commit

Permalink
Fix parsing of edge case involving inline HTML
Browse files Browse the repository at this point in the history
The included test files can be executed.
They will always print the inline HTML,
indicating that PHP is doing the equivalent of inserting semicolons.

Fixes #246

Also, ignore temporary files created by vim
  • Loading branch information
TysonAndre committed Jul 28, 2018
1 parent f3fcb1f commit d3e28a7
Show file tree
Hide file tree
Showing 7 changed files with 505 additions and 22 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Expand Up @@ -3,4 +3,6 @@
vendor/
composer.lock
tests/output
*.ast
*.ast
.*.swp
.*.swo
24 changes: 14 additions & 10 deletions src/Parser.php
Expand Up @@ -1426,13 +1426,15 @@ private function parseIfStatement($parentNode) {
$ifStatement->openParen = $this->eat1(TokenKind::OpenParenToken);
$ifStatement->expression = $this->parseExpression($ifStatement);
$ifStatement->closeParen = $this->eat1(TokenKind::CloseParenToken);
if ($this->checkToken(TokenKind::ColonToken)) {
$curTokenKind = $this->getCurrentToken()->kind;
if ($curTokenKind === TokenKind::ColonToken) {
$ifStatement->colon = $this->eat1(TokenKind::ColonToken);
$ifStatement->statements = $this->parseList($ifStatement, ParseContext::IfClause2Elements);
} else {
} else if ($curTokenKind !== TokenKind::ScriptSectionEndTag) {
// Fix #246 : properly parse `if (false) ?\>echoed text\<?php`
$ifStatement->statements = $this->parseStatement($ifStatement);
}
$ifStatement->elseIfClauses = array(); // TODO - should be some standard for empty arrays vs. null?
$ifStatement->elseIfClauses = []; // TODO - should be some standard for empty arrays vs. null?
while ($this->checkToken(TokenKind::ElseIfKeyword)) {
$ifStatement->elseIfClauses[] = $this->parseElseIfClause($ifStatement);
}
Expand All @@ -1456,10 +1458,11 @@ private function parseElseIfClause($parentNode) {
$elseIfClause->openParen = $this->eat1(TokenKind::OpenParenToken);
$elseIfClause->expression = $this->parseExpression($elseIfClause);
$elseIfClause->closeParen = $this->eat1(TokenKind::CloseParenToken);
if ($this->checkToken(TokenKind::ColonToken)) {
$curTokenKind = $this->getCurrentToken()->kind;
if ($curTokenKind === TokenKind::ColonToken) {
$elseIfClause->colon = $this->eat1(TokenKind::ColonToken);
$elseIfClause->statements = $this->parseList($elseIfClause, ParseContext::IfClause2Elements);
} else {
} elseif ($curTokenKind !== TokenKind::ScriptSectionEndTag) {
$elseIfClause->statements = $this->parseStatement($elseIfClause);
}
return $elseIfClause;
Expand All @@ -1469,10 +1472,11 @@ private function parseElseClause($parentNode) {
$elseClause = new ElseClauseNode();
$elseClause->parent = $parentNode;
$elseClause->elseKeyword = $this->eat1(TokenKind::ElseKeyword);
if ($this->checkToken(TokenKind::ColonToken)) {
$curTokenKind = $this->getCurrentToken()->kind;
if ($curTokenKind === TokenKind::ColonToken) {
$elseClause->colon = $this->eat1(TokenKind::ColonToken);
$elseClause->statements = $this->parseList($elseClause, ParseContext::IfClause2Elements);
} else {
} elseif ($curTokenKind !== TokenKind::ScriptSectionEndTag) {
$elseClause->statements = $this->parseStatement($elseClause);
}
return $elseClause;
Expand Down Expand Up @@ -1525,7 +1529,7 @@ private function parseWhileStatement($parentNode) {
$whileStatement->statements = $this->parseList($whileStatement, ParseContext::WhileStatementElements);
$whileStatement->endWhile = $this->eat1(TokenKind::EndWhileKeyword);
$whileStatement->semicolon = $this->eatSemicolonOrAbortStatement();
} else {
} elseif (!$this->checkToken(TokenKind::ScriptSectionEndTag)) {
$whileStatement->statements = $this->parseStatement($whileStatement);
}
return $whileStatement;
Expand Down Expand Up @@ -1917,7 +1921,7 @@ private function parseForStatement($parentNode) {
$forStatement->statements = $this->parseList($forStatement, ParseContext::ForStatementElements);
$forStatement->endFor = $this->eat1(TokenKind::EndForKeyword);
$forStatement->endForSemicolon = $this->eatSemicolonOrAbortStatement();
} else {
} elseif (!$this->checkToken(TokenKind::ScriptSectionEndTag)) {
$forStatement->statements = $this->parseStatement($forStatement);
}
return $forStatement;
Expand All @@ -1938,7 +1942,7 @@ private function parseForeachStatement($parentNode) {
$foreachStatement->statements = $this->parseList($foreachStatement, ParseContext::ForeachStatementElements);
$foreachStatement->endForeach = $this->eat1(TokenKind::EndForEachKeyword);
$foreachStatement->endForeachSemicolon = $this->eatSemicolonOrAbortStatement();
} else {
} elseif (!$this->checkToken(TokenKind::ScriptSectionEndTag)) {
$foreachStatement->statements = $this->parseStatement($foreachStatement);
}
return $foreachStatement;
Expand Down
8 changes: 8 additions & 0 deletions tests/cases/parser/programStructure36.php
Expand Up @@ -3,3 +3,11 @@
// (i.e. same as `if (false); echo "hello world"` with an implicit semicolon)
// NOTE: If the inline HTML is surrounded by brackets, then this would never echo.
if (false)?>hello world<?php

if (false)
echo "A";
elseif (false)?>Hello, World<?php

if (true)
echo "\ntrue";
else?>After else<?php
213 changes: 202 additions & 11 deletions tests/cases/parser/programStructure36.php.tree
Expand Up @@ -11,6 +11,52 @@
}
}
},
{
"IfStatementNode": {
"ifKeyword": {
"kind": "IfKeyword",
"textLength": 2
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"expression": {
"ReservedWord": {
"children": {
"kind": "FalseReservedWord",
"textLength": 5
}
}
},
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"colon": null,
"statements": null,
"elseIfClauses": [],
"elseClause": null,
"endifKeyword": null,
"semicolon": null
}
},
{
"InlineHtml": {
"scriptSectionEndTag": {
"kind": "ScriptSectionEndTag",
"textLength": 2
},
"text": {
"kind": "InlineHtml",
"textLength": 11
},
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"IfStatementNode": {
"ifKeyword": {
Expand All @@ -35,26 +81,171 @@
},
"colon": null,
"statements": {
"InlineHtml": {
"scriptSectionEndTag": {
"kind": "ScriptSectionEndTag",
"textLength": 2
"ExpressionStatement": {
"expression": {
"EchoExpression": {
"echoKeyword": {
"kind": "EchoKeyword",
"textLength": 4
},
"expressions": {
"ExpressionList": {
"children": [
{
"StringLiteral": {
"startQuote": null,
"children": {
"kind": "StringLiteralToken",
"textLength": 3
},
"endQuote": null
}
}
]
}
}
}
},
"text": {
"kind": "InlineHtml",
"textLength": 11
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
"elseIfClauses": [
{
"ElseIfClauseNode": {
"elseIfKeyword": {
"kind": "ElseIfKeyword",
"textLength": 6
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"expression": {
"ReservedWord": {
"children": {
"kind": "FalseReservedWord",
"textLength": 5
}
}
},
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"colon": null,
"statements": null
}
}
],
"elseClause": null,
"endifKeyword": null,
"semicolon": null
}
},
{
"InlineHtml": {
"scriptSectionEndTag": {
"kind": "ScriptSectionEndTag",
"textLength": 2
},
"text": {
"kind": "InlineHtml",
"textLength": 12
},
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"IfStatementNode": {
"ifKeyword": {
"kind": "IfKeyword",
"textLength": 2
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"expression": {
"ReservedWord": {
"children": {
"kind": "TrueReservedWord",
"textLength": 4
}
}
},
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"colon": null,
"statements": {
"ExpressionStatement": {
"expression": {
"EchoExpression": {
"echoKeyword": {
"kind": "EchoKeyword",
"textLength": 4
},
"expressions": {
"ExpressionList": {
"children": [
{
"StringLiteral": {
"startQuote": null,
"children": {
"kind": "StringLiteralToken",
"textLength": 8
},
"endQuote": null
}
}
]
}
}
}
},
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
"elseIfClauses": [],
"elseClause": null,
"elseClause": {
"ElseClauseNode": {
"elseKeyword": {
"kind": "ElseKeyword",
"textLength": 4
},
"colon": null,
"statements": null
}
},
"endifKeyword": null,
"semicolon": null
}
},
{
"InlineHtml": {
"scriptSectionEndTag": {
"kind": "ScriptSectionEndTag",
"textLength": 2
},
"text": {
"kind": "InlineHtml",
"textLength": 10
},
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
}
],
"endOfFileToken": {
Expand Down
9 changes: 9 additions & 0 deletions tests/cases/parser/programStructure39.php
@@ -0,0 +1,9 @@
<?php

while (false)?>after a while<?php

for (;false;)?> for false<?php

foreach ([] as $elem)?> and after empty foreach<?php

if (0) if (0)?> and after two ifs<?php
1 change: 1 addition & 0 deletions tests/cases/parser/programStructure39.php.diag
@@ -0,0 +1 @@
[]

0 comments on commit d3e28a7

Please sign in to comment.