MediaWiki REL1_41
jsminplus.php
Go to the documentation of this file.
1<?php
2// phpcs:ignoreFile -- File external to MediaWiki. Ignore coding conventions checks.
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 <brendan@mozilla.org>.
48 * Portions created by the Initial Developer are Copyright (C) 2004
49 * the Initial Developer. All Rights Reserved.
50 *
51 * Contributor(s): Tino Zijdel <crisp@tweakers.net>
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
68define('TOKEN_END', 1);
69define('TOKEN_NUMBER', 2);
70define('TOKEN_IDENTIFIER', 3);
71define('TOKEN_STRING', 4);
72define('TOKEN_REGEXP', 5);
73define('TOKEN_NEWLINE', 6);
74define('TOKEN_CONDCOMMENT_START', 7);
75define('TOKEN_CONDCOMMENT_END', 8);
76
77define('JS_SCRIPT', 100);
78define('JS_BLOCK', 101);
79define('JS_LABEL', 102);
80define('JS_FOR_IN', 103);
81define('JS_CALL', 104);
82define('JS_NEW_WITH_ARGS', 105);
83define('JS_INDEX', 106);
84define('JS_ARRAY_INIT', 107);
85define('JS_OBJECT_INIT', 108);
86define('JS_PROPERTY_INIT', 109);
87define('JS_GETTER', 110);
88define('JS_SETTER', 111);
89define('JS_GROUP', 112);
90define('JS_LIST', 113);
91
92define('JS_MINIFIED', 999);
93
94define('DECLARED_FORM', 0);
95define('EXPRESSED_FORM', 1);
96define('STATEMENT_FORM', 2);
97
98/* Operators */
99define('OP_SEMICOLON', ';');
100define('OP_COMMA', ',');
101define('OP_HOOK', '?');
102define('OP_COLON', ':');
103define('OP_OR', '||');
104define('OP_AND', '&&');
105define('OP_BITWISE_OR', '|');
106define('OP_BITWISE_XOR', '^');
107define('OP_BITWISE_AND', '&');
108define('OP_STRICT_EQ', '===');
109define('OP_EQ', '==');
110define('OP_ASSIGN', '=');
111define('OP_STRICT_NE', '!==');
112define('OP_NE', '!=');
113define('OP_LSH', '<<');
114define('OP_LE', '<=');
115define('OP_LT', '<');
116define('OP_URSH', '>>>');
117define('OP_RSH', '>>');
118define('OP_GE', '>=');
119define('OP_GT', '>');
120define('OP_INCREMENT', '++');
121define('OP_DECREMENT', '--');
122define('OP_PLUS', '+');
123define('OP_MINUS', '-');
124define('OP_MUL', '*');
125define('OP_DIV', '/');
126define('OP_MOD', '%');
127define('OP_NOT', '!');
128define('OP_BITWISE_NOT', '~');
129define('OP_DOT', '.');
130define('OP_LEFT_BRACKET', '[');
131define('OP_RIGHT_BRACKET', ']');
132define('OP_LEFT_CURLY', '{');
133define('OP_RIGHT_CURLY', '}');
134define('OP_LEFT_PAREN', '(');
135define('OP_RIGHT_PAREN', ')');
136define('OP_CONDCOMMENT_END', '@*/');
137
138define('OP_UNARY_PLUS', 'U+');
139define('OP_UNARY_MINUS', 'U-');
140
141/* Keywords */
142define('KEYWORD_BREAK', 'break');
143define('KEYWORD_CASE', 'case');
144define('KEYWORD_CATCH', 'catch');
145define('KEYWORD_CONST', 'const');
146define('KEYWORD_CONTINUE', 'continue');
147define('KEYWORD_DEBUGGER', 'debugger');
148define('KEYWORD_DEFAULT', 'default');
149define('KEYWORD_DELETE', 'delete');
150define('KEYWORD_DO', 'do');
151define('KEYWORD_ELSE', 'else');
152define('KEYWORD_ENUM', 'enum');
153define('KEYWORD_FALSE', 'false');
154define('KEYWORD_FINALLY', 'finally');
155define('KEYWORD_FOR', 'for');
156define('KEYWORD_FUNCTION', 'function');
157define('KEYWORD_IF', 'if');
158define('KEYWORD_IN', 'in');
159define('KEYWORD_INSTANCEOF', 'instanceof');
160define('KEYWORD_NEW', 'new');
161define('KEYWORD_NULL', 'null');
162define('KEYWORD_RETURN', 'return');
163define('KEYWORD_SWITCH', 'switch');
164define('KEYWORD_THIS', 'this');
165define('KEYWORD_THROW', 'throw');
166define('KEYWORD_TRUE', 'true');
167define('KEYWORD_TRY', 'try');
168define('KEYWORD_TYPEOF', 'typeof');
169define('KEYWORD_VAR', 'var');
170define('KEYWORD_VOID', 'void');
171define('KEYWORD_WHILE', 'while');
172define('KEYWORD_WITH', 'with');
173
174
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:
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
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 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 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:
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 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 case JS_CALL:
576 $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
577 break;
578
579 case KEYWORD_NEW:
580 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
635 $s = $n->value;
636 break;
637
638 case JS_GROUP:
639 if (in_array(
640 $n->treeNodes[0]->type,
641 array(
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
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,
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);
757
758 // clear tree from node to save memory
759 $n->treeNodes = null;
760 $n->funDecls = null;
761 $n->varDecls = null;
762
763 $n->type = JS_MINIFIED;
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 ?: 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 https://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 break;
977
978 case KEYWORD_TRY:
979 $n = new JSNode($this->t);
980 $n->tryBlock = $this->Block($x);
981 $n->catchClauses = array();
982
983 while ($this->t->match(KEYWORD_CATCH))
984 {
985 $n2 = new JSNode($this->t);
986 $this->t->mustMatch(OP_LEFT_PAREN);
987 $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
988
989 if ($this->t->match(KEYWORD_IF))
990 {
991 if ($x->ecmaStrictMode)
992 throw $this->t->newSyntaxError('Illegal catch guard');
993
994 if (count($n->catchClauses) && !end($n->catchClauses)->guard)
995 throw $this->t->newSyntaxError('Guarded catch after unguarded');
996
997 $n2->guard = $this->Expression($x);
998 }
999 else
1000 {
1001 $n2->guard = null;
1002 }
1003
1004 $this->t->mustMatch(OP_RIGHT_PAREN);
1005 $n2->block = $this->Block($x);
1006 array_push($n->catchClauses, $n2);
1007 }
1008
1009 if ($this->t->match(KEYWORD_FINALLY))
1010 $n->finallyBlock = $this->Block($x);
1011
1012 if (!count($n->catchClauses) && !$n->finallyBlock)
1013 throw $this->t->newSyntaxError('Invalid try statement');
1014 return $n;
1015
1016 case KEYWORD_CATCH:
1017 case KEYWORD_FINALLY:
1018 throw $this->t->newSyntaxError($tt . ' without preceding try');
1019
1020 case KEYWORD_THROW:
1021 $n = new JSNode($this->t);
1022 $n->value = $this->Expression($x);
1023 break;
1024
1025 case KEYWORD_RETURN:
1026 if (!$x->inFunction)
1027 throw $this->t->newSyntaxError('Invalid return');
1028
1029 $n = new JSNode($this->t);
1030 $tt = $this->t->peekOnSameLine();
1031 if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1032 $n->value = $this->Expression($x);
1033 else
1034 $n->value = null;
1035 break;
1036
1037 case KEYWORD_WITH:
1038 $n = new JSNode($this->t);
1039 $n->object = $this->ParenExpression($x);
1040 $n->body = $this->nest($x, $n);
1041 return $n;
1042
1043 case KEYWORD_VAR:
1044 case KEYWORD_CONST:
1045 $n = $this->Variables($x);
1046 break;
1047
1050 $n = new JSNode($this->t);
1051 return $n;
1052
1053 case KEYWORD_DEBUGGER:
1054 $n = new JSNode($this->t);
1055 break;
1056
1057 case TOKEN_NEWLINE:
1058 case OP_SEMICOLON:
1059 $n = new JSNode($this->t, OP_SEMICOLON);
1060 return $n;
1061
1062 default:
1063 if ($tt == TOKEN_IDENTIFIER)
1064 {
1065 $this->t->scanOperand = false;
1066 $tt = $this->t->peek();
1067 $this->t->scanOperand = true;
1068 if ($tt == OP_COLON)
1069 {
1070 $label = $this->t->currentToken()->value;
1071 $ss = $x->stmtStack;
1072 for ($i = count($ss) - 1; $i >= 0; --$i)
1073 {
1074 if ($ss[$i]->label == $label)
1075 throw $this->t->newSyntaxError('Duplicate label');
1076 }
1077
1078 $this->t->get();
1079 $n = new JSNode($this->t, JS_LABEL);
1080 $n->label = $label;
1081 $n->statement = $this->nest($x, $n);
1082
1083 return $n;
1084 }
1085 }
1086
1087 $n = new JSNode($this->t, OP_SEMICOLON);
1088 $this->t->unget();
1089 $n->expression = $this->Expression($x);
1090 $n->end = $n->expression->end;
1091 break;
1092 }
1093
1094 if ($this->t->lineno == $this->t->currentToken()->lineno)
1095 {
1096 $tt = $this->t->peekOnSameLine();
1097 if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1098 throw $this->t->newSyntaxError('Missing ; before statement');
1099 }
1100
1101 $this->t->match(OP_SEMICOLON);
1102
1103 return $n;
1104 }
1105
1106 private function FunctionDefinition($x, $requireName, $functionForm)
1107 {
1108 $f = new JSNode($this->t);
1109
1110 if ($f->type != KEYWORD_FUNCTION)
1111 $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
1112
1113 if ($this->t->match(TOKEN_IDENTIFIER))
1114 $f->name = $this->t->currentToken()->value;
1115 elseif ($requireName)
1116 throw $this->t->newSyntaxError('Missing function identifier');
1117
1118 $this->t->mustMatch(OP_LEFT_PAREN);
1119 $f->params = array();
1120
1121 while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
1122 {
1123 if ($tt != TOKEN_IDENTIFIER)
1124 throw $this->t->newSyntaxError('Missing formal parameter');
1125
1126 array_push($f->params, $this->t->currentToken()->value);
1127
1128 if ($this->t->peek() != OP_RIGHT_PAREN)
1129 $this->t->mustMatch(OP_COMMA);
1130 }
1131
1132 $this->t->mustMatch(OP_LEFT_CURLY);
1133
1134 $x2 = new JSCompilerContext(true);
1135 $f->body = $this->Script($x2);
1136
1137 $this->t->mustMatch(OP_RIGHT_CURLY);
1138 $f->end = $this->t->currentToken()->end;
1139
1140 $f->functionForm = $functionForm;
1141 if ($functionForm == DECLARED_FORM)
1142 array_push($x->funDecls, $f);
1143
1144 return $f;
1145 }
1146
1147 private function Variables($x)
1148 {
1149 $n = new JSNode($this->t);
1150
1151 do
1152 {
1153 $this->t->mustMatch(TOKEN_IDENTIFIER);
1154
1155 $n2 = new JSNode($this->t);
1156 $n2->name = $n2->value;
1157
1158 if ($this->t->match(OP_ASSIGN))
1159 {
1160 if ($this->t->currentToken()->assignOp)
1161 throw $this->t->newSyntaxError('Invalid variable initialization');
1162
1163 $n2->initializer = $this->Expression($x, OP_COMMA);
1164 }
1165
1166 $n2->readOnly = $n->type == KEYWORD_CONST;
1167
1168 $n->addNode($n2);
1169 array_push($x->varDecls, $n2);
1170 }
1171 while ($this->t->match(OP_COMMA));
1172
1173 return $n;
1174 }
1175
1176 private function Expression($x, $stop=false)
1177 {
1178 $operators = array();
1179 $operands = array();
1180 $n = false;
1181
1182 $bl = $x->bracketLevel;
1183 $cl = $x->curlyLevel;
1184 $pl = $x->parenLevel;
1185 $hl = $x->hookLevel;
1186
1187 while (($tt = $this->t->get()) != TOKEN_END)
1188 {
1189 if ($tt == $stop &&
1190 $x->bracketLevel == $bl &&
1191 $x->curlyLevel == $cl &&
1192 $x->parenLevel == $pl &&
1193 $x->hookLevel == $hl
1194 )
1195 {
1196 // Stop only if tt matches the optional stop parameter, and that
1197 // token is not quoted by some kind of bracket.
1198 break;
1199 }
1200
1201 switch ($tt)
1202 {
1203 case OP_SEMICOLON:
1204 // NB: cannot be empty, Statement handled that.
1205 break 2;
1206
1207 case OP_HOOK:
1208 if ($this->t->scanOperand)
1209 break 2;
1210
1211 while ( !empty($operators) &&
1212 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1213 )
1214 $this->reduce($operators, $operands);
1215
1216 array_push($operators, new JSNode($this->t));
1217
1218 ++$x->hookLevel;
1219 $this->t->scanOperand = true;
1220 $n = $this->Expression($x);
1221
1222 if (!$this->t->match(OP_COLON))
1223 break 2;
1224
1225 --$x->hookLevel;
1226 array_push($operands, $n);
1227 break;
1228
1229 case OP_COLON:
1230 if ($x->hookLevel)
1231 break 2;
1232
1233 throw $this->t->newSyntaxError('Invalid label');
1234 break;
1235
1236 case OP_ASSIGN:
1237 if ($this->t->scanOperand)
1238 break 2;
1239
1240 // Use >, not >=, for right-associative ASSIGN
1241 while ( !empty($operators) &&
1242 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1243 )
1244 $this->reduce($operators, $operands);
1245
1246 array_push($operators, new JSNode($this->t));
1247 end($operands)->assignOp = $this->t->currentToken()->assignOp;
1248 $this->t->scanOperand = true;
1249 break;
1250
1251 case KEYWORD_IN:
1252 // An in operator should not be parsed if we're parsing the head of
1253 // a for (...) loop, unless it is in the then part of a conditional
1254 // expression, or parenthesized somehow.
1255 if ($x->inForLoopInit && !$x->hookLevel &&
1256 !$x->bracketLevel && !$x->curlyLevel &&
1257 !$x->parenLevel
1258 )
1259 break 2;
1260 // FALL THROUGH
1261 case OP_COMMA:
1262 // A comma operator should not be parsed if we're parsing the then part
1263 // of a conditional expression unless it's parenthesized somehow.
1264 if ($tt == OP_COMMA && $x->hookLevel &&
1265 !$x->bracketLevel && !$x->curlyLevel &&
1266 !$x->parenLevel
1267 )
1268 break 2;
1269 // Treat comma as left-associative so reduce can fold left-heavy
1270 // COMMA trees into a single array.
1271 // FALL THROUGH
1272 case OP_OR:
1273 case OP_AND:
1274 case OP_BITWISE_OR:
1275 case OP_BITWISE_XOR:
1276 case OP_BITWISE_AND:
1277 case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
1278 case OP_LT: case OP_LE: case OP_GE: case OP_GT:
1279 case KEYWORD_INSTANCEOF:
1280 case OP_LSH: case OP_RSH: case OP_URSH:
1281 case OP_PLUS: case OP_MINUS:
1282 case OP_MUL: case OP_DIV: case OP_MOD:
1283 case OP_DOT:
1284 if ($this->t->scanOperand)
1285 break 2;
1286
1287 while ( !empty($operators) &&
1288 $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1289 )
1290 $this->reduce($operators, $operands);
1291
1292 if ($tt == OP_DOT)
1293 {
1294 $tt = $this->t->get();
1295 if (!$this->isKeyword($tt) && $tt !== TOKEN_IDENTIFIER)
1296 throw $this->t->newSyntaxError("Unexpected token; token identifier or keyword expected.");
1297
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
1308 case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1309 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 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:
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
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 // Accept keywords as property names by treating
1433 // them similarly with identifiers
1434 if ($this->isKeyword($tt))
1435 $tt = TOKEN_IDENTIFIER;
1436
1437 switch ($tt)
1438 {
1439 case TOKEN_IDENTIFIER:
1440 case TOKEN_NUMBER:
1441 case TOKEN_STRING:
1442 $id = new JSNode($this->t);
1443 break;
1444
1445 case OP_RIGHT_CURLY:
1446 if ($x->ecmaStrictMode)
1447 throw $this->t->newSyntaxError('Illegal trailing ,');
1448 break 3;
1449
1450 default:
1451 throw $this->t->newSyntaxError('Invalid property name');
1452 }
1453
1454 $this->t->mustMatch(OP_COLON);
1455 $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
1456 }
1457 }
1458 while ($this->t->match(OP_COMMA));
1459
1460 $this->t->mustMatch(OP_RIGHT_CURLY);
1461 break;
1462 }
1463
1464 array_push($operands, $n);
1465 $this->t->scanOperand = false;
1466 --$x->curlyLevel;
1467 break;
1468
1469 case OP_RIGHT_CURLY:
1470 if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1471 throw new Exception('PANIC: right curly botch');
1472 break 2;
1473
1474 case OP_LEFT_PAREN:
1475 if ($this->t->scanOperand)
1476 {
1477 array_push($operators, new JSNode($this->t, JS_GROUP));
1478 }
1479 else
1480 {
1481 while ( !empty($operators) &&
1482 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
1483 )
1484 $this->reduce($operators, $operands);
1485
1486 // Handle () now, to regularize the n-ary case for n > 0.
1487 // We must set scanOperand in case there are arguments and
1488 // the first one is a regexp or unary+/-.
1489 $n = end($operators);
1490 $this->t->scanOperand = true;
1491 if ($this->t->match(OP_RIGHT_PAREN))
1492 {
1493 if ($n && $n->type == KEYWORD_NEW)
1494 {
1495 array_pop($operators);
1496 $n->addNode(array_pop($operands));
1497 }
1498 else
1499 {
1500 $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
1501 }
1502
1503 array_push($operands, $n);
1504 $this->t->scanOperand = false;
1505 break;
1506 }
1507
1508 if ($n && $n->type == KEYWORD_NEW)
1509 $n->type = JS_NEW_WITH_ARGS;
1510 else
1511 array_push($operators, new JSNode($this->t, JS_CALL));
1512 }
1513
1514 ++$x->parenLevel;
1515 break;
1516
1517 case OP_RIGHT_PAREN:
1518 if ($this->t->scanOperand || $x->parenLevel == $pl)
1519 break 2;
1520
1521 while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
1522 $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
1523 )
1524 {
1525 continue;
1526 }
1527
1528 if ($tt != JS_GROUP)
1529 {
1530 $n = end($operands);
1531 if ($n->treeNodes[1]->type != OP_COMMA)
1532 $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
1533 else
1534 $n->treeNodes[1]->type = JS_LIST;
1535 }
1536
1537 --$x->parenLevel;
1538 break;
1539
1540 // Automatic semicolon insertion means we may scan across a newline
1541 // and into the beginning of another statement. If so, break out of
1542 // the while loop and let the t.scanOperand logic handle errors.
1543 default:
1544 break 2;
1545 }
1546 }
1547
1548 if ($x->hookLevel != $hl)
1549 throw $this->t->newSyntaxError('Missing : in conditional expression');
1550
1551 if ($x->parenLevel != $pl)
1552 throw $this->t->newSyntaxError('Missing ) in parenthetical');
1553
1554 if ($x->bracketLevel != $bl)
1555 throw $this->t->newSyntaxError('Missing ] in index expression');
1556
1557 if ($this->t->scanOperand)
1558 throw $this->t->newSyntaxError('Missing operand');
1559
1560 // Resume default mode, scanning for operands, not operators.
1561 $this->t->scanOperand = true;
1562 $this->t->unget();
1563
1564 while (count($operators))
1565 $this->reduce($operators, $operands);
1566
1567 return array_pop($operands);
1568 }
1569
1570 private function ParenExpression($x)
1571 {
1572 $this->t->mustMatch(OP_LEFT_PAREN);
1573 $n = $this->Expression($x);
1574 $this->t->mustMatch(OP_RIGHT_PAREN);
1575
1576 return $n;
1577 }
1578
1579 // Statement stack and nested statement handler.
1580 private function nest($x, $node, $end = false)
1581 {
1582 array_push($x->stmtStack, $node);
1583 $n = $this->Statement($x);
1584 array_pop($x->stmtStack);
1585
1586 if ($end)
1587 $this->t->mustMatch($end);
1588
1589 return $n;
1590 }
1591
1592 private function reduce(&$operators, &$operands)
1593 {
1594 $n = array_pop($operators);
1595 $op = $n->type;
1596 $arity = $this->opArity[$op];
1597 $c = count($operands);
1598 if ($arity == -2)
1599 {
1600 // Flatten left-associative trees
1601 if ($c >= 2)
1602 {
1603 $left = $operands[$c - 2];
1604 if ($left->type == $op)
1605 {
1606 $right = array_pop($operands);
1607 $left->addNode($right);
1608 return $left;
1609 }
1610 }
1611 $arity = 2;
1612 }
1613
1614 // Always use push to add operands to n, to update start and end
1615 $a = array_splice($operands, $c - $arity);
1616 for ($i = 0; $i < $arity; $i++)
1617 $n->addNode($a[$i]);
1618
1619 // Include closing bracket or postfix operator in [start,end]
1620 $te = $this->t->currentToken()->end;
1621 if ($n->end < $te)
1622 $n->end = $te;
1623
1624 array_push($operands, $n);
1625
1626 return $n;
1627 }
1628
1629 private function isKeyword($tt)
1630 {
1631 switch ($tt) {
1632 case KEYWORD_BREAK:
1633 case KEYWORD_CASE:
1634 case KEYWORD_CATCH:
1635 case KEYWORD_CONST:
1636 case KEYWORD_CONTINUE:
1637 case KEYWORD_DEBUGGER:
1638 case KEYWORD_DEFAULT:
1639 case KEYWORD_DELETE:
1640 case KEYWORD_DO:
1641 case KEYWORD_ELSE:
1642 case KEYWORD_ENUM:
1643 case KEYWORD_FALSE:
1644 case KEYWORD_FINALLY:
1645 case KEYWORD_FOR:
1646 case KEYWORD_FUNCTION:
1647 case KEYWORD_IF:
1648 case KEYWORD_IN:
1649 case KEYWORD_INSTANCEOF:
1650 case KEYWORD_NEW:
1651 case KEYWORD_NULL:
1652 case KEYWORD_RETURN:
1653 case KEYWORD_SWITCH:
1654 case KEYWORD_THIS:
1655 case KEYWORD_THROW:
1656 case KEYWORD_TRUE:
1657 case KEYWORD_TRY:
1658 case KEYWORD_TYPEOF:
1659 case KEYWORD_VAR:
1660 case KEYWORD_VOID:
1661 case KEYWORD_WHILE:
1662 case KEYWORD_WITH:
1663 return true;
1664 default:
1665 return false;
1666 }
1667 }
1668}
1669
1671{
1672 public $inFunction = false;
1673 public $inForLoopInit = false;
1674 public $ecmaStrictMode = false;
1675 public $bracketLevel = 0;
1676 public $curlyLevel = 0;
1677 public $parenLevel = 0;
1678 public $hookLevel = 0;
1679
1680 public $stmtStack = array();
1681 public $funDecls = array();
1682 public $varDecls = array();
1683
1684 public function __construct($inFunction)
1685 {
1686 $this->inFunction = $inFunction;
1687 }
1688}
1689
1691{
1692 private $type;
1693 private $value;
1694 private $lineno;
1695 private $start;
1696 private $end;
1697
1698 public $treeNodes = array();
1699 public $funDecls = array();
1700 public $varDecls = array();
1701 public $expression = null;
1702
1703 public function __construct($t, $type=0, ...$nodes)
1704 {
1705 if ($token = $t->currentToken())
1706 {
1707 $this->type = $type ?: $token->type;
1708 $this->value = $token->value;
1709 $this->lineno = $token->lineno;
1710 $this->start = $token->start;
1711 $this->end = $token->end;
1712 }
1713 else
1714 {
1715 $this->type = $type;
1716 $this->lineno = $t->lineno;
1717 }
1718
1719 foreach($nodes as $node)
1720 {
1721 $this->addNode($node);
1722 }
1723 }
1724
1725 // we don't want to bloat our object with all kind of specific properties, so we use overloading
1726 public function __set($name, $value)
1727 {
1728 $this->$name = $value;
1729 }
1730
1731 public function __get($name)
1732 {
1733 if (isset($this->$name))
1734 return $this->$name;
1735
1736 return null;
1737 }
1738
1739 public function addNode($node)
1740 {
1741 if ($node !== null)
1742 {
1743 if ($node->start < $this->start)
1744 $this->start = $node->start;
1745 if ($this->end < $node->end)
1746 $this->end = $node->end;
1747 }
1748
1749 $this->treeNodes[] = $node;
1750 }
1751}
1752
1754{
1755 private $cursor = 0;
1756 private $source;
1757
1758 public $tokens = array();
1759 public $tokenIndex = 0;
1760 public $lookahead = 0;
1761 public $scanNewlines = false;
1762 public $scanOperand = true;
1763
1765 public $lineno;
1766
1767 private $keywords = array(
1768 'break',
1769 'case', 'catch', 'const', 'continue',
1770 'debugger', 'default', 'delete', 'do',
1771 'else', 'enum',
1772 'false', 'finally', 'for', 'function',
1773 'if', 'in', 'instanceof',
1774 'new', 'null',
1775 'return',
1776 'switch',
1777 'this', 'throw', 'true', 'try', 'typeof',
1778 'var', 'void',
1779 'while', 'with'
1780 );
1781
1782 private $opTypeNames = array(
1783 ';', ',', '?', ':', '||', '&&', '|', '^',
1784 '&', '===', '==', '=', '!==', '!=', '<<', '<=',
1785 '<', '>>>', '>>', '>=', '>', '++', '--', '+',
1786 '-', '*', '/', '%', '!', '~', '.', '[',
1787 ']', '{', '}', '(', ')', '@*/'
1788 );
1789
1790 private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
1791 private $opRegExp;
1792
1793 public function __construct()
1794 {
1795 $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#';
1796 }
1797
1798 public function init($source, $filename = '', $lineno = 1)
1799 {
1800 $this->source = $source;
1801 $this->filename = $filename ?: '[inline]';
1802 $this->lineno = $lineno;
1803
1804 $this->cursor = 0;
1805 $this->tokens = array();
1806 $this->tokenIndex = 0;
1807 $this->lookahead = 0;
1808 $this->scanNewlines = false;
1809 $this->scanOperand = true;
1810 }
1811
1812 public function getInput($chunksize)
1813 {
1814 if ($chunksize)
1815 return substr($this->source, $this->cursor, $chunksize);
1816
1817 return substr($this->source, $this->cursor);
1818 }
1819
1820 public function isDone()
1821 {
1822 return $this->peek() == TOKEN_END;
1823 }
1824
1825 public function match($tt)
1826 {
1827 return $this->get() == $tt || $this->unget();
1828 }
1829
1830 public function mustMatch($tt)
1831 {
1832 if (!$this->match($tt))
1833 throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
1834
1835 return $this->currentToken();
1836 }
1837
1838 public function peek()
1839 {
1840 if ($this->lookahead)
1841 {
1842 $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
1843 if ($this->scanNewlines && $next->lineno != $this->lineno)
1844 $tt = TOKEN_NEWLINE;
1845 else
1846 $tt = $next->type;
1847 }
1848 else
1849 {
1850 $tt = $this->get();
1851 $this->unget();
1852 }
1853
1854 return $tt;
1855 }
1856
1857 public function peekOnSameLine()
1858 {
1859 $this->scanNewlines = true;
1860 $tt = $this->peek();
1861 $this->scanNewlines = false;
1862
1863 return $tt;
1864 }
1865
1866 public function currentToken()
1867 {
1868 if (!empty($this->tokens))
1869 return $this->tokens[$this->tokenIndex];
1870 }
1871
1872 public function get($chunksize = 1000)
1873 {
1874 while($this->lookahead)
1875 {
1876 $this->lookahead--;
1877 $this->tokenIndex = ($this->tokenIndex + 1) & 3;
1878 $token = $this->tokens[$this->tokenIndex];
1879 if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
1880 return $token->type;
1881 }
1882
1883 $conditional_comment = false;
1884
1885 // strip whitespace and comments
1886 while(true)
1887 {
1888 $input = $this->getInput($chunksize);
1889
1890 // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
1891 $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
1892 if (preg_match($re, $input, $match))
1893 {
1894 $spaces = $match[0];
1895 $spacelen = strlen($spaces);
1896 $this->cursor += $spacelen;
1897 if (!$this->scanNewlines)
1898 $this->lineno += substr_count($spaces, "\n");
1899
1900 if ($spacelen == $chunksize)
1901 continue; // complete chunk contained whitespace
1902
1903 $input = $this->getInput($chunksize);
1904 if ($input == '' || $input[0] != '/')
1905 break;
1906 }
1907
1908 // Comments
1909 if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
1910 {
1911 if (!$chunksize)
1912 break;
1913
1914 // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
1915 $chunksize = null;
1916 continue;
1917 }
1918
1919 // check if this is a conditional (JScript) comment
1920 if (!empty($match[1]))
1921 {
1922 $match[0] = '/*' . $match[1];
1923 $conditional_comment = true;
1924 break;
1925 }
1926 else
1927 {
1928 $this->cursor += strlen($match[0]);
1929 $this->lineno += substr_count($match[0], "\n");
1930 }
1931 }
1932
1933 if ($input == '')
1934 {
1935 $tt = TOKEN_END;
1936 $match = array('');
1937 }
1938 elseif ($conditional_comment)
1939 {
1941 }
1942 else
1943 {
1944 switch ($input[0])
1945 {
1946 case '0':
1947 // hexadecimal
1948 if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match))
1949 {
1950 $tt = TOKEN_NUMBER;
1951 break;
1952 }
1953 // FALL THROUGH
1954
1955 case '1': case '2': case '3': case '4': case '5':
1956 case '6': case '7': case '8': case '9':
1957 // should always match
1958 preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match);
1959 $tt = TOKEN_NUMBER;
1960 break;
1961
1962 case "'":
1963 if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*+\'/', $input, $match))
1964 {
1965 $tt = TOKEN_STRING;
1966 }
1967 else
1968 {
1969 if ($chunksize)
1970 return $this->get(null); // retry with a full chunk fetch
1971
1972 throw $this->newSyntaxError('Unterminated string literal');
1973 }
1974 break;
1975
1976 case '"':
1977 if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*+"/', $input, $match))
1978 {
1979 $tt = TOKEN_STRING;
1980 }
1981 else
1982 {
1983 if ($chunksize)
1984 return $this->get(null); // retry with a full chunk fetch
1985
1986 throw $this->newSyntaxError('Unterminated string literal');
1987 }
1988 break;
1989
1990 case '/':
1991 if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])++)\/([gimy]*)/', $input, $match))
1992 {
1993 $tt = TOKEN_REGEXP;
1994 break;
1995 }
1996 // FALL THROUGH
1997
1998 case '|':
1999 case '^':
2000 case '&':
2001 case '<':
2002 case '>':
2003 case '+':
2004 case '-':
2005 case '*':
2006 case '%':
2007 case '=':
2008 case '!':
2009 // should always match
2010 preg_match($this->opRegExp, $input, $match);
2011 $op = $match[0];
2012 if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
2013 {
2014 $tt = OP_ASSIGN;
2015 $match[0] .= '=';
2016 }
2017 else
2018 {
2019 $tt = $op;
2020 if ($this->scanOperand)
2021 {
2022 if ($op == OP_PLUS)
2023 $tt = OP_UNARY_PLUS;
2024 elseif ($op == OP_MINUS)
2025 $tt = OP_UNARY_MINUS;
2026 }
2027 $op = null;
2028 }
2029 break;
2030
2031 case '.':
2032 if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
2033 {
2034 $tt = TOKEN_NUMBER;
2035 break;
2036 }
2037 // FALL THROUGH
2038
2039 case ';':
2040 case ',':
2041 case '?':
2042 case ':':
2043 case '~':
2044 case '[':
2045 case ']':
2046 case '{':
2047 case '}':
2048 case '(':
2049 case ')':
2050 // these are all single
2051 $match = array($input[0]);
2052 $tt = $input[0];
2053 break;
2054
2055 case '@':
2056 // check end of conditional comment
2057 if (substr($input, 0, 3) == '@*/')
2058 {
2059 $match = array('@*/');
2061 }
2062 else
2063 throw $this->newSyntaxError('Illegal token');
2064 break;
2065
2066 case "\n":
2067 if ($this->scanNewlines)
2068 {
2069 $match = array("\n");
2070 $tt = TOKEN_NEWLINE;
2071 }
2072 else
2073 throw $this->newSyntaxError('Illegal token');
2074 break;
2075
2076 default:
2077 // Fast path for identifiers: word chars followed by whitespace or various other tokens.
2078 // Note we don't need to exclude digits in the first char, as they've already been found
2079 // above.
2080 if (!preg_match('/^[$\w]+(?=[\s\/\|\^\&<>\+\-\*%=!.;,\?:~\[\]\{\}\‍(\‍)@])/', $input, $match))
2081 {
2082 // Character classes per ECMA-262 edition 5.1 section 7.6
2083 // Per spec, must accept Unicode 3.0, *may* accept later versions.
2084 // We'll take whatever PCRE understands, which should be more recent.
2085 $identifierStartChars =
2086 "\\p{L}\\p{Nl}" . # UnicodeLetter
2087 "\$" .
2088 "_";
2089 $identifierPartChars =
2090 $identifierStartChars .
2091 "\\p{Mn}\\p{Mc}" . # UnicodeCombiningMark
2092 "\\p{Nd}" . # UnicodeDigit
2093 "\\p{Pc}"; # UnicodeConnectorPunctuation
2094 $unicodeEscape = "\\\\u[0-9A-F-a-f]{4}";
2095 $identifierRegex =
2096 "/^" .
2097 "(?:[$identifierStartChars]|$unicodeEscape)" .
2098 "(?:[$identifierPartChars]|$unicodeEscape)*" .
2099 "/uS";
2100 if (preg_match($identifierRegex, $input, $match))
2101 {
2102 if (strpos($match[0], '\\') !== false) {
2103 // Per ECMA-262 edition 5.1, section 7.6 escape sequences should behave as if they were
2104 // the original chars, but only within the boundaries of the identifier.
2105 $decoded = preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/',
2106 array(__CLASS__, 'unicodeEscapeCallback'),
2107 $match[0]);
2108
2109 // Since our original regex didn't de-escape the originals, we need to check for validity again.
2110 // No need to worry about token boundaries, as anything outside the identifier is illegal!
2111 if (!preg_match("/^[$identifierStartChars][$identifierPartChars]*$/u", $decoded)) {
2112 throw $this->newSyntaxError('Illegal token');
2113 }
2114
2115 // Per spec it _ought_ to work to use these escapes for keywords words as well...
2116 // but IE rejects them as invalid, while Firefox and Chrome treat them as identifiers
2117 // that don't match the keyword.
2118 if (in_array($decoded, $this->keywords)) {
2119 throw $this->newSyntaxError('Illegal token');
2120 }
2121
2122 // TODO: save the decoded form for output?
2123 }
2124 }
2125 else
2126 throw $this->newSyntaxError('Illegal token');
2127 }
2128 $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
2129 }
2130 }
2131
2132 $this->tokenIndex = ($this->tokenIndex + 1) & 3;
2133
2134 if (!isset($this->tokens[$this->tokenIndex]))
2135 $this->tokens[$this->tokenIndex] = new JSToken();
2136
2137 $token = $this->tokens[$this->tokenIndex];
2138 $token->type = $tt;
2139
2140 if ($tt == OP_ASSIGN)
2141 $token->assignOp = $op;
2142
2143 $token->start = $this->cursor;
2144
2145 $token->value = $match[0];
2146 $this->cursor += strlen($match[0]);
2147
2148 $token->end = $this->cursor;
2149 $token->lineno = $this->lineno;
2150
2151 return $tt;
2152 }
2153
2154 public function unget()
2155 {
2156 if (++$this->lookahead == 4)
2157 throw $this->newSyntaxError('PANIC: too much lookahead!');
2158
2159 $this->tokenIndex = ($this->tokenIndex - 1) & 3;
2160 }
2161
2162 public function newSyntaxError($m)
2163 {
2164 return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
2165 }
2166
2167 public static function unicodeEscapeCallback($m)
2168 {
2169 return html_entity_decode('&#x' . $m[1]. ';', ENT_QUOTES, 'UTF-8');
2170 }
2171}
2172
2173class JSToken
2174{
2175 public $type;
2176 public $value;
2177 public $start;
2178 public $end;
2179 public $lineno;
2180 public $assignOp;
2181}
__construct($inFunction)
parseTree($n, $noBlockGrouping=false)
static minify($js, $filename='')
__set($name, $value)
__construct($t, $type=0,... $nodes)
__get($name)
addNode($node)
__construct($minifier=null)
parse($s, $f, $l)
getInput($chunksize)
init($source, $filename='', $lineno=1)
const OP_RIGHT_BRACKET
const OP_NE
const OP_UNARY_PLUS
const TOKEN_STRING
Definition jsminplus.php:71
const OP_DIV
const OP_LEFT_BRACKET
const TOKEN_NEWLINE
Definition jsminplus.php:73
const OP_DECREMENT
const KEYWORD_CATCH
const KEYWORD_DEFAULT
const OP_EQ
const DECLARED_FORM
Definition jsminplus.php:94
const KEYWORD_CONTINUE
const OP_BITWISE_NOT
const TOKEN_CONDCOMMENT_START
Definition jsminplus.php:74
const OP_LSH
const JS_GROUP
Definition jsminplus.php:89
const KEYWORD_TRUE
const OP_GT
const KEYWORD_CONST
const KEYWORD_INSTANCEOF
const KEYWORD_VOID
const STATEMENT_FORM
Definition jsminplus.php:96
const JS_LABEL
Definition jsminplus.php:79
const KEYWORD_DELETE
const OP_RIGHT_CURLY
const KEYWORD_VAR
const OP_UNARY_MINUS
const OP_MOD
const KEYWORD_FUNCTION
const OP_MINUS
const TOKEN_NUMBER
Definition jsminplus.php:69
const OP_RIGHT_PAREN
const OP_AND
const OP_MUL
const KEYWORD_RETURN
const OP_GE
const KEYWORD_WHILE
const KEYWORD_ENUM
const KEYWORD_DEBUGGER
const OP_BITWISE_AND
const KEYWORD_TYPEOF
const OP_BITWISE_XOR
const KEYWORD_SWITCH
const OP_LE
const JS_NEW_WITH_ARGS
Definition jsminplus.php:82
const JS_PROPERTY_INIT
Definition jsminplus.php:86
const KEYWORD_IN
const KEYWORD_FALSE
const JS_SCRIPT
Definition jsminplus.php:77
const KEYWORD_FINALLY
const OP_STRICT_EQ
const JS_FOR_IN
Definition jsminplus.php:80
const KEYWORD_FOR
const KEYWORD_DO
const KEYWORD_THROW
const JS_LIST
Definition jsminplus.php:90
const OP_BITWISE_OR
const JS_OBJECT_INIT
Definition jsminplus.php:85
const JS_MINIFIED
Definition jsminplus.php:92
const JS_GETTER
Definition jsminplus.php:87
const OP_ASSIGN
const KEYWORD_CASE
const OP_PLUS
const TOKEN_REGEXP
Definition jsminplus.php:72
const JS_CALL
Definition jsminplus.php:81
const TOKEN_END
Definition jsminplus.php:68
const KEYWORD_WITH
const KEYWORD_ELSE
const OP_COMMA
const TOKEN_IDENTIFIER
Definition jsminplus.php:70
const OP_STRICT_NE
const OP_DOT
const KEYWORD_THIS
const KEYWORD_IF
const OP_URSH
const TOKEN_CONDCOMMENT_END
Definition jsminplus.php:75
const OP_HOOK
const KEYWORD_NULL
const OP_SEMICOLON
Definition jsminplus.php:99
const OP_LEFT_PAREN
const JS_BLOCK
Definition jsminplus.php:78
const JS_INDEX
Definition jsminplus.php:83
const KEYWORD_BREAK
const OP_RSH
const OP_COLON
const EXPRESSED_FORM
Definition jsminplus.php:95
const KEYWORD_TRY
const JS_ARRAY_INIT
Definition jsminplus.php:84
const OP_LT
const OP_LEFT_CURLY
const OP_OR
const KEYWORD_NEW
const JS_SETTER
Definition jsminplus.php:88
const OP_INCREMENT
const OP_NOT