@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,718 @@
|
|
|
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
|
+
* reading a class
|
|
11
|
+
* ```ebnf
|
|
12
|
+
* class ::= class_scope? T_CLASS T_STRING (T_EXTENDS NAMESPACE_NAME)? (T_IMPLEMENTS (NAMESPACE_NAME ',')* NAMESPACE_NAME)? '{' CLASS_BODY '}'
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
read_class_declaration_statement(attrs) {
|
|
16
|
+
const result = this.node("class");
|
|
17
|
+
const flag = this.read_class_modifiers();
|
|
18
|
+
// graceful mode : ignore token & go next
|
|
19
|
+
if (this.token !== this.tok.T_CLASS) {
|
|
20
|
+
this.error(this.tok.T_CLASS);
|
|
21
|
+
this.next();
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
this.next().expect(this.tok.T_STRING);
|
|
25
|
+
let propName = this.node("identifier");
|
|
26
|
+
const name = this.text();
|
|
27
|
+
this.next();
|
|
28
|
+
propName = propName(name);
|
|
29
|
+
const propExtends = this.read_extends_from();
|
|
30
|
+
const propImplements = this.read_implements_list();
|
|
31
|
+
this.expect("{");
|
|
32
|
+
const body = this.next().read_class_body(true, false);
|
|
33
|
+
const node = result(propName, propExtends, propImplements, body, flag);
|
|
34
|
+
if (attrs) node.attrGroups = attrs;
|
|
35
|
+
return node;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
read_class_modifiers() {
|
|
39
|
+
const modifier = this.read_class_modifier({
|
|
40
|
+
readonly: 0,
|
|
41
|
+
final_or_abstract: 0,
|
|
42
|
+
});
|
|
43
|
+
return [0, 0, modifier.final_or_abstract, modifier.readonly];
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
read_class_modifier(memo) {
|
|
47
|
+
if (this.token === this.tok.T_READ_ONLY) {
|
|
48
|
+
this.next();
|
|
49
|
+
memo.readonly = 1;
|
|
50
|
+
memo = this.read_class_modifier(memo);
|
|
51
|
+
} else if (
|
|
52
|
+
memo.final_or_abstract === 0 &&
|
|
53
|
+
this.token === this.tok.T_ABSTRACT
|
|
54
|
+
) {
|
|
55
|
+
this.next();
|
|
56
|
+
memo.final_or_abstract = 1;
|
|
57
|
+
memo = this.read_class_modifier(memo);
|
|
58
|
+
} else if (
|
|
59
|
+
memo.final_or_abstract === 0 &&
|
|
60
|
+
this.token === this.tok.T_FINAL
|
|
61
|
+
) {
|
|
62
|
+
this.next();
|
|
63
|
+
memo.final_or_abstract = 2;
|
|
64
|
+
memo = this.read_class_modifier(memo);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return memo;
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* Reads a class body
|
|
72
|
+
* ```ebnf
|
|
73
|
+
* class_body ::= (member_flags? (T_VAR | T_STRING | T_FUNCTION))*
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
read_class_body(allow_variables, allow_enum_cases) {
|
|
77
|
+
let result = [];
|
|
78
|
+
let attrs = [];
|
|
79
|
+
while (this.token !== this.EOF && this.token !== "}") {
|
|
80
|
+
if (this.token === this.tok.T_COMMENT) {
|
|
81
|
+
result.push(this.read_comment());
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (this.token === this.tok.T_DOC_COMMENT) {
|
|
86
|
+
result.push(this.read_doc_comment());
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// check T_USE trait
|
|
91
|
+
if (this.token === this.tok.T_USE) {
|
|
92
|
+
result = result.concat(this.read_trait_use_statement());
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// check enum cases
|
|
97
|
+
if (allow_enum_cases && this.token === this.tok.T_CASE) {
|
|
98
|
+
const enumcase = this.read_enum_case();
|
|
99
|
+
if (this.expect(";")) {
|
|
100
|
+
this.next();
|
|
101
|
+
}
|
|
102
|
+
result = result.concat(enumcase);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (this.token === this.tok.T_ATTRIBUTE) {
|
|
107
|
+
attrs = this.read_attr_list();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const locStart = this.position();
|
|
111
|
+
|
|
112
|
+
// read member flags
|
|
113
|
+
const flags = this.read_member_flags(false);
|
|
114
|
+
|
|
115
|
+
// check constant
|
|
116
|
+
if (this.token === this.tok.T_CONST) {
|
|
117
|
+
const constants = this.read_constant_list(flags, attrs);
|
|
118
|
+
if (this.expect(";")) {
|
|
119
|
+
this.next();
|
|
120
|
+
}
|
|
121
|
+
result = result.concat(constants);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// jump over T_VAR then land on T_VARIABLE
|
|
126
|
+
if (allow_variables && this.token === this.tok.T_VAR) {
|
|
127
|
+
this.next().expect(this.tok.T_VARIABLE);
|
|
128
|
+
flags[0] = null; // public (as null)
|
|
129
|
+
flags[1] = 0; // non static var
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (this.token === this.tok.T_FUNCTION) {
|
|
133
|
+
// reads a function
|
|
134
|
+
result.push(this.read_function(false, flags, attrs, locStart));
|
|
135
|
+
attrs = [];
|
|
136
|
+
} else if (
|
|
137
|
+
allow_variables &&
|
|
138
|
+
(this.token === this.tok.T_VARIABLE ||
|
|
139
|
+
(this.version >= 801 && this.token === this.tok.T_READ_ONLY) ||
|
|
140
|
+
// support https://wiki.php.net/rfc/typed_properties_v2
|
|
141
|
+
(this.version >= 704 &&
|
|
142
|
+
(this.token === "?" ||
|
|
143
|
+
this.token === this.tok.T_ARRAY ||
|
|
144
|
+
this.token === this.tok.T_CALLABLE ||
|
|
145
|
+
this.token === this.tok.T_NAMESPACE ||
|
|
146
|
+
this.token === this.tok.T_NAME_FULLY_QUALIFIED ||
|
|
147
|
+
this.token === this.tok.T_NAME_QUALIFIED ||
|
|
148
|
+
this.token === this.tok.T_NAME_RELATIVE ||
|
|
149
|
+
this.token === this.tok.T_NS_SEPARATOR ||
|
|
150
|
+
this.token === this.tok.T_STRING)))
|
|
151
|
+
) {
|
|
152
|
+
// reads a variable
|
|
153
|
+
const variables = this.read_variable_list(flags, attrs);
|
|
154
|
+
attrs = [];
|
|
155
|
+
result = result.concat(variables);
|
|
156
|
+
} else {
|
|
157
|
+
// raise an error
|
|
158
|
+
this.error([
|
|
159
|
+
this.tok.T_CONST,
|
|
160
|
+
...(allow_variables ? [this.tok.T_VARIABLE] : []),
|
|
161
|
+
...(allow_enum_cases ? [this.tok.T_CASE] : []),
|
|
162
|
+
this.tok.T_FUNCTION,
|
|
163
|
+
]);
|
|
164
|
+
// ignore token
|
|
165
|
+
this.next();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
this.expect("}");
|
|
169
|
+
this.next();
|
|
170
|
+
return result;
|
|
171
|
+
},
|
|
172
|
+
/*
|
|
173
|
+
* Reads variable list
|
|
174
|
+
* ```ebnf
|
|
175
|
+
* variable_list ::= (variable_declaration ',')* variable_declaration
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
read_variable_list(flags, attrs) {
|
|
179
|
+
let property_statement = this.node("propertystatement");
|
|
180
|
+
|
|
181
|
+
const properties = this.read_list(
|
|
182
|
+
/*
|
|
183
|
+
* Reads a variable declaration
|
|
184
|
+
*
|
|
185
|
+
* ```ebnf
|
|
186
|
+
* variable_declaration ::= T_VARIABLE '=' scalar
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
function read_variable_declaration() {
|
|
190
|
+
const result = this.node("property");
|
|
191
|
+
let readonly = false;
|
|
192
|
+
if (this.token === this.tok.T_READ_ONLY) {
|
|
193
|
+
readonly = true;
|
|
194
|
+
this.next();
|
|
195
|
+
}
|
|
196
|
+
const [nullable, type] = this.read_optional_type();
|
|
197
|
+
this.expect(this.tok.T_VARIABLE);
|
|
198
|
+
let propName = this.node("identifier");
|
|
199
|
+
const name = this.text().substring(1); // ignore $
|
|
200
|
+
this.next();
|
|
201
|
+
propName = propName(name);
|
|
202
|
+
|
|
203
|
+
let value = null;
|
|
204
|
+
let property_hooks = [];
|
|
205
|
+
|
|
206
|
+
this.expect([",", ";", "=", "{"]);
|
|
207
|
+
|
|
208
|
+
// Property has a value
|
|
209
|
+
if (this.token === "=") {
|
|
210
|
+
// https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L815
|
|
211
|
+
value = this.next().read_expr();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Property is using a hook to define getter/setters
|
|
215
|
+
if (this.token === "{") {
|
|
216
|
+
property_hooks = this.read_property_hooks();
|
|
217
|
+
} else {
|
|
218
|
+
this.expect([";", ","]);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return result(
|
|
222
|
+
propName,
|
|
223
|
+
value,
|
|
224
|
+
readonly,
|
|
225
|
+
nullable,
|
|
226
|
+
type,
|
|
227
|
+
property_hooks,
|
|
228
|
+
attrs || [],
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
",",
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
property_statement = property_statement(null, properties, flags);
|
|
235
|
+
|
|
236
|
+
// semicolons are found only for regular properties definitions.
|
|
237
|
+
// Property hooks are terminated by a closing curly brace, }.
|
|
238
|
+
// property_statement is instanciated before this check to avoid including the semicolon in the AST end location of the property.
|
|
239
|
+
if (this.token === ";") {
|
|
240
|
+
this.next();
|
|
241
|
+
}
|
|
242
|
+
return property_statement;
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Reads property hooks
|
|
247
|
+
*
|
|
248
|
+
* @returns {PropertyHook[]}
|
|
249
|
+
*/
|
|
250
|
+
read_property_hooks() {
|
|
251
|
+
if (this.version < 804) {
|
|
252
|
+
this.raiseError("Parse Error: Typed Class Constants requires PHP 8.4+");
|
|
253
|
+
}
|
|
254
|
+
this.expect("{");
|
|
255
|
+
this.next();
|
|
256
|
+
|
|
257
|
+
const hooks = [];
|
|
258
|
+
|
|
259
|
+
while (this.token !== "}") {
|
|
260
|
+
hooks.push(this.read_property_hook());
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (this.token === "}") {
|
|
264
|
+
this.next();
|
|
265
|
+
return hooks;
|
|
266
|
+
}
|
|
267
|
+
return [];
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
read_property_hook() {
|
|
271
|
+
const property_hooks = this.node("propertyhook");
|
|
272
|
+
|
|
273
|
+
const is_final = this.token === this.tok.T_FINAL;
|
|
274
|
+
if (is_final) this.next();
|
|
275
|
+
|
|
276
|
+
const is_reference = this.token === "&";
|
|
277
|
+
if (is_reference) this.next();
|
|
278
|
+
|
|
279
|
+
const method_name = this.text();
|
|
280
|
+
|
|
281
|
+
if (method_name !== "get" && method_name !== "set") {
|
|
282
|
+
this.raiseError(
|
|
283
|
+
"Parse Error: Property hooks must be either 'get' or 'set'",
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
this.next();
|
|
287
|
+
|
|
288
|
+
let parameter = null;
|
|
289
|
+
let body = null;
|
|
290
|
+
this.expect([this.tok.T_DOUBLE_ARROW, "{", "(", ";"]);
|
|
291
|
+
|
|
292
|
+
// interface or abstract definition
|
|
293
|
+
if (this.token === ";") {
|
|
294
|
+
this.next();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (this.token === "(") {
|
|
298
|
+
this.next();
|
|
299
|
+
parameter = this.read_parameter(false);
|
|
300
|
+
this.expect(")");
|
|
301
|
+
this.next();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (this.token === this.tok.T_DOUBLE_ARROW) {
|
|
305
|
+
this.next();
|
|
306
|
+
body = this.read_expr();
|
|
307
|
+
this.next();
|
|
308
|
+
} else if (this.token === "{") {
|
|
309
|
+
body = this.read_code_block();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return property_hooks(method_name, is_final, is_reference, parameter, body);
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/*
|
|
316
|
+
* Reads constant list
|
|
317
|
+
* ```ebnf
|
|
318
|
+
* constant_list ::= T_CONST [type] (constant_declaration ',')* constant_declaration
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
read_constant_list(flags, attrs) {
|
|
322
|
+
if (this.expect(this.tok.T_CONST)) {
|
|
323
|
+
this.next();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const [nullable, type] =
|
|
327
|
+
this.version >= 803 ? this.read_optional_type() : [false, null];
|
|
328
|
+
|
|
329
|
+
const result = this.node("classconstant");
|
|
330
|
+
const items = this.read_list(
|
|
331
|
+
/*
|
|
332
|
+
* Reads a constant declaration
|
|
333
|
+
*
|
|
334
|
+
* ```ebnf
|
|
335
|
+
* constant_declaration ::= (T_STRING | IDENTIFIER) '=' expr
|
|
336
|
+
* ```
|
|
337
|
+
* @return {Constant} [:link:](AST.md#constant)
|
|
338
|
+
*/
|
|
339
|
+
function read_constant_declaration() {
|
|
340
|
+
const result = this.node("constant");
|
|
341
|
+
let constName = null;
|
|
342
|
+
let value = null;
|
|
343
|
+
if (
|
|
344
|
+
this.token === this.tok.T_STRING ||
|
|
345
|
+
(this.version >= 700 && this.is("IDENTIFIER"))
|
|
346
|
+
) {
|
|
347
|
+
constName = this.node("identifier");
|
|
348
|
+
const name = this.text();
|
|
349
|
+
this.next();
|
|
350
|
+
constName = constName(name);
|
|
351
|
+
} else {
|
|
352
|
+
this.expect("IDENTIFIER");
|
|
353
|
+
}
|
|
354
|
+
if (this.expect("=")) {
|
|
355
|
+
value = this.next().read_expr();
|
|
356
|
+
}
|
|
357
|
+
return result(constName, value);
|
|
358
|
+
},
|
|
359
|
+
",",
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
return result(null, items, flags, nullable, type, attrs || []);
|
|
363
|
+
},
|
|
364
|
+
/*
|
|
365
|
+
* Read member flags
|
|
366
|
+
* @return array
|
|
367
|
+
* 1st index : 0 => public, 1 => protected, 2 => private
|
|
368
|
+
* 2nd index : 0 => instance member, 1 => static member
|
|
369
|
+
* 3rd index : 0 => normal, 1 => abstract member, 2 => final member
|
|
370
|
+
*/
|
|
371
|
+
read_member_flags(asInterface) {
|
|
372
|
+
const result = [-1, -1, -1];
|
|
373
|
+
if (this.is("T_MEMBER_FLAGS")) {
|
|
374
|
+
let idx = 0,
|
|
375
|
+
val = 0;
|
|
376
|
+
do {
|
|
377
|
+
switch (this.token) {
|
|
378
|
+
case this.tok.T_PUBLIC:
|
|
379
|
+
idx = 0;
|
|
380
|
+
val = 0;
|
|
381
|
+
break;
|
|
382
|
+
case this.tok.T_PROTECTED:
|
|
383
|
+
idx = 0;
|
|
384
|
+
val = 1;
|
|
385
|
+
break;
|
|
386
|
+
case this.tok.T_PRIVATE:
|
|
387
|
+
idx = 0;
|
|
388
|
+
val = 2;
|
|
389
|
+
break;
|
|
390
|
+
case this.tok.T_STATIC:
|
|
391
|
+
idx = 1;
|
|
392
|
+
val = 1;
|
|
393
|
+
break;
|
|
394
|
+
case this.tok.T_ABSTRACT:
|
|
395
|
+
idx = 2;
|
|
396
|
+
val = 1;
|
|
397
|
+
break;
|
|
398
|
+
case this.tok.T_FINAL:
|
|
399
|
+
idx = 2;
|
|
400
|
+
val = 2;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
if (asInterface) {
|
|
404
|
+
if (idx === 0 && val === 2) {
|
|
405
|
+
// an interface can't be private
|
|
406
|
+
this.expect([this.tok.T_PUBLIC, this.tok.T_PROTECTED]);
|
|
407
|
+
val = -1;
|
|
408
|
+
} else if (idx === 2 && val === 1) {
|
|
409
|
+
// an interface cant be abstract
|
|
410
|
+
this.error();
|
|
411
|
+
val = -1;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (result[idx] !== -1) {
|
|
415
|
+
// already defined flag
|
|
416
|
+
this.error();
|
|
417
|
+
} else if (val !== -1) {
|
|
418
|
+
result[idx] = val;
|
|
419
|
+
}
|
|
420
|
+
} while (this.next().is("T_MEMBER_FLAGS"));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (result[1] === -1) result[1] = 0;
|
|
424
|
+
if (result[2] === -1) result[2] = 0;
|
|
425
|
+
return result;
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
/*
|
|
429
|
+
* optional_type:
|
|
430
|
+
* /- empty -/ { $$ = NULL; }
|
|
431
|
+
* | type_expr { $$ = $1; }
|
|
432
|
+
* ;
|
|
433
|
+
*
|
|
434
|
+
* type_expr:
|
|
435
|
+
* type { $$ = $1; }
|
|
436
|
+
* | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
|
|
437
|
+
* | union_type { $$ = $1; }
|
|
438
|
+
* ;
|
|
439
|
+
*
|
|
440
|
+
* type:
|
|
441
|
+
* T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
|
|
442
|
+
* | T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); }
|
|
443
|
+
* | name { $$ = $1; }
|
|
444
|
+
* ;
|
|
445
|
+
*
|
|
446
|
+
* union_type:
|
|
447
|
+
* type '|' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); }
|
|
448
|
+
* | union_type '|' type { $$ = zend_ast_list_add($1, $3); }
|
|
449
|
+
* ;
|
|
450
|
+
*/
|
|
451
|
+
read_optional_type() {
|
|
452
|
+
const nullable = this.token === "?";
|
|
453
|
+
if (nullable) {
|
|
454
|
+
this.next();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (this.peekSkipComments() === "=") {
|
|
458
|
+
return [false, null];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
let type = this.read_types();
|
|
462
|
+
if (nullable && !type) {
|
|
463
|
+
this.raiseError(
|
|
464
|
+
"Expecting a type definition combined with nullable operator",
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
if (!nullable && !type) {
|
|
468
|
+
return [false, null];
|
|
469
|
+
}
|
|
470
|
+
if (this.token === "|") {
|
|
471
|
+
type = [type];
|
|
472
|
+
do {
|
|
473
|
+
this.next();
|
|
474
|
+
const variant = this.read_type();
|
|
475
|
+
if (!variant) {
|
|
476
|
+
this.raiseError("Expecting a type definition");
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
type.push(variant);
|
|
480
|
+
} while (this.token === "|");
|
|
481
|
+
}
|
|
482
|
+
return [nullable, type];
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
peekSkipComments() {
|
|
486
|
+
const lexerState = this.lexer.getState();
|
|
487
|
+
let nextToken;
|
|
488
|
+
|
|
489
|
+
do {
|
|
490
|
+
nextToken = this.lexer.lex();
|
|
491
|
+
} while (
|
|
492
|
+
nextToken === this.tok.T_COMMENT ||
|
|
493
|
+
nextToken === this.tok.T_WHITESPACE
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
this.lexer.setState(lexerState);
|
|
497
|
+
return nextToken;
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
/*
|
|
501
|
+
* reading an interface
|
|
502
|
+
* ```ebnf
|
|
503
|
+
* interface ::= T_INTERFACE T_STRING (T_EXTENDS (NAMESPACE_NAME ',')* NAMESPACE_NAME)? '{' INTERFACE_BODY '}'
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
read_interface_declaration_statement(attrs) {
|
|
507
|
+
const result = this.node("interface");
|
|
508
|
+
if (this.token !== this.tok.T_INTERFACE) {
|
|
509
|
+
this.error(this.tok.T_INTERFACE);
|
|
510
|
+
this.next();
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
this.next().expect(this.tok.T_STRING);
|
|
514
|
+
let propName = this.node("identifier");
|
|
515
|
+
const name = this.text();
|
|
516
|
+
this.next();
|
|
517
|
+
propName = propName(name);
|
|
518
|
+
const propExtends = this.read_interface_extends_list();
|
|
519
|
+
this.expect("{");
|
|
520
|
+
const body = this.next().read_interface_body();
|
|
521
|
+
return result(propName, propExtends, body, attrs || []);
|
|
522
|
+
},
|
|
523
|
+
/*
|
|
524
|
+
* Reads an interface body
|
|
525
|
+
* ```ebnf
|
|
526
|
+
* interface_body ::= (member_flags? (T_CONST | T_FUNCTION))*
|
|
527
|
+
* ```
|
|
528
|
+
*/
|
|
529
|
+
read_interface_body() {
|
|
530
|
+
let result = [],
|
|
531
|
+
attrs = [];
|
|
532
|
+
|
|
533
|
+
while (this.token !== this.EOF && this.token !== "}") {
|
|
534
|
+
if (this.token === this.tok.T_COMMENT) {
|
|
535
|
+
result.push(this.read_comment());
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (this.token === this.tok.T_DOC_COMMENT) {
|
|
540
|
+
result.push(this.read_doc_comment());
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const locStart = this.position();
|
|
545
|
+
|
|
546
|
+
attrs = this.read_attr_list();
|
|
547
|
+
// read member flags
|
|
548
|
+
const flags = this.read_member_flags(true);
|
|
549
|
+
|
|
550
|
+
// check constant
|
|
551
|
+
if (this.token === this.tok.T_CONST) {
|
|
552
|
+
const constants = this.read_constant_list(flags, attrs);
|
|
553
|
+
if (this.expect(";")) {
|
|
554
|
+
this.next();
|
|
555
|
+
}
|
|
556
|
+
result = result.concat(constants);
|
|
557
|
+
attrs = [];
|
|
558
|
+
} else if (this.token === this.tok.T_FUNCTION) {
|
|
559
|
+
// reads a function
|
|
560
|
+
const method = this.read_function_declaration(
|
|
561
|
+
2,
|
|
562
|
+
flags,
|
|
563
|
+
attrs,
|
|
564
|
+
locStart,
|
|
565
|
+
);
|
|
566
|
+
method.parseFlags(flags);
|
|
567
|
+
result.push(method);
|
|
568
|
+
if (this.expect(";")) {
|
|
569
|
+
this.next();
|
|
570
|
+
}
|
|
571
|
+
attrs = [];
|
|
572
|
+
} else if (this.token === this.tok.T_STRING) {
|
|
573
|
+
result.push(this.read_variable_list(flags, attrs));
|
|
574
|
+
} else {
|
|
575
|
+
// raise an error
|
|
576
|
+
this.error([this.tok.T_CONST, this.tok.T_FUNCTION, this.tok.T_STRING]);
|
|
577
|
+
this.next();
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (this.expect("}")) {
|
|
581
|
+
this.next();
|
|
582
|
+
}
|
|
583
|
+
return result;
|
|
584
|
+
},
|
|
585
|
+
/*
|
|
586
|
+
* reading a trait
|
|
587
|
+
* ```ebnf
|
|
588
|
+
* trait ::= T_TRAIT T_STRING (T_EXTENDS (NAMESPACE_NAME ',')* NAMESPACE_NAME)? '{' FUNCTION* '}'
|
|
589
|
+
* ```
|
|
590
|
+
*/
|
|
591
|
+
read_trait_declaration_statement() {
|
|
592
|
+
const result = this.node("trait");
|
|
593
|
+
// graceful mode : ignore token & go next
|
|
594
|
+
if (this.token !== this.tok.T_TRAIT) {
|
|
595
|
+
this.error(this.tok.T_TRAIT);
|
|
596
|
+
this.next();
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
this.next().expect(this.tok.T_STRING);
|
|
600
|
+
let propName = this.node("identifier");
|
|
601
|
+
const name = this.text();
|
|
602
|
+
this.next();
|
|
603
|
+
propName = propName(name);
|
|
604
|
+
this.expect("{");
|
|
605
|
+
const body = this.next().read_class_body(true, false);
|
|
606
|
+
return result(propName, body);
|
|
607
|
+
},
|
|
608
|
+
/*
|
|
609
|
+
* reading a use statement
|
|
610
|
+
* ```ebnf
|
|
611
|
+
* trait_use_statement ::= namespace_name (',' namespace_name)* ('{' trait_use_alias '}')?
|
|
612
|
+
* ```
|
|
613
|
+
*/
|
|
614
|
+
read_trait_use_statement() {
|
|
615
|
+
// defines use statements
|
|
616
|
+
const node = this.node("traituse");
|
|
617
|
+
this.expect(this.tok.T_USE) && this.next();
|
|
618
|
+
const traits = [this.read_namespace_name()];
|
|
619
|
+
let adaptations = null;
|
|
620
|
+
while (this.token === ",") {
|
|
621
|
+
traits.push(this.next().read_namespace_name());
|
|
622
|
+
}
|
|
623
|
+
if (this.token === "{") {
|
|
624
|
+
adaptations = [];
|
|
625
|
+
// defines alias statements
|
|
626
|
+
while (this.next().token !== this.EOF) {
|
|
627
|
+
if (this.token === "}") break;
|
|
628
|
+
adaptations.push(this.read_trait_use_alias());
|
|
629
|
+
this.expect(";");
|
|
630
|
+
}
|
|
631
|
+
if (this.expect("}")) {
|
|
632
|
+
this.next();
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
if (this.expect(";")) {
|
|
636
|
+
this.next();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return node(traits, adaptations);
|
|
640
|
+
},
|
|
641
|
+
/*
|
|
642
|
+
* Reading trait alias
|
|
643
|
+
* ```ebnf
|
|
644
|
+
* trait_use_alias ::= namespace_name ( T_DOUBLE_COLON T_STRING )? (T_INSTEADOF namespace_name) | (T_AS member_flags? T_STRING)
|
|
645
|
+
* ```
|
|
646
|
+
* name list : https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L303
|
|
647
|
+
* trait adaptation : https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L742
|
|
648
|
+
*/
|
|
649
|
+
read_trait_use_alias() {
|
|
650
|
+
const node = this.node();
|
|
651
|
+
let trait = null;
|
|
652
|
+
let method;
|
|
653
|
+
|
|
654
|
+
if (this.is("IDENTIFIER")) {
|
|
655
|
+
method = this.node("identifier");
|
|
656
|
+
const methodName = this.text();
|
|
657
|
+
this.next();
|
|
658
|
+
method = method(methodName);
|
|
659
|
+
} else {
|
|
660
|
+
method = this.read_namespace_name();
|
|
661
|
+
|
|
662
|
+
if (this.token === this.tok.T_DOUBLE_COLON) {
|
|
663
|
+
this.next();
|
|
664
|
+
if (
|
|
665
|
+
this.token === this.tok.T_STRING ||
|
|
666
|
+
(this.version >= 700 && this.is("IDENTIFIER"))
|
|
667
|
+
) {
|
|
668
|
+
trait = method;
|
|
669
|
+
method = this.node("identifier");
|
|
670
|
+
const methodName = this.text();
|
|
671
|
+
this.next();
|
|
672
|
+
method = method(methodName);
|
|
673
|
+
} else {
|
|
674
|
+
this.expect(this.tok.T_STRING);
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
// convert identifier as string
|
|
678
|
+
method = method.name;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// handle trait precedence
|
|
683
|
+
if (this.token === this.tok.T_INSTEADOF) {
|
|
684
|
+
return node(
|
|
685
|
+
"traitprecedence",
|
|
686
|
+
trait,
|
|
687
|
+
method,
|
|
688
|
+
this.next().read_name_list(),
|
|
689
|
+
);
|
|
690
|
+
} else if (this.token === this.tok.T_AS) {
|
|
691
|
+
// handle trait alias
|
|
692
|
+
let flags = null;
|
|
693
|
+
let alias = null;
|
|
694
|
+
if (this.next().is("T_MEMBER_FLAGS")) {
|
|
695
|
+
flags = this.read_member_flags();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (
|
|
699
|
+
this.token === this.tok.T_STRING ||
|
|
700
|
+
(this.version >= 700 && this.is("IDENTIFIER"))
|
|
701
|
+
) {
|
|
702
|
+
alias = this.node("identifier");
|
|
703
|
+
const name = this.text();
|
|
704
|
+
this.next();
|
|
705
|
+
alias = alias(name);
|
|
706
|
+
} else if (flags === false) {
|
|
707
|
+
// no visibility flags and no name => too bad
|
|
708
|
+
this.expect(this.tok.T_STRING);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return node("traitalias", trait, method, alias, flags);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// handle errors
|
|
715
|
+
this.expect([this.tok.T_AS, this.tok.T_INSTEADOF]);
|
|
716
|
+
return node("traitalias", trait, method, null, null);
|
|
717
|
+
},
|
|
718
|
+
};
|