MediaWiki  1.34.0
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 
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 
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 
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 
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(
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,
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 
1049  case TOKEN_CONDCOMMENT_END:
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  $n->expression = null;
1061  return $n;
1062 
1063  default:
1064  if ($tt == TOKEN_IDENTIFIER)
1065  {
1066  $this->t->scanOperand = false;
1067  $tt = $this->t->peek();
1068  $this->t->scanOperand = true;
1069  if ($tt == OP_COLON)
1070  {
1071  $label = $this->t->currentToken()->value;
1072  $ss = $x->stmtStack;
1073  for ($i = count($ss) - 1; $i >= 0; --$i)
1074  {
1075  if ($ss[$i]->label == $label)
1076  throw $this->t->newSyntaxError('Duplicate label');
1077  }
1078 
1079  $this->t->get();
1080  $n = new JSNode($this->t, JS_LABEL);
1081  $n->label = $label;
1082  $n->statement = $this->nest($x, $n);
1083 
1084  return $n;
1085  }
1086  }
1087 
1088  $n = new JSNode($this->t, OP_SEMICOLON);
1089  $this->t->unget();
1090  $n->expression = $this->Expression($x);
1091  $n->end = $n->expression->end;
1092  break;
1093  }
1094 
1095  if ($this->t->lineno == $this->t->currentToken()->lineno)
1096  {
1097  $tt = $this->t->peekOnSameLine();
1098  if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1099  throw $this->t->newSyntaxError('Missing ; before statement');
1100  }
1101 
1102  $this->t->match(OP_SEMICOLON);
1103 
1104  return $n;
1105  }
1106 
1107  private function FunctionDefinition($x, $requireName, $functionForm)
1108  {
1109  $f = new JSNode($this->t);
1110 
1111  if ($f->type != KEYWORD_FUNCTION)
1112  $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
1113 
1114  if ($this->t->match(TOKEN_IDENTIFIER))
1115  $f->name = $this->t->currentToken()->value;
1116  elseif ($requireName)
1117  throw $this->t->newSyntaxError('Missing function identifier');
1118 
1119  $this->t->mustMatch(OP_LEFT_PAREN);
1120  $f->params = array();
1121 
1122  while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
1123  {
1124  if ($tt != TOKEN_IDENTIFIER)
1125  throw $this->t->newSyntaxError('Missing formal parameter');
1126 
1127  array_push($f->params, $this->t->currentToken()->value);
1128 
1129  if ($this->t->peek() != OP_RIGHT_PAREN)
1130  $this->t->mustMatch(OP_COMMA);
1131  }
1132 
1133  $this->t->mustMatch(OP_LEFT_CURLY);
1134 
1135  $x2 = new JSCompilerContext(true);
1136  $f->body = $this->Script($x2);
1137 
1138  $this->t->mustMatch(OP_RIGHT_CURLY);
1139  $f->end = $this->t->currentToken()->end;
1140 
1141  $f->functionForm = $functionForm;
1142  if ($functionForm == DECLARED_FORM)
1143  array_push($x->funDecls, $f);
1144 
1145  return $f;
1146  }
1147 
1148  private function Variables($x)
1149  {
1150  $n = new JSNode($this->t);
1151 
1152  do
1153  {
1154  $this->t->mustMatch(TOKEN_IDENTIFIER);
1155 
1156  $n2 = new JSNode($this->t);
1157  $n2->name = $n2->value;
1158 
1159  if ($this->t->match(OP_ASSIGN))
1160  {
1161  if ($this->t->currentToken()->assignOp)
1162  throw $this->t->newSyntaxError('Invalid variable initialization');
1163 
1164  $n2->initializer = $this->Expression($x, OP_COMMA);
1165  }
1166 
1167  $n2->readOnly = $n->type == KEYWORD_CONST;
1168 
1169  $n->addNode($n2);
1170  array_push($x->varDecls, $n2);
1171  }
1172  while ($this->t->match(OP_COMMA));
1173 
1174  return $n;
1175  }
1176 
1177  private function Expression($x, $stop=false)
1178  {
1179  $operators = array();
1180  $operands = array();
1181  $n = false;
1182 
1183  $bl = $x->bracketLevel;
1184  $cl = $x->curlyLevel;
1185  $pl = $x->parenLevel;
1186  $hl = $x->hookLevel;
1187 
1188  while (($tt = $this->t->get()) != TOKEN_END)
1189  {
1190  if ($tt == $stop &&
1191  $x->bracketLevel == $bl &&
1192  $x->curlyLevel == $cl &&
1193  $x->parenLevel == $pl &&
1194  $x->hookLevel == $hl
1195  )
1196  {
1197  // Stop only if tt matches the optional stop parameter, and that
1198  // token is not quoted by some kind of bracket.
1199  break;
1200  }
1201 
1202  switch ($tt)
1203  {
1204  case OP_SEMICOLON:
1205  // NB: cannot be empty, Statement handled that.
1206  break 2;
1207 
1208  case OP_HOOK:
1209  if ($this->t->scanOperand)
1210  break 2;
1211 
1212  while ( !empty($operators) &&
1213  $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1214  )
1215  $this->reduce($operators, $operands);
1216 
1217  array_push($operators, new JSNode($this->t));
1218 
1219  ++$x->hookLevel;
1220  $this->t->scanOperand = true;
1221  $n = $this->Expression($x);
1222 
1223  if (!$this->t->match(OP_COLON))
1224  break 2;
1225 
1226  --$x->hookLevel;
1227  array_push($operands, $n);
1228  break;
1229 
1230  case OP_COLON:
1231  if ($x->hookLevel)
1232  break 2;
1233 
1234  throw $this->t->newSyntaxError('Invalid label');
1235  break;
1236 
1237  case OP_ASSIGN:
1238  if ($this->t->scanOperand)
1239  break 2;
1240 
1241  // Use >, not >=, for right-associative ASSIGN
1242  while ( !empty($operators) &&
1243  $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1244  )
1245  $this->reduce($operators, $operands);
1246 
1247  array_push($operators, new JSNode($this->t));
1248  end($operands)->assignOp = $this->t->currentToken()->assignOp;
1249  $this->t->scanOperand = true;
1250  break;
1251 
1252  case KEYWORD_IN:
1253  // An in operator should not be parsed if we're parsing the head of
1254  // a for (...) loop, unless it is in the then part of a conditional
1255  // expression, or parenthesized somehow.
1256  if ($x->inForLoopInit && !$x->hookLevel &&
1257  !$x->bracketLevel && !$x->curlyLevel &&
1258  !$x->parenLevel
1259  )
1260  break 2;
1261  // FALL THROUGH
1262  case OP_COMMA:
1263  // A comma operator should not be parsed if we're parsing the then part
1264  // of a conditional expression unless it's parenthesized somehow.
1265  if ($tt == OP_COMMA && $x->hookLevel &&
1266  !$x->bracketLevel && !$x->curlyLevel &&
1267  !$x->parenLevel
1268  )
1269  break 2;
1270  // Treat comma as left-associative so reduce can fold left-heavy
1271  // COMMA trees into a single array.
1272  // FALL THROUGH
1273  case OP_OR:
1274  case OP_AND:
1275  case OP_BITWISE_OR:
1276  case OP_BITWISE_XOR:
1277  case OP_BITWISE_AND:
1278  case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
1279  case OP_LT: case OP_LE: case OP_GE: case OP_GT:
1280  case KEYWORD_INSTANCEOF:
1281  case OP_LSH: case OP_RSH: case OP_URSH:
1282  case OP_PLUS: case OP_MINUS:
1283  case OP_MUL: case OP_DIV: case OP_MOD:
1284  case OP_DOT:
1285  if ($this->t->scanOperand)
1286  break 2;
1287 
1288  while ( !empty($operators) &&
1289  $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1290  )
1291  $this->reduce($operators, $operands);
1292 
1293  if ($tt == OP_DOT)
1294  {
1295  $tt = $this->t->get();
1296  if (!$this->isKeyword($tt) && $tt !== TOKEN_IDENTIFIER)
1297  throw $this->t->newSyntaxError("Unexpected token; token identifier or keyword expected.");
1298 
1299  array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
1300  }
1301  else
1302  {
1303  array_push($operators, new JSNode($this->t));
1304  $this->t->scanOperand = true;
1305  }
1306  break;
1307 
1308  case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
1309  case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1310  case KEYWORD_NEW:
1311  if (!$this->t->scanOperand)
1312  break 2;
1313 
1314  array_push($operators, new JSNode($this->t));
1315  break;
1316 
1317  case OP_INCREMENT: case OP_DECREMENT:
1318  if ($this->t->scanOperand)
1319  {
1320  array_push($operators, new JSNode($this->t)); // prefix increment or decrement
1321  }
1322  else
1323  {
1324  // Don't cross a line boundary for postfix {in,de}crement.
1325  $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
1326  if ($t && $t->lineno != $this->t->lineno)
1327  break 2;
1328 
1329  if (!empty($operators))
1330  {
1331  // Use >, not >=, so postfix has higher precedence than prefix.
1332  while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
1333  $this->reduce($operators, $operands);
1334  }
1335 
1336  $n = new JSNode($this->t, $tt, array_pop($operands));
1337  $n->postfix = true;
1338  array_push($operands, $n);
1339  }
1340  break;
1341 
1342  case KEYWORD_FUNCTION:
1343  if (!$this->t->scanOperand)
1344  break 2;
1345 
1346  array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
1347  $this->t->scanOperand = false;
1348  break;
1349 
1350  case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
1351  case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
1352  if (!$this->t->scanOperand)
1353  break 2;
1354 
1355  array_push($operands, new JSNode($this->t));
1356  $this->t->scanOperand = false;
1357  break;
1358 
1360  case TOKEN_CONDCOMMENT_END:
1361  if ($this->t->scanOperand)
1362  array_push($operators, new JSNode($this->t));
1363  else
1364  array_push($operands, new JSNode($this->t));
1365  break;
1366 
1367  case OP_LEFT_BRACKET:
1368  if ($this->t->scanOperand)
1369  {
1370  // Array initialiser. Parse using recursive descent, as the
1371  // sub-grammar here is not an operator grammar.
1372  $n = new JSNode($this->t, JS_ARRAY_INIT);
1373  while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
1374  {
1375  if ($tt == OP_COMMA)
1376  {
1377  $this->t->get();
1378  $n->addNode(null);
1379  continue;
1380  }
1381 
1382  $n->addNode($this->Expression($x, OP_COMMA));
1383  if (!$this->t->match(OP_COMMA))
1384  break;
1385  }
1386 
1387  $this->t->mustMatch(OP_RIGHT_BRACKET);
1388  array_push($operands, $n);
1389  $this->t->scanOperand = false;
1390  }
1391  else
1392  {
1393  // Property indexing operator.
1394  array_push($operators, new JSNode($this->t, JS_INDEX));
1395  $this->t->scanOperand = true;
1396  ++$x->bracketLevel;
1397  }
1398  break;
1399 
1400  case OP_RIGHT_BRACKET:
1401  if ($this->t->scanOperand || $x->bracketLevel == $bl)
1402  break 2;
1403 
1404  while ($this->reduce($operators, $operands)->type != JS_INDEX)
1405  continue;
1406 
1407  --$x->bracketLevel;
1408  break;
1409 
1410  case OP_LEFT_CURLY:
1411  if (!$this->t->scanOperand)
1412  break 2;
1413 
1414  // Object initialiser. As for array initialisers (see above),
1415  // parse using recursive descent.
1416  ++$x->curlyLevel;
1417  $n = new JSNode($this->t, JS_OBJECT_INIT);
1418  while (!$this->t->match(OP_RIGHT_CURLY))
1419  {
1420  do
1421  {
1422  $tt = $this->t->get();
1423  $tv = $this->t->currentToken()->value;
1424  if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
1425  {
1426  if ($x->ecmaStrictMode)
1427  throw $this->t->newSyntaxError('Illegal property accessor');
1428 
1429  $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
1430  }
1431  else
1432  {
1433  // Accept keywords as property names by treating
1434  // them similarly with identifiers
1435  if ($this->isKeyword($tt))
1436  $tt = TOKEN_IDENTIFIER;
1437 
1438  switch ($tt)
1439  {
1440  case TOKEN_IDENTIFIER:
1441  case TOKEN_NUMBER:
1442  case TOKEN_STRING:
1443  $id = new JSNode($this->t);
1444  break;
1445 
1446  case OP_RIGHT_CURLY:
1447  if ($x->ecmaStrictMode)
1448  throw $this->t->newSyntaxError('Illegal trailing ,');
1449  break 3;
1450 
1451  default:
1452  throw $this->t->newSyntaxError('Invalid property name');
1453  }
1454 
1455  $this->t->mustMatch(OP_COLON);
1456  $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
1457  }
1458  }
1459  while ($this->t->match(OP_COMMA));
1460 
1461  $this->t->mustMatch(OP_RIGHT_CURLY);
1462  break;
1463  }
1464 
1465  array_push($operands, $n);
1466  $this->t->scanOperand = false;
1467  --$x->curlyLevel;
1468  break;
1469 
1470  case OP_RIGHT_CURLY:
1471  if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1472  throw new Exception('PANIC: right curly botch');
1473  break 2;
1474 
1475  case OP_LEFT_PAREN:
1476  if ($this->t->scanOperand)
1477  {
1478  array_push($operators, new JSNode($this->t, JS_GROUP));
1479  }
1480  else
1481  {
1482  while ( !empty($operators) &&
1483  $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
1484  )
1485  $this->reduce($operators, $operands);
1486 
1487  // Handle () now, to regularize the n-ary case for n > 0.
1488  // We must set scanOperand in case there are arguments and
1489  // the first one is a regexp or unary+/-.
1490  $n = end($operators);
1491  $this->t->scanOperand = true;
1492  if ($this->t->match(OP_RIGHT_PAREN))
1493  {
1494  if ($n && $n->type == KEYWORD_NEW)
1495  {
1496  array_pop($operators);
1497  $n->addNode(array_pop($operands));
1498  }
1499  else
1500  {
1501  $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
1502  }
1503 
1504  array_push($operands, $n);
1505  $this->t->scanOperand = false;
1506  break;
1507  }
1508 
1509  if ($n && $n->type == KEYWORD_NEW)
1510  $n->type = JS_NEW_WITH_ARGS;
1511  else
1512  array_push($operators, new JSNode($this->t, JS_CALL));
1513  }
1514 
1515  ++$x->parenLevel;
1516  break;
1517 
1518  case OP_RIGHT_PAREN:
1519  if ($this->t->scanOperand || $x->parenLevel == $pl)
1520  break 2;
1521 
1522  while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
1523  $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
1524  )
1525  {
1526  continue;
1527  }
1528 
1529  if ($tt != JS_GROUP)
1530  {
1531  $n = end($operands);
1532  if ($n->treeNodes[1]->type != OP_COMMA)
1533  $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
1534  else
1535  $n->treeNodes[1]->type = JS_LIST;
1536  }
1537 
1538  --$x->parenLevel;
1539  break;
1540 
1541  // Automatic semicolon insertion means we may scan across a newline
1542  // and into the beginning of another statement. If so, break out of
1543  // the while loop and let the t.scanOperand logic handle errors.
1544  default:
1545  break 2;
1546  }
1547  }
1548 
1549  if ($x->hookLevel != $hl)
1550  throw $this->t->newSyntaxError('Missing : in conditional expression');
1551 
1552  if ($x->parenLevel != $pl)
1553  throw $this->t->newSyntaxError('Missing ) in parenthetical');
1554 
1555  if ($x->bracketLevel != $bl)
1556  throw $this->t->newSyntaxError('Missing ] in index expression');
1557 
1558  if ($this->t->scanOperand)
1559  throw $this->t->newSyntaxError('Missing operand');
1560 
1561  // Resume default mode, scanning for operands, not operators.
1562  $this->t->scanOperand = true;
1563  $this->t->unget();
1564 
1565  while (count($operators))
1566  $this->reduce($operators, $operands);
1567 
1568  return array_pop($operands);
1569  }
1570 
1571  private function ParenExpression($x)
1572  {
1573  $this->t->mustMatch(OP_LEFT_PAREN);
1574  $n = $this->Expression($x);
1575  $this->t->mustMatch(OP_RIGHT_PAREN);
1576 
1577  return $n;
1578  }
1579 
1580  // Statement stack and nested statement handler.
1581  private function nest($x, $node, $end = false)
1582  {
1583  array_push($x->stmtStack, $node);
1584  $n = $this->statement($x);
1585  array_pop($x->stmtStack);
1586 
1587  if ($end)
1588  $this->t->mustMatch($end);
1589 
1590  return $n;
1591  }
1592 
1593  private function reduce(&$operators, &$operands)
1594  {
1595  $n = array_pop($operators);
1596  $op = $n->type;
1597  $arity = $this->opArity[$op];
1598  $c = count($operands);
1599  if ($arity == -2)
1600  {
1601  // Flatten left-associative trees
1602  if ($c >= 2)
1603  {
1604  $left = $operands[$c - 2];
1605  if ($left->type == $op)
1606  {
1607  $right = array_pop($operands);
1608  $left->addNode($right);
1609  return $left;
1610  }
1611  }
1612  $arity = 2;
1613  }
1614 
1615  // Always use push to add operands to n, to update start and end
1616  $a = array_splice($operands, $c - $arity);
1617  for ($i = 0; $i < $arity; $i++)
1618  $n->addNode($a[$i]);
1619 
1620  // Include closing bracket or postfix operator in [start,end]
1621  $te = $this->t->currentToken()->end;
1622  if ($n->end < $te)
1623  $n->end = $te;
1624 
1625  array_push($operands, $n);
1626 
1627  return $n;
1628  }
1629 
1630  private function isKeyword($tt)
1631  {
1632  switch ($tt) {
1633  case KEYWORD_BREAK:
1634  case KEYWORD_CASE:
1635  case KEYWORD_CATCH:
1636  case KEYWORD_CONST:
1637  case KEYWORD_CONTINUE:
1638  case KEYWORD_DEBUGGER:
1639  case KEYWORD_DEFAULT:
1640  case KEYWORD_DELETE:
1641  case KEYWORD_DO:
1642  case KEYWORD_ELSE:
1643  case KEYWORD_ENUM:
1644  case KEYWORD_FALSE:
1645  case KEYWORD_FINALLY:
1646  case KEYWORD_FOR:
1647  case KEYWORD_FUNCTION:
1648  case KEYWORD_IF:
1649  case KEYWORD_IN:
1650  case KEYWORD_INSTANCEOF:
1651  case KEYWORD_NEW:
1652  case KEYWORD_NULL:
1653  case KEYWORD_RETURN:
1654  case KEYWORD_SWITCH:
1655  case KEYWORD_THIS:
1656  case KEYWORD_THROW:
1657  case KEYWORD_TRUE:
1658  case KEYWORD_TRY:
1659  case KEYWORD_TYPEOF:
1660  case KEYWORD_VAR:
1661  case KEYWORD_VOID:
1662  case KEYWORD_WHILE:
1663  case KEYWORD_WITH:
1664  return true;
1665  default:
1666  return false;
1667  }
1668  }
1669 }
1670 
1672 {
1673  public $inFunction = false;
1674  public $inForLoopInit = false;
1675  public $ecmaStrictMode = false;
1676  public $bracketLevel = 0;
1677  public $curlyLevel = 0;
1678  public $parenLevel = 0;
1679  public $hookLevel = 0;
1680 
1681  public $stmtStack = array();
1682  public $funDecls = array();
1683  public $varDecls = array();
1684 
1685  public function __construct($inFunction)
1686  {
1687  $this->inFunction = $inFunction;
1688  }
1689 }
1690 
1691 class JSNode
1692 {
1693  private $type;
1694  private $value;
1695  private $lineno;
1696  private $start;
1697  private $end;
1698 
1699  public $treeNodes = array();
1700  public $funDecls = array();
1701  public $varDecls = array();
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 
1764  public $filename;
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('@*/');
2060  $tt = TOKEN_CONDCOMMENT_END;
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 = "\\p{L}\\p{Nl}" . # UnicodeLetter
2086  "\$" .
2087  "_";
2088  $identifierPartChars = $identifierStartChars .
2089  "\\p{Mn}\\p{Mc}" . # UnicodeCombiningMark
2090  "\\p{Nd}" . # UnicodeDigit
2091  "\\p{Pc}"; # UnicodeConnectorPunctuation
2092  $unicodeEscape = "\\\\u[0-9A-F-a-f]{4}";
2093  $identifierRegex = "/^" .
2094  "(?:[$identifierStartChars]|$unicodeEscape)" .
2095  "(?:[$identifierPartChars]|$unicodeEscape)*" .
2096  "/uS";
2097  if (preg_match($identifierRegex, $input, $match))
2098  {
2099  if (strpos($match[0], '\\') !== false) {
2100  // Per ECMA-262 edition 5.1, section 7.6 escape sequences should behave as if they were
2101  // the original chars, but only within the boundaries of the identifier.
2102  $decoded = preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/',
2103  array(__CLASS__, 'unicodeEscapeCallback'),
2104  $match[0]);
2105 
2106  // Since our original regex didn't de-escape the originals, we need to check for validity again.
2107  // No need to worry about token boundaries, as anything outside the identifier is illegal!
2108  if (!preg_match("/^[$identifierStartChars][$identifierPartChars]*$/u", $decoded)) {
2109  throw $this->newSyntaxError('Illegal token');
2110  }
2111 
2112  // Per spec it _ought_ to work to use these escapes for keywords words as well...
2113  // but IE rejects them as invalid, while Firefox and Chrome treat them as identifiers
2114  // that don't match the keyword.
2115  if (in_array($decoded, $this->keywords)) {
2116  throw $this->newSyntaxError('Illegal token');
2117  }
2118 
2119  // TODO: save the decoded form for output?
2120  }
2121  }
2122  else
2123  throw $this->newSyntaxError('Illegal token');
2124  }
2125  $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
2126  }
2127  }
2128 
2129  $this->tokenIndex = ($this->tokenIndex + 1) & 3;
2130 
2131  if (!isset($this->tokens[$this->tokenIndex]))
2132  $this->tokens[$this->tokenIndex] = new JSToken();
2133 
2134  $token = $this->tokens[$this->tokenIndex];
2135  $token->type = $tt;
2136 
2137  if ($tt == OP_ASSIGN)
2138  $token->assignOp = $op;
2139 
2140  $token->start = $this->cursor;
2141 
2142  $token->value = $match[0];
2143  $this->cursor += strlen($match[0]);
2144 
2145  $token->end = $this->cursor;
2146  $token->lineno = $this->lineno;
2147 
2148  return $tt;
2149  }
2150 
2151  public function unget()
2152  {
2153  if (++$this->lookahead == 4)
2154  throw $this->newSyntaxError('PANIC: too much lookahead!');
2155 
2156  $this->tokenIndex = ($this->tokenIndex - 1) & 3;
2157  }
2158 
2159  public function newSyntaxError($m)
2160  {
2161  return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
2162  }
2163 
2164  public static function unicodeEscapeCallback($m)
2165  {
2166  return html_entity_decode('&#x' . $m[1]. ';', ENT_QUOTES, 'UTF-8');
2167  }
2168 }
2169 
2170 class JSToken
2171 {
2172  public $type;
2173  public $value;
2174  public $start;
2175  public $end;
2176  public $lineno;
2177  public $assignOp;
2178 }
KEYWORD_CASE
const KEYWORD_CASE
Definition: jsminplus.php:143
JSTokenizer\mustMatch
mustMatch($tt)
Definition: jsminplus.php:1830
OP_STRICT_NE
const OP_STRICT_NE
Definition: jsminplus.php:111
JS_MINIFIED
const JS_MINIFIED
Definition: jsminplus.php:92
OP_STRICT_EQ
const OP_STRICT_EQ
Definition: jsminplus.php:108
KEYWORD_VOID
const KEYWORD_VOID
Definition: jsminplus.php:170
JSTokenizer
Definition: jsminplus.php:1753
JSParser\Statements
Statements($x)
Definition: jsminplus.php:769
KEYWORD_NEW
const KEYWORD_NEW
Definition: jsminplus.php:160
OP_NE
const OP_NE
Definition: jsminplus.php:112
JSParser\__construct
__construct($minifier=null)
Definition: jsminplus.php:727
JSNode\__set
__set($name, $value)
Definition: jsminplus.php:1726
JSTokenizer\$tokens
$tokens
Definition: jsminplus.php:1758
OP_DIV
const OP_DIV
Definition: jsminplus.php:125
JSTokenizer\$scanOperand
$scanOperand
Definition: jsminplus.php:1762
OP_GT
const OP_GT
Definition: jsminplus.php:119
OP_MUL
const OP_MUL
Definition: jsminplus.php:124
KEYWORD_TRUE
const KEYWORD_TRUE
Definition: jsminplus.php:166
OP_LSH
const OP_LSH
Definition: jsminplus.php:113
TOKEN_NEWLINE
const TOKEN_NEWLINE
Definition: jsminplus.php:73
JS_BLOCK
const JS_BLOCK
Definition: jsminplus.php:78
JSNode\$value
$value
Definition: jsminplus.php:1694
KEYWORD_ELSE
const KEYWORD_ELSE
Definition: jsminplus.php:151
JSParser\$minifier
$minifier
Definition: jsminplus.php:677
JSCompilerContext\$curlyLevel
$curlyLevel
Definition: jsminplus.php:1677
JSTokenizer\$lookahead
$lookahead
Definition: jsminplus.php:1760
JS_LABEL
const JS_LABEL
Definition: jsminplus.php:79
JS_INDEX
const JS_INDEX
Definition: jsminplus.php:83
JSNode\__construct
__construct($t, $type=0,... $nodes)
Definition: jsminplus.php:1703
KEYWORD_SWITCH
const KEYWORD_SWITCH
Definition: jsminplus.php:163
JSNode\$treeNodes
$treeNodes
Definition: jsminplus.php:1699
OP_UNARY_MINUS
const OP_UNARY_MINUS
Definition: jsminplus.php:139
OP_URSH
const OP_URSH
Definition: jsminplus.php:116
$s
$s
Definition: mergeMessageFileList.php:185
value
if( $inline) $status value
Definition: SyntaxHighlight.php:346
KEYWORD_ENUM
const KEYWORD_ENUM
Definition: jsminplus.php:152
JS_PROPERTY_INIT
const JS_PROPERTY_INIT
Definition: jsminplus.php:86
KEYWORD_CONTINUE
const KEYWORD_CONTINUE
Definition: jsminplus.php:146
JSTokenizer\match
match($tt)
Definition: jsminplus.php:1825
OP_LEFT_BRACKET
const OP_LEFT_BRACKET
Definition: jsminplus.php:130
JSParser\$opPrecedence
$opPrecedence
Definition: jsminplus.php:679
KEYWORD_VAR
const KEYWORD_VAR
Definition: jsminplus.php:169
JSTokenizer\$lineno
$lineno
Definition: jsminplus.php:1765
JSTokenizer\peek
peek()
Definition: jsminplus.php:1838
OP_UNARY_PLUS
const OP_UNARY_PLUS
Definition: jsminplus.php:138
TOKEN_CONDCOMMENT_START
const TOKEN_CONDCOMMENT_START
Definition: jsminplus.php:74
TOKEN_END
const TOKEN_END
Definition: jsminplus.php:68
JSParser\Script
Script($x)
Definition: jsminplus.php:746
JSParser\isKeyword
isKeyword($tt)
Definition: jsminplus.php:1630
JSMinPlus\minify
static minify($js, $filename='')
Definition: jsminplus.php:199
EXPRESSED_FORM
const EXPRESSED_FORM
Definition: jsminplus.php:95
JSParser\Block
Block($x)
Definition: jsminplus.php:782
OP_MOD
const OP_MOD
Definition: jsminplus.php:126
KEYWORD_WITH
const KEYWORD_WITH
Definition: jsminplus.php:172
TOKEN_CONDCOMMENT_END
const TOKEN_CONDCOMMENT_END
Definition: jsminplus.php:75
JSParser\reduce
reduce(&$operators,&$operands)
Definition: jsminplus.php:1593
JS_SCRIPT
const JS_SCRIPT
Definition: jsminplus.php:77
JSNode
Definition: jsminplus.php:1691
OP_DOT
const OP_DOT
Definition: jsminplus.php:129
JSCompilerContext\$inForLoopInit
$inForLoopInit
Definition: jsminplus.php:1674
JS_SETTER
const JS_SETTER
Definition: jsminplus.php:88
JSCompilerContext\$funDecls
$funDecls
Definition: jsminplus.php:1682
JSMinPlus\__construct
__construct()
Definition: jsminplus.php:194
JSCompilerContext\$ecmaStrictMode
$ecmaStrictMode
Definition: jsminplus.php:1675
JS_LIST
const JS_LIST
Definition: jsminplus.php:90
JSTokenizer\newSyntaxError
newSyntaxError($m)
Definition: jsminplus.php:2159
KEYWORD_RETURN
const KEYWORD_RETURN
Definition: jsminplus.php:162
JSCompilerContext\$stmtStack
$stmtStack
Definition: jsminplus.php:1681
JSParser\Statement
Statement($x)
Definition: jsminplus.php:791
JSNode\$type
$type
Definition: jsminplus.php:1693
OP_BITWISE_XOR
const OP_BITWISE_XOR
Definition: jsminplus.php:106
OP_PLUS
const OP_PLUS
Definition: jsminplus.php:122
JSParser\Expression
Expression($x, $stop=false)
Definition: jsminplus.php:1177
KEYWORD_FALSE
const KEYWORD_FALSE
Definition: jsminplus.php:153
JSCompilerContext\$varDecls
$varDecls
Definition: jsminplus.php:1683
JSNode\$end
$end
Definition: jsminplus.php:1697
JSMinPlus\$reserved
$reserved
Definition: jsminplus.php:178
JSCompilerContext\$inFunction
$inFunction
Definition: jsminplus.php:1673
JSTokenizer\__construct
__construct()
Definition: jsminplus.php:1793
$t
$t
Definition: make-normalization-table.php:143
OP_RSH
const OP_RSH
Definition: jsminplus.php:117
KEYWORD_WHILE
const KEYWORD_WHILE
Definition: jsminplus.php:171
JSParser\FunctionDefinition
FunctionDefinition($x, $requireName, $functionForm)
Definition: jsminplus.php:1107
JSMinPlus\isValidIdentifier
isValidIdentifier($string)
Definition: jsminplus.php:663
JS_ARRAY_INIT
const JS_ARRAY_INIT
Definition: jsminplus.php:84
DECLARED_FORM
const DECLARED_FORM
Definition: jsminplus.php:94
JSParser\Variables
Variables($x)
Definition: jsminplus.php:1148
OP_RIGHT_CURLY
const OP_RIGHT_CURLY
Definition: jsminplus.php:133
OP_EQ
const OP_EQ
Definition: jsminplus.php:109
KEYWORD_IF
const KEYWORD_IF
Definition: jsminplus.php:157
KEYWORD_FOR
const KEYWORD_FOR
Definition: jsminplus.php:155
KEYWORD_NULL
const KEYWORD_NULL
Definition: jsminplus.php:161
OP_BITWISE_AND
const OP_BITWISE_AND
Definition: jsminplus.php:107
OP_AND
const OP_AND
Definition: jsminplus.php:104
OP_LEFT_PAREN
const OP_LEFT_PAREN
Definition: jsminplus.php:134
JSNode\$start
$start
Definition: jsminplus.php:1696
KEYWORD_TYPEOF
const KEYWORD_TYPEOF
Definition: jsminplus.php:168
OP_COMMA
const OP_COMMA
Definition: jsminplus.php:100
KEYWORD_CONST
const KEYWORD_CONST
Definition: jsminplus.php:145
JSMinPlus
Definition: jsminplus.php:175
OP_SEMICOLON
const OP_SEMICOLON
Definition: jsminplus.php:99
KEYWORD_DELETE
const KEYWORD_DELETE
Definition: jsminplus.php:149
OP_LE
const OP_LE
Definition: jsminplus.php:114
JSCompilerContext\$hookLevel
$hookLevel
Definition: jsminplus.php:1679
JSMinPlus\$parser
$parser
Definition: jsminplus.php:177
OP_LT
const OP_LT
Definition: jsminplus.php:115
OP_RIGHT_BRACKET
const OP_RIGHT_BRACKET
Definition: jsminplus.php:131
JSTokenizer\$filename
$filename
Definition: jsminplus.php:1764
JSNode\$lineno
$lineno
Definition: jsminplus.php:1695
JS_GETTER
const JS_GETTER
Definition: jsminplus.php:87
OP_OR
const OP_OR
Definition: jsminplus.php:103
TOKEN_REGEXP
const TOKEN_REGEXP
Definition: jsminplus.php:72
OP_BITWISE_NOT
const OP_BITWISE_NOT
Definition: jsminplus.php:128
JS_GROUP
const JS_GROUP
Definition: jsminplus.php:89
JS_OBJECT_INIT
const JS_OBJECT_INIT
Definition: jsminplus.php:85
JSTokenizer\isDone
isDone()
Definition: jsminplus.php:1820
OP_HOOK
const OP_HOOK
Definition: jsminplus.php:101
JSParser\$opArity
$opArity
Definition: jsminplus.php:703
JS_CALL
const JS_CALL
Definition: jsminplus.php:81
JSTokenizer\$opRegExp
$opRegExp
Definition: jsminplus.php:1791
KEYWORD_INSTANCEOF
const KEYWORD_INSTANCEOF
Definition: jsminplus.php:159
OP_INCREMENT
const OP_INCREMENT
Definition: jsminplus.php:120
JSCompilerContext\__construct
__construct($inFunction)
Definition: jsminplus.php:1685
STATEMENT_FORM
const STATEMENT_FORM
Definition: jsminplus.php:96
JSTokenizer\$source
$source
Definition: jsminplus.php:1756
JSTokenizer\unget
unget()
Definition: jsminplus.php:2151
JSParser\ParenExpression
ParenExpression($x)
Definition: jsminplus.php:1571
KEYWORD_THIS
const KEYWORD_THIS
Definition: jsminplus.php:164
OP_LEFT_CURLY
const OP_LEFT_CURLY
Definition: jsminplus.php:132
JSTokenizer\$assignOps
$assignOps
Definition: jsminplus.php:1790
JSMinPlus\parseTree
parseTree($n, $noBlockGrouping=false)
Definition: jsminplus.php:225
JSTokenizer\$cursor
$cursor
Definition: jsminplus.php:1755
KEYWORD_DEFAULT
const KEYWORD_DEFAULT
Definition: jsminplus.php:148
TOKEN_NUMBER
const TOKEN_NUMBER
Definition: jsminplus.php:69
KEYWORD_FINALLY
const KEYWORD_FINALLY
Definition: jsminplus.php:154
JSParser\nest
nest($x, $node, $end=false)
Definition: jsminplus.php:1581
JSCompilerContext\$parenLevel
$parenLevel
Definition: jsminplus.php:1678
TOKEN_STRING
const TOKEN_STRING
Definition: jsminplus.php:71
OP_ASSIGN
const OP_ASSIGN
Definition: jsminplus.php:110
JSTokenizer\$keywords
$keywords
Definition: jsminplus.php:1767
JSNode\addNode
addNode($node)
Definition: jsminplus.php:1739
JSMinPlus\min
min($js, $filename)
Definition: jsminplus.php:210
JSNode\$funDecls
$funDecls
Definition: jsminplus.php:1700
JSTokenizer\$opTypeNames
$opTypeNames
Definition: jsminplus.php:1782
OP_RIGHT_PAREN
const OP_RIGHT_PAREN
Definition: jsminplus.php:135
KEYWORD_DEBUGGER
const KEYWORD_DEBUGGER
Definition: jsminplus.php:147
JSMinPlus\isWordChar
isWordChar($char)
Definition: jsminplus.php:668
JSTokenizer\$scanNewlines
$scanNewlines
Definition: jsminplus.php:1761
JSNode\__get
__get($name)
Definition: jsminplus.php:1731
JSTokenizer\$tokenIndex
$tokenIndex
Definition: jsminplus.php:1759
JSParser
Definition: jsminplus.php:674
KEYWORD_BREAK
const KEYWORD_BREAK
Definition: jsminplus.php:142
JSTokenizer\getInput
getInput($chunksize)
Definition: jsminplus.php:1812
JSCompilerContext\$bracketLevel
$bracketLevel
Definition: jsminplus.php:1676
OP_GE
const OP_GE
Definition: jsminplus.php:118
TOKEN_IDENTIFIER
const TOKEN_IDENTIFIER
Definition: jsminplus.php:70
JSTokenizer\peekOnSameLine
peekOnSameLine()
Definition: jsminplus.php:1857
JSToken
Definition: jsminplus.php:2170
OP_DECREMENT
const OP_DECREMENT
Definition: jsminplus.php:121
JS_FOR_IN
const JS_FOR_IN
Definition: jsminplus.php:80
JSTokenizer\init
init($source, $filename='', $lineno=1)
Definition: jsminplus.php:1798
JSParser\$t
$t
Definition: jsminplus.php:676
OP_MINUS
const OP_MINUS
Definition: jsminplus.php:123
KEYWORD_CATCH
const KEYWORD_CATCH
Definition: jsminplus.php:144
JSParser\parse
parse($s, $f, $l)
Definition: jsminplus.php:733
JS_NEW_WITH_ARGS
const JS_NEW_WITH_ARGS
Definition: jsminplus.php:82
KEYWORD_FUNCTION
const KEYWORD_FUNCTION
Definition: jsminplus.php:156
JSNode\$varDecls
$varDecls
Definition: jsminplus.php:1701
JSCompilerContext
Definition: jsminplus.php:1671
JSTokenizer\currentToken
currentToken()
Definition: jsminplus.php:1866
KEYWORD_THROW
const KEYWORD_THROW
Definition: jsminplus.php:165
OP_BITWISE_OR
const OP_BITWISE_OR
Definition: jsminplus.php:105
KEYWORD_TRY
const KEYWORD_TRY
Definition: jsminplus.php:167
KEYWORD_IN
const KEYWORD_IN
Definition: jsminplus.php:158
OP_COLON
const OP_COLON
Definition: jsminplus.php:102
KEYWORD_DO
const KEYWORD_DO
Definition: jsminplus.php:150
OP_NOT
const OP_NOT
Definition: jsminplus.php:127
if
if($IP===false)
Definition: initImageData.php:4
$type
$type
Definition: testCompression.php:48