MediaWiki REL1_28
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
68define('TOKEN_END', 1);
69define('TOKEN_NUMBER', 2);
70define('TOKEN_IDENTIFIER', 3);
71define('TOKEN_STRING', 4);
72define('TOKEN_REGEXP', 5);
73define('TOKEN_NEWLINE', 6);
74define('TOKEN_CONDCOMMENT_START', 7);
75define('TOKEN_CONDCOMMENT_END', 8);
76
77define('JS_SCRIPT', 100);
78define('JS_BLOCK', 101);
79define('JS_LABEL', 102);
80define('JS_FOR_IN', 103);
81define('JS_CALL', 104);
82define('JS_NEW_WITH_ARGS', 105);
83define('JS_INDEX', 106);
84define('JS_ARRAY_INIT', 107);
85define('JS_OBJECT_INIT', 108);
86define('JS_PROPERTY_INIT', 109);
87define('JS_GETTER', 110);
88define('JS_SETTER', 111);
89define('JS_GROUP', 112);
90define('JS_LIST', 113);
91
92define('JS_MINIFIED', 999);
93
94define('DECLARED_FORM', 0);
95define('EXPRESSED_FORM', 1);
96define('STATEMENT_FORM', 2);
97
98/* Operators */
99define('OP_SEMICOLON', ';');
100define('OP_COMMA', ',');
101define('OP_HOOK', '?');
102define('OP_COLON', ':');
103define('OP_OR', '||');
104define('OP_AND', '&&');
105define('OP_BITWISE_OR', '|');
106define('OP_BITWISE_XOR', '^');
107define('OP_BITWISE_AND', '&');
108define('OP_STRICT_EQ', '===');
109define('OP_EQ', '==');
110define('OP_ASSIGN', '=');
111define('OP_STRICT_NE', '!==');
112define('OP_NE', '!=');
113define('OP_LSH', '<<');
114define('OP_LE', '<=');
115define('OP_LT', '<');
116define('OP_URSH', '>>>');
117define('OP_RSH', '>>');
118define('OP_GE', '>=');
119define('OP_GT', '>');
120define('OP_INCREMENT', '++');
121define('OP_DECREMENT', '--');
122define('OP_PLUS', '+');
123define('OP_MINUS', '-');
124define('OP_MUL', '*');
125define('OP_DIV', '/');
126define('OP_MOD', '%');
127define('OP_NOT', '!');
128define('OP_BITWISE_NOT', '~');
129define('OP_DOT', '.');
130define('OP_LEFT_BRACKET', '[');
131define('OP_RIGHT_BRACKET', ']');
132define('OP_LEFT_CURLY', '{');
133define('OP_RIGHT_CURLY', '}');
134define('OP_LEFT_PAREN', '(');
135define('OP_RIGHT_PAREN', ')');
136define('OP_CONDCOMMENT_END', '@*/');
137
138define('OP_UNARY_PLUS', 'U+');
139define('OP_UNARY_MINUS', 'U-');
140
141/* Keywords */
142define('KEYWORD_BREAK', 'break');
143define('KEYWORD_CASE', 'case');
144define('KEYWORD_CATCH', 'catch');
145define('KEYWORD_CONST', 'const');
146define('KEYWORD_CONTINUE', 'continue');
147define('KEYWORD_DEBUGGER', 'debugger');
148define('KEYWORD_DEFAULT', 'default');
149define('KEYWORD_DELETE', 'delete');
150define('KEYWORD_DO', 'do');
151define('KEYWORD_ELSE', 'else');
152define('KEYWORD_ENUM', 'enum');
153define('KEYWORD_FALSE', 'false');
154define('KEYWORD_FINALLY', 'finally');
155define('KEYWORD_FOR', 'for');
156define('KEYWORD_FUNCTION', 'function');
157define('KEYWORD_IF', 'if');
158define('KEYWORD_IN', 'in');
159define('KEYWORD_INSTANCEOF', 'instanceof');
160define('KEYWORD_NEW', 'new');
161define('KEYWORD_NULL', 'null');
162define('KEYWORD_RETURN', 'return');
163define('KEYWORD_SWITCH', 'switch');
164define('KEYWORD_THIS', 'this');
165define('KEYWORD_THROW', 'throw');
166define('KEYWORD_TRUE', 'true');
167define('KEYWORD_TRY', 'try');
168define('KEYWORD_TYPEOF', 'typeof');
169define('KEYWORD_VAR', 'var');
170define('KEYWORD_VOID', 'void');
171define('KEYWORD_WHILE', 'while');
172define('KEYWORD_WITH', 'with');
173
174
176{
177 private $parser;
178 private $reserved = array(
179 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
180 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
181 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
182 'void', 'while', 'with',
183 // Words reserved for future use
184 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
185 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
186 'implements', 'import', 'int', 'interface', 'long', 'native',
187 'package', 'private', 'protected', 'public', 'short', 'static',
188 'super', 'synchronized', 'throws', 'transient', 'volatile',
189 // These are not reserved, but should be taken into account
190 // in isValidIdentifier (See jslint source code)
191 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
192 );
193
194 private function __construct()
195 {
196 $this->parser = new JSParser($this);
197 }
198
199 public static function minify($js, $filename='')
200 {
201 static $instance;
202
203 // this is a singleton
204 if(!$instance)
205 $instance = new JSMinPlus();
206
207 return $instance->min($js, $filename);
208 }
209
210 private function min($js, $filename)
211 {
212 try
213 {
214 $n = $this->parser->parse($js, $filename, 1);
215 return $this->parseTree($n);
216 }
217 catch(Exception $e)
218 {
219 echo $e->getMessage() . "\n";
220 }
221
222 return false;
223 }
224
225 public function parseTree($n, $noBlockGrouping = false)
226 {
227 $s = '';
228
229 switch ($n->type)
230 {
231 case JS_MINIFIED:
232 $s = $n->value;
233 break;
234
235 case JS_SCRIPT:
236 // we do nothing yet with funDecls or varDecls
237 $noBlockGrouping = true;
238 // FALL THROUGH
239
240 case JS_BLOCK:
241 $childs = $n->treeNodes;
242 $lastType = 0;
243 for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
244 {
245 $type = $childs[$i]->type;
246 $t = $this->parseTree($childs[$i]);
247 if (strlen($t))
248 {
249 if ($c)
250 {
251 $s = rtrim($s, ';');
252
253 if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
254 {
255 // put declared functions on a new line
256 $s .= "\n";
257 }
258 elseif ($type == KEYWORD_VAR && $type == $lastType)
259 {
260 // multiple var-statements can go into one
261 $t = ',' . substr($t, 4);
262 }
263 else
264 {
265 // add terminator
266 $s .= ';';
267 }
268 }
269
270 $s .= $t;
271
272 $c++;
273 $lastType = $type;
274 }
275 }
276
277 if ($c > 1 && !$noBlockGrouping)
278 {
279 $s = '{' . $s . '}';
280 }
281 break;
282
283 case KEYWORD_FUNCTION:
284 $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
285 $params = $n->params;
286 for ($i = 0, $j = count($params); $i < $j; $i++)
287 $s .= ($i ? ',' : '') . $params[$i];
288 $s .= '){' . $this->parseTree($n->body, true) . '}';
289 break;
290
291 case KEYWORD_IF:
292 $s = 'if(' . $this->parseTree($n->condition) . ')';
293 $thenPart = $this->parseTree($n->thenPart);
294 $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
295
296 // empty if-statement
297 if ($thenPart == '')
298 $thenPart = ';';
299
300 if ($elsePart)
301 {
302 // be careful and always make a block out of the thenPart; could be more optimized but is a lot of trouble
303 if ($thenPart != ';' && $thenPart[0] != '{')
304 $thenPart = '{' . $thenPart . '}';
305
306 $s .= $thenPart . 'else';
307
308 // we could check for more, but that hardly ever applies so go for performance
309 if ($elsePart[0] != '{')
310 $s .= ' ';
311
312 $s .= $elsePart;
313 }
314 else
315 {
316 $s .= $thenPart;
317 }
318 break;
319
320 case KEYWORD_SWITCH:
321 $s = 'switch(' . $this->parseTree($n->discriminant) . '){';
322 $cases = $n->cases;
323 for ($i = 0, $j = count($cases); $i < $j; $i++)
324 {
325 $case = $cases[$i];
326 if ($case->type == KEYWORD_CASE)
327 $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':';
328 else
329 $s .= 'default:';
330
331 $statement = $this->parseTree($case->statements, true);
332 if ($statement)
333 {
334 $s .= $statement;
335 // no terminator for last statement
336 if ($i + 1 < $j)
337 $s .= ';';
338 }
339 }
340 $s .= '}';
341 break;
342
343 case KEYWORD_FOR:
344 $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
345 . ';' . ($n->condition ? $this->parseTree($n->condition) : '')
346 . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')';
347
348 $body = $this->parseTree($n->body);
349 if ($body == '')
350 $body = ';';
351
352 $s .= $body;
353 break;
354
355 case KEYWORD_WHILE:
356 $s = 'while(' . $this->parseTree($n->condition) . ')';
357
358 $body = $this->parseTree($n->body);
359 if ($body == '')
360 $body = ';';
361
362 $s .= $body;
363 break;
364
365 case JS_FOR_IN:
366 $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')';
367
368 $body = $this->parseTree($n->body);
369 if ($body == '')
370 $body = ';';
371
372 $s .= $body;
373 break;
374
375 case KEYWORD_DO:
376 $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')';
377 break;
378
379 case KEYWORD_BREAK:
380 case KEYWORD_CONTINUE:
381 $s = $n->value . ($n->label ? ' ' . $n->label : '');
382 break;
383
384 case KEYWORD_TRY:
385 $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}';
386 $catchClauses = $n->catchClauses;
387 for ($i = 0, $j = count($catchClauses); $i < $j; $i++)
388 {
389 $t = $catchClauses[$i];
390 $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}';
391 }
392 if ($n->finallyBlock)
393 $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}';
394 break;
395
396 case KEYWORD_THROW:
397 case KEYWORD_RETURN:
398 $s = $n->type;
399 if ($n->value)
400 {
401 $t = $this->parseTree($n->value);
402 if (strlen($t))
403 {
404 if ($this->isWordChar($t[0]) || $t[0] == '\\')
405 $s .= ' ';
406
407 $s .= $t;
408 }
409 }
410 break;
411
412 case KEYWORD_WITH:
413 $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
414 break;
415
416 case KEYWORD_VAR:
417 case KEYWORD_CONST:
418 $s = $n->value . ' ';
419 $childs = $n->treeNodes;
420 for ($i = 0, $j = count($childs); $i < $j; $i++)
421 {
422 $t = $childs[$i];
423 $s .= ($i ? ',' : '') . $t->name;
424 $u = $t->initializer;
425 if ($u)
426 $s .= '=' . $this->parseTree($u);
427 }
428 break;
429
430 case KEYWORD_IN:
432 $left = $this->parseTree($n->treeNodes[0]);
433 $right = $this->parseTree($n->treeNodes[1]);
434
435 $s = $left;
436
437 if ($this->isWordChar(substr($left, -1)))
438 $s .= ' ';
439
440 $s .= $n->type;
441
442 if ($this->isWordChar($right[0]) || $right[0] == '\\')
443 $s .= ' ';
444
445 $s .= $right;
446 break;
447
448 case KEYWORD_DELETE:
449 case KEYWORD_TYPEOF:
450 $right = $this->parseTree($n->treeNodes[0]);
451
452 $s = $n->type;
453
454 if ($this->isWordChar($right[0]) || $right[0] == '\\')
455 $s .= ' ';
456
457 $s .= $right;
458 break;
459
460 case KEYWORD_VOID:
461 $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
462 break;
463
464 case KEYWORD_DEBUGGER:
465 throw new Exception('NOT IMPLEMENTED: DEBUGGER');
466 break;
467
470 $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : '');
471 $childs = $n->treeNodes;
472 for ($i = 0, $j = count($childs); $i < $j; $i++)
473 $s .= $this->parseTree($childs[$i]);
474 break;
475
476 case OP_SEMICOLON:
477 if ($expression = $n->expression)
478 $s = $this->parseTree($expression);
479 break;
480
481 case JS_LABEL:
482 $s = $n->label . ':' . $this->parseTree($n->statement);
483 break;
484
485 case OP_COMMA:
486 $childs = $n->treeNodes;
487 for ($i = 0, $j = count($childs); $i < $j; $i++)
488 $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
489 break;
490
491 case OP_ASSIGN:
492 $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]);
493 break;
494
495 case OP_HOOK:
496 $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]);
497 break;
498
499 case OP_OR: case OP_AND:
501 case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
502 case OP_LT: case OP_LE: case OP_GE: case OP_GT:
503 case OP_LSH: case OP_RSH: case OP_URSH:
504 case OP_MUL: case OP_DIV: case OP_MOD:
505 $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]);
506 break;
507
508 case OP_PLUS:
509 case OP_MINUS:
510 $left = $this->parseTree($n->treeNodes[0]);
511 $right = $this->parseTree($n->treeNodes[1]);
512
513 switch ($n->treeNodes[1]->type)
514 {
515 case OP_PLUS:
516 case OP_MINUS:
517 case OP_INCREMENT:
518 case OP_DECREMENT:
519 case OP_UNARY_PLUS:
520 case OP_UNARY_MINUS:
521 $s = $left . $n->type . ' ' . $right;
522 break;
523
524 case TOKEN_STRING:
525 //combine concatenated strings with same quote style
526 if ($n->type == OP_PLUS && substr($left, -1) == $right[0])
527 {
528 $s = substr($left, 0, -1) . substr($right, 1);
529 break;
530 }
531 // FALL THROUGH
532
533 default:
534 $s = $left . $n->type . $right;
535 }
536 break;
537
538 case OP_NOT:
539 case OP_BITWISE_NOT:
540 case OP_UNARY_PLUS:
541 case OP_UNARY_MINUS:
542 $s = $n->value . $this->parseTree($n->treeNodes[0]);
543 break;
544
545 case OP_INCREMENT:
546 case OP_DECREMENT:
547 if ($n->postfix)
548 $s = $this->parseTree($n->treeNodes[0]) . $n->value;
549 else
550 $s = $n->value . $this->parseTree($n->treeNodes[0]);
551 break;
552
553 case OP_DOT:
554 $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]);
555 break;
556
557 case JS_INDEX:
558 $s = $this->parseTree($n->treeNodes[0]);
559 // See if we can replace named index with a dot saving 3 bytes
560 if ( $n->treeNodes[0]->type == TOKEN_IDENTIFIER &&
561 $n->treeNodes[1]->type == TOKEN_STRING &&
562 $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))
563 )
564 $s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
565 else
566 $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']';
567 break;
568
569 case JS_LIST:
570 $childs = $n->treeNodes;
571 for ($i = 0, $j = count($childs); $i < $j; $i++)
572 $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
573 break;
574
575 case JS_CALL:
576 $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
577 break;
578
579 case KEYWORD_NEW:
580 case JS_NEW_WITH_ARGS:
581 $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')';
582 break;
583
584 case JS_ARRAY_INIT:
585 $s = '[';
586 $childs = $n->treeNodes;
587 for ($i = 0, $j = count($childs); $i < $j; $i++)
588 {
589 $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
590 }
591 $s .= ']';
592 break;
593
594 case JS_OBJECT_INIT:
595 $s = '{';
596 $childs = $n->treeNodes;
597 for ($i = 0, $j = count($childs); $i < $j; $i++)
598 {
599 $t = $childs[$i];
600 if ($i)
601 $s .= ',';
602 if ($t->type == JS_PROPERTY_INIT)
603 {
604 // Ditch the quotes when the index is a valid identifier
605 if ( $t->treeNodes[0]->type == TOKEN_STRING &&
606 $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))
607 )
608 $s .= substr($t->treeNodes[0]->value, 1, -1);
609 else
610 $s .= $t->treeNodes[0]->value;
611
612 $s .= ':' . $this->parseTree($t->treeNodes[1]);
613 }
614 else
615 {
616 $s .= $t->type == JS_GETTER ? 'get' : 'set';
617 $s .= ' ' . $t->name . '(';
618 $params = $t->params;
619 for ($i = 0, $j = count($params); $i < $j; $i++)
620 $s .= ($i ? ',' : '') . $params[$i];
621 $s .= '){' . $this->parseTree($t->body, true) . '}';
622 }
623 }
624 $s .= '}';
625 break;
626
627 case TOKEN_NUMBER:
628 $s = $n->value;
629 if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m))
630 $s = $m[1] . 'e' . strlen($m[2]);
631 break;
632
635 $s = $n->value;
636 break;
637
638 case JS_GROUP:
639 if (in_array(
640 $n->treeNodes[0]->type,
641 array(
645 )
646 ))
647 {
648 $s = $this->parseTree($n->treeNodes[0]);
649 }
650 else
651 {
652 $s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
653 }
654 break;
655
656 default:
657 throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
658 }
659
660 return $s;
661 }
662
663 private function isValidIdentifier($string)
664 {
665 return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
666 }
667
668 private function isWordChar($char)
669 {
670 return $char == '_' || $char == '$' || ctype_alnum($char);
671 }
672}
673
675{
676 private $t;
677 private $minifier;
678
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
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
1308 case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1309 case KEYWORD_NEW:
1310 if (!$this->t->scanOperand)
1311 break 2;
1312
1313 array_push($operators, new JSNode($this->t));
1314 break;
1315
1316 case OP_INCREMENT: case OP_DECREMENT:
1317 if ($this->t->scanOperand)
1318 {
1319 array_push($operators, new JSNode($this->t)); // prefix increment or decrement
1320 }
1321 else
1322 {
1323 // Don't cross a line boundary for postfix {in,de}crement.
1324 $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
1325 if ($t && $t->lineno != $this->t->lineno)
1326 break 2;
1327
1328 if (!empty($operators))
1329 {
1330 // Use >, not >=, so postfix has higher precedence than prefix.
1331 while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
1332 $this->reduce($operators, $operands);
1333 }
1334
1335 $n = new JSNode($this->t, $tt, array_pop($operands));
1336 $n->postfix = true;
1337 array_push($operands, $n);
1338 }
1339 break;
1340
1341 case KEYWORD_FUNCTION:
1342 if (!$this->t->scanOperand)
1343 break 2;
1344
1345 array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
1346 $this->t->scanOperand = false;
1347 break;
1348
1349 case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
1351 if (!$this->t->scanOperand)
1352 break 2;
1353
1354 array_push($operands, new JSNode($this->t));
1355 $this->t->scanOperand = false;
1356 break;
1357
1360 if ($this->t->scanOperand)
1361 array_push($operators, new JSNode($this->t));
1362 else
1363 array_push($operands, new JSNode($this->t));
1364 break;
1365
1366 case OP_LEFT_BRACKET:
1367 if ($this->t->scanOperand)
1368 {
1369 // Array initialiser. Parse using recursive descent, as the
1370 // sub-grammar here is not an operator grammar.
1371 $n = new JSNode($this->t, JS_ARRAY_INIT);
1372 while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
1373 {
1374 if ($tt == OP_COMMA)
1375 {
1376 $this->t->get();
1377 $n->addNode(null);
1378 continue;
1379 }
1380
1381 $n->addNode($this->Expression($x, OP_COMMA));
1382 if (!$this->t->match(OP_COMMA))
1383 break;
1384 }
1385
1386 $this->t->mustMatch(OP_RIGHT_BRACKET);
1387 array_push($operands, $n);
1388 $this->t->scanOperand = false;
1389 }
1390 else
1391 {
1392 // Property indexing operator.
1393 array_push($operators, new JSNode($this->t, JS_INDEX));
1394 $this->t->scanOperand = true;
1395 ++$x->bracketLevel;
1396 }
1397 break;
1398
1399 case OP_RIGHT_BRACKET:
1400 if ($this->t->scanOperand || $x->bracketLevel == $bl)
1401 break 2;
1402
1403 while ($this->reduce($operators, $operands)->type != JS_INDEX)
1404 continue;
1405
1406 --$x->bracketLevel;
1407 break;
1408
1409 case OP_LEFT_CURLY:
1410 if (!$this->t->scanOperand)
1411 break 2;
1412
1413 // Object initialiser. As for array initialisers (see above),
1414 // parse using recursive descent.
1415 ++$x->curlyLevel;
1416 $n = new JSNode($this->t, JS_OBJECT_INIT);
1417 while (!$this->t->match(OP_RIGHT_CURLY))
1418 {
1419 do
1420 {
1421 $tt = $this->t->get();
1422 $tv = $this->t->currentToken()->value;
1423 if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
1424 {
1425 if ($x->ecmaStrictMode)
1426 throw $this->t->newSyntaxError('Illegal property accessor');
1427
1428 $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
1429 }
1430 else
1431 {
1432 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
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
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
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('@*/');
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
2126class JSToken
2127{
2128 public $type;
2129 public $value;
2130 public $start;
2131 public $end;
2132 public $lineno;
2133 public $assignOp;
2134}
if( $line===false) $args
Definition cdb.php:64
__construct($inFunction)
isWordChar($char)
isValidIdentifier($string)
min($js, $filename)
parseTree($n, $noBlockGrouping=false)
static minify($js, $filename='')
__set($name, $value)
__construct($t, $type=0)
__get($name)
addNode($node)
Variables($x)
Statement($x)
FunctionDefinition($x, $requireName, $functionForm)
reduce(&$operators, &$operands)
__construct($minifier=null)
parse($s, $f, $l)
Statements($x)
nest($x, $node, $end=false)
Expression($x, $stop=false)
ParenExpression($x)
getInput($chunksize)
init($source, $filename='', $lineno=1)
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:80
the array() calling protocol came about after MediaWiki 1.4rc1.
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' 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:2568
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:1609
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:88
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
returning false will NOT prevent logging $e
Definition hooks.txt:2110
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:37
const OP_RIGHT_BRACKET
const OP_NE
const OP_UNARY_PLUS
const TOKEN_STRING
Definition jsminplus.php:71
const OP_DIV
const OP_LEFT_BRACKET
const TOKEN_NEWLINE
Definition jsminplus.php:73
const OP_DECREMENT
const KEYWORD_CATCH
const KEYWORD_DEFAULT
const OP_EQ
const DECLARED_FORM
Definition jsminplus.php:94
const KEYWORD_CONTINUE
const OP_BITWISE_NOT
const TOKEN_CONDCOMMENT_START
Definition jsminplus.php:74
const OP_LSH
const JS_GROUP
Definition jsminplus.php:89
const KEYWORD_TRUE
const OP_GT
const KEYWORD_CONST
const KEYWORD_INSTANCEOF
const KEYWORD_VOID
const STATEMENT_FORM
Definition jsminplus.php:96
const JS_LABEL
Definition jsminplus.php:79
const KEYWORD_DELETE
const OP_RIGHT_CURLY
const KEYWORD_VAR
const OP_UNARY_MINUS
const OP_MOD
const KEYWORD_FUNCTION
const OP_MINUS
const TOKEN_NUMBER
Definition jsminplus.php:69
const OP_RIGHT_PAREN
const OP_AND
const OP_MUL
const KEYWORD_RETURN
const OP_GE
const KEYWORD_WHILE
const KEYWORD_DEBUGGER
const OP_BITWISE_AND
const KEYWORD_TYPEOF
const OP_BITWISE_XOR
const KEYWORD_SWITCH
const OP_LE
const JS_NEW_WITH_ARGS
Definition jsminplus.php:82
const JS_PROPERTY_INIT
Definition jsminplus.php:86
const KEYWORD_IN
const KEYWORD_FALSE
const JS_SCRIPT
Definition jsminplus.php:77
const KEYWORD_FINALLY
const OP_STRICT_EQ
const JS_FOR_IN
Definition jsminplus.php:80
const KEYWORD_FOR
const KEYWORD_DO
const KEYWORD_THROW
const JS_LIST
Definition jsminplus.php:90
const OP_BITWISE_OR
const JS_OBJECT_INIT
Definition jsminplus.php:85
const JS_MINIFIED
Definition jsminplus.php:92
const JS_GETTER
Definition jsminplus.php:87
const OP_ASSIGN
const KEYWORD_CASE
const OP_PLUS
const TOKEN_REGEXP
Definition jsminplus.php:72
const JS_CALL
Definition jsminplus.php:81
const TOKEN_END
Definition jsminplus.php:68
const KEYWORD_WITH
const KEYWORD_ELSE
const OP_COMMA
const TOKEN_IDENTIFIER
Definition jsminplus.php:70
const OP_STRICT_NE
const OP_DOT
const KEYWORD_THIS
const KEYWORD_IF
const OP_URSH
const TOKEN_CONDCOMMENT_END
Definition jsminplus.php:75
const OP_HOOK
const KEYWORD_NULL
const OP_SEMICOLON
Definition jsminplus.php:99
const OP_LEFT_PAREN
const JS_BLOCK
Definition jsminplus.php:78
const JS_INDEX
Definition jsminplus.php:83
const KEYWORD_BREAK
const OP_RSH
const OP_COLON
const EXPRESSED_FORM
Definition jsminplus.php:95
const KEYWORD_TRY
const JS_ARRAY_INIT
Definition jsminplus.php:84
const OP_LT
const OP_LEFT_CURLY
const OP_OR
const KEYWORD_NEW
const JS_SETTER
Definition jsminplus.php:88
const OP_INCREMENT
const OP_NOT
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:36
$params