Skip to content

Commit 0fb9353

Browse files
committed
implement encapsed nodes
1 parent 309afd7 commit 0fb9353

File tree

5 files changed

+239
-76
lines changed

5 files changed

+239
-76
lines changed

docs/AST.md

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
- [Inline](#inline)
4242
- [Magic](#magic)
4343
- [Shell](#shell)
44+
- [Nowdoc](#nowdoc)
45+
- [Encapsed](#encapsed)
4446
- [Statement](#statement)
4547
- [Eval](#eval)
4648
- [Exit](#exit)
@@ -486,6 +488,53 @@ Defines system based call
486488

487489
Defines an empty check call
488490

491+
# Encapsed
492+
493+
**Extends Literal**
494+
495+
Defines an encapsed string (contains expressions)
496+
497+
**Properties**
498+
499+
- `type` **[String](#string)** Defines the type of encapsed string (shell, heredoc, string)
500+
- `label` **([String](#string) \| [Null](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null))** The heredoc label, defined only when the type is heredoc
501+
502+
## TYPE_STRING
503+
504+
The node is a double quote string :
505+
506+
```php
507+
<?php
508+
echo "hello $world";
509+
```
510+
511+
Type: [String](#string)
512+
513+
## TYPE_SHELL
514+
515+
The node is a shell execute string :
516+
517+
```php
518+
<?php
519+
echo `ls -larth $path`;
520+
```
521+
522+
Type: [String](#string)
523+
524+
## TYPE_HEREDOC
525+
526+
The node is a shell execute string :
527+
528+
```php
529+
<?php
530+
echo <<<STR
531+
Hello $world
532+
STR
533+
;
534+
```
535+
536+
Type: [String](#string)
537+
489538
# Entry
490539

491540
**Extends Node**
@@ -812,6 +861,26 @@ Helper for extending the Node class
812861

813862
Returns **[Function](#function)**
814863

864+
# String
865+
866+
**Extends Literal**
867+
868+
Defines a nowdoc string
869+
870+
**Properties**
871+
872+
- `label` **[String](#string)**
873+
874+
# String
875+
876+
**Extends Literal**
877+
878+
Defines a string (simple ou double quoted) - chars are already escaped
879+
880+
**Properties**
881+
882+
- `isDoubleQuote` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)**
883+
815884
# Number
816885

817886
**Extends Literal**
@@ -987,16 +1056,6 @@ Declares a static variable into the current scope
9871056

9881057
Lookup to a static property
9891058

990-
# String
991-
992-
**Extends Literal**
993-
994-
Defines inline html output (treated as echo output)
995-
996-
**Properties**
997-
998-
- `isDoubleQuote` **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)**
999-
10001059
# Switch
10011060

10021061
**Extends Statement**

docs/parser.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,15 +545,21 @@ Handles the dereferencing
545545

546546
# read_encapsed_string_item
547547

548+
Reads and extracts an encapsed item
549+
548550
```ebnf
549551
encapsed_string_item ::= T_ENCAPSED_AND_WHITESPACE
550552
| T_DOLLAR_OPEN_CURLY_BRACES expr '}'
551553
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}'
552554
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
553-
| variable
554555
| T_CURLY_OPEN variable '}'
556+
| variable
557+
| variable '[' expr ']'
558+
| variable T_OBJECT_OPERATOR T_STRING
555559
```
556560

561+
Returns **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) | Variable | Expr | Lookup)**
562+
557563
# read_encapsed_string
558564

559565
Reads an encapsed string

src/ast/string.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ var Literal = require('./literal');
88
var KIND = 'string';
99

1010
/**
11-
* Defines inline html output (treated as echo output)
11+
* Defines a string (simple ou double quoted) - chars are already escaped
1212
* @constructor String
1313
* @extends {Literal}
1414
* @property {boolean} isDoubleQuote
15-
15+
* @see {Encapsed}
1616
*/
1717
var String = Literal.extends(function String(isDoubleQuote, value, location) {
1818
Literal.apply(this, [KIND, value, location]);

src/parser/scalar.js

Lines changed: 120 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,10 @@ module.exports = {
4949
case this.tok.T_CONSTANT_ENCAPSED_STRING:
5050
var value = this.node('string');
5151
var text = this.text();
52-
var isDoubleQuote = false;
53-
var isBinCast = value[0] === 'b' || value[0] === 'B';
54-
if (isBinCast) {
55-
isDoubleQuote = text[1] === '"';
56-
text = text.substring(2, text.length - 1);
57-
} else {
58-
isDoubleQuote = text[0] === '"';
59-
text = text.substring(1, text.length - 1);
60-
}
52+
var isDoubleQuote = text[0] === '"';
53+
text = text.substring(1, text.length - 1);
6154
this.next();
6255
value = value(isDoubleQuote, this.resolve_special_chars(text));
63-
if (isBinCast) {
64-
value = ['cast', 'binary', value];
65-
}
6656
if (this.token === this.tok.T_DOUBLE_COLON) {
6757
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1151
6858
return this.read_static_getter(value);
@@ -71,14 +61,41 @@ module.exports = {
7161
return value;
7262
}
7363
case this.tok.T_START_HEREDOC:
74-
return this.next().read_encapsed_string(
75-
this.tok.T_END_HEREDOC
76-
);
64+
if (this.lexer.curCondition === 'ST_NOWDOC') {
65+
var node = this.node('nowdoc');
66+
var value = this.next().text();
67+
// strip the last line return char
68+
var lastCh = value[value.length-1];
69+
if (lastCh === '\n') {
70+
if (value[value.length-2] === '\r') {
71+
// windows style
72+
value = value.substring(0, value.length - 2);
73+
} else {
74+
// linux style
75+
value = value.substring(0, value.length - 1);
76+
}
77+
} else if (lastCh === '\r') {
78+
// mac style
79+
value = value.substring(0, value.length - 1);
80+
}
81+
this.expect(this.tok.T_ENCAPSED_AND_WHITESPACE) && this.next();
82+
node = node(value, this.lexer.heredoc_label);
83+
this.expect(this.tok.T_END_HEREDOC) && this.next();
84+
return node;
85+
} else {
86+
return this.next().read_encapsed_string(
87+
this.tok.T_END_HEREDOC
88+
);
89+
}
90+
7791
case '"':
7892
return this.next().read_encapsed_string('"');
93+
7994
case 'b"':
8095
case 'B"':
81-
return ['cast', 'binary', this.next().read_encapsed_string('"')];
96+
var node = this.node('cast');
97+
var what = this.next().read_encapsed_string('"');
98+
return node('binary', what);
8299

83100
// NUMERIC
84101
case '-': // long
@@ -145,78 +162,124 @@ module.exports = {
145162
return result;
146163
}
147164
/**
165+
* Reads and extracts an encapsed item
148166
* ```ebnf
149167
* encapsed_string_item ::= T_ENCAPSED_AND_WHITESPACE
150168
* | T_DOLLAR_OPEN_CURLY_BRACES expr '}'
151169
* | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}'
152170
* | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
153-
* | variable
154171
* | T_CURLY_OPEN variable '}'
172+
* | variable
173+
* | variable '[' expr ']'
174+
* | variable T_OBJECT_OPERATOR T_STRING
155175
* ```
176+
* @return {String|Variable|Expr|Lookup}
177+
* @see https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1219
156178
*/
157179
,read_encapsed_string_item: function() {
158-
var result = null;
180+
var result = this.node();
181+
182+
// plain text
183+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1222
159184
if (this.token === this.tok.T_ENCAPSED_AND_WHITESPACE) {
160-
result = this.node('string')(false, this.text());
185+
var text = this.text();
161186
this.next();
162-
} else if (this.token === this.tok.T_DOLLAR_OPEN_CURLY_BRACES) {
187+
result = result(
188+
'string', false, this.resolve_special_chars(text)
189+
);
190+
}
191+
192+
// dynamic variable name
193+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1239
194+
else if (this.token === this.tok.T_DOLLAR_OPEN_CURLY_BRACES) {
195+
var name = null;
163196
if (this.next().token === this.tok.T_STRING_VARNAME) {
164-
result = ['var', this.text()];
165-
if (this.next().token === '[') {
197+
var varName = this.text().substring(1);
198+
name = this.node('variable');
199+
this.next();
200+
name = name(varName, false);
201+
// check if lookup an offset
202+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1243
203+
if (this.token === '[') {
166204
var node = this.node('offsetlookup');
167205
var offset = this.next().read_expr();
168-
if (this.expect(']')) this.next();
169-
result = node(result, offset);
206+
this.expect(']') && this.next();
207+
name = node(name, offset);
170208
}
171209
} else {
172-
result = this.read_expr();
210+
name = this.read_expr();
173211
}
174-
if (this.expect('}')) this.next();
175-
} else if (this.token === this.tok.T_CURLY_OPEN) {
212+
this.expect('}') && this.next();
213+
result = result('variable', name, false);
214+
}
215+
216+
// expression
217+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1246
218+
else if (this.token === this.tok.T_CURLY_OPEN) {
176219
result = this.next().read_variable(false, false, false);
177220
if (this.expect('}')) this.next();
178-
} else if (this.token === '[') {
179-
var node = this.node('offsetlookup');
180-
var offset = this.next().read_expr();
181-
if (this.expect(']')) this.next();
182-
result = node(result, offset);
183-
} else if (this.token === this.tok.T_VARIABLE) {
221+
}
222+
223+
// plain variable
224+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1231
225+
else if (this.token === this.tok.T_VARIABLE) {
184226
result = this.read_variable(false, true, false);
227+
228+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1233
229+
if (this.token === '[') {
230+
var node = this.node('offsetlookup');
231+
var offset = this.next().read_expr();
232+
this.expect(']') && this.next();
233+
result = node(result, offset);
234+
}
235+
236+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1236
237+
if (this.token === this.tok.T_OBJECT_OPERATOR) {
238+
var node = this.node('propertylookup');
239+
var what = this.node('constref');
240+
this.next().expect(this.tok.T_STRING);
241+
var name = this.text();
242+
this.next();
243+
result = node(result, what(name));
244+
}
245+
246+
// error / fallback
185247
} else {
186-
this.expect([
187-
this.tok.T_VARIABLE,
188-
this.tok.T_CURLY_OPEN,
189-
this.tok.T_DOLLAR_OPEN_CURLY_BRACES,
190-
this.tok.T_ENCAPSED_AND_WHITESPACE
191-
]);
248+
this.expect(this.tok.T_ENCAPSED_AND_WHITESPACE);
249+
var value = this.text();
250+
this.next();
251+
// consider it as string
252+
result = result('string', false, value);
192253
}
254+
193255
return result;
194256
}
195257
/**
196258
* Reads an encapsed string
197259
*/
198260
,read_encapsed_string: function(expect) {
199-
if (this.token === expect) {
200-
this.next();
201-
return null; // empty
202-
}
203-
var first = this.read_encapsed_string_item();
204-
if (this.token === expect) {
205-
this.next();
206-
return first;
261+
var node = this.node('encapsed'), value = [], type = null;
262+
263+
if (expect === '`') {
264+
type = this.ast.encapsed.TYPE_SHELL;
265+
} else if (expect === '"') {
266+
type = this.ast.encapsed.TYPE_STRING;
267+
} else {
268+
type = this.ast.encapsed.TYPE_HEREDOC;
207269
}
208-
var result = [
209-
'bin', '.'
210-
, first
211-
, this.read_encapsed_string_item()
212-
];
270+
271+
// reading encapsed parts
213272
while(this.token !== expect && this.token !== this.EOF) {
214-
result[3] = [
215-
'bin', '.', result[3], this.read_encapsed_string_item()
216-
];
273+
value.push(this.read_encapsed_string_item());
217274
}
218-
if (this.expect(expect)) this.next();
219-
return result;
275+
276+
this.expect(expect) && this.next();
277+
node = node(value, type);
278+
279+
if (expect === this.tok.T_END_HEREDOC) {
280+
node.label = this.lexer.heredoc_label;
281+
}
282+
return node;
220283
}
221284
/**
222285
* Constant token

0 commit comments

Comments
 (0)