These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | // @codingStandardsIgnoreFile File external to MediaWiki. Ignore coding conventions checks. |
||
3 | /** |
||
4 | * JSMinPlus version 1.4 |
||
5 | * |
||
6 | * Minifies a javascript file using a javascript parser |
||
7 | * |
||
8 | * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript) |
||
9 | * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine) |
||
10 | * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/ |
||
11 | * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716 |
||
12 | * |
||
13 | * Tino Zijdel <[email protected]> |
||
14 | * |
||
15 | * Usage: $minified = JSMinPlus::minify($script [, $filename]) |
||
16 | * |
||
17 | * Versionlog (see also changelog.txt): |
||
18 | * 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top |
||
19 | * reduce memory footprint by minifying by block-scope |
||
20 | * some small byte-saving and performance improvements |
||
21 | * 12-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs |
||
22 | * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes |
||
23 | * 12-04-2009 - some small bugfixes and performance improvements |
||
24 | * 09-04-2009 - initial open sourced version 1.0 |
||
25 | * |
||
26 | * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip |
||
27 | * |
||
28 | * @file |
||
29 | */ |
||
30 | |||
31 | /* ***** BEGIN LICENSE BLOCK ***** |
||
32 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
||
33 | * |
||
34 | * The contents of this file are subject to the Mozilla Public License Version |
||
35 | * 1.1 (the "License"); you may not use this file except in compliance with |
||
36 | * the License. You may obtain a copy of the License at |
||
37 | * http://www.mozilla.org/MPL/ |
||
38 | * |
||
39 | * Software distributed under the License is distributed on an "AS IS" basis, |
||
40 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
||
41 | * for the specific language governing rights and limitations under the |
||
42 | * License. |
||
43 | * |
||
44 | * The Original Code is the Narcissus JavaScript engine. |
||
45 | * |
||
46 | * The Initial Developer of the Original Code is |
||
47 | * Brendan Eich <[email protected]>. |
||
48 | * Portions created by the Initial Developer are Copyright (C) 2004 |
||
49 | * the Initial Developer. All Rights Reserved. |
||
50 | * |
||
51 | * Contributor(s): Tino Zijdel <[email protected]> |
||
52 | * PHP port, modifications and minifier routine are (C) 2009-2011 |
||
53 | * |
||
54 | * Alternatively, the contents of this file may be used under the terms of |
||
55 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
||
56 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
||
57 | * in which case the provisions of the GPL or the LGPL are applicable instead |
||
58 | * of those above. If you wish to allow use of your version of this file only |
||
59 | * under the terms of either the GPL or the LGPL, and not to allow others to |
||
60 | * use your version of this file under the terms of the MPL, indicate your |
||
61 | * decision by deleting the provisions above and replace them with the notice |
||
62 | * and other provisions required by the GPL or the LGPL. If you do not delete |
||
63 | * the provisions above, a recipient may use your version of this file under |
||
64 | * the terms of any one of the MPL, the GPL or the LGPL. |
||
65 | * |
||
66 | * ***** END LICENSE BLOCK ***** */ |
||
67 | |||
68 | define('TOKEN_END', 1); |
||
69 | define('TOKEN_NUMBER', 2); |
||
70 | define('TOKEN_IDENTIFIER', 3); |
||
71 | define('TOKEN_STRING', 4); |
||
72 | define('TOKEN_REGEXP', 5); |
||
73 | define('TOKEN_NEWLINE', 6); |
||
74 | define('TOKEN_CONDCOMMENT_START', 7); |
||
75 | define('TOKEN_CONDCOMMENT_END', 8); |
||
76 | |||
77 | define('JS_SCRIPT', 100); |
||
78 | define('JS_BLOCK', 101); |
||
79 | define('JS_LABEL', 102); |
||
80 | define('JS_FOR_IN', 103); |
||
81 | define('JS_CALL', 104); |
||
82 | define('JS_NEW_WITH_ARGS', 105); |
||
83 | define('JS_INDEX', 106); |
||
84 | define('JS_ARRAY_INIT', 107); |
||
85 | define('JS_OBJECT_INIT', 108); |
||
86 | define('JS_PROPERTY_INIT', 109); |
||
87 | define('JS_GETTER', 110); |
||
88 | define('JS_SETTER', 111); |
||
89 | define('JS_GROUP', 112); |
||
90 | define('JS_LIST', 113); |
||
91 | |||
92 | define('JS_MINIFIED', 999); |
||
93 | |||
94 | define('DECLARED_FORM', 0); |
||
95 | define('EXPRESSED_FORM', 1); |
||
96 | define('STATEMENT_FORM', 2); |
||
97 | |||
98 | /* Operators */ |
||
99 | define('OP_SEMICOLON', ';'); |
||
100 | define('OP_COMMA', ','); |
||
101 | define('OP_HOOK', '?'); |
||
102 | define('OP_COLON', ':'); |
||
103 | define('OP_OR', '||'); |
||
104 | define('OP_AND', '&&'); |
||
105 | define('OP_BITWISE_OR', '|'); |
||
106 | define('OP_BITWISE_XOR', '^'); |
||
107 | define('OP_BITWISE_AND', '&'); |
||
108 | define('OP_STRICT_EQ', '==='); |
||
109 | define('OP_EQ', '=='); |
||
110 | define('OP_ASSIGN', '='); |
||
111 | define('OP_STRICT_NE', '!=='); |
||
112 | define('OP_NE', '!='); |
||
113 | define('OP_LSH', '<<'); |
||
114 | define('OP_LE', '<='); |
||
115 | define('OP_LT', '<'); |
||
116 | define('OP_URSH', '>>>'); |
||
117 | define('OP_RSH', '>>'); |
||
118 | define('OP_GE', '>='); |
||
119 | define('OP_GT', '>'); |
||
120 | define('OP_INCREMENT', '++'); |
||
121 | define('OP_DECREMENT', '--'); |
||
122 | define('OP_PLUS', '+'); |
||
123 | define('OP_MINUS', '-'); |
||
124 | define('OP_MUL', '*'); |
||
125 | define('OP_DIV', '/'); |
||
126 | define('OP_MOD', '%'); |
||
127 | define('OP_NOT', '!'); |
||
128 | define('OP_BITWISE_NOT', '~'); |
||
129 | define('OP_DOT', '.'); |
||
130 | define('OP_LEFT_BRACKET', '['); |
||
131 | define('OP_RIGHT_BRACKET', ']'); |
||
132 | define('OP_LEFT_CURLY', '{'); |
||
133 | define('OP_RIGHT_CURLY', '}'); |
||
134 | define('OP_LEFT_PAREN', '('); |
||
135 | define('OP_RIGHT_PAREN', ')'); |
||
136 | define('OP_CONDCOMMENT_END', '@*/'); |
||
137 | |||
138 | define('OP_UNARY_PLUS', 'U+'); |
||
139 | define('OP_UNARY_MINUS', 'U-'); |
||
140 | |||
141 | /* Keywords */ |
||
142 | define('KEYWORD_BREAK', 'break'); |
||
143 | define('KEYWORD_CASE', 'case'); |
||
144 | define('KEYWORD_CATCH', 'catch'); |
||
145 | define('KEYWORD_CONST', 'const'); |
||
146 | define('KEYWORD_CONTINUE', 'continue'); |
||
147 | define('KEYWORD_DEBUGGER', 'debugger'); |
||
148 | define('KEYWORD_DEFAULT', 'default'); |
||
149 | define('KEYWORD_DELETE', 'delete'); |
||
150 | define('KEYWORD_DO', 'do'); |
||
151 | define('KEYWORD_ELSE', 'else'); |
||
152 | define('KEYWORD_ENUM', 'enum'); |
||
153 | define('KEYWORD_FALSE', 'false'); |
||
154 | define('KEYWORD_FINALLY', 'finally'); |
||
155 | define('KEYWORD_FOR', 'for'); |
||
156 | define('KEYWORD_FUNCTION', 'function'); |
||
157 | define('KEYWORD_IF', 'if'); |
||
158 | define('KEYWORD_IN', 'in'); |
||
159 | define('KEYWORD_INSTANCEOF', 'instanceof'); |
||
160 | define('KEYWORD_NEW', 'new'); |
||
161 | define('KEYWORD_NULL', 'null'); |
||
162 | define('KEYWORD_RETURN', 'return'); |
||
163 | define('KEYWORD_SWITCH', 'switch'); |
||
164 | define('KEYWORD_THIS', 'this'); |
||
165 | define('KEYWORD_THROW', 'throw'); |
||
166 | define('KEYWORD_TRUE', 'true'); |
||
167 | define('KEYWORD_TRY', 'try'); |
||
168 | define('KEYWORD_TYPEOF', 'typeof'); |
||
169 | define('KEYWORD_VAR', 'var'); |
||
170 | define('KEYWORD_VOID', 'void'); |
||
171 | define('KEYWORD_WHILE', 'while'); |
||
172 | define('KEYWORD_WITH', 'with'); |
||
173 | |||
174 | |||
175 | class JSMinPlus |
||
176 | { |
||
177 | private $parser; |
||
178 | private $reserved = array( |
||
179 | 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do', |
||
180 | 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof', |
||
181 | 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var', |
||
182 | 'void', 'while', 'with', |
||
183 | // Words reserved for future use |
||
184 | 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger', |
||
185 | 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto', |
||
186 | 'implements', 'import', 'int', 'interface', 'long', 'native', |
||
187 | 'package', 'private', 'protected', 'public', 'short', 'static', |
||
188 | 'super', 'synchronized', 'throws', 'transient', 'volatile', |
||
189 | // These are not reserved, but should be taken into account |
||
190 | // in isValidIdentifier (See jslint source code) |
||
191 | 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined' |
||
192 | ); |
||
193 | |||
194 | private function __construct() |
||
195 | { |
||
196 | $this->parser = new JSParser($this); |
||
197 | } |
||
198 | |||
199 | public static function minify($js, $filename='') |
||
200 | { |
||
201 | static $instance; |
||
202 | |||
203 | // this is a singleton |
||
204 | if(!$instance) |
||
205 | $instance = new JSMinPlus(); |
||
206 | |||
207 | return $instance->min($js, $filename); |
||
208 | } |
||
209 | |||
210 | private function min($js, $filename) |
||
211 | { |
||
212 | try |
||
213 | { |
||
214 | $n = $this->parser->parse($js, $filename, 1); |
||
215 | return $this->parseTree($n); |
||
216 | } |
||
217 | catch(Exception $e) |
||
218 | { |
||
219 | echo $e->getMessage() . "\n"; |
||
220 | } |
||
221 | |||
222 | return false; |
||
223 | } |
||
224 | |||
225 | public function parseTree($n, $noBlockGrouping = false) |
||
226 | { |
||
227 | $s = ''; |
||
228 | |||
229 | switch ($n->type) |
||
230 | { |
||
231 | case JS_MINIFIED: |
||
232 | $s = $n->value; |
||
233 | break; |
||
234 | |||
235 | case JS_SCRIPT: |
||
236 | // we do nothing yet with funDecls or varDecls |
||
237 | $noBlockGrouping = true; |
||
238 | // FALL THROUGH |
||
239 | |||
240 | case JS_BLOCK: |
||
241 | $childs = $n->treeNodes; |
||
242 | $lastType = 0; |
||
243 | for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++) |
||
244 | { |
||
245 | $type = $childs[$i]->type; |
||
246 | $t = $this->parseTree($childs[$i]); |
||
247 | if (strlen($t)) |
||
248 | { |
||
249 | if ($c) |
||
250 | { |
||
251 | $s = rtrim($s, ';'); |
||
252 | |||
253 | if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM) |
||
254 | { |
||
255 | // put declared functions on a new line |
||
256 | $s .= "\n"; |
||
257 | } |
||
258 | elseif ($type == KEYWORD_VAR && $type == $lastType) |
||
259 | { |
||
260 | // multiple var-statements can go into one |
||
261 | $t = ',' . substr($t, 4); |
||
262 | } |
||
263 | else |
||
264 | { |
||
265 | // add terminator |
||
266 | $s .= ';'; |
||
267 | } |
||
268 | } |
||
269 | |||
270 | $s .= $t; |
||
271 | |||
272 | $c++; |
||
273 | $lastType = $type; |
||
274 | } |
||
275 | } |
||
276 | |||
277 | if ($c > 1 && !$noBlockGrouping) |
||
278 | { |
||
279 | $s = '{' . $s . '}'; |
||
280 | } |
||
281 | break; |
||
282 | |||
283 | case KEYWORD_FUNCTION: |
||
284 | $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '('; |
||
285 | $params = $n->params; |
||
286 | for ($i = 0, $j = count($params); $i < $j; $i++) |
||
287 | $s .= ($i ? ',' : '') . $params[$i]; |
||
288 | $s .= '){' . $this->parseTree($n->body, true) . '}'; |
||
289 | break; |
||
290 | |||
291 | case KEYWORD_IF: |
||
292 | $s = 'if(' . $this->parseTree($n->condition) . ')'; |
||
293 | $thenPart = $this->parseTree($n->thenPart); |
||
294 | $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null; |
||
295 | |||
296 | // empty if-statement |
||
297 | if ($thenPart == '') |
||
298 | $thenPart = ';'; |
||
299 | |||
300 | if ($elsePart) |
||
301 | { |
||
302 | // be careful and always make a block out of the thenPart; could be more optimized but is a lot of trouble |
||
303 | if ($thenPart != ';' && $thenPart[0] != '{') |
||
304 | $thenPart = '{' . $thenPart . '}'; |
||
305 | |||
306 | $s .= $thenPart . 'else'; |
||
307 | |||
308 | // we could check for more, but that hardly ever applies so go for performance |
||
309 | if ($elsePart[0] != '{') |
||
310 | $s .= ' '; |
||
311 | |||
312 | $s .= $elsePart; |
||
313 | } |
||
314 | else |
||
315 | { |
||
316 | $s .= $thenPart; |
||
317 | } |
||
318 | break; |
||
319 | |||
320 | case KEYWORD_SWITCH: |
||
321 | $s = 'switch(' . $this->parseTree($n->discriminant) . '){'; |
||
322 | $cases = $n->cases; |
||
323 | for ($i = 0, $j = count($cases); $i < $j; $i++) |
||
324 | { |
||
325 | $case = $cases[$i]; |
||
326 | if ($case->type == KEYWORD_CASE) |
||
327 | $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':'; |
||
328 | else |
||
329 | $s .= 'default:'; |
||
330 | |||
331 | $statement = $this->parseTree($case->statements, true); |
||
332 | if ($statement) |
||
333 | { |
||
334 | $s .= $statement; |
||
335 | // no terminator for last statement |
||
336 | if ($i + 1 < $j) |
||
337 | $s .= ';'; |
||
338 | } |
||
339 | } |
||
340 | $s .= '}'; |
||
341 | break; |
||
342 | |||
343 | case KEYWORD_FOR: |
||
344 | $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '') |
||
345 | . ';' . ($n->condition ? $this->parseTree($n->condition) : '') |
||
346 | . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')'; |
||
347 | |||
348 | $body = $this->parseTree($n->body); |
||
349 | if ($body == '') |
||
350 | $body = ';'; |
||
351 | |||
352 | $s .= $body; |
||
353 | break; |
||
354 | |||
355 | case KEYWORD_WHILE: |
||
356 | $s = 'while(' . $this->parseTree($n->condition) . ')'; |
||
357 | |||
358 | $body = $this->parseTree($n->body); |
||
359 | if ($body == '') |
||
360 | $body = ';'; |
||
361 | |||
362 | $s .= $body; |
||
363 | break; |
||
364 | |||
365 | case JS_FOR_IN: |
||
366 | $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')'; |
||
367 | |||
368 | $body = $this->parseTree($n->body); |
||
369 | if ($body == '') |
||
370 | $body = ';'; |
||
371 | |||
372 | $s .= $body; |
||
373 | break; |
||
374 | |||
375 | case KEYWORD_DO: |
||
376 | $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')'; |
||
377 | break; |
||
378 | |||
379 | case KEYWORD_BREAK: |
||
380 | case KEYWORD_CONTINUE: |
||
381 | $s = $n->value . ($n->label ? ' ' . $n->label : ''); |
||
382 | break; |
||
383 | |||
384 | case KEYWORD_TRY: |
||
385 | $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}'; |
||
386 | $catchClauses = $n->catchClauses; |
||
387 | for ($i = 0, $j = count($catchClauses); $i < $j; $i++) |
||
388 | { |
||
389 | $t = $catchClauses[$i]; |
||
390 | $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}'; |
||
391 | } |
||
392 | if ($n->finallyBlock) |
||
393 | $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}'; |
||
394 | break; |
||
395 | |||
396 | case KEYWORD_THROW: |
||
397 | case KEYWORD_RETURN: |
||
398 | $s = $n->type; |
||
399 | if ($n->value) |
||
400 | { |
||
401 | $t = $this->parseTree($n->value); |
||
402 | if (strlen($t)) |
||
403 | { |
||
404 | if ($this->isWordChar($t[0]) || $t[0] == '\\') |
||
405 | $s .= ' '; |
||
406 | |||
407 | $s .= $t; |
||
408 | } |
||
409 | } |
||
410 | break; |
||
411 | |||
412 | case KEYWORD_WITH: |
||
413 | $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body); |
||
414 | break; |
||
415 | |||
416 | case KEYWORD_VAR: |
||
417 | case KEYWORD_CONST: |
||
418 | $s = $n->value . ' '; |
||
419 | $childs = $n->treeNodes; |
||
420 | for ($i = 0, $j = count($childs); $i < $j; $i++) |
||
421 | { |
||
422 | $t = $childs[$i]; |
||
423 | $s .= ($i ? ',' : '') . $t->name; |
||
424 | $u = $t->initializer; |
||
425 | if ($u) |
||
426 | $s .= '=' . $this->parseTree($u); |
||
427 | } |
||
428 | break; |
||
429 | |||
430 | case KEYWORD_IN: |
||
431 | case KEYWORD_INSTANCEOF: |
||
432 | $left = $this->parseTree($n->treeNodes[0]); |
||
433 | $right = $this->parseTree($n->treeNodes[1]); |
||
434 | |||
435 | $s = $left; |
||
436 | |||
437 | if ($this->isWordChar(substr($left, -1))) |
||
438 | $s .= ' '; |
||
439 | |||
440 | $s .= $n->type; |
||
441 | |||
442 | if ($this->isWordChar($right[0]) || $right[0] == '\\') |
||
443 | $s .= ' '; |
||
444 | |||
445 | $s .= $right; |
||
446 | break; |
||
447 | |||
448 | case KEYWORD_DELETE: |
||
449 | case KEYWORD_TYPEOF: |
||
450 | $right = $this->parseTree($n->treeNodes[0]); |
||
451 | |||
452 | $s = $n->type; |
||
453 | |||
454 | if ($this->isWordChar($right[0]) || $right[0] == '\\') |
||
455 | $s .= ' '; |
||
456 | |||
457 | $s .= $right; |
||
458 | break; |
||
459 | |||
460 | case KEYWORD_VOID: |
||
461 | $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')'; |
||
462 | break; |
||
463 | |||
464 | case KEYWORD_DEBUGGER: |
||
465 | throw new Exception('NOT IMPLEMENTED: DEBUGGER'); |
||
466 | break; |
||
467 | |||
468 | case TOKEN_CONDCOMMENT_START: |
||
469 | case TOKEN_CONDCOMMENT_END: |
||
470 | $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : ''); |
||
471 | $childs = $n->treeNodes; |
||
472 | for ($i = 0, $j = count($childs); $i < $j; $i++) |
||
473 | $s .= $this->parseTree($childs[$i]); |
||
474 | break; |
||
475 | |||
476 | case OP_SEMICOLON: |
||
477 | if ($expression = $n->expression) |
||
478 | $s = $this->parseTree($expression); |
||
479 | break; |
||
480 | |||
481 | case JS_LABEL: |
||
482 | $s = $n->label . ':' . $this->parseTree($n->statement); |
||
483 | break; |
||
484 | |||
485 | View Code Duplication | case OP_COMMA: |
|
486 | $childs = $n->treeNodes; |
||
487 | for ($i = 0, $j = count($childs); $i < $j; $i++) |
||
488 | $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); |
||
489 | break; |
||
490 | |||
491 | case OP_ASSIGN: |
||
492 | $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]); |
||
493 | break; |
||
494 | |||
495 | View Code Duplication | case OP_HOOK: |
|
496 | $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]); |
||
497 | break; |
||
498 | |||
499 | case OP_OR: case OP_AND: |
||
500 | case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND: |
||
501 | case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE: |
||
502 | case OP_LT: case OP_LE: case OP_GE: case OP_GT: |
||
503 | case OP_LSH: case OP_RSH: case OP_URSH: |
||
504 | case OP_MUL: case OP_DIV: case OP_MOD: |
||
505 | $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]); |
||
506 | break; |
||
507 | |||
508 | case OP_PLUS: |
||
509 | case OP_MINUS: |
||
510 | $left = $this->parseTree($n->treeNodes[0]); |
||
511 | $right = $this->parseTree($n->treeNodes[1]); |
||
512 | |||
513 | switch ($n->treeNodes[1]->type) |
||
514 | { |
||
515 | case OP_PLUS: |
||
516 | case OP_MINUS: |
||
517 | case OP_INCREMENT: |
||
518 | case OP_DECREMENT: |
||
519 | case OP_UNARY_PLUS: |
||
520 | case OP_UNARY_MINUS: |
||
521 | $s = $left . $n->type . ' ' . $right; |
||
522 | break; |
||
523 | |||
524 | case TOKEN_STRING: |
||
525 | //combine concatenated strings with same quote style |
||
526 | if ($n->type == OP_PLUS && substr($left, -1) == $right[0]) |
||
527 | { |
||
528 | $s = substr($left, 0, -1) . substr($right, 1); |
||
529 | break; |
||
530 | } |
||
531 | // FALL THROUGH |
||
532 | |||
533 | default: |
||
534 | $s = $left . $n->type . $right; |
||
535 | } |
||
536 | break; |
||
537 | |||
538 | case OP_NOT: |
||
539 | case OP_BITWISE_NOT: |
||
540 | case OP_UNARY_PLUS: |
||
541 | case OP_UNARY_MINUS: |
||
542 | $s = $n->value . $this->parseTree($n->treeNodes[0]); |
||
543 | break; |
||
544 | |||
545 | case OP_INCREMENT: |
||
546 | case OP_DECREMENT: |
||
547 | if ($n->postfix) |
||
548 | $s = $this->parseTree($n->treeNodes[0]) . $n->value; |
||
549 | else |
||
550 | $s = $n->value . $this->parseTree($n->treeNodes[0]); |
||
551 | break; |
||
552 | |||
553 | case OP_DOT: |
||
554 | $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]); |
||
555 | break; |
||
556 | |||
557 | case JS_INDEX: |
||
558 | $s = $this->parseTree($n->treeNodes[0]); |
||
559 | // See if we can replace named index with a dot saving 3 bytes |
||
560 | if ( $n->treeNodes[0]->type == TOKEN_IDENTIFIER && |
||
561 | $n->treeNodes[1]->type == TOKEN_STRING && |
||
562 | $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1)) |
||
563 | ) |
||
564 | $s .= '.' . substr($n->treeNodes[1]->value, 1, -1); |
||
565 | else |
||
566 | $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']'; |
||
567 | break; |
||
568 | |||
569 | View Code Duplication | case JS_LIST: |
|
570 | $childs = $n->treeNodes; |
||
571 | for ($i = 0, $j = count($childs); $i < $j; $i++) |
||
572 | $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); |
||
573 | break; |
||
574 | |||
575 | View Code Duplication | case JS_CALL: |
|
576 | $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')'; |
||
577 | break; |
||
578 | |||
579 | case KEYWORD_NEW: |
||
580 | View Code Duplication | case JS_NEW_WITH_ARGS: |
|
581 | $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')'; |
||
582 | break; |
||
583 | |||
584 | case JS_ARRAY_INIT: |
||
585 | $s = '['; |
||
586 | $childs = $n->treeNodes; |
||
587 | for ($i = 0, $j = count($childs); $i < $j; $i++) |
||
588 | { |
||
589 | $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); |
||
590 | } |
||
591 | $s .= ']'; |
||
592 | break; |
||
593 | |||
594 | case JS_OBJECT_INIT: |
||
595 | $s = '{'; |
||
596 | $childs = $n->treeNodes; |
||
597 | for ($i = 0, $j = count($childs); $i < $j; $i++) |
||
598 | { |
||
599 | $t = $childs[$i]; |
||
600 | if ($i) |
||
601 | $s .= ','; |
||
602 | if ($t->type == JS_PROPERTY_INIT) |
||
603 | { |
||
604 | // Ditch the quotes when the index is a valid identifier |
||
605 | if ( $t->treeNodes[0]->type == TOKEN_STRING && |
||
606 | $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1)) |
||
607 | ) |
||
608 | $s .= substr($t->treeNodes[0]->value, 1, -1); |
||
609 | else |
||
610 | $s .= $t->treeNodes[0]->value; |
||
611 | |||
612 | $s .= ':' . $this->parseTree($t->treeNodes[1]); |
||
613 | } |
||
614 | else |
||
615 | { |
||
616 | $s .= $t->type == JS_GETTER ? 'get' : 'set'; |
||
617 | $s .= ' ' . $t->name . '('; |
||
618 | $params = $t->params; |
||
619 | for ($i = 0, $j = count($params); $i < $j; $i++) |
||
620 | $s .= ($i ? ',' : '') . $params[$i]; |
||
621 | $s .= '){' . $this->parseTree($t->body, true) . '}'; |
||
622 | } |
||
623 | } |
||
624 | $s .= '}'; |
||
625 | break; |
||
626 | |||
627 | case TOKEN_NUMBER: |
||
628 | $s = $n->value; |
||
629 | if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m)) |
||
630 | $s = $m[1] . 'e' . strlen($m[2]); |
||
631 | break; |
||
632 | |||
633 | case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE: |
||
634 | case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP: |
||
635 | $s = $n->value; |
||
636 | break; |
||
637 | |||
638 | case JS_GROUP: |
||
639 | if (in_array( |
||
640 | $n->treeNodes[0]->type, |
||
641 | array( |
||
642 | JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP, |
||
643 | TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER, |
||
644 | KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE |
||
645 | ) |
||
646 | )) |
||
647 | { |
||
648 | $s = $this->parseTree($n->treeNodes[0]); |
||
649 | } |
||
650 | else |
||
651 | { |
||
652 | $s = '(' . $this->parseTree($n->treeNodes[0]) . ')'; |
||
653 | } |
||
654 | break; |
||
655 | |||
656 | default: |
||
657 | throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type); |
||
658 | } |
||
659 | |||
660 | return $s; |
||
661 | } |
||
662 | |||
663 | private function isValidIdentifier($string) |
||
664 | { |
||
665 | return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved); |
||
666 | } |
||
667 | |||
668 | private function isWordChar($char) |
||
669 | { |
||
670 | return $char == '_' || $char == '$' || ctype_alnum($char); |
||
671 | } |
||
672 | } |
||
673 | |||
674 | class JSParser |
||
675 | { |
||
676 | private $t; |
||
677 | private $minifier; |
||
678 | |||
679 | private $opPrecedence = array( |
||
680 | ';' => 0, |
||
681 | ',' => 1, |
||
682 | '=' => 2, '?' => 2, ':' => 2, |
||
683 | // The above all have to have the same precedence, see bug 330975 |
||
684 | '||' => 4, |
||
685 | '&&' => 5, |
||
686 | '|' => 6, |
||
687 | '^' => 7, |
||
688 | '&' => 8, |
||
689 | '==' => 9, '!=' => 9, '===' => 9, '!==' => 9, |
||
690 | '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10, |
||
691 | '<<' => 11, '>>' => 11, '>>>' => 11, |
||
692 | '+' => 12, '-' => 12, |
||
693 | '*' => 13, '/' => 13, '%' => 13, |
||
694 | 'delete' => 14, 'void' => 14, 'typeof' => 14, |
||
695 | '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14, |
||
696 | '++' => 15, '--' => 15, |
||
697 | 'new' => 16, |
||
698 | '.' => 17, |
||
699 | JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0, |
||
700 | JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0 |
||
701 | ); |
||
702 | |||
703 | private $opArity = array( |
||
704 | ',' => -2, |
||
705 | '=' => 2, |
||
706 | '?' => 3, |
||
707 | '||' => 2, |
||
708 | '&&' => 2, |
||
709 | '|' => 2, |
||
710 | '^' => 2, |
||
711 | '&' => 2, |
||
712 | '==' => 2, '!=' => 2, '===' => 2, '!==' => 2, |
||
713 | '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2, |
||
714 | '<<' => 2, '>>' => 2, '>>>' => 2, |
||
715 | '+' => 2, '-' => 2, |
||
716 | '*' => 2, '/' => 2, '%' => 2, |
||
717 | 'delete' => 1, 'void' => 1, 'typeof' => 1, |
||
718 | '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1, |
||
719 | '++' => 1, '--' => 1, |
||
720 | 'new' => 1, |
||
721 | '.' => 2, |
||
722 | JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2, |
||
723 | JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1, |
||
724 | TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1 |
||
725 | ); |
||
726 | |||
727 | public function __construct($minifier=null) |
||
728 | { |
||
729 | $this->minifier = $minifier; |
||
730 | $this->t = new JSTokenizer(); |
||
731 | } |
||
732 | |||
733 | public function parse($s, $f, $l) |
||
734 | { |
||
735 | // initialize tokenizer |
||
736 | $this->t->init($s, $f, $l); |
||
737 | |||
738 | $x = new JSCompilerContext(false); |
||
739 | $n = $this->Script($x); |
||
740 | if (!$this->t->isDone()) |
||
741 | throw $this->t->newSyntaxError('Syntax error'); |
||
742 | |||
743 | return $n; |
||
744 | } |
||
745 | |||
746 | private function Script($x) |
||
747 | { |
||
748 | $n = $this->Statements($x); |
||
749 | $n->type = JS_SCRIPT; |
||
750 | $n->funDecls = $x->funDecls; |
||
751 | $n->varDecls = $x->varDecls; |
||
752 | |||
753 | // minify by scope |
||
754 | if ($this->minifier) |
||
755 | { |
||
756 | $n->value = $this->minifier->parseTree($n); |
||
0 ignored issues
–
show
|
|||
757 | |||
758 | // clear tree from node to save memory |
||
759 | $n->treeNodes = null; |
||
0 ignored issues
–
show
It seems like
null of type null is incompatible with the declared type array of property $treeNodes .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||
760 | $n->funDecls = null; |
||
0 ignored issues
–
show
It seems like
null of type null is incompatible with the declared type array of property $funDecls .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||
761 | $n->varDecls = null; |
||
0 ignored issues
–
show
It seems like
null of type null is incompatible with the declared type array of property $varDecls .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||
762 | |||
763 | $n->type = JS_MINIFIED; |
||
0 ignored issues
–
show
The property
$type is declared private in JSNode . Since you implemented __set() , maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
764 | } |
||
765 | |||
766 | return $n; |
||
767 | } |
||
768 | |||
769 | private function Statements($x) |
||
770 | { |
||
771 | $n = new JSNode($this->t, JS_BLOCK); |
||
772 | array_push($x->stmtStack, $n); |
||
773 | |||
774 | while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY) |
||
775 | $n->addNode($this->Statement($x)); |
||
776 | |||
777 | array_pop($x->stmtStack); |
||
778 | |||
779 | return $n; |
||
780 | } |
||
781 | |||
782 | private function Block($x) |
||
783 | { |
||
784 | $this->t->mustMatch(OP_LEFT_CURLY); |
||
785 | $n = $this->Statements($x); |
||
786 | $this->t->mustMatch(OP_RIGHT_CURLY); |
||
787 | |||
788 | return $n; |
||
789 | } |
||
790 | |||
791 | private function Statement($x) |
||
792 | { |
||
793 | $tt = $this->t->get(); |
||
794 | $n2 = null; |
||
795 | |||
796 | // Cases for statements ending in a right curly return early, avoiding the |
||
797 | // common semicolon insertion magic after this switch. |
||
798 | switch ($tt) |
||
799 | { |
||
800 | case KEYWORD_FUNCTION: |
||
801 | return $this->FunctionDefinition( |
||
802 | $x, |
||
803 | true, |
||
804 | count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM |
||
805 | ); |
||
806 | break; |
||
807 | |||
808 | case OP_LEFT_CURLY: |
||
809 | $n = $this->Statements($x); |
||
810 | $this->t->mustMatch(OP_RIGHT_CURLY); |
||
811 | return $n; |
||
812 | |||
813 | case KEYWORD_IF: |
||
814 | $n = new JSNode($this->t); |
||
815 | $n->condition = $this->ParenExpression($x); |
||
816 | array_push($x->stmtStack, $n); |
||
817 | $n->thenPart = $this->Statement($x); |
||
818 | $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null; |
||
819 | array_pop($x->stmtStack); |
||
820 | return $n; |
||
821 | |||
822 | case KEYWORD_SWITCH: |
||
823 | $n = new JSNode($this->t); |
||
824 | $this->t->mustMatch(OP_LEFT_PAREN); |
||
825 | $n->discriminant = $this->Expression($x); |
||
826 | $this->t->mustMatch(OP_RIGHT_PAREN); |
||
827 | $n->cases = array(); |
||
828 | $n->defaultIndex = -1; |
||
829 | |||
830 | array_push($x->stmtStack, $n); |
||
831 | |||
832 | $this->t->mustMatch(OP_LEFT_CURLY); |
||
833 | |||
834 | while (($tt = $this->t->get()) != OP_RIGHT_CURLY) |
||
835 | { |
||
836 | switch ($tt) |
||
837 | { |
||
838 | case KEYWORD_DEFAULT: |
||
839 | if ($n->defaultIndex >= 0) |
||
840 | throw $this->t->newSyntaxError('More than one switch default'); |
||
841 | // FALL THROUGH |
||
842 | case KEYWORD_CASE: |
||
843 | $n2 = new JSNode($this->t); |
||
844 | if ($tt == KEYWORD_DEFAULT) |
||
845 | $n->defaultIndex = count($n->cases); |
||
846 | else |
||
847 | $n2->caseLabel = $this->Expression($x, OP_COLON); |
||
848 | break; |
||
849 | default: |
||
850 | throw $this->t->newSyntaxError('Invalid switch case'); |
||
851 | } |
||
852 | |||
853 | $this->t->mustMatch(OP_COLON); |
||
854 | $n2->statements = new JSNode($this->t, JS_BLOCK); |
||
855 | while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY) |
||
856 | $n2->statements->addNode($this->Statement($x)); |
||
857 | |||
858 | array_push($n->cases, $n2); |
||
859 | } |
||
860 | |||
861 | array_pop($x->stmtStack); |
||
862 | return $n; |
||
863 | |||
864 | case KEYWORD_FOR: |
||
865 | $n = new JSNode($this->t); |
||
866 | $n->isLoop = true; |
||
867 | $this->t->mustMatch(OP_LEFT_PAREN); |
||
868 | |||
869 | if (($tt = $this->t->peek()) != OP_SEMICOLON) |
||
870 | { |
||
871 | $x->inForLoopInit = true; |
||
872 | if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST) |
||
873 | { |
||
874 | $this->t->get(); |
||
875 | $n2 = $this->Variables($x); |
||
876 | } |
||
877 | else |
||
878 | { |
||
879 | $n2 = $this->Expression($x); |
||
880 | } |
||
881 | $x->inForLoopInit = false; |
||
882 | } |
||
883 | |||
884 | if ($n2 && $this->t->match(KEYWORD_IN)) |
||
885 | { |
||
886 | $n->type = JS_FOR_IN; |
||
887 | if ($n2->type == KEYWORD_VAR) |
||
888 | { |
||
889 | if (count($n2->treeNodes) != 1) |
||
890 | { |
||
891 | throw $this->t->SyntaxError( |
||
892 | 'Invalid for..in left-hand side', |
||
893 | $this->t->filename, |
||
894 | $n2->lineno |
||
895 | ); |
||
896 | } |
||
897 | |||
898 | // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name. |
||
899 | $n->iterator = $n2->treeNodes[0]; |
||
900 | $n->varDecl = $n2; |
||
901 | } |
||
902 | else |
||
903 | { |
||
904 | $n->iterator = $n2; |
||
905 | $n->varDecl = null; |
||
906 | } |
||
907 | |||
908 | $n->object = $this->Expression($x); |
||
909 | } |
||
910 | else |
||
911 | { |
||
912 | $n->setup = $n2 ? $n2 : null; |
||
913 | $this->t->mustMatch(OP_SEMICOLON); |
||
914 | $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x); |
||
915 | $this->t->mustMatch(OP_SEMICOLON); |
||
916 | $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x); |
||
917 | } |
||
918 | |||
919 | $this->t->mustMatch(OP_RIGHT_PAREN); |
||
920 | $n->body = $this->nest($x, $n); |
||
921 | return $n; |
||
922 | |||
923 | case KEYWORD_WHILE: |
||
924 | $n = new JSNode($this->t); |
||
925 | $n->isLoop = true; |
||
926 | $n->condition = $this->ParenExpression($x); |
||
927 | $n->body = $this->nest($x, $n); |
||
928 | return $n; |
||
929 | |||
930 | case KEYWORD_DO: |
||
931 | $n = new JSNode($this->t); |
||
932 | $n->isLoop = true; |
||
933 | $n->body = $this->nest($x, $n, KEYWORD_WHILE); |
||
934 | $n->condition = $this->ParenExpression($x); |
||
935 | if (!$x->ecmaStrictMode) |
||
936 | { |
||
937 | // <script language="JavaScript"> (without version hints) may need |
||
938 | // automatic semicolon insertion without a newline after do-while. |
||
939 | // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945. |
||
940 | $this->t->match(OP_SEMICOLON); |
||
941 | return $n; |
||
942 | } |
||
943 | break; |
||
944 | |||
945 | case KEYWORD_BREAK: |
||
946 | case KEYWORD_CONTINUE: |
||
947 | $n = new JSNode($this->t); |
||
948 | |||
949 | if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER) |
||
950 | { |
||
951 | $this->t->get(); |
||
952 | $n->label = $this->t->currentToken()->value; |
||
953 | } |
||
954 | |||
955 | $ss = $x->stmtStack; |
||
956 | $i = count($ss); |
||
957 | $label = $n->label; |
||
958 | if ($label) |
||
959 | { |
||
960 | do |
||
961 | { |
||
962 | if (--$i < 0) |
||
963 | throw $this->t->newSyntaxError('Label not found'); |
||
964 | } |
||
965 | while ($ss[$i]->label != $label); |
||
966 | } |
||
967 | else |
||
968 | { |
||
969 | do |
||
970 | { |
||
971 | if (--$i < 0) |
||
972 | throw $this->t->newSyntaxError('Invalid ' . $tt); |
||
973 | } |
||
974 | while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH)); |
||
975 | } |
||
976 | |||
977 | $n->target = $ss[$i]; |
||
978 | break; |
||
979 | |||
980 | case KEYWORD_TRY: |
||
981 | $n = new JSNode($this->t); |
||
982 | $n->tryBlock = $this->Block($x); |
||
983 | $n->catchClauses = array(); |
||
984 | |||
985 | while ($this->t->match(KEYWORD_CATCH)) |
||
986 | { |
||
987 | $n2 = new JSNode($this->t); |
||
988 | $this->t->mustMatch(OP_LEFT_PAREN); |
||
989 | $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value; |
||
990 | |||
991 | if ($this->t->match(KEYWORD_IF)) |
||
992 | { |
||
993 | if ($x->ecmaStrictMode) |
||
994 | throw $this->t->newSyntaxError('Illegal catch guard'); |
||
995 | |||
996 | if (count($n->catchClauses) && !end($n->catchClauses)->guard) |
||
997 | throw $this->t->newSyntaxError('Guarded catch after unguarded'); |
||
998 | |||
999 | $n2->guard = $this->Expression($x); |
||
1000 | } |
||
1001 | else |
||
1002 | { |
||
1003 | $n2->guard = null; |
||
1004 | } |
||
1005 | |||
1006 | $this->t->mustMatch(OP_RIGHT_PAREN); |
||
1007 | $n2->block = $this->Block($x); |
||
1008 | array_push($n->catchClauses, $n2); |
||
1009 | } |
||
1010 | |||
1011 | if ($this->t->match(KEYWORD_FINALLY)) |
||
1012 | $n->finallyBlock = $this->Block($x); |
||
1013 | |||
1014 | if (!count($n->catchClauses) && !$n->finallyBlock) |
||
1015 | throw $this->t->newSyntaxError('Invalid try statement'); |
||
1016 | return $n; |
||
1017 | |||
1018 | case KEYWORD_CATCH: |
||
1019 | case KEYWORD_FINALLY: |
||
1020 | throw $this->t->newSyntaxError($tt . ' without preceding try'); |
||
1021 | |||
1022 | case KEYWORD_THROW: |
||
1023 | $n = new JSNode($this->t); |
||
1024 | $n->value = $this->Expression($x); |
||
0 ignored issues
–
show
The property
$value is declared private in JSNode . Since you implemented __set() , maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
1025 | break; |
||
1026 | |||
1027 | case KEYWORD_RETURN: |
||
1028 | if (!$x->inFunction) |
||
1029 | throw $this->t->newSyntaxError('Invalid return'); |
||
1030 | |||
1031 | $n = new JSNode($this->t); |
||
1032 | $tt = $this->t->peekOnSameLine(); |
||
1033 | if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY) |
||
1034 | $n->value = $this->Expression($x); |
||
1035 | else |
||
1036 | $n->value = null; |
||
1037 | break; |
||
1038 | |||
1039 | case KEYWORD_WITH: |
||
1040 | $n = new JSNode($this->t); |
||
1041 | $n->object = $this->ParenExpression($x); |
||
1042 | $n->body = $this->nest($x, $n); |
||
1043 | return $n; |
||
1044 | |||
1045 | case KEYWORD_VAR: |
||
1046 | case KEYWORD_CONST: |
||
1047 | $n = $this->Variables($x); |
||
1048 | break; |
||
1049 | |||
1050 | case TOKEN_CONDCOMMENT_START: |
||
1051 | case TOKEN_CONDCOMMENT_END: |
||
1052 | $n = new JSNode($this->t); |
||
1053 | return $n; |
||
1054 | |||
1055 | case KEYWORD_DEBUGGER: |
||
1056 | $n = new JSNode($this->t); |
||
1057 | break; |
||
1058 | |||
1059 | case TOKEN_NEWLINE: |
||
1060 | case OP_SEMICOLON: |
||
1061 | $n = new JSNode($this->t, OP_SEMICOLON); |
||
1062 | $n->expression = null; |
||
1063 | return $n; |
||
1064 | |||
1065 | default: |
||
1066 | if ($tt == TOKEN_IDENTIFIER) |
||
1067 | { |
||
1068 | $this->t->scanOperand = false; |
||
1069 | $tt = $this->t->peek(); |
||
1070 | $this->t->scanOperand = true; |
||
1071 | if ($tt == OP_COLON) |
||
1072 | { |
||
1073 | $label = $this->t->currentToken()->value; |
||
1074 | $ss = $x->stmtStack; |
||
1075 | for ($i = count($ss) - 1; $i >= 0; --$i) |
||
1076 | { |
||
1077 | if ($ss[$i]->label == $label) |
||
1078 | throw $this->t->newSyntaxError('Duplicate label'); |
||
1079 | } |
||
1080 | |||
1081 | $this->t->get(); |
||
1082 | $n = new JSNode($this->t, JS_LABEL); |
||
1083 | $n->label = $label; |
||
1084 | $n->statement = $this->nest($x, $n); |
||
1085 | |||
1086 | return $n; |
||
1087 | } |
||
1088 | } |
||
1089 | |||
1090 | $n = new JSNode($this->t, OP_SEMICOLON); |
||
1091 | $this->t->unget(); |
||
1092 | $n->expression = $this->Expression($x); |
||
1093 | $n->end = $n->expression->end; |
||
1094 | break; |
||
1095 | } |
||
1096 | |||
1097 | if ($this->t->lineno == $this->t->currentToken()->lineno) |
||
1098 | { |
||
1099 | $tt = $this->t->peekOnSameLine(); |
||
1100 | if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY) |
||
1101 | throw $this->t->newSyntaxError('Missing ; before statement'); |
||
1102 | } |
||
1103 | |||
1104 | $this->t->match(OP_SEMICOLON); |
||
1105 | |||
1106 | return $n; |
||
1107 | } |
||
1108 | |||
1109 | private function FunctionDefinition($x, $requireName, $functionForm) |
||
1110 | { |
||
1111 | $f = new JSNode($this->t); |
||
1112 | |||
1113 | if ($f->type != KEYWORD_FUNCTION) |
||
1114 | $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER; |
||
1115 | |||
1116 | if ($this->t->match(TOKEN_IDENTIFIER)) |
||
1117 | $f->name = $this->t->currentToken()->value; |
||
1118 | elseif ($requireName) |
||
1119 | throw $this->t->newSyntaxError('Missing function identifier'); |
||
1120 | |||
1121 | $this->t->mustMatch(OP_LEFT_PAREN); |
||
1122 | $f->params = array(); |
||
1123 | |||
1124 | while (($tt = $this->t->get()) != OP_RIGHT_PAREN) |
||
1125 | { |
||
1126 | if ($tt != TOKEN_IDENTIFIER) |
||
1127 | throw $this->t->newSyntaxError('Missing formal parameter'); |
||
1128 | |||
1129 | array_push($f->params, $this->t->currentToken()->value); |
||
1130 | |||
1131 | if ($this->t->peek() != OP_RIGHT_PAREN) |
||
1132 | $this->t->mustMatch(OP_COMMA); |
||
1133 | } |
||
1134 | |||
1135 | $this->t->mustMatch(OP_LEFT_CURLY); |
||
1136 | |||
1137 | $x2 = new JSCompilerContext(true); |
||
1138 | $f->body = $this->Script($x2); |
||
1139 | |||
1140 | $this->t->mustMatch(OP_RIGHT_CURLY); |
||
1141 | $f->end = $this->t->currentToken()->end; |
||
1142 | |||
1143 | $f->functionForm = $functionForm; |
||
1144 | if ($functionForm == DECLARED_FORM) |
||
1145 | array_push($x->funDecls, $f); |
||
1146 | |||
1147 | return $f; |
||
1148 | } |
||
1149 | |||
1150 | private function Variables($x) |
||
1151 | { |
||
1152 | $n = new JSNode($this->t); |
||
1153 | |||
1154 | do |
||
1155 | { |
||
1156 | $this->t->mustMatch(TOKEN_IDENTIFIER); |
||
1157 | |||
1158 | $n2 = new JSNode($this->t); |
||
1159 | $n2->name = $n2->value; |
||
1160 | |||
1161 | if ($this->t->match(OP_ASSIGN)) |
||
1162 | { |
||
1163 | if ($this->t->currentToken()->assignOp) |
||
1164 | throw $this->t->newSyntaxError('Invalid variable initialization'); |
||
1165 | |||
1166 | $n2->initializer = $this->Expression($x, OP_COMMA); |
||
1167 | } |
||
1168 | |||
1169 | $n2->readOnly = $n->type == KEYWORD_CONST; |
||
1170 | |||
1171 | $n->addNode($n2); |
||
1172 | array_push($x->varDecls, $n2); |
||
1173 | } |
||
1174 | while ($this->t->match(OP_COMMA)); |
||
1175 | |||
1176 | return $n; |
||
1177 | } |
||
1178 | |||
1179 | private function Expression($x, $stop=false) |
||
1180 | { |
||
1181 | $operators = array(); |
||
1182 | $operands = array(); |
||
1183 | $n = false; |
||
1184 | |||
1185 | $bl = $x->bracketLevel; |
||
1186 | $cl = $x->curlyLevel; |
||
1187 | $pl = $x->parenLevel; |
||
1188 | $hl = $x->hookLevel; |
||
1189 | |||
1190 | while (($tt = $this->t->get()) != TOKEN_END) |
||
1191 | { |
||
1192 | if ($tt == $stop && |
||
1193 | $x->bracketLevel == $bl && |
||
1194 | $x->curlyLevel == $cl && |
||
1195 | $x->parenLevel == $pl && |
||
1196 | $x->hookLevel == $hl |
||
1197 | ) |
||
1198 | { |
||
1199 | // Stop only if tt matches the optional stop parameter, and that |
||
1200 | // token is not quoted by some kind of bracket. |
||
1201 | break; |
||
1202 | } |
||
1203 | |||
1204 | switch ($tt) |
||
1205 | { |
||
1206 | case OP_SEMICOLON: |
||
1207 | // NB: cannot be empty, Statement handled that. |
||
1208 | break 2; |
||
1209 | |||
1210 | case OP_HOOK: |
||
1211 | if ($this->t->scanOperand) |
||
1212 | break 2; |
||
1213 | |||
1214 | View Code Duplication | while ( !empty($operators) && |
|
1215 | $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] |
||
1216 | ) |
||
1217 | $this->reduce($operators, $operands); |
||
1218 | |||
1219 | array_push($operators, new JSNode($this->t)); |
||
1220 | |||
1221 | ++$x->hookLevel; |
||
1222 | $this->t->scanOperand = true; |
||
1223 | $n = $this->Expression($x); |
||
1224 | |||
1225 | if (!$this->t->match(OP_COLON)) |
||
1226 | break 2; |
||
1227 | |||
1228 | --$x->hookLevel; |
||
1229 | array_push($operands, $n); |
||
1230 | break; |
||
1231 | |||
1232 | case OP_COLON: |
||
1233 | if ($x->hookLevel) |
||
1234 | break 2; |
||
1235 | |||
1236 | throw $this->t->newSyntaxError('Invalid label'); |
||
1237 | break; |
||
1238 | |||
1239 | case OP_ASSIGN: |
||
1240 | if ($this->t->scanOperand) |
||
1241 | break 2; |
||
1242 | |||
1243 | // Use >, not >=, for right-associative ASSIGN |
||
1244 | View Code Duplication | while ( !empty($operators) && |
|
1245 | $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] |
||
1246 | ) |
||
1247 | $this->reduce($operators, $operands); |
||
1248 | |||
1249 | array_push($operators, new JSNode($this->t)); |
||
1250 | end($operands)->assignOp = $this->t->currentToken()->assignOp; |
||
1251 | $this->t->scanOperand = true; |
||
1252 | break; |
||
1253 | |||
1254 | View Code Duplication | case KEYWORD_IN: |
|
1255 | // An in operator should not be parsed if we're parsing the head of |
||
1256 | // a for (...) loop, unless it is in the then part of a conditional |
||
1257 | // expression, or parenthesized somehow. |
||
1258 | if ($x->inForLoopInit && !$x->hookLevel && |
||
1259 | !$x->bracketLevel && !$x->curlyLevel && |
||
1260 | !$x->parenLevel |
||
1261 | ) |
||
1262 | break 2; |
||
1263 | // FALL THROUGH |
||
1264 | View Code Duplication | case OP_COMMA: |
|
1265 | // A comma operator should not be parsed if we're parsing the then part |
||
1266 | // of a conditional expression unless it's parenthesized somehow. |
||
1267 | if ($tt == OP_COMMA && $x->hookLevel && |
||
1268 | !$x->bracketLevel && !$x->curlyLevel && |
||
1269 | !$x->parenLevel |
||
1270 | ) |
||
1271 | break 2; |
||
1272 | // Treat comma as left-associative so reduce can fold left-heavy |
||
1273 | // COMMA trees into a single array. |
||
1274 | // FALL THROUGH |
||
1275 | case OP_OR: |
||
1276 | case OP_AND: |
||
1277 | case OP_BITWISE_OR: |
||
1278 | case OP_BITWISE_XOR: |
||
1279 | case OP_BITWISE_AND: |
||
1280 | case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE: |
||
1281 | case OP_LT: case OP_LE: case OP_GE: case OP_GT: |
||
1282 | case KEYWORD_INSTANCEOF: |
||
1283 | case OP_LSH: case OP_RSH: case OP_URSH: |
||
1284 | case OP_PLUS: case OP_MINUS: |
||
1285 | case OP_MUL: case OP_DIV: case OP_MOD: |
||
1286 | case OP_DOT: |
||
1287 | if ($this->t->scanOperand) |
||
1288 | break 2; |
||
1289 | |||
1290 | View Code Duplication | while ( !empty($operators) && |
|
1291 | $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt] |
||
1292 | ) |
||
1293 | $this->reduce($operators, $operands); |
||
1294 | |||
1295 | if ($tt == OP_DOT) |
||
1296 | { |
||
1297 | $this->t->mustMatch(TOKEN_IDENTIFIER); |
||
1298 | array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t))); |
||
1299 | } |
||
1300 | else |
||
1301 | { |
||
1302 | array_push($operators, new JSNode($this->t)); |
||
1303 | $this->t->scanOperand = true; |
||
1304 | } |
||
1305 | break; |
||
1306 | |||
1307 | case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF: |
||
1308 | case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS: |
||
1309 | View Code Duplication | case KEYWORD_NEW: |
|
1310 | if (!$this->t->scanOperand) |
||
1311 | break 2; |
||
1312 | |||
1313 | array_push($operators, new JSNode($this->t)); |
||
1314 | break; |
||
1315 | |||
1316 | case OP_INCREMENT: case OP_DECREMENT: |
||
1317 | if ($this->t->scanOperand) |
||
1318 | { |
||
1319 | array_push($operators, new JSNode($this->t)); // prefix increment or decrement |
||
1320 | } |
||
1321 | else |
||
1322 | { |
||
1323 | // Don't cross a line boundary for postfix {in,de}crement. |
||
1324 | $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3]; |
||
1325 | if ($t && $t->lineno != $this->t->lineno) |
||
1326 | break 2; |
||
1327 | |||
1328 | if (!empty($operators)) |
||
1329 | { |
||
1330 | // Use >, not >=, so postfix has higher precedence than prefix. |
||
1331 | View Code Duplication | while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]) |
|
1332 | $this->reduce($operators, $operands); |
||
1333 | } |
||
1334 | |||
1335 | $n = new JSNode($this->t, $tt, array_pop($operands)); |
||
1336 | $n->postfix = true; |
||
1337 | array_push($operands, $n); |
||
1338 | } |
||
1339 | break; |
||
1340 | |||
1341 | case KEYWORD_FUNCTION: |
||
1342 | if (!$this->t->scanOperand) |
||
1343 | break 2; |
||
1344 | |||
1345 | array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM)); |
||
1346 | $this->t->scanOperand = false; |
||
1347 | break; |
||
1348 | |||
1349 | case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE: |
||
1350 | View Code Duplication | case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP: |
|
1351 | if (!$this->t->scanOperand) |
||
1352 | break 2; |
||
1353 | |||
1354 | array_push($operands, new JSNode($this->t)); |
||
1355 | $this->t->scanOperand = false; |
||
1356 | break; |
||
1357 | |||
1358 | case TOKEN_CONDCOMMENT_START: |
||
1359 | case TOKEN_CONDCOMMENT_END: |
||
1360 | if ($this->t->scanOperand) |
||
1361 | array_push($operators, new JSNode($this->t)); |
||
1362 | else |
||
1363 | array_push($operands, new JSNode($this->t)); |
||
1364 | break; |
||
1365 | |||
1366 | case OP_LEFT_BRACKET: |
||
1367 | if ($this->t->scanOperand) |
||
1368 | { |
||
1369 | // Array initialiser. Parse using recursive descent, as the |
||
1370 | // sub-grammar here is not an operator grammar. |
||
1371 | $n = new JSNode($this->t, JS_ARRAY_INIT); |
||
1372 | while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET) |
||
1373 | { |
||
1374 | if ($tt == OP_COMMA) |
||
1375 | { |
||
1376 | $this->t->get(); |
||
1377 | $n->addNode(null); |
||
1378 | continue; |
||
1379 | } |
||
1380 | |||
1381 | $n->addNode($this->Expression($x, OP_COMMA)); |
||
1382 | if (!$this->t->match(OP_COMMA)) |
||
1383 | break; |
||
1384 | } |
||
1385 | |||
1386 | $this->t->mustMatch(OP_RIGHT_BRACKET); |
||
1387 | array_push($operands, $n); |
||
1388 | $this->t->scanOperand = false; |
||
1389 | } |
||
1390 | else |
||
1391 | { |
||
1392 | // Property indexing operator. |
||
1393 | array_push($operators, new JSNode($this->t, JS_INDEX)); |
||
1394 | $this->t->scanOperand = true; |
||
1395 | ++$x->bracketLevel; |
||
1396 | } |
||
1397 | break; |
||
1398 | |||
1399 | case OP_RIGHT_BRACKET: |
||
1400 | if ($this->t->scanOperand || $x->bracketLevel == $bl) |
||
1401 | break 2; |
||
1402 | |||
1403 | while ($this->reduce($operators, $operands)->type != JS_INDEX) |
||
1404 | continue; |
||
1405 | |||
1406 | --$x->bracketLevel; |
||
1407 | break; |
||
1408 | |||
1409 | case OP_LEFT_CURLY: |
||
1410 | if (!$this->t->scanOperand) |
||
1411 | break 2; |
||
1412 | |||
1413 | // Object initialiser. As for array initialisers (see above), |
||
1414 | // parse using recursive descent. |
||
1415 | ++$x->curlyLevel; |
||
1416 | $n = new JSNode($this->t, JS_OBJECT_INIT); |
||
1417 | while (!$this->t->match(OP_RIGHT_CURLY)) |
||
1418 | { |
||
1419 | do |
||
1420 | { |
||
1421 | $tt = $this->t->get(); |
||
1422 | $tv = $this->t->currentToken()->value; |
||
1423 | if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER) |
||
1424 | { |
||
1425 | if ($x->ecmaStrictMode) |
||
1426 | throw $this->t->newSyntaxError('Illegal property accessor'); |
||
1427 | |||
1428 | $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM)); |
||
1429 | } |
||
1430 | else |
||
1431 | { |
||
1432 | switch ($tt) |
||
1433 | { |
||
1434 | case TOKEN_IDENTIFIER: |
||
1435 | case TOKEN_NUMBER: |
||
1436 | case TOKEN_STRING: |
||
1437 | $id = new JSNode($this->t); |
||
1438 | break; |
||
1439 | |||
1440 | case OP_RIGHT_CURLY: |
||
1441 | if ($x->ecmaStrictMode) |
||
1442 | throw $this->t->newSyntaxError('Illegal trailing ,'); |
||
1443 | break 3; |
||
1444 | |||
1445 | default: |
||
1446 | throw $this->t->newSyntaxError('Invalid property name'); |
||
1447 | } |
||
1448 | |||
1449 | $this->t->mustMatch(OP_COLON); |
||
1450 | $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA))); |
||
1451 | } |
||
1452 | } |
||
1453 | while ($this->t->match(OP_COMMA)); |
||
1454 | |||
1455 | $this->t->mustMatch(OP_RIGHT_CURLY); |
||
1456 | break; |
||
1457 | } |
||
1458 | |||
1459 | array_push($operands, $n); |
||
1460 | $this->t->scanOperand = false; |
||
1461 | --$x->curlyLevel; |
||
1462 | break; |
||
1463 | |||
1464 | case OP_RIGHT_CURLY: |
||
1465 | if (!$this->t->scanOperand && $x->curlyLevel != $cl) |
||
1466 | throw new Exception('PANIC: right curly botch'); |
||
1467 | break 2; |
||
1468 | |||
1469 | case OP_LEFT_PAREN: |
||
1470 | if ($this->t->scanOperand) |
||
1471 | { |
||
1472 | array_push($operators, new JSNode($this->t, JS_GROUP)); |
||
1473 | } |
||
1474 | else |
||
1475 | { |
||
1476 | View Code Duplication | while ( !empty($operators) && |
|
1477 | $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW] |
||
1478 | ) |
||
1479 | $this->reduce($operators, $operands); |
||
1480 | |||
1481 | // Handle () now, to regularize the n-ary case for n > 0. |
||
1482 | // We must set scanOperand in case there are arguments and |
||
1483 | // the first one is a regexp or unary+/-. |
||
1484 | $n = end($operators); |
||
1485 | $this->t->scanOperand = true; |
||
1486 | if ($this->t->match(OP_RIGHT_PAREN)) |
||
1487 | { |
||
1488 | if ($n && $n->type == KEYWORD_NEW) |
||
1489 | { |
||
1490 | array_pop($operators); |
||
1491 | $n->addNode(array_pop($operands)); |
||
1492 | } |
||
1493 | else |
||
1494 | { |
||
1495 | $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST)); |
||
1496 | } |
||
1497 | |||
1498 | array_push($operands, $n); |
||
1499 | $this->t->scanOperand = false; |
||
1500 | break; |
||
1501 | } |
||
1502 | |||
1503 | if ($n && $n->type == KEYWORD_NEW) |
||
1504 | $n->type = JS_NEW_WITH_ARGS; |
||
1505 | else |
||
1506 | array_push($operators, new JSNode($this->t, JS_CALL)); |
||
1507 | } |
||
1508 | |||
1509 | ++$x->parenLevel; |
||
1510 | break; |
||
1511 | |||
1512 | case OP_RIGHT_PAREN: |
||
1513 | if ($this->t->scanOperand || $x->parenLevel == $pl) |
||
1514 | break 2; |
||
1515 | |||
1516 | while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP && |
||
1517 | $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS |
||
1518 | ) |
||
1519 | { |
||
1520 | continue; |
||
1521 | } |
||
1522 | |||
1523 | if ($tt != JS_GROUP) |
||
1524 | { |
||
1525 | $n = end($operands); |
||
1526 | if ($n->treeNodes[1]->type != OP_COMMA) |
||
1527 | $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]); |
||
1528 | else |
||
1529 | $n->treeNodes[1]->type = JS_LIST; |
||
1530 | } |
||
1531 | |||
1532 | --$x->parenLevel; |
||
1533 | break; |
||
1534 | |||
1535 | // Automatic semicolon insertion means we may scan across a newline |
||
1536 | // and into the beginning of another statement. If so, break out of |
||
1537 | // the while loop and let the t.scanOperand logic handle errors. |
||
1538 | default: |
||
1539 | break 2; |
||
1540 | } |
||
1541 | } |
||
1542 | |||
1543 | if ($x->hookLevel != $hl) |
||
1544 | throw $this->t->newSyntaxError('Missing : in conditional expression'); |
||
1545 | |||
1546 | if ($x->parenLevel != $pl) |
||
1547 | throw $this->t->newSyntaxError('Missing ) in parenthetical'); |
||
1548 | |||
1549 | if ($x->bracketLevel != $bl) |
||
1550 | throw $this->t->newSyntaxError('Missing ] in index expression'); |
||
1551 | |||
1552 | if ($this->t->scanOperand) |
||
1553 | throw $this->t->newSyntaxError('Missing operand'); |
||
1554 | |||
1555 | // Resume default mode, scanning for operands, not operators. |
||
1556 | $this->t->scanOperand = true; |
||
1557 | $this->t->unget(); |
||
1558 | |||
1559 | while (count($operators)) |
||
1560 | $this->reduce($operators, $operands); |
||
1561 | |||
1562 | return array_pop($operands); |
||
1563 | } |
||
1564 | |||
1565 | private function ParenExpression($x) |
||
1566 | { |
||
1567 | $this->t->mustMatch(OP_LEFT_PAREN); |
||
1568 | $n = $this->Expression($x); |
||
1569 | $this->t->mustMatch(OP_RIGHT_PAREN); |
||
1570 | |||
1571 | return $n; |
||
1572 | } |
||
1573 | |||
1574 | // Statement stack and nested statement handler. |
||
1575 | private function nest($x, $node, $end = false) |
||
1576 | { |
||
1577 | array_push($x->stmtStack, $node); |
||
1578 | $n = $this->statement($x); |
||
1579 | array_pop($x->stmtStack); |
||
1580 | |||
1581 | if ($end) |
||
1582 | $this->t->mustMatch($end); |
||
1583 | |||
1584 | return $n; |
||
1585 | } |
||
1586 | |||
1587 | private function reduce(&$operators, &$operands) |
||
1588 | { |
||
1589 | $n = array_pop($operators); |
||
1590 | $op = $n->type; |
||
1591 | $arity = $this->opArity[$op]; |
||
1592 | $c = count($operands); |
||
1593 | if ($arity == -2) |
||
1594 | { |
||
1595 | // Flatten left-associative trees |
||
1596 | if ($c >= 2) |
||
1597 | { |
||
1598 | $left = $operands[$c - 2]; |
||
1599 | if ($left->type == $op) |
||
1600 | { |
||
1601 | $right = array_pop($operands); |
||
1602 | $left->addNode($right); |
||
1603 | return $left; |
||
1604 | } |
||
1605 | } |
||
1606 | $arity = 2; |
||
1607 | } |
||
1608 | |||
1609 | // Always use push to add operands to n, to update start and end |
||
1610 | $a = array_splice($operands, $c - $arity); |
||
1611 | for ($i = 0; $i < $arity; $i++) |
||
1612 | $n->addNode($a[$i]); |
||
1613 | |||
1614 | // Include closing bracket or postfix operator in [start,end] |
||
1615 | $te = $this->t->currentToken()->end; |
||
1616 | if ($n->end < $te) |
||
1617 | $n->end = $te; |
||
1618 | |||
1619 | array_push($operands, $n); |
||
1620 | |||
1621 | return $n; |
||
1622 | } |
||
1623 | } |
||
1624 | |||
1625 | class JSCompilerContext |
||
1626 | { |
||
1627 | public $inFunction = false; |
||
1628 | public $inForLoopInit = false; |
||
1629 | public $ecmaStrictMode = false; |
||
1630 | public $bracketLevel = 0; |
||
1631 | public $curlyLevel = 0; |
||
1632 | public $parenLevel = 0; |
||
1633 | public $hookLevel = 0; |
||
1634 | |||
1635 | public $stmtStack = array(); |
||
1636 | public $funDecls = array(); |
||
1637 | public $varDecls = array(); |
||
1638 | |||
1639 | public function __construct($inFunction) |
||
1640 | { |
||
1641 | $this->inFunction = $inFunction; |
||
1642 | } |
||
1643 | } |
||
1644 | |||
1645 | class JSNode |
||
1646 | { |
||
1647 | private $type; |
||
1648 | private $value; |
||
1649 | private $lineno; |
||
1650 | private $start; |
||
1651 | private $end; |
||
1652 | |||
1653 | public $treeNodes = array(); |
||
1654 | public $funDecls = array(); |
||
1655 | public $varDecls = array(); |
||
1656 | |||
1657 | public function __construct($t, $type=0) |
||
1658 | { |
||
1659 | if ($token = $t->currentToken()) |
||
1660 | { |
||
1661 | $this->type = $type ? $type : $token->type; |
||
1662 | $this->value = $token->value; |
||
1663 | $this->lineno = $token->lineno; |
||
1664 | $this->start = $token->start; |
||
1665 | $this->end = $token->end; |
||
1666 | } |
||
1667 | else |
||
1668 | { |
||
1669 | $this->type = $type; |
||
1670 | $this->lineno = $t->lineno; |
||
1671 | } |
||
1672 | |||
1673 | if (($numargs = func_num_args()) > 2) |
||
1674 | { |
||
1675 | $args = func_get_args(); |
||
1676 | for ($i = 2; $i < $numargs; $i++) |
||
1677 | $this->addNode($args[$i]); |
||
1678 | } |
||
1679 | } |
||
1680 | |||
1681 | // we don't want to bloat our object with all kind of specific properties, so we use overloading |
||
1682 | public function __set($name, $value) |
||
1683 | { |
||
1684 | $this->$name = $value; |
||
1685 | } |
||
1686 | |||
1687 | public function __get($name) |
||
1688 | { |
||
1689 | if (isset($this->$name)) |
||
1690 | return $this->$name; |
||
1691 | |||
1692 | return null; |
||
1693 | } |
||
1694 | |||
1695 | public function addNode($node) |
||
1696 | { |
||
1697 | if ($node !== null) |
||
1698 | { |
||
1699 | if ($node->start < $this->start) |
||
1700 | $this->start = $node->start; |
||
1701 | if ($this->end < $node->end) |
||
1702 | $this->end = $node->end; |
||
1703 | } |
||
1704 | |||
1705 | $this->treeNodes[] = $node; |
||
1706 | } |
||
1707 | } |
||
1708 | |||
1709 | class JSTokenizer |
||
1710 | { |
||
1711 | private $cursor = 0; |
||
1712 | private $source; |
||
1713 | |||
1714 | public $tokens = array(); |
||
1715 | public $tokenIndex = 0; |
||
1716 | public $lookahead = 0; |
||
1717 | public $scanNewlines = false; |
||
1718 | public $scanOperand = true; |
||
1719 | |||
1720 | public $filename; |
||
1721 | public $lineno; |
||
1722 | |||
1723 | private $keywords = array( |
||
1724 | 'break', |
||
1725 | 'case', 'catch', 'const', 'continue', |
||
1726 | 'debugger', 'default', 'delete', 'do', |
||
1727 | 'else', 'enum', |
||
1728 | 'false', 'finally', 'for', 'function', |
||
1729 | 'if', 'in', 'instanceof', |
||
1730 | 'new', 'null', |
||
1731 | 'return', |
||
1732 | 'switch', |
||
1733 | 'this', 'throw', 'true', 'try', 'typeof', |
||
1734 | 'var', 'void', |
||
1735 | 'while', 'with' |
||
1736 | ); |
||
1737 | |||
1738 | private $opTypeNames = array( |
||
1739 | ';', ',', '?', ':', '||', '&&', '|', '^', |
||
1740 | '&', '===', '==', '=', '!==', '!=', '<<', '<=', |
||
1741 | '<', '>>>', '>>', '>=', '>', '++', '--', '+', |
||
1742 | '-', '*', '/', '%', '!', '~', '.', '[', |
||
1743 | ']', '{', '}', '(', ')', '@*/' |
||
1744 | ); |
||
1745 | |||
1746 | private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%'); |
||
1747 | private $opRegExp; |
||
1748 | |||
1749 | public function __construct() |
||
1750 | { |
||
1751 | $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#'; |
||
1752 | } |
||
1753 | |||
1754 | public function init($source, $filename = '', $lineno = 1) |
||
1755 | { |
||
1756 | $this->source = $source; |
||
1757 | $this->filename = $filename ? $filename : '[inline]'; |
||
1758 | $this->lineno = $lineno; |
||
1759 | |||
1760 | $this->cursor = 0; |
||
1761 | $this->tokens = array(); |
||
1762 | $this->tokenIndex = 0; |
||
1763 | $this->lookahead = 0; |
||
1764 | $this->scanNewlines = false; |
||
1765 | $this->scanOperand = true; |
||
1766 | } |
||
1767 | |||
1768 | public function getInput($chunksize) |
||
1769 | { |
||
1770 | if ($chunksize) |
||
1771 | return substr($this->source, $this->cursor, $chunksize); |
||
1772 | |||
1773 | return substr($this->source, $this->cursor); |
||
1774 | } |
||
1775 | |||
1776 | public function isDone() |
||
1777 | { |
||
1778 | return $this->peek() == TOKEN_END; |
||
1779 | } |
||
1780 | |||
1781 | public function match($tt) |
||
1782 | { |
||
1783 | return $this->get() == $tt || $this->unget(); |
||
1784 | } |
||
1785 | |||
1786 | public function mustMatch($tt) |
||
1787 | { |
||
1788 | if (!$this->match($tt)) |
||
1789 | throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected'); |
||
1790 | |||
1791 | return $this->currentToken(); |
||
1792 | } |
||
1793 | |||
1794 | public function peek() |
||
1795 | { |
||
1796 | if ($this->lookahead) |
||
1797 | { |
||
1798 | $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3]; |
||
1799 | if ($this->scanNewlines && $next->lineno != $this->lineno) |
||
1800 | $tt = TOKEN_NEWLINE; |
||
1801 | else |
||
1802 | $tt = $next->type; |
||
1803 | } |
||
1804 | else |
||
1805 | { |
||
1806 | $tt = $this->get(); |
||
1807 | $this->unget(); |
||
1808 | } |
||
1809 | |||
1810 | return $tt; |
||
1811 | } |
||
1812 | |||
1813 | public function peekOnSameLine() |
||
1814 | { |
||
1815 | $this->scanNewlines = true; |
||
1816 | $tt = $this->peek(); |
||
1817 | $this->scanNewlines = false; |
||
1818 | |||
1819 | return $tt; |
||
1820 | } |
||
1821 | |||
1822 | public function currentToken() |
||
1823 | { |
||
1824 | if (!empty($this->tokens)) |
||
1825 | return $this->tokens[$this->tokenIndex]; |
||
1826 | } |
||
1827 | |||
1828 | public function get($chunksize = 1000) |
||
1829 | { |
||
1830 | while($this->lookahead) |
||
1831 | { |
||
1832 | $this->lookahead--; |
||
1833 | $this->tokenIndex = ($this->tokenIndex + 1) & 3; |
||
1834 | $token = $this->tokens[$this->tokenIndex]; |
||
1835 | if ($token->type != TOKEN_NEWLINE || $this->scanNewlines) |
||
1836 | return $token->type; |
||
1837 | } |
||
1838 | |||
1839 | $conditional_comment = false; |
||
1840 | |||
1841 | // strip whitespace and comments |
||
1842 | while(true) |
||
1843 | { |
||
1844 | $input = $this->getInput($chunksize); |
||
1845 | |||
1846 | // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!) |
||
1847 | $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/'; |
||
1848 | if (preg_match($re, $input, $match)) |
||
1849 | { |
||
1850 | $spaces = $match[0]; |
||
1851 | $spacelen = strlen($spaces); |
||
1852 | $this->cursor += $spacelen; |
||
1853 | if (!$this->scanNewlines) |
||
1854 | $this->lineno += substr_count($spaces, "\n"); |
||
1855 | |||
1856 | if ($spacelen == $chunksize) |
||
1857 | continue; // complete chunk contained whitespace |
||
1858 | |||
1859 | $input = $this->getInput($chunksize); |
||
1860 | if ($input == '' || $input[0] != '/') |
||
1861 | break; |
||
1862 | } |
||
1863 | |||
1864 | // Comments |
||
1865 | if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match)) |
||
1866 | { |
||
1867 | if (!$chunksize) |
||
1868 | break; |
||
1869 | |||
1870 | // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment) |
||
1871 | $chunksize = null; |
||
1872 | continue; |
||
1873 | } |
||
1874 | |||
1875 | // check if this is a conditional (JScript) comment |
||
1876 | if (!empty($match[1])) |
||
1877 | { |
||
1878 | $match[0] = '/*' . $match[1]; |
||
1879 | $conditional_comment = true; |
||
1880 | break; |
||
1881 | } |
||
1882 | else |
||
1883 | { |
||
1884 | $this->cursor += strlen($match[0]); |
||
1885 | $this->lineno += substr_count($match[0], "\n"); |
||
1886 | } |
||
1887 | } |
||
1888 | |||
1889 | if ($input == '') |
||
1890 | { |
||
1891 | $tt = TOKEN_END; |
||
1892 | $match = array(''); |
||
1893 | } |
||
1894 | elseif ($conditional_comment) |
||
1895 | { |
||
1896 | $tt = TOKEN_CONDCOMMENT_START; |
||
1897 | } |
||
1898 | else |
||
1899 | { |
||
1900 | switch ($input[0]) |
||
1901 | { |
||
1902 | case '0': |
||
1903 | // hexadecimal |
||
1904 | if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match)) |
||
1905 | { |
||
1906 | $tt = TOKEN_NUMBER; |
||
1907 | break; |
||
1908 | } |
||
1909 | // FALL THROUGH |
||
1910 | |||
1911 | case '1': case '2': case '3': case '4': case '5': |
||
1912 | case '6': case '7': case '8': case '9': |
||
1913 | // should always match |
||
1914 | preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match); |
||
1915 | $tt = TOKEN_NUMBER; |
||
1916 | break; |
||
1917 | |||
1918 | View Code Duplication | case "'": |
|
1919 | if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match)) |
||
1920 | { |
||
1921 | $tt = TOKEN_STRING; |
||
1922 | } |
||
1923 | else |
||
1924 | { |
||
1925 | if ($chunksize) |
||
0 ignored issues
–
show
The expression
$chunksize of type integer|null is loosely compared to true ; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
Loading history...
|
|||
1926 | return $this->get(null); // retry with a full chunk fetch |
||
1927 | |||
1928 | throw $this->newSyntaxError('Unterminated string literal'); |
||
1929 | } |
||
1930 | break; |
||
1931 | |||
1932 | View Code Duplication | case '"': |
|
1933 | if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match)) |
||
1934 | { |
||
1935 | $tt = TOKEN_STRING; |
||
1936 | } |
||
1937 | else |
||
1938 | { |
||
1939 | if ($chunksize) |
||
1940 | return $this->get(null); // retry with a full chunk fetch |
||
1941 | |||
1942 | throw $this->newSyntaxError('Unterminated string literal'); |
||
1943 | } |
||
1944 | break; |
||
1945 | |||
1946 | case '/': |
||
1947 | if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match)) |
||
1948 | { |
||
1949 | $tt = TOKEN_REGEXP; |
||
1950 | break; |
||
1951 | } |
||
1952 | // FALL THROUGH |
||
1953 | |||
1954 | case '|': |
||
1955 | case '^': |
||
1956 | case '&': |
||
1957 | case '<': |
||
1958 | case '>': |
||
1959 | case '+': |
||
1960 | case '-': |
||
1961 | case '*': |
||
1962 | case '%': |
||
1963 | case '=': |
||
1964 | case '!': |
||
1965 | // should always match |
||
1966 | preg_match($this->opRegExp, $input, $match); |
||
1967 | $op = $match[0]; |
||
1968 | if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=') |
||
1969 | { |
||
1970 | $tt = OP_ASSIGN; |
||
1971 | $match[0] .= '='; |
||
1972 | } |
||
1973 | else |
||
1974 | { |
||
1975 | $tt = $op; |
||
1976 | if ($this->scanOperand) |
||
1977 | { |
||
1978 | if ($op == OP_PLUS) |
||
1979 | $tt = OP_UNARY_PLUS; |
||
1980 | elseif ($op == OP_MINUS) |
||
1981 | $tt = OP_UNARY_MINUS; |
||
1982 | } |
||
1983 | $op = null; |
||
1984 | } |
||
1985 | break; |
||
1986 | |||
1987 | case '.': |
||
1988 | if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match)) |
||
1989 | { |
||
1990 | $tt = TOKEN_NUMBER; |
||
1991 | break; |
||
1992 | } |
||
1993 | // FALL THROUGH |
||
1994 | |||
1995 | case ';': |
||
1996 | case ',': |
||
1997 | case '?': |
||
1998 | case ':': |
||
1999 | case '~': |
||
2000 | case '[': |
||
2001 | case ']': |
||
2002 | case '{': |
||
2003 | case '}': |
||
2004 | case '(': |
||
2005 | case ')': |
||
2006 | // these are all single |
||
2007 | $match = array($input[0]); |
||
2008 | $tt = $input[0]; |
||
2009 | break; |
||
2010 | |||
2011 | case '@': |
||
2012 | // check end of conditional comment |
||
2013 | if (substr($input, 0, 3) == '@*/') |
||
2014 | { |
||
2015 | $match = array('@*/'); |
||
2016 | $tt = TOKEN_CONDCOMMENT_END; |
||
2017 | } |
||
2018 | else |
||
2019 | throw $this->newSyntaxError('Illegal token'); |
||
2020 | break; |
||
2021 | |||
2022 | case "\n": |
||
2023 | if ($this->scanNewlines) |
||
2024 | { |
||
2025 | $match = array("\n"); |
||
2026 | $tt = TOKEN_NEWLINE; |
||
2027 | } |
||
2028 | else |
||
2029 | throw $this->newSyntaxError('Illegal token'); |
||
2030 | break; |
||
2031 | |||
2032 | default: |
||
2033 | // Fast path for identifiers: word chars followed by whitespace or various other tokens. |
||
2034 | // Note we don't need to exclude digits in the first char, as they've already been found |
||
2035 | // above. |
||
2036 | if (!preg_match('/^[$\w]+(?=[\s\/\|\^\&<>\+\-\*%=!.;,\?:~\[\]\{\}\(\)@])/', $input, $match)) |
||
2037 | { |
||
2038 | // Character classes per ECMA-262 edition 5.1 section 7.6 |
||
2039 | // Per spec, must accept Unicode 3.0, *may* accept later versions. |
||
2040 | // We'll take whatever PCRE understands, which should be more recent. |
||
2041 | $identifierStartChars = "\\p{L}\\p{Nl}" . # UnicodeLetter |
||
2042 | "\$" . |
||
2043 | "_"; |
||
2044 | $identifierPartChars = $identifierStartChars . |
||
2045 | "\\p{Mn}\\p{Mc}" . # UnicodeCombiningMark |
||
2046 | "\\p{Nd}" . # UnicodeDigit |
||
2047 | "\\p{Pc}"; # UnicodeConnectorPunctuation |
||
2048 | $unicodeEscape = "\\\\u[0-9A-F-a-f]{4}"; |
||
2049 | $identifierRegex = "/^" . |
||
2050 | "(?:[$identifierStartChars]|$unicodeEscape)" . |
||
2051 | "(?:[$identifierPartChars]|$unicodeEscape)*" . |
||
2052 | "/uS"; |
||
2053 | if (preg_match($identifierRegex, $input, $match)) |
||
2054 | { |
||
2055 | if (strpos($match[0], '\\') !== false) { |
||
2056 | // Per ECMA-262 edition 5.1, section 7.6 escape sequences should behave as if they were |
||
2057 | // the original chars, but only within the boundaries of the identifier. |
||
2058 | $decoded = preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/', |
||
2059 | array(__CLASS__, 'unicodeEscapeCallback'), |
||
2060 | $match[0]); |
||
2061 | |||
2062 | // Since our original regex didn't de-escape the originals, we need to check for validity again. |
||
2063 | // No need to worry about token boundaries, as anything outside the identifier is illegal! |
||
2064 | if (!preg_match("/^[$identifierStartChars][$identifierPartChars]*$/u", $decoded)) { |
||
2065 | throw $this->newSyntaxError('Illegal token'); |
||
2066 | } |
||
2067 | |||
2068 | // Per spec it _ought_ to work to use these escapes for keywords words as well... |
||
2069 | // but IE rejects them as invalid, while Firefox and Chrome treat them as identifiers |
||
2070 | // that don't match the keyword. |
||
2071 | if (in_array($decoded, $this->keywords)) { |
||
2072 | throw $this->newSyntaxError('Illegal token'); |
||
2073 | } |
||
2074 | |||
2075 | // TODO: save the decoded form for output? |
||
2076 | } |
||
2077 | } |
||
2078 | else |
||
2079 | throw $this->newSyntaxError('Illegal token'); |
||
2080 | } |
||
2081 | $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER; |
||
2082 | } |
||
2083 | } |
||
2084 | |||
2085 | $this->tokenIndex = ($this->tokenIndex + 1) & 3; |
||
2086 | |||
2087 | if (!isset($this->tokens[$this->tokenIndex])) |
||
2088 | $this->tokens[$this->tokenIndex] = new JSToken(); |
||
2089 | |||
2090 | $token = $this->tokens[$this->tokenIndex]; |
||
2091 | $token->type = $tt; |
||
2092 | |||
2093 | if ($tt == OP_ASSIGN) |
||
2094 | $token->assignOp = $op; |
||
2095 | |||
2096 | $token->start = $this->cursor; |
||
2097 | |||
2098 | $token->value = $match[0]; |
||
2099 | $this->cursor += strlen($match[0]); |
||
2100 | |||
2101 | $token->end = $this->cursor; |
||
2102 | $token->lineno = $this->lineno; |
||
2103 | |||
2104 | return $tt; |
||
2105 | } |
||
2106 | |||
2107 | public function unget() |
||
2108 | { |
||
2109 | if (++$this->lookahead == 4) |
||
2110 | throw $this->newSyntaxError('PANIC: too much lookahead!'); |
||
2111 | |||
2112 | $this->tokenIndex = ($this->tokenIndex - 1) & 3; |
||
2113 | } |
||
2114 | |||
2115 | public function newSyntaxError($m) |
||
2116 | { |
||
2117 | return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno); |
||
2118 | } |
||
2119 | |||
2120 | public static function unicodeEscapeCallback($m) |
||
2121 | { |
||
2122 | return html_entity_decode('&#x' . $m[1]. ';', ENT_QUOTES, 'UTF-8'); |
||
2123 | } |
||
2124 | } |
||
2125 | |||
2126 | class JSToken |
||
2127 | { |
||
2128 | public $type; |
||
2129 | public $value; |
||
2130 | public $start; |
||
2131 | public $end; |
||
2132 | public $lineno; |
||
2133 | public $assignOp; |
||
2134 | } |
||
2135 |
Since your code implements the magic setter
_set
, this function will be called for any write access on an undefined variable. You can add the@property
annotation to your class or interface to document the existence of this variable.Since the property has write access only, you can use the @property-write annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.