@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.
- package/LICENSE +27 -0
- package/README.md +108 -0
- package/dist/@jorgsowa/php-parser.js +11239 -0
- package/dist/@jorgsowa/php-parser.min.js +2 -0
- package/dist/@jorgsowa/php-parser.min.js.LICENSE.txt +10 -0
- package/package.json +86 -0
- package/src/ast/array.js +44 -0
- package/src/ast/arrowfunc.js +43 -0
- package/src/ast/assign.js +28 -0
- package/src/ast/assignref.js +27 -0
- package/src/ast/attrgroup.js +21 -0
- package/src/ast/attribute.js +26 -0
- package/src/ast/bin.js +27 -0
- package/src/ast/block.js +24 -0
- package/src/ast/boolean.js +23 -0
- package/src/ast/break.js +21 -0
- package/src/ast/byref.js +21 -0
- package/src/ast/call.js +26 -0
- package/src/ast/case.js +26 -0
- package/src/ast/cast.js +28 -0
- package/src/ast/catch.js +29 -0
- package/src/ast/class.js +36 -0
- package/src/ast/classconstant.js +71 -0
- package/src/ast/clone.js +21 -0
- package/src/ast/closure.js +47 -0
- package/src/ast/comment.js +23 -0
- package/src/ast/commentblock.js +22 -0
- package/src/ast/commentline.js +22 -0
- package/src/ast/constant.js +26 -0
- package/src/ast/constantstatement.js +24 -0
- package/src/ast/continue.js +24 -0
- package/src/ast/declaration.js +60 -0
- package/src/ast/declare.js +71 -0
- package/src/ast/declaredirective.js +26 -0
- package/src/ast/do.js +26 -0
- package/src/ast/echo.js +26 -0
- package/src/ast/empty.js +23 -0
- package/src/ast/encapsed.js +75 -0
- package/src/ast/encapsedpart.js +28 -0
- package/src/ast/entry.js +30 -0
- package/src/ast/enum.js +30 -0
- package/src/ast/enumcase.js +26 -0
- package/src/ast/error.js +30 -0
- package/src/ast/eval.js +24 -0
- package/src/ast/exit.js +26 -0
- package/src/ast/expression.js +20 -0
- package/src/ast/expressionstatement.js +24 -0
- package/src/ast/for.js +33 -0
- package/src/ast/foreach.js +33 -0
- package/src/ast/function.js +34 -0
- package/src/ast/global.js +24 -0
- package/src/ast/goto.js +22 -0
- package/src/ast/halt.js +22 -0
- package/src/ast/identifier.js +26 -0
- package/src/ast/if.js +30 -0
- package/src/ast/include.js +28 -0
- package/src/ast/inline.js +23 -0
- package/src/ast/interface.js +28 -0
- package/src/ast/intersectiontype.js +24 -0
- package/src/ast/isset.js +23 -0
- package/src/ast/label.js +21 -0
- package/src/ast/list.js +26 -0
- package/src/ast/literal.js +28 -0
- package/src/ast/location.js +22 -0
- package/src/ast/lookup.js +26 -0
- package/src/ast/magic.js +22 -0
- package/src/ast/match.js +26 -0
- package/src/ast/matcharm.js +26 -0
- package/src/ast/method.js +24 -0
- package/src/ast/name.js +55 -0
- package/src/ast/namedargument.js +27 -0
- package/src/ast/namespace.js +26 -0
- package/src/ast/new.js +26 -0
- package/src/ast/node.js +111 -0
- package/src/ast/noop.js +20 -0
- package/src/ast/nowdoc.js +26 -0
- package/src/ast/nullkeyword.js +20 -0
- package/src/ast/nullsafepropertylookup.js +22 -0
- package/src/ast/number.js +23 -0
- package/src/ast/offsetlookup.js +22 -0
- package/src/ast/operation.js +19 -0
- package/src/ast/parameter.js +61 -0
- package/src/ast/parentreference.js +24 -0
- package/src/ast/position.js +22 -0
- package/src/ast/post.js +26 -0
- package/src/ast/pre.js +26 -0
- package/src/ast/print.js +23 -0
- package/src/ast/program.js +32 -0
- package/src/ast/property.js +46 -0
- package/src/ast/propertyhook.js +33 -0
- package/src/ast/propertylookup.js +22 -0
- package/src/ast/propertystatement.js +59 -0
- package/src/ast/reference.js +21 -0
- package/src/ast/retif.js +28 -0
- package/src/ast/return.js +21 -0
- package/src/ast/selfreference.js +24 -0
- package/src/ast/silent.js +24 -0
- package/src/ast/statement.js +19 -0
- package/src/ast/static.js +24 -0
- package/src/ast/staticlookup.js +22 -0
- package/src/ast/staticreference.js +24 -0
- package/src/ast/staticvariable.js +26 -0
- package/src/ast/string.js +28 -0
- package/src/ast/switch.js +28 -0
- package/src/ast/throw.js +21 -0
- package/src/ast/trait.js +24 -0
- package/src/ast/traitalias.js +44 -0
- package/src/ast/traitprecedence.js +28 -0
- package/src/ast/traituse.js +26 -0
- package/src/ast/try.js +28 -0
- package/src/ast/typereference.js +40 -0
- package/src/ast/unary.js +26 -0
- package/src/ast/uniontype.js +24 -0
- package/src/ast/unset.js +23 -0
- package/src/ast/usegroup.js +30 -0
- package/src/ast/useitem.js +45 -0
- package/src/ast/variable.js +36 -0
- package/src/ast/variadic.js +25 -0
- package/src/ast/variadicplaceholder.js +24 -0
- package/src/ast/while.js +28 -0
- package/src/ast/yield.js +27 -0
- package/src/ast/yieldfrom.js +25 -0
- package/src/ast.js +593 -0
- package/src/index.js +239 -0
- package/src/lexer/attribute.js +85 -0
- package/src/lexer/comments.js +63 -0
- package/src/lexer/initial.js +64 -0
- package/src/lexer/numbers.js +171 -0
- package/src/lexer/property.js +96 -0
- package/src/lexer/scripting.js +114 -0
- package/src/lexer/strings.js +524 -0
- package/src/lexer/tokens.js +356 -0
- package/src/lexer/utils.js +112 -0
- package/src/lexer.js +561 -0
- package/src/parser/array.js +113 -0
- package/src/parser/class.js +718 -0
- package/src/parser/comment.js +52 -0
- package/src/parser/enum.js +56 -0
- package/src/parser/expr.js +848 -0
- package/src/parser/function.js +507 -0
- package/src/parser/if.js +94 -0
- package/src/parser/loops.js +168 -0
- package/src/parser/main.js +21 -0
- package/src/parser/namespace.js +231 -0
- package/src/parser/scalar.js +492 -0
- package/src/parser/statement.js +444 -0
- package/src/parser/switch.js +99 -0
- package/src/parser/try.js +43 -0
- package/src/parser/utils.js +203 -0
- package/src/parser/variable.js +363 -0
- package/src/parser.js +748 -0
- package/src/tokens.js +177 -0
- package/types.d.ts +1457 -0
|
@@ -0,0 +1,848 @@
|
|
|
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
|
+
read_expr(expr) {
|
|
10
|
+
const result = this.node();
|
|
11
|
+
if (this.token === "@") {
|
|
12
|
+
if (!expr) {
|
|
13
|
+
expr = this.next().read_expr();
|
|
14
|
+
}
|
|
15
|
+
return result("silent", expr);
|
|
16
|
+
}
|
|
17
|
+
if (!expr) {
|
|
18
|
+
expr = this.read_expr_item();
|
|
19
|
+
}
|
|
20
|
+
// binary operations
|
|
21
|
+
if (this.token === "|") {
|
|
22
|
+
return result("bin", "|", expr, this.next().read_expr());
|
|
23
|
+
}
|
|
24
|
+
if (this.token === "&") {
|
|
25
|
+
return result("bin", "&", expr, this.next().read_expr());
|
|
26
|
+
}
|
|
27
|
+
if (this.token === "^") {
|
|
28
|
+
return result("bin", "^", expr, this.next().read_expr());
|
|
29
|
+
}
|
|
30
|
+
if (this.token === ".") {
|
|
31
|
+
return result("bin", ".", expr, this.next().read_expr());
|
|
32
|
+
}
|
|
33
|
+
if (this.token === "+") {
|
|
34
|
+
return result("bin", "+", expr, this.next().read_expr());
|
|
35
|
+
}
|
|
36
|
+
if (this.token === "-") {
|
|
37
|
+
return result("bin", "-", expr, this.next().read_expr());
|
|
38
|
+
}
|
|
39
|
+
if (this.token === "*") {
|
|
40
|
+
return result("bin", "*", expr, this.next().read_expr());
|
|
41
|
+
}
|
|
42
|
+
if (this.token === "/") {
|
|
43
|
+
return result("bin", "/", expr, this.next().read_expr());
|
|
44
|
+
}
|
|
45
|
+
if (this.token === "%") {
|
|
46
|
+
return result("bin", "%", expr, this.next().read_expr());
|
|
47
|
+
}
|
|
48
|
+
if (this.token === this.tok.T_POW) {
|
|
49
|
+
return result("bin", "**", expr, this.next().read_expr());
|
|
50
|
+
}
|
|
51
|
+
if (this.token === this.tok.T_SL) {
|
|
52
|
+
return result("bin", "<<", expr, this.next().read_expr());
|
|
53
|
+
}
|
|
54
|
+
if (this.token === this.tok.T_SR) {
|
|
55
|
+
return result("bin", ">>", expr, this.next().read_expr());
|
|
56
|
+
}
|
|
57
|
+
// more binary operations (formerly bool)
|
|
58
|
+
if (this.token === this.tok.T_BOOLEAN_OR) {
|
|
59
|
+
return result("bin", "||", expr, this.next().read_expr());
|
|
60
|
+
}
|
|
61
|
+
if (this.token === this.tok.T_LOGICAL_OR) {
|
|
62
|
+
return result("bin", "or", expr, this.next().read_expr());
|
|
63
|
+
}
|
|
64
|
+
if (this.token === this.tok.T_BOOLEAN_AND) {
|
|
65
|
+
return result("bin", "&&", expr, this.next().read_expr());
|
|
66
|
+
}
|
|
67
|
+
if (this.token === this.tok.T_LOGICAL_AND) {
|
|
68
|
+
return result("bin", "and", expr, this.next().read_expr());
|
|
69
|
+
}
|
|
70
|
+
if (this.token === this.tok.T_LOGICAL_XOR) {
|
|
71
|
+
return result("bin", "xor", expr, this.next().read_expr());
|
|
72
|
+
}
|
|
73
|
+
if (this.token === this.tok.T_IS_IDENTICAL) {
|
|
74
|
+
return result("bin", "===", expr, this.next().read_expr());
|
|
75
|
+
}
|
|
76
|
+
if (this.token === this.tok.T_IS_NOT_IDENTICAL) {
|
|
77
|
+
return result("bin", "!==", expr, this.next().read_expr());
|
|
78
|
+
}
|
|
79
|
+
if (this.token === this.tok.T_IS_EQUAL) {
|
|
80
|
+
return result("bin", "==", expr, this.next().read_expr());
|
|
81
|
+
}
|
|
82
|
+
if (this.token === this.tok.T_IS_NOT_EQUAL) {
|
|
83
|
+
return result("bin", "!=", expr, this.next().read_expr());
|
|
84
|
+
}
|
|
85
|
+
if (this.token === "<") {
|
|
86
|
+
return result("bin", "<", expr, this.next().read_expr());
|
|
87
|
+
}
|
|
88
|
+
if (this.token === ">") {
|
|
89
|
+
return result("bin", ">", expr, this.next().read_expr());
|
|
90
|
+
}
|
|
91
|
+
if (this.token === this.tok.T_IS_SMALLER_OR_EQUAL) {
|
|
92
|
+
return result("bin", "<=", expr, this.next().read_expr());
|
|
93
|
+
}
|
|
94
|
+
if (this.token === this.tok.T_IS_GREATER_OR_EQUAL) {
|
|
95
|
+
return result("bin", ">=", expr, this.next().read_expr());
|
|
96
|
+
}
|
|
97
|
+
if (this.token === this.tok.T_SPACESHIP) {
|
|
98
|
+
return result("bin", "<=>", expr, this.next().read_expr());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (this.token === this.tok.T_INSTANCEOF) {
|
|
102
|
+
expr = result(
|
|
103
|
+
"bin",
|
|
104
|
+
"instanceof",
|
|
105
|
+
expr,
|
|
106
|
+
this.next().read_class_name_reference(),
|
|
107
|
+
);
|
|
108
|
+
if (
|
|
109
|
+
this.token !== ";" &&
|
|
110
|
+
this.token !== this.tok.T_INLINE_HTML &&
|
|
111
|
+
this.token !== this.EOF
|
|
112
|
+
) {
|
|
113
|
+
expr = this.read_expr(expr);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// extra operations :
|
|
118
|
+
// $username = $_GET['user'] ?? 'nobody';
|
|
119
|
+
if (this.token === this.tok.T_COALESCE) {
|
|
120
|
+
return result("bin", "??", expr, this.next().read_expr());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// extra operations :
|
|
124
|
+
// $username = $_GET['user'] ? true : false;
|
|
125
|
+
if (this.token === "?") {
|
|
126
|
+
let trueArg = null;
|
|
127
|
+
if (this.next().token !== ":") {
|
|
128
|
+
trueArg = this.read_expr();
|
|
129
|
+
}
|
|
130
|
+
this.expect(":") && this.next();
|
|
131
|
+
return result("retif", expr, trueArg, this.read_expr());
|
|
132
|
+
} else {
|
|
133
|
+
// see #193
|
|
134
|
+
result.destroy(expr);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return expr;
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* Reads a cast expression
|
|
142
|
+
*/
|
|
143
|
+
read_expr_cast(type) {
|
|
144
|
+
return this.node("cast")(type, this.text(), this.next().read_expr());
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/*
|
|
148
|
+
* Read a isset variable
|
|
149
|
+
*/
|
|
150
|
+
read_isset_variable() {
|
|
151
|
+
return this.read_expr();
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* Reads isset variables
|
|
156
|
+
*/
|
|
157
|
+
read_isset_variables() {
|
|
158
|
+
return this.read_function_list(this.read_isset_variable, ",");
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
* Reads internal PHP functions
|
|
163
|
+
*/
|
|
164
|
+
read_internal_functions_in_yacc() {
|
|
165
|
+
let result = null;
|
|
166
|
+
switch (this.token) {
|
|
167
|
+
case this.tok.T_ISSET:
|
|
168
|
+
{
|
|
169
|
+
result = this.node("isset");
|
|
170
|
+
if (this.next().expect("(")) {
|
|
171
|
+
this.next();
|
|
172
|
+
}
|
|
173
|
+
const variables = this.read_isset_variables();
|
|
174
|
+
if (this.expect(")")) {
|
|
175
|
+
this.next();
|
|
176
|
+
}
|
|
177
|
+
result = result(variables);
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
case this.tok.T_EMPTY:
|
|
181
|
+
{
|
|
182
|
+
result = this.node("empty");
|
|
183
|
+
if (this.next().expect("(")) {
|
|
184
|
+
this.next();
|
|
185
|
+
}
|
|
186
|
+
const expression = this.read_expr();
|
|
187
|
+
if (this.expect(")")) {
|
|
188
|
+
this.next();
|
|
189
|
+
}
|
|
190
|
+
result = result(expression);
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case this.tok.T_INCLUDE:
|
|
194
|
+
result = this.node("include")(false, false, this.next().read_expr());
|
|
195
|
+
break;
|
|
196
|
+
case this.tok.T_INCLUDE_ONCE:
|
|
197
|
+
result = this.node("include")(true, false, this.next().read_expr());
|
|
198
|
+
break;
|
|
199
|
+
case this.tok.T_EVAL:
|
|
200
|
+
{
|
|
201
|
+
result = this.node("eval");
|
|
202
|
+
if (this.next().expect("(")) {
|
|
203
|
+
this.next();
|
|
204
|
+
}
|
|
205
|
+
const expr = this.read_expr();
|
|
206
|
+
if (this.expect(")")) {
|
|
207
|
+
this.next();
|
|
208
|
+
}
|
|
209
|
+
result = result(expr);
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case this.tok.T_REQUIRE:
|
|
213
|
+
result = this.node("include")(false, true, this.next().read_expr());
|
|
214
|
+
break;
|
|
215
|
+
case this.tok.T_REQUIRE_ONCE:
|
|
216
|
+
result = this.node("include")(true, true, this.next().read_expr());
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/*
|
|
224
|
+
* Reads optional expression
|
|
225
|
+
*/
|
|
226
|
+
read_optional_expr(stopToken) {
|
|
227
|
+
if (this.token !== stopToken) {
|
|
228
|
+
return this.read_expr();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return null;
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* Reads exit expression
|
|
236
|
+
*/
|
|
237
|
+
read_exit_expr() {
|
|
238
|
+
let expression = null;
|
|
239
|
+
|
|
240
|
+
if (this.token === "(") {
|
|
241
|
+
this.next();
|
|
242
|
+
expression = this.read_optional_expr(")");
|
|
243
|
+
this.expect(")") && this.next();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return expression;
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* ```ebnf
|
|
251
|
+
* Reads an expression
|
|
252
|
+
* expr ::= @todo
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
read_expr_item() {
|
|
256
|
+
let result,
|
|
257
|
+
expr,
|
|
258
|
+
attrs = [];
|
|
259
|
+
if (this.token === "+") {
|
|
260
|
+
return this.node("unary")("+", this.next().read_expr());
|
|
261
|
+
}
|
|
262
|
+
if (this.token === "-") {
|
|
263
|
+
return this.node("unary")("-", this.next().read_expr());
|
|
264
|
+
}
|
|
265
|
+
if (this.token === "!") {
|
|
266
|
+
return this.node("unary")("!", this.next().read_expr());
|
|
267
|
+
}
|
|
268
|
+
if (this.token === "~") {
|
|
269
|
+
return this.node("unary")("~", this.next().read_expr());
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (this.token === "(") {
|
|
273
|
+
expr = this.next().read_expr();
|
|
274
|
+
expr.parenthesizedExpression = true;
|
|
275
|
+
this.expect(")") && this.next();
|
|
276
|
+
return this.handleDereferencable(expr);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (this.token === "`") {
|
|
280
|
+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1048
|
|
281
|
+
return this.read_encapsed_string("`");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (this.token === this.tok.T_LIST) {
|
|
285
|
+
let assign = null;
|
|
286
|
+
const isInner = this.innerList;
|
|
287
|
+
result = this.node("list");
|
|
288
|
+
if (!isInner) {
|
|
289
|
+
assign = this.node("assign");
|
|
290
|
+
}
|
|
291
|
+
if (this.next().expect("(")) {
|
|
292
|
+
this.next();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!this.innerList) this.innerList = true;
|
|
296
|
+
|
|
297
|
+
// reads inner items
|
|
298
|
+
const assignList = this.read_array_pair_list(false);
|
|
299
|
+
if (this.expect(")")) {
|
|
300
|
+
this.next();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// check if contains at least one assignment statement
|
|
304
|
+
let hasItem = false;
|
|
305
|
+
for (let i = 0; i < assignList.length; i++) {
|
|
306
|
+
if (assignList[i] !== null && assignList[i].kind !== "noop") {
|
|
307
|
+
hasItem = true;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!hasItem) {
|
|
312
|
+
/* istanbul ignore next */
|
|
313
|
+
this.raiseError(
|
|
314
|
+
"Fatal Error : Cannot use empty list on line " +
|
|
315
|
+
this.lexer.yylloc.first_line,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// handles the node resolution
|
|
320
|
+
if (!isInner) {
|
|
321
|
+
this.innerList = false;
|
|
322
|
+
if (this.expect("=")) {
|
|
323
|
+
return assign(
|
|
324
|
+
result(assignList, false),
|
|
325
|
+
this.next().read_expr(),
|
|
326
|
+
"=",
|
|
327
|
+
);
|
|
328
|
+
} else {
|
|
329
|
+
// error fallback : list($a, $b);
|
|
330
|
+
/* istanbul ignore next */
|
|
331
|
+
return result(assignList, false);
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
return result(assignList, false);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (this.token === this.tok.T_ATTRIBUTE) {
|
|
339
|
+
attrs = this.read_attr_list();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (this.token === this.tok.T_CLONE) {
|
|
343
|
+
return this.node("clone")(this.next().read_expr());
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
switch (this.token) {
|
|
347
|
+
case this.tok.T_INC:
|
|
348
|
+
return this.node("pre")("+", this.next().read_variable(false, false));
|
|
349
|
+
|
|
350
|
+
case this.tok.T_DEC:
|
|
351
|
+
return this.node("pre")("-", this.next().read_variable(false, false));
|
|
352
|
+
|
|
353
|
+
case this.tok.T_NEW:
|
|
354
|
+
expr = this.read_new_expr();
|
|
355
|
+
if (this.token === this.tok.T_OBJECT_OPERATOR && this.version < 804) {
|
|
356
|
+
this.raiseError(
|
|
357
|
+
"New without parenthesis is not allowed before PHP 8.4",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return this.handleDereferencable(expr);
|
|
361
|
+
|
|
362
|
+
case this.tok.T_ISSET:
|
|
363
|
+
case this.tok.T_EMPTY:
|
|
364
|
+
case this.tok.T_INCLUDE:
|
|
365
|
+
case this.tok.T_INCLUDE_ONCE:
|
|
366
|
+
case this.tok.T_EVAL:
|
|
367
|
+
case this.tok.T_REQUIRE:
|
|
368
|
+
case this.tok.T_REQUIRE_ONCE:
|
|
369
|
+
return this.read_internal_functions_in_yacc();
|
|
370
|
+
|
|
371
|
+
case this.tok.T_MATCH:
|
|
372
|
+
return this.read_match_expression();
|
|
373
|
+
case this.tok.T_INT_CAST:
|
|
374
|
+
return this.read_expr_cast("int");
|
|
375
|
+
|
|
376
|
+
case this.tok.T_DOUBLE_CAST:
|
|
377
|
+
return this.read_expr_cast("float");
|
|
378
|
+
|
|
379
|
+
case this.tok.T_STRING_CAST:
|
|
380
|
+
return this.read_expr_cast(
|
|
381
|
+
this.text().indexOf("binary") !== -1 ? "binary" : "string",
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
case this.tok.T_ARRAY_CAST:
|
|
385
|
+
return this.read_expr_cast("array");
|
|
386
|
+
|
|
387
|
+
case this.tok.T_OBJECT_CAST:
|
|
388
|
+
return this.read_expr_cast("object");
|
|
389
|
+
|
|
390
|
+
case this.tok.T_BOOL_CAST:
|
|
391
|
+
return this.read_expr_cast("bool");
|
|
392
|
+
|
|
393
|
+
case this.tok.T_UNSET_CAST:
|
|
394
|
+
return this.read_expr_cast("unset");
|
|
395
|
+
|
|
396
|
+
case this.tok.T_THROW: {
|
|
397
|
+
if (this.version < 800) {
|
|
398
|
+
this.raiseError("PHP 8+ is required to use throw as an expression");
|
|
399
|
+
}
|
|
400
|
+
const result = this.node("throw");
|
|
401
|
+
const expr = this.next().read_expr();
|
|
402
|
+
return result(expr);
|
|
403
|
+
}
|
|
404
|
+
case this.tok.T_EXIT: {
|
|
405
|
+
const useDie = this.lexer.yytext.toLowerCase() === "die";
|
|
406
|
+
result = this.node("exit");
|
|
407
|
+
this.next();
|
|
408
|
+
const expression = this.read_exit_expr();
|
|
409
|
+
return result(expression, useDie);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
case this.tok.T_PRINT:
|
|
413
|
+
return this.node("print")(this.next().read_expr());
|
|
414
|
+
|
|
415
|
+
// T_YIELD (expr (T_DOUBLE_ARROW expr)?)?
|
|
416
|
+
case this.tok.T_YIELD: {
|
|
417
|
+
let value = null;
|
|
418
|
+
let key = null;
|
|
419
|
+
result = this.node("yield");
|
|
420
|
+
if (this.next().is("EXPR")) {
|
|
421
|
+
// reads the yield return value
|
|
422
|
+
value = this.read_expr();
|
|
423
|
+
if (this.token === this.tok.T_DOUBLE_ARROW) {
|
|
424
|
+
// reads the yield returned key
|
|
425
|
+
key = value;
|
|
426
|
+
value = this.next().read_expr();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return result(value, key);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// T_YIELD_FROM expr
|
|
433
|
+
case this.tok.T_YIELD_FROM:
|
|
434
|
+
result = this.node("yieldfrom");
|
|
435
|
+
expr = this.next().read_expr();
|
|
436
|
+
return result(expr);
|
|
437
|
+
|
|
438
|
+
case this.tok.T_FN:
|
|
439
|
+
case this.tok.T_FUNCTION:
|
|
440
|
+
return this.read_inline_function(undefined, attrs);
|
|
441
|
+
|
|
442
|
+
case this.tok.T_STATIC: {
|
|
443
|
+
const backup = [this.token, this.lexer.getState()];
|
|
444
|
+
this.next();
|
|
445
|
+
if (
|
|
446
|
+
this.token === this.tok.T_FUNCTION ||
|
|
447
|
+
(this.version >= 704 && this.token === this.tok.T_FN)
|
|
448
|
+
) {
|
|
449
|
+
// handles static function
|
|
450
|
+
return this.read_inline_function([0, 1, 0], attrs);
|
|
451
|
+
} else {
|
|
452
|
+
// rollback
|
|
453
|
+
this.lexer.tokens.push(backup);
|
|
454
|
+
this.next();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// SCALAR | VARIABLE
|
|
460
|
+
if (this.is("VARIABLE")) {
|
|
461
|
+
result = this.node();
|
|
462
|
+
expr = this.read_variable(false, false);
|
|
463
|
+
|
|
464
|
+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L877
|
|
465
|
+
// should accept only a variable
|
|
466
|
+
const isConst =
|
|
467
|
+
expr.kind === "identifier" ||
|
|
468
|
+
(expr.kind === "staticlookup" && expr.offset.kind === "identifier");
|
|
469
|
+
|
|
470
|
+
// VARIABLES SPECIFIC OPERATIONS
|
|
471
|
+
switch (this.token) {
|
|
472
|
+
case "=": {
|
|
473
|
+
if (isConst) this.error("VARIABLE");
|
|
474
|
+
if (this.next().token == "&") {
|
|
475
|
+
return this.read_assignref(result, expr);
|
|
476
|
+
}
|
|
477
|
+
return result("assign", expr, this.read_expr(), "=");
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// operations :
|
|
481
|
+
case this.tok.T_PLUS_EQUAL:
|
|
482
|
+
if (isConst) this.error("VARIABLE");
|
|
483
|
+
return result("assign", expr, this.next().read_expr(), "+=");
|
|
484
|
+
|
|
485
|
+
case this.tok.T_MINUS_EQUAL:
|
|
486
|
+
if (isConst) this.error("VARIABLE");
|
|
487
|
+
return result("assign", expr, this.next().read_expr(), "-=");
|
|
488
|
+
|
|
489
|
+
case this.tok.T_MUL_EQUAL:
|
|
490
|
+
if (isConst) this.error("VARIABLE");
|
|
491
|
+
return result("assign", expr, this.next().read_expr(), "*=");
|
|
492
|
+
|
|
493
|
+
case this.tok.T_POW_EQUAL:
|
|
494
|
+
if (isConst) this.error("VARIABLE");
|
|
495
|
+
return result("assign", expr, this.next().read_expr(), "**=");
|
|
496
|
+
|
|
497
|
+
case this.tok.T_DIV_EQUAL:
|
|
498
|
+
if (isConst) this.error("VARIABLE");
|
|
499
|
+
return result("assign", expr, this.next().read_expr(), "/=");
|
|
500
|
+
|
|
501
|
+
case this.tok.T_CONCAT_EQUAL:
|
|
502
|
+
if (isConst) this.error("VARIABLE");
|
|
503
|
+
return result("assign", expr, this.next().read_expr(), ".=");
|
|
504
|
+
|
|
505
|
+
case this.tok.T_MOD_EQUAL:
|
|
506
|
+
if (isConst) this.error("VARIABLE");
|
|
507
|
+
return result("assign", expr, this.next().read_expr(), "%=");
|
|
508
|
+
|
|
509
|
+
case this.tok.T_AND_EQUAL:
|
|
510
|
+
if (isConst) this.error("VARIABLE");
|
|
511
|
+
return result("assign", expr, this.next().read_expr(), "&=");
|
|
512
|
+
|
|
513
|
+
case this.tok.T_OR_EQUAL:
|
|
514
|
+
if (isConst) this.error("VARIABLE");
|
|
515
|
+
return result("assign", expr, this.next().read_expr(), "|=");
|
|
516
|
+
|
|
517
|
+
case this.tok.T_XOR_EQUAL:
|
|
518
|
+
if (isConst) this.error("VARIABLE");
|
|
519
|
+
return result("assign", expr, this.next().read_expr(), "^=");
|
|
520
|
+
|
|
521
|
+
case this.tok.T_SL_EQUAL:
|
|
522
|
+
if (isConst) this.error("VARIABLE");
|
|
523
|
+
return result("assign", expr, this.next().read_expr(), "<<=");
|
|
524
|
+
|
|
525
|
+
case this.tok.T_SR_EQUAL:
|
|
526
|
+
if (isConst) this.error("VARIABLE");
|
|
527
|
+
return result("assign", expr, this.next().read_expr(), ">>=");
|
|
528
|
+
|
|
529
|
+
case this.tok.T_COALESCE_EQUAL:
|
|
530
|
+
if (isConst) this.error("VARIABLE");
|
|
531
|
+
return result("assign", expr, this.next().read_expr(), "??=");
|
|
532
|
+
|
|
533
|
+
case this.tok.T_INC:
|
|
534
|
+
if (isConst) this.error("VARIABLE");
|
|
535
|
+
this.next();
|
|
536
|
+
return result("post", "+", expr);
|
|
537
|
+
case this.tok.T_DEC:
|
|
538
|
+
if (isConst) this.error("VARIABLE");
|
|
539
|
+
this.next();
|
|
540
|
+
return result("post", "-", expr);
|
|
541
|
+
default:
|
|
542
|
+
// see #193
|
|
543
|
+
result.destroy(expr);
|
|
544
|
+
}
|
|
545
|
+
} else if (this.is("SCALAR")) {
|
|
546
|
+
result = this.node();
|
|
547
|
+
expr = this.read_scalar();
|
|
548
|
+
if (expr.kind === "array" && expr.shortForm && this.token === "=") {
|
|
549
|
+
// list assign
|
|
550
|
+
const list = this.convertToList(expr);
|
|
551
|
+
if (expr.loc) list.loc = expr.loc;
|
|
552
|
+
const right = this.next().read_expr();
|
|
553
|
+
return result("assign", list, right, "=");
|
|
554
|
+
} else {
|
|
555
|
+
// see #189 - swap docs on nodes
|
|
556
|
+
result.destroy(expr);
|
|
557
|
+
}
|
|
558
|
+
// classic array
|
|
559
|
+
return this.handleDereferencable(expr);
|
|
560
|
+
} else {
|
|
561
|
+
this.error("EXPR");
|
|
562
|
+
this.next();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// returns variable | scalar
|
|
566
|
+
return expr;
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
/*
|
|
570
|
+
* Recursively convert nested array to nested list.
|
|
571
|
+
*/
|
|
572
|
+
convertToList(array) {
|
|
573
|
+
const convertedItems = array.items.map((entry) => {
|
|
574
|
+
if (
|
|
575
|
+
entry.value &&
|
|
576
|
+
entry.value.kind === "array" &&
|
|
577
|
+
entry.value.shortForm
|
|
578
|
+
) {
|
|
579
|
+
entry.value = this.convertToList(entry.value);
|
|
580
|
+
}
|
|
581
|
+
return entry;
|
|
582
|
+
});
|
|
583
|
+
const node = this.node("list")(convertedItems, true);
|
|
584
|
+
if (array.loc) node.loc = array.loc;
|
|
585
|
+
if (array.leadingComments) node.leadingComments = array.leadingComments;
|
|
586
|
+
if (array.trailingComments) node.trailingComments = array.trailingComments;
|
|
587
|
+
return node;
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
/*
|
|
591
|
+
* Reads assignment
|
|
592
|
+
* @param {*} left
|
|
593
|
+
*/
|
|
594
|
+
read_assignref(result, left) {
|
|
595
|
+
this.next();
|
|
596
|
+
let right;
|
|
597
|
+
if (this.token === this.tok.T_NEW) {
|
|
598
|
+
if (this.version >= 700) {
|
|
599
|
+
this.error();
|
|
600
|
+
}
|
|
601
|
+
right = this.read_new_expr();
|
|
602
|
+
} else {
|
|
603
|
+
right = this.read_variable(false, false);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return result("assignref", left, right);
|
|
607
|
+
},
|
|
608
|
+
|
|
609
|
+
/*
|
|
610
|
+
*
|
|
611
|
+
* inline_function:
|
|
612
|
+
* function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type
|
|
613
|
+
* backup_fn_flags '{' inner_statement_list '}' backup_fn_flags
|
|
614
|
+
* { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2 | $13, $1, $3,
|
|
615
|
+
* zend_string_init("{closure}", sizeof("{closure}") - 1, 0),
|
|
616
|
+
* $5, $7, $11, $8); CG(extra_fn_flags) = $9; }
|
|
617
|
+
* | fn returns_ref '(' parameter_list ')' return_type backup_doc_comment T_DOUBLE_ARROW backup_fn_flags backup_lex_pos expr backup_fn_flags
|
|
618
|
+
* { $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12, $1, $7,
|
|
619
|
+
* zend_string_init("{closure}", sizeof("{closure}") - 1, 0), $4, NULL,
|
|
620
|
+
* zend_ast_create(ZEND_AST_RETURN, $11), $6);
|
|
621
|
+
* ((zend_ast_decl *) $$)->lex_pos = $10;
|
|
622
|
+
* CG(extra_fn_flags) = $9; } *
|
|
623
|
+
*/
|
|
624
|
+
read_inline_function(flags, attrs) {
|
|
625
|
+
if (this.token === this.tok.T_FUNCTION) {
|
|
626
|
+
const result = this.read_function(true, flags, attrs);
|
|
627
|
+
result.attrGroups = attrs;
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
630
|
+
// introduced in PHP 7.4
|
|
631
|
+
if (!this.version >= 704) {
|
|
632
|
+
this.raiseError("Arrow Functions are not allowed");
|
|
633
|
+
}
|
|
634
|
+
// as an arrowfunc
|
|
635
|
+
const node = this.node("arrowfunc");
|
|
636
|
+
// eat T_FN
|
|
637
|
+
if (this.expect(this.tok.T_FN)) this.next();
|
|
638
|
+
// check the &
|
|
639
|
+
const isRef = this.is_reference();
|
|
640
|
+
// ...
|
|
641
|
+
if (this.expect("(")) this.next();
|
|
642
|
+
const params = this.read_parameter_list();
|
|
643
|
+
if (this.expect(")")) this.next();
|
|
644
|
+
let nullable = false;
|
|
645
|
+
let returnType = null;
|
|
646
|
+
if (this.token === ":") {
|
|
647
|
+
if (this.next().token === "?") {
|
|
648
|
+
nullable = true;
|
|
649
|
+
this.next();
|
|
650
|
+
}
|
|
651
|
+
returnType = this.read_types();
|
|
652
|
+
}
|
|
653
|
+
if (this.expect(this.tok.T_DOUBLE_ARROW)) this.next();
|
|
654
|
+
const body = this.read_expr();
|
|
655
|
+
const result = node(
|
|
656
|
+
params,
|
|
657
|
+
isRef,
|
|
658
|
+
body,
|
|
659
|
+
returnType,
|
|
660
|
+
nullable,
|
|
661
|
+
flags ? true : false,
|
|
662
|
+
);
|
|
663
|
+
result.attrGroups = attrs;
|
|
664
|
+
return result;
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
read_match_expression() {
|
|
668
|
+
const node = this.node("match");
|
|
669
|
+
this.expect(this.tok.T_MATCH) && this.next();
|
|
670
|
+
if (this.version < 800) {
|
|
671
|
+
this.raiseError("Match statements are not allowed before PHP 8");
|
|
672
|
+
}
|
|
673
|
+
let cond = null;
|
|
674
|
+
let arms = [];
|
|
675
|
+
if (this.expect("(")) this.next();
|
|
676
|
+
cond = this.read_expr();
|
|
677
|
+
if (this.expect(")")) this.next();
|
|
678
|
+
if (this.expect("{")) this.next();
|
|
679
|
+
arms = this.read_match_arms();
|
|
680
|
+
if (this.expect("}")) this.next();
|
|
681
|
+
return node(cond, arms);
|
|
682
|
+
},
|
|
683
|
+
|
|
684
|
+
read_match_arms() {
|
|
685
|
+
return this.read_list(() => this.read_match_arm(), ",", true);
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
read_match_arm() {
|
|
689
|
+
if (this.token === "}") {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
return this.node("matcharm")(this.read_match_arm_conds(), this.read_expr());
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
read_match_arm_conds() {
|
|
696
|
+
let conds = [];
|
|
697
|
+
if (this.token === this.tok.T_DEFAULT) {
|
|
698
|
+
conds = null;
|
|
699
|
+
this.next();
|
|
700
|
+
} else {
|
|
701
|
+
conds.push(this.read_expr());
|
|
702
|
+
while (this.token === ",") {
|
|
703
|
+
this.next();
|
|
704
|
+
if (this.token === this.tok.T_DOUBLE_ARROW) {
|
|
705
|
+
this.next();
|
|
706
|
+
return conds;
|
|
707
|
+
}
|
|
708
|
+
conds.push(this.read_expr());
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (this.expect(this.tok.T_DOUBLE_ARROW)) {
|
|
712
|
+
this.next();
|
|
713
|
+
}
|
|
714
|
+
return conds;
|
|
715
|
+
},
|
|
716
|
+
|
|
717
|
+
read_attribute() {
|
|
718
|
+
const name = this.text();
|
|
719
|
+
let args = [];
|
|
720
|
+
this.next();
|
|
721
|
+
if (this.token === "(") {
|
|
722
|
+
args = this.read_argument_list();
|
|
723
|
+
}
|
|
724
|
+
return this.node("attribute")(name, args);
|
|
725
|
+
},
|
|
726
|
+
read_attr_list() {
|
|
727
|
+
const list = [];
|
|
728
|
+
if (this.token === this.tok.T_ATTRIBUTE) {
|
|
729
|
+
do {
|
|
730
|
+
const attrGr = this.node("attrgroup")([]);
|
|
731
|
+
this.next();
|
|
732
|
+
attrGr.attrs.push(this.read_attribute());
|
|
733
|
+
while (this.token === ",") {
|
|
734
|
+
this.next();
|
|
735
|
+
if (this.token !== "]") attrGr.attrs.push(this.read_attribute());
|
|
736
|
+
}
|
|
737
|
+
list.push(attrGr);
|
|
738
|
+
this.expect("]");
|
|
739
|
+
this.next();
|
|
740
|
+
} while (this.token === this.tok.T_ATTRIBUTE);
|
|
741
|
+
}
|
|
742
|
+
return list;
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
/*
|
|
746
|
+
* ```ebnf
|
|
747
|
+
* new_expr ::= T_NEW (namespace_name function_argument_list) | (T_CLASS ... class declaration)
|
|
748
|
+
* ```
|
|
749
|
+
* https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L850
|
|
750
|
+
*/
|
|
751
|
+
read_new_expr() {
|
|
752
|
+
const result = this.node("new");
|
|
753
|
+
this.expect(this.tok.T_NEW) && this.next();
|
|
754
|
+
let args = [];
|
|
755
|
+
if (this.token === "(") {
|
|
756
|
+
this.next();
|
|
757
|
+
const newExp = this.read_expr();
|
|
758
|
+
this.expect(")");
|
|
759
|
+
this.next();
|
|
760
|
+
if (this.token === "(") {
|
|
761
|
+
args = this.read_argument_list();
|
|
762
|
+
}
|
|
763
|
+
return result(newExp, args);
|
|
764
|
+
}
|
|
765
|
+
const attrs = this.read_attr_list();
|
|
766
|
+
const isReadonly = this.token === this.tok.T_READ_ONLY;
|
|
767
|
+
if (
|
|
768
|
+
this.token === this.tok.T_CLASS ||
|
|
769
|
+
(isReadonly && this.next().token === this.tok.T_CLASS)
|
|
770
|
+
) {
|
|
771
|
+
const what = this.node("class");
|
|
772
|
+
// Annonymous class declaration
|
|
773
|
+
if (this.next().token === "(") {
|
|
774
|
+
args = this.read_argument_list();
|
|
775
|
+
}
|
|
776
|
+
const propExtends = this.read_extends_from();
|
|
777
|
+
const propImplements = this.read_implements_list();
|
|
778
|
+
let body = null;
|
|
779
|
+
if (this.expect("{")) {
|
|
780
|
+
body = this.next().read_class_body(true, false);
|
|
781
|
+
}
|
|
782
|
+
const whatNode = what(null, propExtends, propImplements, body, [
|
|
783
|
+
0,
|
|
784
|
+
0,
|
|
785
|
+
0,
|
|
786
|
+
isReadonly ? 1 : 0,
|
|
787
|
+
]);
|
|
788
|
+
whatNode.attrGroups = attrs;
|
|
789
|
+
return result(whatNode, args);
|
|
790
|
+
}
|
|
791
|
+
// Already existing class
|
|
792
|
+
let name = this.read_new_class_name();
|
|
793
|
+
while (this.token === "[") {
|
|
794
|
+
const offsetNode = this.node("offsetlookup");
|
|
795
|
+
const offset = this.next().read_encaps_var_offset();
|
|
796
|
+
this.expect("]") && this.next();
|
|
797
|
+
name = offsetNode(name, offset);
|
|
798
|
+
}
|
|
799
|
+
if (this.token === "(") {
|
|
800
|
+
args = this.read_argument_list();
|
|
801
|
+
}
|
|
802
|
+
return result(name, args);
|
|
803
|
+
},
|
|
804
|
+
/*
|
|
805
|
+
* Reads a class name
|
|
806
|
+
* ```ebnf
|
|
807
|
+
* read_new_class_name ::= namespace_name | variable
|
|
808
|
+
* ```
|
|
809
|
+
*/
|
|
810
|
+
read_new_class_name() {
|
|
811
|
+
if (
|
|
812
|
+
this.token === this.tok.T_NS_SEPARATOR ||
|
|
813
|
+
this.token === this.tok.T_NAME_RELATIVE ||
|
|
814
|
+
this.token === this.tok.T_NAME_QUALIFIED ||
|
|
815
|
+
this.token === this.tok.T_NAME_FULLY_QUALIFIED ||
|
|
816
|
+
this.token === this.tok.T_STRING ||
|
|
817
|
+
this.token === this.tok.T_NAMESPACE
|
|
818
|
+
) {
|
|
819
|
+
let result = this.read_namespace_name(true);
|
|
820
|
+
if (this.token === this.tok.T_DOUBLE_COLON) {
|
|
821
|
+
result = this.read_static_getter(result);
|
|
822
|
+
}
|
|
823
|
+
return result;
|
|
824
|
+
} else if (this.is("VARIABLE")) {
|
|
825
|
+
return this.read_variable(true, false);
|
|
826
|
+
} else {
|
|
827
|
+
this.expect([this.tok.T_STRING, "VARIABLE"]);
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
handleDereferencable(expr) {
|
|
831
|
+
while (this.token !== this.EOF) {
|
|
832
|
+
if (
|
|
833
|
+
this.token === this.tok.T_OBJECT_OPERATOR ||
|
|
834
|
+
this.token === this.tok.T_DOUBLE_COLON
|
|
835
|
+
) {
|
|
836
|
+
expr = this.recursive_variable_chain_scan(expr, false, false, true);
|
|
837
|
+
} else if (this.token === this.tok.T_CURLY_OPEN || this.token === "[") {
|
|
838
|
+
expr = this.read_dereferencable(expr);
|
|
839
|
+
} else if (this.token === "(") {
|
|
840
|
+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L1118
|
|
841
|
+
expr = this.node("call")(expr, this.read_argument_list());
|
|
842
|
+
} else {
|
|
843
|
+
return expr;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return expr;
|
|
847
|
+
},
|
|
848
|
+
};
|