MediaWiki  1.28.1
jsminplus.php
Go to the documentation of this file.
1 <?php
2 // @codingStandardsIgnoreFile 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 ? $n2 : null;
913  $this->t->mustMatch(OP_SEMICOLON);
914  $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x);
915  $this->t->mustMatch(OP_SEMICOLON);
916  $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x);
917  }
918 
919  $this->t->mustMatch(OP_RIGHT_PAREN);
920  $n->body = $this->nest($x, $n);
921  return $n;
922 
923  case KEYWORD_WHILE:
924  $n = new JSNode($this->t);
925  $n->isLoop = true;
926  $n->condition = $this->ParenExpression($x);
927  $n->body = $this->nest($x, $n);
928  return $n;
929 
930  case KEYWORD_DO:
931  $n = new JSNode($this->t);
932  $n->isLoop = true;
933  $n->body = $this->nest($x, $n, KEYWORD_WHILE);
934  $n->condition = $this->ParenExpression($x);
935  if (!$x->ecmaStrictMode)
936  {
937  // <script language="JavaScript"> (without version hints) may need
938  // automatic semicolon insertion without a newline after do-while.
939  // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
940  $this->t->match(OP_SEMICOLON);
941  return $n;
942  }
943  break;
944 
945  case KEYWORD_BREAK:
946  case KEYWORD_CONTINUE:
947  $n = new JSNode($this->t);
948 
949  if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER)
950  {
951  $this->t->get();
952  $n->label = $this->t->currentToken()->value;
953  }
954 
955  $ss = $x->stmtStack;
956  $i = count($ss);
957  $label = $n->label;
958  if ($label)
959  {
960  do
961  {
962  if (--$i < 0)
963  throw $this->t->newSyntaxError('Label not found');
964  }
965  while ($ss[$i]->label != $label);
966  }
967  else
968  {
969  do
970  {
971  if (--$i < 0)
972  throw $this->t->newSyntaxError('Invalid ' . $tt);
973  }
974  while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH));
975  }
976 
977  $n->target = $ss[$i];
978  break;
979 
980  case KEYWORD_TRY:
981  $n = new JSNode($this->t);
982  $n->tryBlock = $this->Block($x);
983  $n->catchClauses = array();
984 
985  while ($this->t->match(KEYWORD_CATCH))
986  {
987  $n2 = new JSNode($this->t);
988  $this->t->mustMatch(OP_LEFT_PAREN);
989  $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
990 
991  if ($this->t->match(KEYWORD_IF))
992  {
993  if ($x->ecmaStrictMode)
994  throw $this->t->newSyntaxError('Illegal catch guard');
995 
996  if (count($n->catchClauses) && !end($n->catchClauses)->guard)
997  throw $this->t->newSyntaxError('Guarded catch after unguarded');
998 
999  $n2->guard = $this->Expression($x);
1000  }
1001  else
1002  {
1003  $n2->guard = null;
1004  }
1005 
1006  $this->t->mustMatch(OP_RIGHT_PAREN);
1007  $n2->block = $this->Block($x);
1008  array_push($n->catchClauses, $n2);
1009  }
1010 
1011  if ($this->t->match(KEYWORD_FINALLY))
1012  $n->finallyBlock = $this->Block($x);
1013 
1014  if (!count($n->catchClauses) && !$n->finallyBlock)
1015  throw $this->t->newSyntaxError('Invalid try statement');
1016  return $n;
1017 
1018  case KEYWORD_CATCH:
1019  case KEYWORD_FINALLY:
1020  throw $this->t->newSyntaxError($tt . ' without preceding try');
1021 
1022  case KEYWORD_THROW:
1023  $n = new JSNode($this->t);
1024  $n->value = $this->Expression($x);
1025  break;
1026 
1027  case KEYWORD_RETURN:
1028  if (!$x->inFunction)
1029  throw $this->t->newSyntaxError('Invalid return');
1030 
1031  $n = new JSNode($this->t);
1032  $tt = $this->t->peekOnSameLine();
1033  if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1034  $n->value = $this->Expression($x);
1035  else
1036  $n->value = null;
1037  break;
1038 
1039  case KEYWORD_WITH:
1040  $n = new JSNode($this->t);
1041  $n->object = $this->ParenExpression($x);
1042  $n->body = $this->nest($x, $n);
1043  return $n;
1044 
1045  case KEYWORD_VAR:
1046  case KEYWORD_CONST:
1047  $n = $this->Variables($x);
1048  break;
1049 
1051  case TOKEN_CONDCOMMENT_END:
1052  $n = new JSNode($this->t);
1053  return $n;
1054 
1055  case KEYWORD_DEBUGGER:
1056  $n = new JSNode($this->t);
1057  break;
1058 
1059  case TOKEN_NEWLINE:
1060  case OP_SEMICOLON:
1061  $n = new JSNode($this->t, OP_SEMICOLON);
1062  $n->expression = null;
1063  return $n;
1064 
1065  default:
1066  if ($tt == TOKEN_IDENTIFIER)
1067  {
1068  $this->t->scanOperand = false;
1069  $tt = $this->t->peek();
1070  $this->t->scanOperand = true;
1071  if ($tt == OP_COLON)
1072  {
1073  $label = $this->t->currentToken()->value;
1074  $ss = $x->stmtStack;
1075  for ($i = count($ss) - 1; $i >= 0; --$i)
1076  {
1077  if ($ss[$i]->label == $label)
1078  throw $this->t->newSyntaxError('Duplicate label');
1079  }
1080 
1081  $this->t->get();
1082  $n = new JSNode($this->t, JS_LABEL);
1083  $n->label = $label;
1084  $n->statement = $this->nest($x, $n);
1085 
1086  return $n;
1087  }
1088  }
1089 
1090  $n = new JSNode($this->t, OP_SEMICOLON);
1091  $this->t->unget();
1092  $n->expression = $this->Expression($x);
1093  $n->end = $n->expression->end;
1094  break;
1095  }
1096 
1097  if ($this->t->lineno == $this->t->currentToken()->lineno)
1098  {
1099  $tt = $this->t->peekOnSameLine();
1100  if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1101  throw $this->t->newSyntaxError('Missing ; before statement');
1102  }
1103 
1104  $this->t->match(OP_SEMICOLON);
1105 
1106  return $n;
1107  }
1108 
1109  private function FunctionDefinition($x, $requireName, $functionForm)
1110  {
1111  $f = new JSNode($this->t);
1112 
1113  if ($f->type != KEYWORD_FUNCTION)
1114  $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
1115 
1116  if ($this->t->match(TOKEN_IDENTIFIER))
1117  $f->name = $this->t->currentToken()->value;
1118  elseif ($requireName)
1119  throw $this->t->newSyntaxError('Missing function identifier');
1120 
1121  $this->t->mustMatch(OP_LEFT_PAREN);
1122  $f->params = array();
1123 
1124  while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
1125  {
1126  if ($tt != TOKEN_IDENTIFIER)
1127  throw $this->t->newSyntaxError('Missing formal parameter');
1128 
1129  array_push($f->params, $this->t->currentToken()->value);
1130 
1131  if ($this->t->peek() != OP_RIGHT_PAREN)
1132  $this->t->mustMatch(OP_COMMA);
1133  }
1134 
1135  $this->t->mustMatch(OP_LEFT_CURLY);
1136 
1137  $x2 = new JSCompilerContext(true);
1138  $f->body = $this->Script($x2);
1139 
1140  $this->t->mustMatch(OP_RIGHT_CURLY);
1141  $f->end = $this->t->currentToken()->end;
1142 
1143  $f->functionForm = $functionForm;
1144  if ($functionForm == DECLARED_FORM)
1145  array_push($x->funDecls, $f);
1146 
1147  return $f;
1148  }
1149 
1150  private function Variables($x)
1151  {
1152  $n = new JSNode($this->t);
1153 
1154  do
1155  {
1156  $this->t->mustMatch(TOKEN_IDENTIFIER);
1157 
1158  $n2 = new JSNode($this->t);
1159  $n2->name = $n2->value;
1160 
1161  if ($this->t->match(OP_ASSIGN))
1162  {
1163  if ($this->t->currentToken()->assignOp)
1164  throw $this->t->newSyntaxError('Invalid variable initialization');
1165 
1166  $n2->initializer = $this->Expression($x, OP_COMMA);
1167  }
1168 
1169  $n2->readOnly = $n->type == KEYWORD_CONST;
1170 
1171  $n->addNode($n2);
1172  array_push($x->varDecls, $n2);
1173  }
1174  while ($this->t->match(OP_COMMA));
1175 
1176  return $n;
1177  }
1178 
1179  private function Expression($x, $stop=false)
1180  {
1181  $operators = array();
1182  $operands = array();
1183  $n = false;
1184 
1185  $bl = $x->bracketLevel;
1186  $cl = $x->curlyLevel;
1187  $pl = $x->parenLevel;
1188  $hl = $x->hookLevel;
1189 
1190  while (($tt = $this->t->get()) != TOKEN_END)
1191  {
1192  if ($tt == $stop &&
1193  $x->bracketLevel == $bl &&
1194  $x->curlyLevel == $cl &&
1195  $x->parenLevel == $pl &&
1196  $x->hookLevel == $hl
1197  )
1198  {
1199  // Stop only if tt matches the optional stop parameter, and that
1200  // token is not quoted by some kind of bracket.
1201  break;
1202  }
1203 
1204  switch ($tt)
1205  {
1206  case OP_SEMICOLON:
1207  // NB: cannot be empty, Statement handled that.
1208  break 2;
1209 
1210  case OP_HOOK:
1211  if ($this->t->scanOperand)
1212  break 2;
1213 
1214  while ( !empty($operators) &&
1215  $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1216  )
1217  $this->reduce($operators, $operands);
1218 
1219  array_push($operators, new JSNode($this->t));
1220 
1221  ++$x->hookLevel;
1222  $this->t->scanOperand = true;
1223  $n = $this->Expression($x);
1224 
1225  if (!$this->t->match(OP_COLON))
1226  break 2;
1227 
1228  --$x->hookLevel;
1229  array_push($operands, $n);
1230  break;
1231 
1232  case OP_COLON:
1233  if ($x->hookLevel)
1234  break 2;
1235 
1236  throw $this->t->newSyntaxError('Invalid label');
1237  break;
1238 
1239  case OP_ASSIGN:
1240  if ($this->t->scanOperand)
1241  break 2;
1242 
1243  // Use >, not >=, for right-associative ASSIGN
1244  while ( !empty($operators) &&
1245  $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1246  )
1247  $this->reduce($operators, $operands);
1248 
1249  array_push($operators, new JSNode($this->t));
1250  end($operands)->assignOp = $this->t->currentToken()->assignOp;
1251  $this->t->scanOperand = true;
1252  break;
1253 
1254  case KEYWORD_IN:
1255  // An in operator should not be parsed if we're parsing the head of
1256  // a for (...) loop, unless it is in the then part of a conditional
1257  // expression, or parenthesized somehow.
1258  if ($x->inForLoopInit && !$x->hookLevel &&
1259  !$x->bracketLevel && !$x->curlyLevel &&
1260  !$x->parenLevel
1261  )
1262  break 2;
1263  // FALL THROUGH
1264  case OP_COMMA:
1265  // A comma operator should not be parsed if we're parsing the then part
1266  // of a conditional expression unless it's parenthesized somehow.
1267  if ($tt == OP_COMMA && $x->hookLevel &&
1268  !$x->bracketLevel && !$x->curlyLevel &&
1269  !$x->parenLevel
1270  )
1271  break 2;
1272  // Treat comma as left-associative so reduce can fold left-heavy
1273  // COMMA trees into a single array.
1274  // FALL THROUGH
1275  case OP_OR:
1276  case OP_AND:
1277  case OP_BITWISE_OR:
1278  case OP_BITWISE_XOR:
1279  case OP_BITWISE_AND:
1280  case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
1281  case OP_LT: case OP_LE: case OP_GE: case OP_GT:
1282  case KEYWORD_INSTANCEOF:
1283  case OP_LSH: case OP_RSH: case OP_URSH:
1284  case OP_PLUS: case OP_MINUS:
1285  case OP_MUL: case OP_DIV: case OP_MOD:
1286  case OP_DOT:
1287  if ($this->t->scanOperand)
1288  break 2;
1289 
1290  while ( !empty($operators) &&
1291  $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1292  )
1293  $this->reduce($operators, $operands);
1294 
1295  if ($tt == OP_DOT)
1296  {
1297  $this->t->mustMatch(TOKEN_IDENTIFIER);
1298  array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
1299  }
1300  else
1301  {
1302  array_push($operators, new JSNode($this->t));
1303  $this->t->scanOperand = true;
1304  }
1305  break;
1306 
1307  case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
1308  case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1309  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:
1350  case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
1351  if (!$this->t->scanOperand)
1352  break 2;
1353 
1354  array_push($operands, new JSNode($this->t));
1355  $this->t->scanOperand = false;
1356  break;
1357 
1359  case TOKEN_CONDCOMMENT_END:
1360  if ($this->t->scanOperand)
1361  array_push($operators, new JSNode($this->t));
1362  else
1363  array_push($operands, new JSNode($this->t));
1364  break;
1365 
1366  case OP_LEFT_BRACKET:
1367  if ($this->t->scanOperand)
1368  {
1369  // Array initialiser. Parse using recursive descent, as the
1370  // sub-grammar here is not an operator grammar.
1371  $n = new JSNode($this->t, JS_ARRAY_INIT);
1372  while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
1373  {
1374  if ($tt == OP_COMMA)
1375  {
1376  $this->t->get();
1377  $n->addNode(null);
1378  continue;
1379  }
1380 
1381  $n->addNode($this->Expression($x, OP_COMMA));
1382  if (!$this->t->match(OP_COMMA))
1383  break;
1384  }
1385 
1386  $this->t->mustMatch(OP_RIGHT_BRACKET);
1387  array_push($operands, $n);
1388  $this->t->scanOperand = false;
1389  }
1390  else
1391  {
1392  // Property indexing operator.
1393  array_push($operators, new JSNode($this->t, JS_INDEX));
1394  $this->t->scanOperand = true;
1395  ++$x->bracketLevel;
1396  }
1397  break;
1398 
1399  case OP_RIGHT_BRACKET:
1400  if ($this->t->scanOperand || $x->bracketLevel == $bl)
1401  break 2;
1402 
1403  while ($this->reduce($operators, $operands)->type != JS_INDEX)
1404  continue;
1405 
1406  --$x->bracketLevel;
1407  break;
1408 
1409  case OP_LEFT_CURLY:
1410  if (!$this->t->scanOperand)
1411  break 2;
1412 
1413  // Object initialiser. As for array initialisers (see above),
1414  // parse using recursive descent.
1415  ++$x->curlyLevel;
1416  $n = new JSNode($this->t, JS_OBJECT_INIT);
1417  while (!$this->t->match(OP_RIGHT_CURLY))
1418  {
1419  do
1420  {
1421  $tt = $this->t->get();
1422  $tv = $this->t->currentToken()->value;
1423  if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
1424  {
1425  if ($x->ecmaStrictMode)
1426  throw $this->t->newSyntaxError('Illegal property accessor');
1427 
1428  $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
1429  }
1430  else
1431  {
1432  switch ($tt)
1433  {
1434  case TOKEN_IDENTIFIER:
1435  case TOKEN_NUMBER:
1436  case TOKEN_STRING:
1437  $id = new JSNode($this->t);
1438  break;
1439 
1440  case OP_RIGHT_CURLY:
1441  if ($x->ecmaStrictMode)
1442  throw $this->t->newSyntaxError('Illegal trailing ,');
1443  break 3;
1444 
1445  default:
1446  throw $this->t->newSyntaxError('Invalid property name');
1447  }
1448 
1449  $this->t->mustMatch(OP_COLON);
1450  $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
1451  }
1452  }
1453  while ($this->t->match(OP_COMMA));
1454 
1455  $this->t->mustMatch(OP_RIGHT_CURLY);
1456  break;
1457  }
1458 
1459  array_push($operands, $n);
1460  $this->t->scanOperand = false;
1461  --$x->curlyLevel;
1462  break;
1463 
1464  case OP_RIGHT_CURLY:
1465  if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1466  throw new Exception('PANIC: right curly botch');
1467  break 2;
1468 
1469  case OP_LEFT_PAREN:
1470  if ($this->t->scanOperand)
1471  {
1472  array_push($operators, new JSNode($this->t, JS_GROUP));
1473  }
1474  else
1475  {
1476  while ( !empty($operators) &&
1477  $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
1478  )
1479  $this->reduce($operators, $operands);
1480 
1481  // Handle () now, to regularize the n-ary case for n > 0.
1482  // We must set scanOperand in case there are arguments and
1483  // the first one is a regexp or unary+/-.
1484  $n = end($operators);
1485  $this->t->scanOperand = true;
1486  if ($this->t->match(OP_RIGHT_PAREN))
1487  {
1488  if ($n && $n->type == KEYWORD_NEW)
1489  {
1490  array_pop($operators);
1491  $n->addNode(array_pop($operands));
1492  }
1493  else
1494  {
1495  $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
1496  }
1497 
1498  array_push($operands, $n);
1499  $this->t->scanOperand = false;
1500  break;
1501  }
1502 
1503  if ($n && $n->type == KEYWORD_NEW)
1504  $n->type = JS_NEW_WITH_ARGS;
1505  else
1506  array_push($operators, new JSNode($this->t, JS_CALL));
1507  }
1508 
1509  ++$x->parenLevel;
1510  break;
1511 
1512  case OP_RIGHT_PAREN:
1513  if ($this->t->scanOperand || $x->parenLevel == $pl)
1514  break 2;
1515 
1516  while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
1517  $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
1518  )
1519  {
1520  continue;
1521  }
1522 
1523  if ($tt != JS_GROUP)
1524  {
1525  $n = end($operands);
1526  if ($n->treeNodes[1]->type != OP_COMMA)
1527  $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
1528  else
1529  $n->treeNodes[1]->type = JS_LIST;
1530  }
1531 
1532  --$x->parenLevel;
1533  break;
1534 
1535  // Automatic semicolon insertion means we may scan across a newline
1536  // and into the beginning of another statement. If so, break out of
1537  // the while loop and let the t.scanOperand logic handle errors.
1538  default:
1539  break 2;
1540  }
1541  }
1542 
1543  if ($x->hookLevel != $hl)
1544  throw $this->t->newSyntaxError('Missing : in conditional expression');
1545 
1546  if ($x->parenLevel != $pl)
1547  throw $this->t->newSyntaxError('Missing ) in parenthetical');
1548 
1549  if ($x->bracketLevel != $bl)
1550  throw $this->t->newSyntaxError('Missing ] in index expression');
1551 
1552  if ($this->t->scanOperand)
1553  throw $this->t->newSyntaxError('Missing operand');
1554 
1555  // Resume default mode, scanning for operands, not operators.
1556  $this->t->scanOperand = true;
1557  $this->t->unget();
1558 
1559  while (count($operators))
1560  $this->reduce($operators, $operands);
1561 
1562  return array_pop($operands);
1563  }
1564 
1565  private function ParenExpression($x)
1566  {
1567  $this->t->mustMatch(OP_LEFT_PAREN);
1568  $n = $this->Expression($x);
1569  $this->t->mustMatch(OP_RIGHT_PAREN);
1570 
1571  return $n;
1572  }
1573 
1574  // Statement stack and nested statement handler.
1575  private function nest($x, $node, $end = false)
1576  {
1577  array_push($x->stmtStack, $node);
1578  $n = $this->statement($x);
1579  array_pop($x->stmtStack);
1580 
1581  if ($end)
1582  $this->t->mustMatch($end);
1583 
1584  return $n;
1585  }
1586 
1587  private function reduce(&$operators, &$operands)
1588  {
1589  $n = array_pop($operators);
1590  $op = $n->type;
1591  $arity = $this->opArity[$op];
1592  $c = count($operands);
1593  if ($arity == -2)
1594  {
1595  // Flatten left-associative trees
1596  if ($c >= 2)
1597  {
1598  $left = $operands[$c - 2];
1599  if ($left->type == $op)
1600  {
1601  $right = array_pop($operands);
1602  $left->addNode($right);
1603  return $left;
1604  }
1605  }
1606  $arity = 2;
1607  }
1608 
1609  // Always use push to add operands to n, to update start and end
1610  $a = array_splice($operands, $c - $arity);
1611  for ($i = 0; $i < $arity; $i++)
1612  $n->addNode($a[$i]);
1613 
1614  // Include closing bracket or postfix operator in [start,end]
1615  $te = $this->t->currentToken()->end;
1616  if ($n->end < $te)
1617  $n->end = $te;
1618 
1619  array_push($operands, $n);
1620 
1621  return $n;
1622  }
1623 }
1624 
1626 {
1627  public $inFunction = false;
1628  public $inForLoopInit = false;
1629  public $ecmaStrictMode = false;
1630  public $bracketLevel = 0;
1631  public $curlyLevel = 0;
1632  public $parenLevel = 0;
1633  public $hookLevel = 0;
1634 
1635  public $stmtStack = array();
1636  public $funDecls = array();
1637  public $varDecls = array();
1638 
1639  public function __construct($inFunction)
1640  {
1641  $this->inFunction = $inFunction;
1642  }
1643 }
1644 
1645 class JSNode
1646 {
1647  private $type;
1648  private $value;
1649  private $lineno;
1650  private $start;
1651  private $end;
1652 
1653  public $treeNodes = array();
1654  public $funDecls = array();
1655  public $varDecls = array();
1656 
1657  public function __construct($t, $type=0)
1658  {
1659  if ($token = $t->currentToken())
1660  {
1661  $this->type = $type ? $type : $token->type;
1662  $this->value = $token->value;
1663  $this->lineno = $token->lineno;
1664  $this->start = $token->start;
1665  $this->end = $token->end;
1666  }
1667  else
1668  {
1669  $this->type = $type;
1670  $this->lineno = $t->lineno;
1671  }
1672 
1673  if (($numargs = func_num_args()) > 2)
1674  {
1675  $args = func_get_args();
1676  for ($i = 2; $i < $numargs; $i++)
1677  $this->addNode($args[$i]);
1678  }
1679  }
1680 
1681  // we don't want to bloat our object with all kind of specific properties, so we use overloading
1682  public function __set($name, $value)
1683  {
1684  $this->$name = $value;
1685  }
1686 
1687  public function __get($name)
1688  {
1689  if (isset($this->$name))
1690  return $this->$name;
1691 
1692  return null;
1693  }
1694 
1695  public function addNode($node)
1696  {
1697  if ($node !== null)
1698  {
1699  if ($node->start < $this->start)
1700  $this->start = $node->start;
1701  if ($this->end < $node->end)
1702  $this->end = $node->end;
1703  }
1704 
1705  $this->treeNodes[] = $node;
1706  }
1707 }
1708 
1710 {
1711  private $cursor = 0;
1712  private $source;
1713 
1714  public $tokens = array();
1715  public $tokenIndex = 0;
1716  public $lookahead = 0;
1717  public $scanNewlines = false;
1718  public $scanOperand = true;
1719 
1720  public $filename;
1721  public $lineno;
1722 
1723  private $keywords = array(
1724  'break',
1725  'case', 'catch', 'const', 'continue',
1726  'debugger', 'default', 'delete', 'do',
1727  'else', 'enum',
1728  'false', 'finally', 'for', 'function',
1729  'if', 'in', 'instanceof',
1730  'new', 'null',
1731  'return',
1732  'switch',
1733  'this', 'throw', 'true', 'try', 'typeof',
1734  'var', 'void',
1735  'while', 'with'
1736  );
1737 
1738  private $opTypeNames = array(
1739  ';', ',', '?', ':', '||', '&&', '|', '^',
1740  '&', '===', '==', '=', '!==', '!=', '<<', '<=',
1741  '<', '>>>', '>>', '>=', '>', '++', '--', '+',
1742  '-', '*', '/', '%', '!', '~', '.', '[',
1743  ']', '{', '}', '(', ')', '@*/'
1744  );
1745 
1746  private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
1747  private $opRegExp;
1748 
1749  public function __construct()
1750  {
1751  $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#';
1752  }
1753 
1754  public function init($source, $filename = '', $lineno = 1)
1755  {
1756  $this->source = $source;
1757  $this->filename = $filename ? $filename : '[inline]';
1758  $this->lineno = $lineno;
1759 
1760  $this->cursor = 0;
1761  $this->tokens = array();
1762  $this->tokenIndex = 0;
1763  $this->lookahead = 0;
1764  $this->scanNewlines = false;
1765  $this->scanOperand = true;
1766  }
1767 
1768  public function getInput($chunksize)
1769  {
1770  if ($chunksize)
1771  return substr($this->source, $this->cursor, $chunksize);
1772 
1773  return substr($this->source, $this->cursor);
1774  }
1775 
1776  public function isDone()
1777  {
1778  return $this->peek() == TOKEN_END;
1779  }
1780 
1781  public function match($tt)
1782  {
1783  return $this->get() == $tt || $this->unget();
1784  }
1785 
1786  public function mustMatch($tt)
1787  {
1788  if (!$this->match($tt))
1789  throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
1790 
1791  return $this->currentToken();
1792  }
1793 
1794  public function peek()
1795  {
1796  if ($this->lookahead)
1797  {
1798  $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
1799  if ($this->scanNewlines && $next->lineno != $this->lineno)
1800  $tt = TOKEN_NEWLINE;
1801  else
1802  $tt = $next->type;
1803  }
1804  else
1805  {
1806  $tt = $this->get();
1807  $this->unget();
1808  }
1809 
1810  return $tt;
1811  }
1812 
1813  public function peekOnSameLine()
1814  {
1815  $this->scanNewlines = true;
1816  $tt = $this->peek();
1817  $this->scanNewlines = false;
1818 
1819  return $tt;
1820  }
1821 
1822  public function currentToken()
1823  {
1824  if (!empty($this->tokens))
1825  return $this->tokens[$this->tokenIndex];
1826  }
1827 
1828  public function get($chunksize = 1000)
1829  {
1830  while($this->lookahead)
1831  {
1832  $this->lookahead--;
1833  $this->tokenIndex = ($this->tokenIndex + 1) & 3;
1834  $token = $this->tokens[$this->tokenIndex];
1835  if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
1836  return $token->type;
1837  }
1838 
1839  $conditional_comment = false;
1840 
1841  // strip whitespace and comments
1842  while(true)
1843  {
1844  $input = $this->getInput($chunksize);
1845 
1846  // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
1847  $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
1848  if (preg_match($re, $input, $match))
1849  {
1850  $spaces = $match[0];
1851  $spacelen = strlen($spaces);
1852  $this->cursor += $spacelen;
1853  if (!$this->scanNewlines)
1854  $this->lineno += substr_count($spaces, "\n");
1855 
1856  if ($spacelen == $chunksize)
1857  continue; // complete chunk contained whitespace
1858 
1859  $input = $this->getInput($chunksize);
1860  if ($input == '' || $input[0] != '/')
1861  break;
1862  }
1863 
1864  // Comments
1865  if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
1866  {
1867  if (!$chunksize)
1868  break;
1869 
1870  // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
1871  $chunksize = null;
1872  continue;
1873  }
1874 
1875  // check if this is a conditional (JScript) comment
1876  if (!empty($match[1]))
1877  {
1878  $match[0] = '/*' . $match[1];
1879  $conditional_comment = true;
1880  break;
1881  }
1882  else
1883  {
1884  $this->cursor += strlen($match[0]);
1885  $this->lineno += substr_count($match[0], "\n");
1886  }
1887  }
1888 
1889  if ($input == '')
1890  {
1891  $tt = TOKEN_END;
1892  $match = array('');
1893  }
1894  elseif ($conditional_comment)
1895  {
1897  }
1898  else
1899  {
1900  switch ($input[0])
1901  {
1902  case '0':
1903  // hexadecimal
1904  if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match))
1905  {
1906  $tt = TOKEN_NUMBER;
1907  break;
1908  }
1909  // FALL THROUGH
1910 
1911  case '1': case '2': case '3': case '4': case '5':
1912  case '6': case '7': case '8': case '9':
1913  // should always match
1914  preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match);
1915  $tt = TOKEN_NUMBER;
1916  break;
1917 
1918  case "'":
1919  if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match))
1920  {
1921  $tt = TOKEN_STRING;
1922  }
1923  else
1924  {
1925  if ($chunksize)
1926  return $this->get(null); // retry with a full chunk fetch
1927 
1928  throw $this->newSyntaxError('Unterminated string literal');
1929  }
1930  break;
1931 
1932  case '"':
1933  if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match))
1934  {
1935  $tt = TOKEN_STRING;
1936  }
1937  else
1938  {
1939  if ($chunksize)
1940  return $this->get(null); // retry with a full chunk fetch
1941 
1942  throw $this->newSyntaxError('Unterminated string literal');
1943  }
1944  break;
1945 
1946  case '/':
1947  if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
1948  {
1949  $tt = TOKEN_REGEXP;
1950  break;
1951  }
1952  // FALL THROUGH
1953 
1954  case '|':
1955  case '^':
1956  case '&':
1957  case '<':
1958  case '>':
1959  case '+':
1960  case '-':
1961  case '*':
1962  case '%':
1963  case '=':
1964  case '!':
1965  // should always match
1966  preg_match($this->opRegExp, $input, $match);
1967  $op = $match[0];
1968  if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
1969  {
1970  $tt = OP_ASSIGN;
1971  $match[0] .= '=';
1972  }
1973  else
1974  {
1975  $tt = $op;
1976  if ($this->scanOperand)
1977  {
1978  if ($op == OP_PLUS)
1979  $tt = OP_UNARY_PLUS;
1980  elseif ($op == OP_MINUS)
1981  $tt = OP_UNARY_MINUS;
1982  }
1983  $op = null;
1984  }
1985  break;
1986 
1987  case '.':
1988  if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
1989  {
1990  $tt = TOKEN_NUMBER;
1991  break;
1992  }
1993  // FALL THROUGH
1994 
1995  case ';':
1996  case ',':
1997  case '?':
1998  case ':':
1999  case '~':
2000  case '[':
2001  case ']':
2002  case '{':
2003  case '}':
2004  case '(':
2005  case ')':
2006  // these are all single
2007  $match = array($input[0]);
2008  $tt = $input[0];
2009  break;
2010 
2011  case '@':
2012  // check end of conditional comment
2013  if (substr($input, 0, 3) == '@*/')
2014  {
2015  $match = array('@*/');
2016  $tt = TOKEN_CONDCOMMENT_END;
2017  }
2018  else
2019  throw $this->newSyntaxError('Illegal token');
2020  break;
2021 
2022  case "\n":
2023  if ($this->scanNewlines)
2024  {
2025  $match = array("\n");
2026  $tt = TOKEN_NEWLINE;
2027  }
2028  else
2029  throw $this->newSyntaxError('Illegal token');
2030  break;
2031 
2032  default:
2033  // Fast path for identifiers: word chars followed by whitespace or various other tokens.
2034  // Note we don't need to exclude digits in the first char, as they've already been found
2035  // above.
2036  if (!preg_match('/^[$\w]+(?=[\s\/\|\^\&<>\+\-\*%=!.;,\?:~\[\]\{\}\(\)@])/', $input, $match))
2037  {
2038  // Character classes per ECMA-262 edition 5.1 section 7.6
2039  // Per spec, must accept Unicode 3.0, *may* accept later versions.
2040  // We'll take whatever PCRE understands, which should be more recent.
2041  $identifierStartChars = "\\p{L}\\p{Nl}" . # UnicodeLetter
2042  "\$" .
2043  "_";
2044  $identifierPartChars = $identifierStartChars .
2045  "\\p{Mn}\\p{Mc}" . # UnicodeCombiningMark
2046  "\\p{Nd}" . # UnicodeDigit
2047  "\\p{Pc}"; # UnicodeConnectorPunctuation
2048  $unicodeEscape = "\\\\u[0-9A-F-a-f]{4}";
2049  $identifierRegex = "/^" .
2050  "(?:[$identifierStartChars]|$unicodeEscape)" .
2051  "(?:[$identifierPartChars]|$unicodeEscape)*" .
2052  "/uS";
2053  if (preg_match($identifierRegex, $input, $match))
2054  {
2055  if (strpos($match[0], '\\') !== false) {
2056  // Per ECMA-262 edition 5.1, section 7.6 escape sequences should behave as if they were
2057  // the original chars, but only within the boundaries of the identifier.
2058  $decoded = preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/',
2059  array(__CLASS__, 'unicodeEscapeCallback'),
2060  $match[0]);
2061 
2062  // Since our original regex didn't de-escape the originals, we need to check for validity again.
2063  // No need to worry about token boundaries, as anything outside the identifier is illegal!
2064  if (!preg_match("/^[$identifierStartChars][$identifierPartChars]*$/u", $decoded)) {
2065  throw $this->newSyntaxError('Illegal token');
2066  }
2067 
2068  // Per spec it _ought_ to work to use these escapes for keywords words as well...
2069  // but IE rejects them as invalid, while Firefox and Chrome treat them as identifiers
2070  // that don't match the keyword.
2071  if (in_array($decoded, $this->keywords)) {
2072  throw $this->newSyntaxError('Illegal token');
2073  }
2074 
2075  // TODO: save the decoded form for output?
2076  }
2077  }
2078  else
2079  throw $this->newSyntaxError('Illegal token');
2080  }
2081  $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
2082  }
2083  }
2084 
2085  $this->tokenIndex = ($this->tokenIndex + 1) & 3;
2086 
2087  if (!isset($this->tokens[$this->tokenIndex]))
2088  $this->tokens[$this->tokenIndex] = new JSToken();
2089 
2090  $token = $this->tokens[$this->tokenIndex];
2091  $token->type = $tt;
2092 
2093  if ($tt == OP_ASSIGN)
2094  $token->assignOp = $op;
2095 
2096  $token->start = $this->cursor;
2097 
2098  $token->value = $match[0];
2099  $this->cursor += strlen($match[0]);
2100 
2101  $token->end = $this->cursor;
2102  $token->lineno = $this->lineno;
2103 
2104  return $tt;
2105  }
2106 
2107  public function unget()
2108  {
2109  if (++$this->lookahead == 4)
2110  throw $this->newSyntaxError('PANIC: too much lookahead!');
2111 
2112  $this->tokenIndex = ($this->tokenIndex - 1) & 3;
2113  }
2114 
2115  public function newSyntaxError($m)
2116  {
2117  return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
2118  }
2119 
2120  public static function unicodeEscapeCallback($m)
2121  {
2122  return html_entity_decode('&#x' . $m[1]. ';', ENT_QUOTES, 'UTF-8');
2123  }
2124 }
2125 
2126 class JSToken
2127 {
2128  public $type;
2129  public $value;
2130  public $start;
2131  public $end;
2132  public $lineno;
2133  public $assignOp;
2134 }
const JS_PROPERTY_INIT
Definition: jsminplus.php:86
__get($name)
Definition: jsminplus.php:1687
const KEYWORD_VAR
Definition: jsminplus.php:169
const OP_BITWISE_AND
Definition: jsminplus.php:107
const OP_COLON
Definition: jsminplus.php:102
parse($s, $f, $l)
Definition: jsminplus.php:733
const OP_LEFT_CURLY
Definition: jsminplus.php:132
the array() calling protocol came about after MediaWiki 1.4rc1.
const OP_EQ
Definition: jsminplus.php:109
const OP_LSH
Definition: jsminplus.php:113
init($source, $filename= '', $lineno=1)
Definition: jsminplus.php:1754
__construct($minifier=null)
Definition: jsminplus.php:727
const OP_MOD
Definition: jsminplus.php:126
const TOKEN_NUMBER
Definition: jsminplus.php:69
const KEYWORD_IN
Definition: jsminplus.php:158
const KEYWORD_CONST
Definition: jsminplus.php:145
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2102
const KEYWORD_NEW
Definition: jsminplus.php:160
const KEYWORD_FALSE
Definition: jsminplus.php:153
const KEYWORD_DEBUGGER
Definition: jsminplus.php:147
__construct($t, $type=0)
Definition: jsminplus.php:1657
const OP_NE
Definition: jsminplus.php:112
const JS_CALL
Definition: jsminplus.php:81
Script($x)
Definition: jsminplus.php:746
const OP_MINUS
Definition: jsminplus.php:123
const KEYWORD_THIS
Definition: jsminplus.php:164
const OP_RIGHT_CURLY
Definition: jsminplus.php:133
const JS_NEW_WITH_ARGS
Definition: jsminplus.php:82
const TOKEN_NEWLINE
Definition: jsminplus.php:73
const KEYWORD_ELSE
Definition: jsminplus.php:151
const KEYWORD_DEFAULT
Definition: jsminplus.php:148
const KEYWORD_THROW
Definition: jsminplus.php:165
const KEYWORD_FINALLY
Definition: jsminplus.php:154
const STATEMENT_FORM
Definition: jsminplus.php:96
Statements($x)
Definition: jsminplus.php:769
const KEYWORD_SWITCH
Definition: jsminplus.php:163
Statement($x)
Definition: jsminplus.php:791
min($js, $filename)
Definition: jsminplus.php:210
newSyntaxError($m)
Definition: jsminplus.php:2115
const KEYWORD_TRY
Definition: jsminplus.php:167
const OP_LT
Definition: jsminplus.php:115
if($line===false) $args
Definition: cdb.php:64
const KEYWORD_CONTINUE
Definition: jsminplus.php:146
const OP_STRICT_EQ
Definition: jsminplus.php:108
const KEYWORD_CASE
Definition: jsminplus.php:143
const TOKEN_CONDCOMMENT_END
Definition: jsminplus.php:75
const OP_DECREMENT
Definition: jsminplus.php:121
const OP_GT
Definition: jsminplus.php:119
const JS_GETTER
Definition: jsminplus.php:87
const JS_GROUP
Definition: jsminplus.php:89
const KEYWORD_CATCH
Definition: jsminplus.php:144
const OP_HOOK
Definition: jsminplus.php:101
parseTree($n, $noBlockGrouping=false)
Definition: jsminplus.php:225
const OP_LEFT_PAREN
Definition: jsminplus.php:134
const DECLARED_FORM
Definition: jsminplus.php:94
const JS_MINIFIED
Definition: jsminplus.php:92
const KEYWORD_IF
Definition: jsminplus.php:157
$params
const OP_AND
Definition: jsminplus.php:104
ParenExpression($x)
Definition: jsminplus.php:1565
Variables($x)
Definition: jsminplus.php:1150
const OP_COMMA
Definition: jsminplus.php:100
Expression($x, $stop=false)
Definition: jsminplus.php:1179
const OP_STRICT_NE
Definition: jsminplus.php:111
Block($x)
Definition: jsminplus.php:782
const JS_BLOCK
Definition: jsminplus.php:78
const OP_URSH
Definition: jsminplus.php:116
const OP_RSH
Definition: jsminplus.php:117
const OP_GE
Definition: jsminplus.php:118
nest($x, $node, $end=false)
Definition: jsminplus.php:1575
const OP_PLUS
Definition: jsminplus.php:122
const JS_ARRAY_INIT
Definition: jsminplus.php:84
const OP_INCREMENT
Definition: jsminplus.php:120
const KEYWORD_BREAK
Definition: jsminplus.php:142
FunctionDefinition($x, $requireName, $functionForm)
Definition: jsminplus.php:1109
const OP_DIV
Definition: jsminplus.php:125
isValidIdentifier($string)
Definition: jsminplus.php:663
addNode($node)
Definition: jsminplus.php:1695
const KEYWORD_INSTANCEOF
Definition: jsminplus.php:159
const KEYWORD_TYPEOF
Definition: jsminplus.php:168
const OP_NOT
Definition: jsminplus.php:127
if($IP===false)
Definition: WebStart.php:59
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
const KEYWORD_FUNCTION
Definition: jsminplus.php:156
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
Definition: postgres.txt:22
const KEYWORD_FOR
Definition: jsminplus.php:155
const OP_SEMICOLON
Definition: jsminplus.php:99
const OP_DOT
Definition: jsminplus.php:129
const KEYWORD_RETURN
Definition: jsminplus.php:162
const JS_LABEL
Definition: jsminplus.php:79
const OP_LEFT_BRACKET
Definition: jsminplus.php:130
const OP_ASSIGN
Definition: jsminplus.php:110
const OP_UNARY_PLUS
Definition: jsminplus.php:138
const OP_OR
Definition: jsminplus.php:103
const TOKEN_END
Definition: jsminplus.php:68
const TOKEN_REGEXP
Definition: jsminplus.php:72
const OP_BITWISE_NOT
Definition: jsminplus.php:128
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify prev or next refreshes the diff cache allow viewing deleted revs difference engine object to be used for diff source
Definition: hooks.txt:1606
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next line
Definition: design.txt:79
const JS_FOR_IN
Definition: jsminplus.php:80
getInput($chunksize)
Definition: jsminplus.php:1768
const JS_LIST
Definition: jsminplus.php:90
const KEYWORD_NULL
Definition: jsminplus.php:161
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:86
const JS_SCRIPT
Definition: jsminplus.php:77
const JS_SETTER
Definition: jsminplus.php:88
const TOKEN_STRING
Definition: jsminplus.php:71
__construct($inFunction)
Definition: jsminplus.php:1639
const KEYWORD_WHILE
Definition: jsminplus.php:171
const JS_INDEX
Definition: jsminplus.php:83
const EXPRESSED_FORM
Definition: jsminplus.php:95
const KEYWORD_TRUE
Definition: jsminplus.php:166
const KEYWORD_WITH
Definition: jsminplus.php:172
const KEYWORD_DELETE
Definition: jsminplus.php:149
mustMatch($tt)
Definition: jsminplus.php:1786
const OP_RIGHT_PAREN
Definition: jsminplus.php:135
const OP_BITWISE_OR
Definition: jsminplus.php:105
__set($name, $value)
Definition: jsminplus.php:1682
const KEYWORD_DO
Definition: jsminplus.php:150
const OP_BITWISE_XOR
Definition: jsminplus.php:106
isWordChar($char)
Definition: jsminplus.php:668
const OP_MUL
Definition: jsminplus.php:124
const OP_LE
Definition: jsminplus.php:114
const TOKEN_CONDCOMMENT_START
Definition: jsminplus.php:74
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2491
reduce(&$operators, &$operands)
Definition: jsminplus.php:1587
static minify($js, $filename='')
Definition: jsminplus.php:199
const OP_RIGHT_BRACKET
Definition: jsminplus.php:131
const OP_UNARY_MINUS
Definition: jsminplus.php:139
const TOKEN_IDENTIFIER
Definition: jsminplus.php:70
const KEYWORD_VOID
Definition: jsminplus.php:170
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300
const JS_OBJECT_INIT
Definition: jsminplus.php:85