@jorgsowa/php-parser 3.2.5-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +108 -0
  3. package/dist/@jorgsowa/php-parser.js +11239 -0
  4. package/dist/@jorgsowa/php-parser.min.js +2 -0
  5. package/dist/@jorgsowa/php-parser.min.js.LICENSE.txt +10 -0
  6. package/package.json +86 -0
  7. package/src/ast/array.js +44 -0
  8. package/src/ast/arrowfunc.js +43 -0
  9. package/src/ast/assign.js +28 -0
  10. package/src/ast/assignref.js +27 -0
  11. package/src/ast/attrgroup.js +21 -0
  12. package/src/ast/attribute.js +26 -0
  13. package/src/ast/bin.js +27 -0
  14. package/src/ast/block.js +24 -0
  15. package/src/ast/boolean.js +23 -0
  16. package/src/ast/break.js +21 -0
  17. package/src/ast/byref.js +21 -0
  18. package/src/ast/call.js +26 -0
  19. package/src/ast/case.js +26 -0
  20. package/src/ast/cast.js +28 -0
  21. package/src/ast/catch.js +29 -0
  22. package/src/ast/class.js +36 -0
  23. package/src/ast/classconstant.js +71 -0
  24. package/src/ast/clone.js +21 -0
  25. package/src/ast/closure.js +47 -0
  26. package/src/ast/comment.js +23 -0
  27. package/src/ast/commentblock.js +22 -0
  28. package/src/ast/commentline.js +22 -0
  29. package/src/ast/constant.js +26 -0
  30. package/src/ast/constantstatement.js +24 -0
  31. package/src/ast/continue.js +24 -0
  32. package/src/ast/declaration.js +60 -0
  33. package/src/ast/declare.js +71 -0
  34. package/src/ast/declaredirective.js +26 -0
  35. package/src/ast/do.js +26 -0
  36. package/src/ast/echo.js +26 -0
  37. package/src/ast/empty.js +23 -0
  38. package/src/ast/encapsed.js +75 -0
  39. package/src/ast/encapsedpart.js +28 -0
  40. package/src/ast/entry.js +30 -0
  41. package/src/ast/enum.js +30 -0
  42. package/src/ast/enumcase.js +26 -0
  43. package/src/ast/error.js +30 -0
  44. package/src/ast/eval.js +24 -0
  45. package/src/ast/exit.js +26 -0
  46. package/src/ast/expression.js +20 -0
  47. package/src/ast/expressionstatement.js +24 -0
  48. package/src/ast/for.js +33 -0
  49. package/src/ast/foreach.js +33 -0
  50. package/src/ast/function.js +34 -0
  51. package/src/ast/global.js +24 -0
  52. package/src/ast/goto.js +22 -0
  53. package/src/ast/halt.js +22 -0
  54. package/src/ast/identifier.js +26 -0
  55. package/src/ast/if.js +30 -0
  56. package/src/ast/include.js +28 -0
  57. package/src/ast/inline.js +23 -0
  58. package/src/ast/interface.js +28 -0
  59. package/src/ast/intersectiontype.js +24 -0
  60. package/src/ast/isset.js +23 -0
  61. package/src/ast/label.js +21 -0
  62. package/src/ast/list.js +26 -0
  63. package/src/ast/literal.js +28 -0
  64. package/src/ast/location.js +22 -0
  65. package/src/ast/lookup.js +26 -0
  66. package/src/ast/magic.js +22 -0
  67. package/src/ast/match.js +26 -0
  68. package/src/ast/matcharm.js +26 -0
  69. package/src/ast/method.js +24 -0
  70. package/src/ast/name.js +55 -0
  71. package/src/ast/namedargument.js +27 -0
  72. package/src/ast/namespace.js +26 -0
  73. package/src/ast/new.js +26 -0
  74. package/src/ast/node.js +111 -0
  75. package/src/ast/noop.js +20 -0
  76. package/src/ast/nowdoc.js +26 -0
  77. package/src/ast/nullkeyword.js +20 -0
  78. package/src/ast/nullsafepropertylookup.js +22 -0
  79. package/src/ast/number.js +23 -0
  80. package/src/ast/offsetlookup.js +22 -0
  81. package/src/ast/operation.js +19 -0
  82. package/src/ast/parameter.js +61 -0
  83. package/src/ast/parentreference.js +24 -0
  84. package/src/ast/position.js +22 -0
  85. package/src/ast/post.js +26 -0
  86. package/src/ast/pre.js +26 -0
  87. package/src/ast/print.js +23 -0
  88. package/src/ast/program.js +32 -0
  89. package/src/ast/property.js +46 -0
  90. package/src/ast/propertyhook.js +33 -0
  91. package/src/ast/propertylookup.js +22 -0
  92. package/src/ast/propertystatement.js +59 -0
  93. package/src/ast/reference.js +21 -0
  94. package/src/ast/retif.js +28 -0
  95. package/src/ast/return.js +21 -0
  96. package/src/ast/selfreference.js +24 -0
  97. package/src/ast/silent.js +24 -0
  98. package/src/ast/statement.js +19 -0
  99. package/src/ast/static.js +24 -0
  100. package/src/ast/staticlookup.js +22 -0
  101. package/src/ast/staticreference.js +24 -0
  102. package/src/ast/staticvariable.js +26 -0
  103. package/src/ast/string.js +28 -0
  104. package/src/ast/switch.js +28 -0
  105. package/src/ast/throw.js +21 -0
  106. package/src/ast/trait.js +24 -0
  107. package/src/ast/traitalias.js +44 -0
  108. package/src/ast/traitprecedence.js +28 -0
  109. package/src/ast/traituse.js +26 -0
  110. package/src/ast/try.js +28 -0
  111. package/src/ast/typereference.js +40 -0
  112. package/src/ast/unary.js +26 -0
  113. package/src/ast/uniontype.js +24 -0
  114. package/src/ast/unset.js +23 -0
  115. package/src/ast/usegroup.js +30 -0
  116. package/src/ast/useitem.js +45 -0
  117. package/src/ast/variable.js +36 -0
  118. package/src/ast/variadic.js +25 -0
  119. package/src/ast/variadicplaceholder.js +24 -0
  120. package/src/ast/while.js +28 -0
  121. package/src/ast/yield.js +27 -0
  122. package/src/ast/yieldfrom.js +25 -0
  123. package/src/ast.js +593 -0
  124. package/src/index.js +239 -0
  125. package/src/lexer/attribute.js +85 -0
  126. package/src/lexer/comments.js +63 -0
  127. package/src/lexer/initial.js +64 -0
  128. package/src/lexer/numbers.js +171 -0
  129. package/src/lexer/property.js +96 -0
  130. package/src/lexer/scripting.js +114 -0
  131. package/src/lexer/strings.js +524 -0
  132. package/src/lexer/tokens.js +356 -0
  133. package/src/lexer/utils.js +112 -0
  134. package/src/lexer.js +561 -0
  135. package/src/parser/array.js +113 -0
  136. package/src/parser/class.js +718 -0
  137. package/src/parser/comment.js +52 -0
  138. package/src/parser/enum.js +56 -0
  139. package/src/parser/expr.js +848 -0
  140. package/src/parser/function.js +507 -0
  141. package/src/parser/if.js +94 -0
  142. package/src/parser/loops.js +168 -0
  143. package/src/parser/main.js +21 -0
  144. package/src/parser/namespace.js +231 -0
  145. package/src/parser/scalar.js +492 -0
  146. package/src/parser/statement.js +444 -0
  147. package/src/parser/switch.js +99 -0
  148. package/src/parser/try.js +43 -0
  149. package/src/parser/utils.js +203 -0
  150. package/src/parser/variable.js +363 -0
  151. package/src/parser.js +748 -0
  152. package/src/tokens.js +177 -0
  153. package/types.d.ts +1457 -0
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Copyright (C) 2018 Glayzzle (BSD3 License)
3
+ * @authors https://github.com/glayzzle/php-parser/graphs/contributors
4
+ * @url http://glayzzle.com
5
+ */
6
+ "use strict";
7
+
8
+ module.exports = {
9
+ /*
10
+ * Reads a short form of tokens
11
+ * @param {Number} token - The ending token
12
+ * @return {Block}
13
+ */
14
+ read_short_form(token) {
15
+ const body = this.node("block");
16
+ const items = [];
17
+ /* istanbul ignore next */
18
+ if (this.expect(":")) this.next();
19
+ while (this.token != this.EOF && this.token !== token) {
20
+ items.push(this.read_inner_statement());
21
+ }
22
+ if (
23
+ items.length === 0 &&
24
+ this.extractDoc &&
25
+ this._docs.length > this._docIndex
26
+ ) {
27
+ items.push(this.node("noop")());
28
+ }
29
+ /* istanbul ignore next */
30
+ if (this.expect(token)) this.next();
31
+ this.expectEndOfStatement();
32
+ return body(null, items);
33
+ },
34
+
35
+ /*
36
+ * https://wiki.php.net/rfc/trailing-comma-function-calls
37
+ * @param {*} item
38
+ * @param {*} separator
39
+ */
40
+ read_function_list(item, separator) {
41
+ const result = [];
42
+ do {
43
+ if (this.token == separator && this.version >= 703 && result.length > 0) {
44
+ result.push(this.node("noop")());
45
+ break;
46
+ }
47
+ result.push(item.apply(this, []));
48
+ if (this.token != separator) {
49
+ break;
50
+ }
51
+ if (this.next().token == ")" && this.version >= 703) {
52
+ break;
53
+ }
54
+ } while (this.token != this.EOF);
55
+ return result;
56
+ },
57
+
58
+ /*
59
+ * Helper : reads a list of tokens / sample : T_STRING ',' T_STRING ...
60
+ * ```ebnf
61
+ * list ::= separator? ( item separator )* item
62
+ * ```
63
+ */
64
+ read_list(item, separator, preserveFirstSeparator) {
65
+ const result = [];
66
+
67
+ if (this.token == separator) {
68
+ if (preserveFirstSeparator) {
69
+ result.push(typeof item === "function" ? this.node("noop")() : null);
70
+ }
71
+ this.next();
72
+ }
73
+
74
+ if (typeof item === "function") {
75
+ do {
76
+ const itemResult = item.apply(this, []);
77
+ if (itemResult) {
78
+ result.push(itemResult);
79
+ }
80
+ if (this.token != separator) {
81
+ break;
82
+ }
83
+ } while (this.next().token != this.EOF);
84
+ } else {
85
+ if (this.expect(item)) {
86
+ result.push(this.text());
87
+ } else {
88
+ return [];
89
+ }
90
+ while (this.next().token != this.EOF) {
91
+ if (this.token != separator) break;
92
+ // trim current separator & check item
93
+ if (this.next().token != item) break;
94
+ result.push(this.text());
95
+ }
96
+ }
97
+ return result;
98
+ },
99
+
100
+ /*
101
+ * Reads a list of names separated by a comma
102
+ *
103
+ * ```ebnf
104
+ * name_list ::= namespace (',' namespace)*
105
+ * ```
106
+ *
107
+ * Sample code :
108
+ * ```php
109
+ * <?php class foo extends bar, baz { }
110
+ * ```
111
+ *
112
+ * @see https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L726
113
+ * @return {Reference[]}
114
+ */
115
+ read_name_list() {
116
+ return this.read_list(this.read_namespace_name, ",", false);
117
+ },
118
+
119
+ /*
120
+ * Reads the byref token and assign it to the specified node
121
+ * @param {*} cb
122
+ */
123
+ read_byref(cb) {
124
+ let byref = this.node("byref");
125
+ this.next();
126
+ byref = byref(null);
127
+ const result = cb();
128
+ if (result) {
129
+ this.ast.swapLocations(result, byref, result, this);
130
+ result.byref = true;
131
+ }
132
+ return result;
133
+ },
134
+
135
+ /*
136
+ * Reads a list of variables declarations
137
+ *
138
+ * ```ebnf
139
+ * variable_declaration ::= T_VARIABLE ('=' expr)?*
140
+ * variable_declarations ::= variable_declaration (',' variable_declaration)*
141
+ * ```
142
+ *
143
+ * Sample code :
144
+ * ```php
145
+ * <?php static $a = 'hello', $b = 'world';
146
+ * ```
147
+ * @return {StaticVariable[]} Returns an array composed by a list of variables, or
148
+ * assign values
149
+ */
150
+ read_variable_declarations() {
151
+ return this.read_list(function () {
152
+ const node = this.node("staticvariable");
153
+ let variable = this.node("variable");
154
+ // plain variable name
155
+ /* istanbul ignore else */
156
+ if (this.expect(this.tok.T_VARIABLE)) {
157
+ const name = this.text().substring(1);
158
+ this.next();
159
+ variable = variable(name, false);
160
+ } else {
161
+ variable = variable("#ERR", false);
162
+ }
163
+ if (this.token === "=") {
164
+ return node(variable, this.next().read_expr());
165
+ } else {
166
+ return variable;
167
+ }
168
+ }, ",");
169
+ },
170
+
171
+ /*
172
+ * Reads class extends
173
+ */
174
+ read_extends_from() {
175
+ if (this.token === this.tok.T_EXTENDS) {
176
+ return this.next().read_namespace_name();
177
+ }
178
+
179
+ return null;
180
+ },
181
+
182
+ /*
183
+ * Reads interface extends list
184
+ */
185
+ read_interface_extends_list() {
186
+ if (this.token === this.tok.T_EXTENDS) {
187
+ return this.next().read_name_list();
188
+ }
189
+
190
+ return null;
191
+ },
192
+
193
+ /*
194
+ * Reads implements list
195
+ */
196
+ read_implements_list() {
197
+ if (this.token === this.tok.T_IMPLEMENTS) {
198
+ return this.next().read_name_list();
199
+ }
200
+
201
+ return null;
202
+ },
203
+ };
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Copyright (C) 2018 Glayzzle (BSD3 License)
3
+ * @authors https://github.com/glayzzle/php-parser/graphs/contributors
4
+ * @url http://glayzzle.com
5
+ */
6
+ "use strict";
7
+
8
+ module.exports = {
9
+ /*
10
+ * Reads a variable
11
+ *
12
+ * ```ebnf
13
+ * variable ::= &? ...complex @todo
14
+ * ```
15
+ *
16
+ * Some samples of parsed code :
17
+ * ```php
18
+ * &$var // simple var
19
+ * $var // simple var
20
+ * classname::CONST_NAME // dynamic class name with const retrieval
21
+ * foo() // function call
22
+ * $var->func()->property // chained calls
23
+ * ```
24
+ */
25
+ read_variable(read_only, encapsed) {
26
+ let result;
27
+ // check the byref flag
28
+ if (this.token === "&") {
29
+ return this.read_byref(
30
+ this.read_variable.bind(this, read_only, encapsed),
31
+ );
32
+ }
33
+
34
+ // reads the entry point
35
+ if (this.is([this.tok.T_VARIABLE, "$"])) {
36
+ result = this.read_reference_variable(encapsed);
37
+ } else if (
38
+ this.is([
39
+ this.tok.T_NS_SEPARATOR,
40
+ this.tok.T_STRING,
41
+ this.tok.T_NAME_RELATIVE,
42
+ this.tok.T_NAME_QUALIFIED,
43
+ this.tok.T_NAME_FULLY_QUALIFIED,
44
+ this.tok.T_NAMESPACE,
45
+ ])
46
+ ) {
47
+ result = this.node();
48
+ const name = this.read_namespace_name();
49
+ if (
50
+ this.token != this.tok.T_DOUBLE_COLON &&
51
+ this.token != "(" &&
52
+ ["parentreference", "selfreference"].indexOf(name.kind) === -1
53
+ ) {
54
+ // @see parser.js line 130 : resolves a conflict with scalar
55
+ const literal = name.name.toLowerCase();
56
+ if (literal === "true") {
57
+ result = name.destroy(result("boolean", true, name.name));
58
+ } else if (literal === "false") {
59
+ result = name.destroy(result("boolean", false, name.name));
60
+ } else if (literal === "null") {
61
+ result = name.destroy(result("nullkeyword", name.name));
62
+ } else {
63
+ result.destroy(name);
64
+ result = name;
65
+ }
66
+ } else {
67
+ // @fixme possible #193 bug
68
+ result.destroy(name);
69
+ result = name;
70
+ }
71
+ } else if (this.token === this.tok.T_STATIC) {
72
+ result = this.node("staticreference");
73
+ const raw = this.text();
74
+ this.next();
75
+ result = result(raw);
76
+ } else {
77
+ this.expect("VARIABLE");
78
+ }
79
+
80
+ // static mode
81
+ if (this.token === this.tok.T_DOUBLE_COLON) {
82
+ result = this.read_static_getter(result, encapsed);
83
+ }
84
+
85
+ return this.recursive_variable_chain_scan(result, read_only, encapsed);
86
+ },
87
+
88
+ // resolves a static call
89
+ read_static_getter(what, encapsed) {
90
+ const result = this.node("staticlookup");
91
+ let offset, name;
92
+ if (this.next().is([this.tok.T_VARIABLE, "$"])) {
93
+ offset = this.read_reference_variable(encapsed);
94
+ } else if (
95
+ this.token === this.tok.T_STRING ||
96
+ this.token === this.tok.T_CLASS ||
97
+ (this.version >= 700 && this.is("IDENTIFIER"))
98
+ ) {
99
+ offset = this.node("identifier");
100
+ name = this.text();
101
+ this.next();
102
+ offset = offset(name);
103
+ } else if (this.token === "{") {
104
+ offset = this.node("literal");
105
+ name = this.next().read_expr();
106
+ this.expect("}") && this.next();
107
+ offset = offset("literal", name, null);
108
+ } else {
109
+ this.error([this.tok.T_VARIABLE, this.tok.T_STRING]);
110
+ // graceful mode : set getter as error node and continue
111
+ offset = this.node("identifier");
112
+ name = this.text();
113
+ this.next();
114
+ offset = offset(name);
115
+ }
116
+ return result(what, offset);
117
+ },
118
+
119
+ read_what(is_static_lookup = false) {
120
+ let what = null;
121
+ let name = null;
122
+ switch (this.next().token) {
123
+ case this.tok.T_STRING:
124
+ what = this.node("identifier");
125
+ name = this.text();
126
+ this.next();
127
+ what = what(name);
128
+
129
+ if (is_static_lookup && this.token === this.tok.T_OBJECT_OPERATOR) {
130
+ this.error();
131
+ }
132
+ break;
133
+ case this.tok.T_VARIABLE:
134
+ what = this.node("variable");
135
+ name = this.text().substring(1);
136
+ this.next();
137
+ what = what(name, false);
138
+ break;
139
+ case this.tok.T_CLASS:
140
+ if (!is_static_lookup) {
141
+ this.error();
142
+ }
143
+ what = this.node("identifier");
144
+ name = this.text();
145
+ this.next();
146
+ what = what(name, false);
147
+ break;
148
+ case "$":
149
+ what = this.node();
150
+ this.next().expect(["$", "{", this.tok.T_VARIABLE]);
151
+ if (this.token === "{") {
152
+ // $obj->${$varname}
153
+ name = this.next().read_expr();
154
+ this.expect("}") && this.next();
155
+ what = what("variable", name, true);
156
+ } else {
157
+ // $obj->$$varname
158
+ name = this.read_expr();
159
+ what = what("variable", name, false);
160
+ }
161
+ break;
162
+ case "{":
163
+ what = this.node("encapsedpart");
164
+ name = this.next().read_expr();
165
+ this.expect("}") && this.next();
166
+ what = what(name, "complex", false);
167
+ break;
168
+ default:
169
+ this.error([this.tok.T_STRING, this.tok.T_VARIABLE, "$", "{"]);
170
+ // graceful mode : set what as error mode & continue
171
+ what = this.node("identifier");
172
+ name = this.text();
173
+ this.next();
174
+ what = what(name);
175
+ break;
176
+ }
177
+
178
+ return what;
179
+ },
180
+
181
+ recursive_variable_chain_scan(result, read_only, encapsed) {
182
+ let node, offset;
183
+ recursive_scan_loop: while (this.token != this.EOF) {
184
+ switch (this.token) {
185
+ case "(":
186
+ if (read_only) {
187
+ // @fixme : add more informations & test
188
+ return result;
189
+ } else {
190
+ result = this.node("call")(result, this.read_argument_list());
191
+ }
192
+ break;
193
+ case "[":
194
+ case "{": {
195
+ const backet = this.token;
196
+ const isSquareBracket = backet === "[";
197
+ node = this.node("offsetlookup");
198
+ this.next();
199
+ offset = false;
200
+ if (encapsed) {
201
+ offset = this.read_encaps_var_offset();
202
+ this.expect(isSquareBracket ? "]" : "}") && this.next();
203
+ } else {
204
+ const isCallableVariable = isSquareBracket
205
+ ? this.token !== "]"
206
+ : this.token !== "}";
207
+ // callable_variable : https://github.com/php/php-src/blob/493524454d66adde84e00d249d607ecd540de99f/Zend/zend_language_parser.y#L1122
208
+ if (isCallableVariable) {
209
+ offset = this.read_expr();
210
+ this.expect(isSquareBracket ? "]" : "}") && this.next();
211
+ } else {
212
+ this.next();
213
+ }
214
+ }
215
+ result = node(result, offset);
216
+ break;
217
+ }
218
+ case this.tok.T_DOUBLE_COLON:
219
+ // @see https://github.com/glayzzle/php-parser/issues/107#issuecomment-354104574
220
+ if (
221
+ result.kind === "staticlookup" &&
222
+ result.offset.kind === "identifier"
223
+ ) {
224
+ this.error();
225
+ }
226
+
227
+ node = this.node("staticlookup");
228
+ result = node(result, this.read_what(true));
229
+
230
+ // fix 185
231
+ // static lookup dereferencables are limited to staticlookup over functions
232
+ /*if (dereferencable && this.token !== "(") {
233
+ this.error("(");
234
+ }*/
235
+ break;
236
+ case this.tok.T_OBJECT_OPERATOR: {
237
+ node = this.node("propertylookup");
238
+ result = node(result, this.read_what());
239
+ break;
240
+ }
241
+ case this.tok.T_NULLSAFE_OBJECT_OPERATOR: {
242
+ node = this.node("nullsafepropertylookup");
243
+ result = node(result, this.read_what());
244
+ break;
245
+ }
246
+ default:
247
+ break recursive_scan_loop;
248
+ }
249
+ }
250
+ return result;
251
+ },
252
+ /*
253
+ * https://github.com/php/php-src/blob/493524454d66adde84e00d249d607ecd540de99f/Zend/zend_language_parser.y#L1231
254
+ */
255
+ read_encaps_var_offset() {
256
+ let offset = this.node();
257
+ if (this.token === this.tok.T_STRING) {
258
+ const text = this.text();
259
+ this.next();
260
+ offset = offset("identifier", text);
261
+ } else if (this.token === this.tok.T_NUM_STRING) {
262
+ const num = this.text();
263
+ this.next();
264
+ offset = offset("number", num, null);
265
+ } else if (this.token === "-") {
266
+ this.next();
267
+ const num = -1 * this.text();
268
+ this.expect(this.tok.T_NUM_STRING) && this.next();
269
+ offset = offset("number", num, null);
270
+ } else if (this.token === this.tok.T_VARIABLE) {
271
+ const name = this.text().substring(1);
272
+ this.next();
273
+ offset = offset("variable", name, false);
274
+ } else {
275
+ this.expect([
276
+ this.tok.T_STRING,
277
+ this.tok.T_NUM_STRING,
278
+ "-",
279
+ this.tok.T_VARIABLE,
280
+ ]);
281
+ // fallback : consider as identifier
282
+ const text = this.text();
283
+ this.next();
284
+ offset = offset("identifier", text);
285
+ }
286
+ return offset;
287
+ },
288
+ /*
289
+ * ```ebnf
290
+ * reference_variable ::= simple_variable ('[' OFFSET ']')* | '{' EXPR '}'
291
+ * ```
292
+ * <code>
293
+ * $foo[123]; // foo is an array ==> gets its entry
294
+ * $foo{1}; // foo is a string ==> get the 2nd char offset
295
+ * ${'foo'}[123]; // get the dynamic var $foo
296
+ * $foo[123]{1}; // gets the 2nd char from the 123 array entry
297
+ * </code>
298
+ */
299
+ read_reference_variable(encapsed) {
300
+ let result = this.read_simple_variable();
301
+ let offset;
302
+ while (this.token != this.EOF) {
303
+ const node = this.node();
304
+ if (this.token == "{" && !encapsed) {
305
+ // @fixme check coverage, not sure thats working
306
+ offset = this.next().read_expr();
307
+ this.expect("}") && this.next();
308
+ result = node("offsetlookup", result, offset);
309
+ } else {
310
+ node.destroy();
311
+ break;
312
+ }
313
+ }
314
+ return result;
315
+ },
316
+ /*
317
+ * ```ebnf
318
+ * simple_variable ::= T_VARIABLE | '$' '{' expr '}' | '$' simple_variable
319
+ * ```
320
+ */
321
+ read_simple_variable() {
322
+ let result = this.node("variable");
323
+ let name;
324
+ if (
325
+ this.expect([this.tok.T_VARIABLE, "$"]) &&
326
+ this.token === this.tok.T_VARIABLE
327
+ ) {
328
+ // plain variable name
329
+ name = this.text().substring(1);
330
+ this.next();
331
+ result = result(name, false);
332
+ } else {
333
+ if (this.token === "$") this.next();
334
+ // dynamic variable name
335
+ switch (this.token) {
336
+ case "{": {
337
+ const expr = this.next().read_expr();
338
+ this.expect("}") && this.next();
339
+ result = result(expr, true);
340
+ break;
341
+ }
342
+ case "$": // $$$var
343
+ result = result(this.read_simple_variable(), false);
344
+ break;
345
+ case this.tok.T_VARIABLE: {
346
+ // $$var
347
+ name = this.text().substring(1);
348
+ const node = this.node("variable");
349
+ this.next();
350
+ result = result(node(name, false), false);
351
+ break;
352
+ }
353
+ default:
354
+ this.error(["{", "$", this.tok.T_VARIABLE]);
355
+ // graceful mode
356
+ name = this.text();
357
+ this.next();
358
+ result = result(name, false);
359
+ }
360
+ }
361
+ return result;
362
+ },
363
+ };