Skip to content

Commit e40b820

Browse files
committed
Parse template string variables, and the basic expressions that are allowed
1 parent 5cff923 commit e40b820

28 files changed

+1091
-3
lines changed

src/Parser.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,9 @@ private function parseStringLiteralExpression2($parentNode) {
10151015
case TokenKind::HeredocEnd:
10161016
$expression->endQuote = $this->eat($startQuoteKind, TokenKind::HeredocEnd);
10171017
return $expression;
1018+
case TokenKind::VariableName:
1019+
$expression->children[] = $this->parseTemplateStringExpression($expression);
1020+
continue;
10181021
default:
10191022
$expression->children[] = $this->getCurrentToken();
10201023
$this->advanceToken();
@@ -1025,6 +1028,71 @@ private function parseStringLiteralExpression2($parentNode) {
10251028
return $expression;
10261029
}
10271030

1031+
/**
1032+
* Double-quoted and heredoc strings support a basic set of expression types, described in http://php.net/manual/en/language.types.string.php#language.types.string.parsing
1033+
* Supported: $x, $x->p, $x[0], $x[$y]
1034+
* Not supported: $x->p1->p2, $x[0][1], etc.
1035+
* Since there is a relatively small finite set of allowed forms, I implement it here rather than trying to reuse the general expression parsing code.
1036+
*/
1037+
private function parseTemplateStringExpression($parentNode) {
1038+
$token = $this->getCurrentToken();
1039+
if ($token->kind === TokenKind::VariableName) {
1040+
$var = $this->parseSimpleVariable($parentNode);
1041+
$token = $this->getCurrentToken();
1042+
if ($token->kind === TokenKind::OpenBracketToken) {
1043+
return $this->parseTemplateStringSubscriptExpression($var);
1044+
} else if ($token->kind === TokenKind::ArrowToken) {
1045+
return $this->parseTemplateStringMemberAccessExpression($var);
1046+
} else {
1047+
return $var;
1048+
}
1049+
}
1050+
1051+
return null;
1052+
}
1053+
1054+
private function parseTemplateStringSubscriptExpression($postfixExpression) : SubscriptExpression {
1055+
$subscriptExpression = new SubscriptExpression();
1056+
$subscriptExpression->parent = $postfixExpression->parent;
1057+
$postfixExpression->parent = $subscriptExpression;
1058+
1059+
$subscriptExpression->postfixExpression = $postfixExpression;
1060+
$subscriptExpression->openBracketOrBrace = $this->eat(TokenKind::OpenBracketToken); // Only [] syntax is supported, not {}
1061+
$token = $this->getCurrentToken();
1062+
if ($token->kind === TokenKind::VariableName) {
1063+
$subscriptExpression->accessExpression = $this->parseSimpleVariable($subscriptExpression);
1064+
} elseif ($token->kind === TokenKind::IntegerLiteralToken) {
1065+
$subscriptExpression->accessExpression = $this->parseNumericLiteralExpression($subscriptExpression);
1066+
} elseif ($token->kind === TokenKind::Name) {
1067+
$subscriptExpression->accessExpression = $this->parseTemplateStringSubscriptStringLiteral($subscriptExpression);
1068+
} else {
1069+
$subscriptExpression->accessExpression = new MissingToken(TokenKind::Expression, $token->fullStart);
1070+
}
1071+
1072+
$subscriptExpression->closeBracketOrBrace = $this->eat(TokenKind::CloseBracketToken);
1073+
1074+
return $subscriptExpression;
1075+
}
1076+
1077+
private function parseTemplateStringSubscriptStringLiteral($parentNode) : StringLiteral {
1078+
$expression = new StringLiteral();
1079+
$expression->parent = $parentNode;
1080+
$expression->children = $this->eat(TokenKind::Name);
1081+
return $expression;
1082+
}
1083+
1084+
private function parseTemplateStringMemberAccessExpression($expression) : MemberAccessExpression {
1085+
$memberAccessExpression = new MemberAccessExpression();
1086+
$memberAccessExpression->parent = $expression->parent;
1087+
$expression->parent = $memberAccessExpression;
1088+
1089+
$memberAccessExpression->dereferencableExpression = $expression;
1090+
$memberAccessExpression->arrowToken = $this->eat(TokenKind::ArrowToken);
1091+
$memberAccessExpression->memberName = $this->eat(TokenKind::Name);
1092+
1093+
return $memberAccessExpression;
1094+
}
1095+
10281096
private function parseNumericLiteralExpression($parentNode) {
10291097
$numericLiteral = new NumericLiteral();
10301098
$numericLiteral->parent = $parentNode;

src/PhpTokenizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ public static function getTokensArrayFromContent(
295295
T_STRING_VARNAME => TokenKind::StringVarname,
296296
T_COMMENT => TokenKind::CommentToken,
297297
T_DOC_COMMENT => TokenKind::DocCommentToken,
298-
T_NUM_STRING => TokenKind::NumStringToken
298+
T_NUM_STRING => TokenKind::IntegerLiteralToken
299299
];
300300

301301
const PARSE_CONTEXT_TO_PREFIX = [

src/TokenKind.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ class TokenKind {
214214
const IntegerLiteralToken = 416;
215215
const CommentToken = 417;
216216
const DocCommentToken = 418;
217-
const NumStringToken = 419;
218217

219218
// TODO type annotations - PHP7
220219
}

tests/cases/lexical/stringLiteral27.php.tokens

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"textLength": 1
1717
},
1818
{
19-
"kind": "NumStringToken",
19+
"kind": "IntegerLiteralToken",
2020
"textLength": 1
2121
},
2222
{
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
"$x[a]"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[
2+
{
3+
"kind": "ScriptSectionStartTag",
4+
"textLength": 6
5+
},
6+
{
7+
"kind": "DoubleQuoteToken",
8+
"textLength": 1
9+
},
10+
{
11+
"kind": "VariableName",
12+
"textLength": 2
13+
},
14+
{
15+
"kind": "OpenBracketToken",
16+
"textLength": 1
17+
},
18+
{
19+
"kind": "Name",
20+
"textLength": 1
21+
},
22+
{
23+
"kind": "CloseBracketToken",
24+
"textLength": 1
25+
},
26+
{
27+
"kind": "DoubleQuoteToken",
28+
"textLength": 1
29+
},
30+
{
31+
"kind": "EndOfFileToken",
32+
"textLength": 0
33+
}
34+
]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
$a = "test";
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"SourceFileNode": {
3+
"statementList": [
4+
{
5+
"InlineHtml": {
6+
"scriptSectionEndTag": null,
7+
"text": null,
8+
"scriptSectionStartTag": {
9+
"kind": "ScriptSectionStartTag",
10+
"textLength": 6
11+
}
12+
}
13+
},
14+
{
15+
"ExpressionStatement": {
16+
"expression": {
17+
"AssignmentExpression": {
18+
"leftOperand": {
19+
"Variable": {
20+
"dollar": null,
21+
"name": {
22+
"kind": "VariableName",
23+
"textLength": 2
24+
}
25+
}
26+
},
27+
"operator": {
28+
"kind": "EqualsToken",
29+
"textLength": 1
30+
},
31+
"byRef": null,
32+
"rightOperand": {
33+
"StringLiteral": {
34+
"startQuote": null,
35+
"children": {
36+
"kind": "StringLiteralToken",
37+
"textLength": 6
38+
},
39+
"endQuote": null
40+
}
41+
}
42+
}
43+
},
44+
"semicolon": {
45+
"kind": "SemicolonToken",
46+
"textLength": 1
47+
}
48+
}
49+
}
50+
],
51+
"endOfFileToken": {
52+
"kind": "EndOfFileToken",
53+
"textLength": 0
54+
}
55+
}
56+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
$a = "abc $foo->";
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"SourceFileNode": {
3+
"statementList": [
4+
{
5+
"InlineHtml": {
6+
"scriptSectionEndTag": null,
7+
"text": null,
8+
"scriptSectionStartTag": {
9+
"kind": "ScriptSectionStartTag",
10+
"textLength": 6
11+
}
12+
}
13+
},
14+
{
15+
"ExpressionStatement": {
16+
"expression": {
17+
"AssignmentExpression": {
18+
"leftOperand": {
19+
"Variable": {
20+
"dollar": null,
21+
"name": {
22+
"kind": "VariableName",
23+
"textLength": 2
24+
}
25+
}
26+
},
27+
"operator": {
28+
"kind": "EqualsToken",
29+
"textLength": 1
30+
},
31+
"byRef": null,
32+
"rightOperand": {
33+
"StringLiteral": {
34+
"startQuote": {
35+
"kind": "DoubleQuoteToken",
36+
"textLength": 1
37+
},
38+
"children": [
39+
{
40+
"kind": "EncapsedAndWhitespace",
41+
"textLength": 4
42+
},
43+
{
44+
"Variable": {
45+
"dollar": null,
46+
"name": {
47+
"kind": "VariableName",
48+
"textLength": 4
49+
}
50+
}
51+
},
52+
{
53+
"kind": "EncapsedAndWhitespace",
54+
"textLength": 2
55+
}
56+
],
57+
"endQuote": {
58+
"kind": "DoubleQuoteToken",
59+
"textLength": 1
60+
}
61+
}
62+
}
63+
}
64+
},
65+
"semicolon": {
66+
"kind": "SemicolonToken",
67+
"textLength": 1
68+
}
69+
}
70+
}
71+
],
72+
"endOfFileToken": {
73+
"kind": "EndOfFileToken",
74+
"textLength": 0
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)