4 * JSMinPlus version 1.3
6 * Minifies a javascript file using a javascript parser
8 * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript)
9 * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine)
10 * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/
11 * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716
13 * Tino Zijdel <crisp@tweakers.net>
15 * Usage: $minified = JSMinPlus::minify($script [, $filename])
17 * Versionlog (see also changelog.txt):
18 * 17-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs
19 * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes
20 * 12-04-2009 - some small bugfixes and performance improvements
21 * 09-04-2009 - initial open sourced version 1.0
23 * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip
27 /* ***** BEGIN LICENSE BLOCK *****
28 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
30 * The contents of this file are subject to the Mozilla Public License Version
31 * 1.1 (the "License"); you may not use this file except in compliance with
32 * the License. You may obtain a copy of the License at
33 * http://www.mozilla.org/MPL/
35 * Software distributed under the License is distributed on an "AS IS" basis,
36 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
37 * for the specific language governing rights and limitations under the
40 * The Original Code is the Narcissus JavaScript engine.
42 * The Initial Developer of the Original Code is
43 * Brendan Eich <brendan@mozilla.org>.
44 * Portions created by the Initial Developer are Copyright (C) 2004
45 * the Initial Developer. All Rights Reserved.
47 * Contributor(s): Tino Zijdel <crisp@tweakers.net>
48 * PHP port, modifications and minifier routine are (C) 2009
50 * Alternatively, the contents of this file may be used under the terms of
51 * either the GNU General Public License Version 2 or later (the "GPL"), or
52 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
53 * in which case the provisions of the GPL or the LGPL are applicable instead
54 * of those above. If you wish to allow use of your version of this file only
55 * under the terms of either the GPL or the LGPL, and not to allow others to
56 * use your version of this file under the terms of the MPL, indicate your
57 * decision by deleting the provisions above and replace them with the notice
58 * and other provisions required by the GPL or the LGPL. If you do not delete
59 * the provisions above, a recipient may use your version of this file under
60 * the terms of any one of the MPL, the GPL or the LGPL.
62 * ***** END LICENSE BLOCK ***** */
64 define('TOKEN_END', 1);
65 define('TOKEN_NUMBER', 2);
66 define('TOKEN_IDENTIFIER', 3);
67 define('TOKEN_STRING', 4);
68 define('TOKEN_REGEXP', 5);
69 define('TOKEN_NEWLINE', 6);
70 define('TOKEN_CONDCOMMENT_START', 7);
71 define('TOKEN_CONDCOMMENT_END', 8);
73 define('JS_SCRIPT', 100);
74 define('JS_BLOCK', 101);
75 define('JS_LABEL', 102);
76 define('JS_FOR_IN', 103);
77 define('JS_CALL', 104);
78 define('JS_NEW_WITH_ARGS', 105);
79 define('JS_INDEX', 106);
80 define('JS_ARRAY_INIT', 107);
81 define('JS_OBJECT_INIT', 108);
82 define('JS_PROPERTY_INIT', 109);
83 define('JS_GETTER', 110);
84 define('JS_SETTER', 111);
85 define('JS_GROUP', 112);
86 define('JS_LIST', 113);
88 define('DECLARED_FORM', 0);
89 define('EXPRESSED_FORM', 1);
90 define('STATEMENT_FORM', 2);
95 private $reserved = array(
96 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
97 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
98 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
99 'void', 'while', 'with',
100 // Words reserved for future use
101 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
102 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
103 'implements', 'import', 'int', 'interface', 'long', 'native',
104 'package', 'private', 'protected', 'public', 'short', 'static',
105 'super', 'synchronized', 'throws', 'transient', 'volatile',
106 // These are not reserved, but should be taken into account
107 // in isValidIdentifier (See jslint source code)
108 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
111 private function __construct()
113 $this->parser
= new JSParser();
116 public static function minify($js, $filename='')
120 // this is a singleton
122 $instance = new JSMinPlus();
124 return $instance->min($js, $filename);
127 private function min($js, $filename)
131 $n = $this->parser
->parse($js, $filename, 1);
132 return $this->parseTree($n);
136 echo $e->getMessage() . "\n";
142 private function parseTree($n, $noBlockGrouping = false)
148 case KEYWORD_FUNCTION
:
149 $s .= 'function' . ($n->name ?
' ' . $n->name
: '') . '(';
150 $params = $n->params
;
151 for ($i = 0, $j = count($params); $i < $j; $i++
)
152 $s .= ($i ?
',' : '') . $params[$i];
153 $s .= '){' . $this->parseTree($n->body
, true) . '}';
157 // we do nothing with funDecls or varDecls
158 $noBlockGrouping = true;
162 $childs = $n->treeNodes
;
164 for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++
)
166 $type = $childs[$i]->type
;
167 $t = $this->parseTree($childs[$i]);
174 if ($type == KEYWORD_FUNCTION
&& $childs[$i]->functionForm
== DECLARED_FORM
)
176 // put declared functions on a new line
179 elseif ($type == KEYWORD_VAR
&& $type == $lastType)
181 // mutiple var-statements can go into one
182 $t = ',' . substr($t, 4);
198 if ($c > 1 && !$noBlockGrouping)
205 $s = 'if(' . $this->parseTree($n->condition
) . ')';
206 $thenPart = $this->parseTree($n->thenPart
);
207 $elsePart = $n->elsePart ?
$this->parseTree($n->elsePart
) : null;
209 // empty if-statement
215 // be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
216 if ($thenPart != ';' && $thenPart[0] != '{')
217 $thenPart = '{' . $thenPart . '}';
219 $s .= $thenPart . 'else';
221 // we could check for more, but that hardly ever applies so go for performance
222 if ($elsePart[0] != '{')
234 $s = 'switch(' . $this->parseTree($n->discriminant
) . '){';
236 for ($i = 0, $j = count($cases); $i < $j; $i++
)
239 if ($case->type
== KEYWORD_CASE
)
240 $s .= 'case' . ($case->caseLabel
->type
!= TOKEN_STRING ?
' ' : '') . $this->parseTree($case->caseLabel
) . ':';
244 $statement = $this->parseTree($case->statements
, true);
248 // no terminator for last statement
257 $s = 'for(' . ($n->setup ?
$this->parseTree($n->setup
) : '')
258 . ';' . ($n->condition ?
$this->parseTree($n->condition
) : '')
259 . ';' . ($n->update ?
$this->parseTree($n->update
) : '') . ')';
261 $body = $this->parseTree($n->body
);
269 $s = 'while(' . $this->parseTree($n->condition
) . ')';
271 $body = $this->parseTree($n->body
);
279 $s = 'for(' . ($n->varDecl ?
$this->parseTree($n->varDecl
) : $this->parseTree($n->iterator
)) . ' in ' . $this->parseTree($n->object) . ')';
281 $body = $this->parseTree($n->body
);
289 $s = 'do{' . $this->parseTree($n->body
, true) . '}while(' . $this->parseTree($n->condition
) . ')';
293 case KEYWORD_CONTINUE
:
294 $s = $n->value
. ($n->label ?
' ' . $n->label
: '');
298 $s = 'try{' . $this->parseTree($n->tryBlock
, true) . '}';
299 $catchClauses = $n->catchClauses
;
300 for ($i = 0, $j = count($catchClauses); $i < $j; $i++
)
302 $t = $catchClauses[$i];
303 $s .= 'catch(' . $t->varName
. ($t->guard ?
' if ' . $this->parseTree($t->guard
) : '') . '){' . $this->parseTree($t->block
, true) . '}';
305 if ($n->finallyBlock
)
306 $s .= 'finally{' . $this->parseTree($n->finallyBlock
, true) . '}';
310 $s = 'throw ' . $this->parseTree($n->exception
);
317 $t = $this->parseTree($n->value
);
320 if ( $t[0] != '(' && $t[0] != '[' && $t[0] != '{' &&
321 $t[0] != '"' && $t[0] != "'" && $t[0] != '/'
331 $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body
);
336 $s = $n->value
. ' ';
337 $childs = $n->treeNodes
;
338 for ($i = 0, $j = count($childs); $i < $j; $i++
)
341 $s .= ($i ?
',' : '') . $t->name
;
342 $u = $t->initializer
;
344 $s .= '=' . $this->parseTree($u);
348 case KEYWORD_DEBUGGER
:
349 throw new Exception('NOT IMPLEMENTED: DEBUGGER');
352 case TOKEN_CONDCOMMENT_START
:
353 case TOKEN_CONDCOMMENT_END
:
354 $s = $n->value
. ($n->type
== TOKEN_CONDCOMMENT_START ?
' ' : '');
355 $childs = $n->treeNodes
;
356 for ($i = 0, $j = count($childs); $i < $j; $i++
)
357 $s .= $this->parseTree($childs[$i]);
361 if ($expression = $n->expression
)
362 $s = $this->parseTree($expression);
366 $s = $n->label
. ':' . $this->parseTree($n->statement
);
370 $childs = $n->treeNodes
;
371 for ($i = 0, $j = count($childs); $i < $j; $i++
)
372 $s .= ($i ?
',' : '') . $this->parseTree($childs[$i]);
376 $s = $this->parseTree($n->treeNodes
[0]) . $n->value
. $this->parseTree($n->treeNodes
[1]);
380 $s = $this->parseTree($n->treeNodes
[0]) . '?' . $this->parseTree($n->treeNodes
[1]) . ':' . $this->parseTree($n->treeNodes
[2]);
383 case OP_OR
: case OP_AND
:
384 case OP_BITWISE_OR
: case OP_BITWISE_XOR
: case OP_BITWISE_AND
:
385 case OP_EQ
: case OP_NE
: case OP_STRICT_EQ
: case OP_STRICT_NE
:
386 case OP_LT
: case OP_LE
: case OP_GE
: case OP_GT
:
387 case OP_LSH
: case OP_RSH
: case OP_URSH
:
388 case OP_MUL
: case OP_DIV
: case OP_MOD
:
389 $s = $this->parseTree($n->treeNodes
[0]) . $n->type
. $this->parseTree($n->treeNodes
[1]);
394 $left = $this->parseTree($n->treeNodes
[0]);
395 $right = $this->parseTree($n->treeNodes
[1]);
397 switch ($n->treeNodes
[1]->type
)
405 $s = $left . $n->type
. ' ' . $right;
409 //combine concatted strings with same quotestyle
410 if ($n->type
== OP_PLUS
&& substr($left, -1) == $right[0])
412 $s = substr($left, 0, -1) . substr($right, 1);
418 $s = $left . $n->type
. $right;
423 $s = $this->parseTree($n->treeNodes
[0]) . ' in ' . $this->parseTree($n->treeNodes
[1]);
426 case KEYWORD_INSTANCEOF
:
427 $s = $this->parseTree($n->treeNodes
[0]) . ' instanceof ' . $this->parseTree($n->treeNodes
[1]);
431 $s = 'delete ' . $this->parseTree($n->treeNodes
[0]);
435 $s = 'void(' . $this->parseTree($n->treeNodes
[0]) . ')';
439 $s = 'typeof ' . $this->parseTree($n->treeNodes
[0]);
446 $s = $n->value
. $this->parseTree($n->treeNodes
[0]);
452 $s = $this->parseTree($n->treeNodes
[0]) . $n->value
;
454 $s = $n->value
. $this->parseTree($n->treeNodes
[0]);
458 $s = $this->parseTree($n->treeNodes
[0]) . '.' . $this->parseTree($n->treeNodes
[1]);
462 $s = $this->parseTree($n->treeNodes
[0]);
463 // See if we can replace named index with a dot saving 3 bytes
464 if ( $n->treeNodes
[0]->type
== TOKEN_IDENTIFIER
&&
465 $n->treeNodes
[1]->type
== TOKEN_STRING
&&
466 $this->isValidIdentifier(substr($n->treeNodes
[1]->value
, 1, -1))
468 $s .= '.' . substr($n->treeNodes
[1]->value
, 1, -1);
470 $s .= '[' . $this->parseTree($n->treeNodes
[1]) . ']';
474 $childs = $n->treeNodes
;
475 for ($i = 0, $j = count($childs); $i < $j; $i++
)
476 $s .= ($i ?
',' : '') . $this->parseTree($childs[$i]);
480 $s = $this->parseTree($n->treeNodes
[0]) . '(' . $this->parseTree($n->treeNodes
[1]) . ')';
484 case JS_NEW_WITH_ARGS
:
485 $s = 'new ' . $this->parseTree($n->treeNodes
[0]) . '(' . ($n->type
== JS_NEW_WITH_ARGS ?
$this->parseTree($n->treeNodes
[1]) : '') . ')';
490 $childs = $n->treeNodes
;
491 for ($i = 0, $j = count($childs); $i < $j; $i++
)
493 $s .= ($i ?
',' : '') . $this->parseTree($childs[$i]);
500 $childs = $n->treeNodes
;
501 for ($i = 0, $j = count($childs); $i < $j; $i++
)
506 if ($t->type
== JS_PROPERTY_INIT
)
508 // Ditch the quotes when the index is a valid identifier
509 if ( $t->treeNodes
[0]->type
== TOKEN_STRING
&&
510 $this->isValidIdentifier(substr($t->treeNodes
[0]->value
, 1, -1))
512 $s .= substr($t->treeNodes
[0]->value
, 1, -1);
514 $s .= $t->treeNodes
[0]->value
;
516 $s .= ':' . $this->parseTree($t->treeNodes
[1]);
520 $s .= $t->type
== JS_GETTER ?
'get' : 'set';
521 $s .= ' ' . $t->name
. '(';
522 $params = $t->params
;
523 for ($i = 0, $j = count($params); $i < $j; $i++
)
524 $s .= ($i ?
',' : '') . $params[$i];
525 $s .= '){' . $this->parseTree($t->body
, true) . '}';
531 case KEYWORD_NULL
: case KEYWORD_THIS
: case KEYWORD_TRUE
: case KEYWORD_FALSE
:
532 case TOKEN_IDENTIFIER
: case TOKEN_NUMBER
: case TOKEN_STRING
: case TOKEN_REGEXP
:
537 $s = '(' . $this->parseTree($n->treeNodes
[0]) . ')';
541 throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type
);
547 private function isValidIdentifier($string)
549 return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved
);
557 private $opPrecedence = array(
560 '=' => 2, '?' => 2, ':' => 2,
561 // The above all have to have the same precedence, see bug 330975
567 '==' => 9, '!=' => 9, '===' => 9, '!==' => 9,
568 '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,
569 '<<' => 11, '>>' => 11, '>>>' => 11,
570 '+' => 12, '-' => 12,
571 '*' => 13, '/' => 13, '%' => 13,
572 'delete' => 14, 'void' => 14, 'typeof' => 14,
573 '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,
574 '++' => 15, '--' => 15,
577 JS_NEW_WITH_ARGS
=> 0, JS_INDEX
=> 0, JS_CALL
=> 0,
578 JS_ARRAY_INIT
=> 0, JS_OBJECT_INIT
=> 0, JS_GROUP
=> 0
581 private $opArity = array(
590 '==' => 2, '!=' => 2, '===' => 2, '!==' => 2,
591 '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,
592 '<<' => 2, '>>' => 2, '>>>' => 2,
594 '*' => 2, '/' => 2, '%' => 2,
595 'delete' => 1, 'void' => 1, 'typeof' => 1,
596 '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,
597 '++' => 1, '--' => 1,
600 JS_NEW_WITH_ARGS
=> 2, JS_INDEX
=> 2, JS_CALL
=> 2,
601 JS_ARRAY_INIT
=> 1, JS_OBJECT_INIT
=> 1, JS_GROUP
=> 1,
602 TOKEN_CONDCOMMENT_START
=> 1, TOKEN_CONDCOMMENT_END
=> 1
605 public function __construct()
607 $this->t
= new JSTokenizer();
610 public function parse($s, $f, $l)
612 // initialize tokenizer
613 $this->t
->init($s, $f, $l);
615 $x = new JSCompilerContext(false);
616 $n = $this->Script($x);
617 if (!$this->t
->isDone())
618 throw $this->t
->newSyntaxError('Syntax error');
623 private function Script($x)
625 $n = $this->Statements($x);
626 $n->type
= JS_SCRIPT
;
627 $n->funDecls
= $x->funDecls
;
628 $n->varDecls
= $x->varDecls
;
633 private function Statements($x)
635 $n = new JSNode($this->t
, JS_BLOCK
);
636 array_push($x->stmtStack
, $n);
638 while (!$this->t
->isDone() && $this->t
->peek() != OP_RIGHT_CURLY
)
639 $n->addNode($this->Statement($x));
641 array_pop($x->stmtStack
);
646 private function Block($x)
648 $this->t
->mustMatch(OP_LEFT_CURLY
);
649 $n = $this->Statements($x);
650 $this->t
->mustMatch(OP_RIGHT_CURLY
);
655 private function Statement($x)
657 $tt = $this->t
->get();
660 // Cases for statements ending in a right curly return early, avoiding the
661 // common semicolon insertion magic after this switch.
664 case KEYWORD_FUNCTION
:
665 return $this->FunctionDefinition(
668 count($x->stmtStack
) > 1 ? STATEMENT_FORM
: DECLARED_FORM
673 $n = $this->Statements($x);
674 $this->t
->mustMatch(OP_RIGHT_CURLY
);
678 $n = new JSNode($this->t
);
679 $n->condition
= $this->ParenExpression($x);
680 array_push($x->stmtStack
, $n);
681 $n->thenPart
= $this->Statement($x);
682 $n->elsePart
= $this->t
->match(KEYWORD_ELSE
) ?
$this->Statement($x) : null;
683 array_pop($x->stmtStack
);
687 $n = new JSNode($this->t
);
688 $this->t
->mustMatch(OP_LEFT_PAREN
);
689 $n->discriminant
= $this->Expression($x);
690 $this->t
->mustMatch(OP_RIGHT_PAREN
);
692 $n->defaultIndex
= -1;
694 array_push($x->stmtStack
, $n);
696 $this->t
->mustMatch(OP_LEFT_CURLY
);
698 while (($tt = $this->t
->get()) != OP_RIGHT_CURLY
)
702 case KEYWORD_DEFAULT
:
703 if ($n->defaultIndex
>= 0)
704 throw $this->t
->newSyntaxError('More than one switch default');
707 $n2 = new JSNode($this->t
);
708 if ($tt == KEYWORD_DEFAULT
)
709 $n->defaultIndex
= count($n->cases
);
711 $n2->caseLabel
= $this->Expression($x, OP_COLON
);
714 throw $this->t
->newSyntaxError('Invalid switch case');
717 $this->t
->mustMatch(OP_COLON
);
718 $n2->statements
= new JSNode($this->t
, JS_BLOCK
);
719 while (($tt = $this->t
->peek()) != KEYWORD_CASE
&& $tt != KEYWORD_DEFAULT
&& $tt != OP_RIGHT_CURLY
)
720 $n2->statements
->addNode($this->Statement($x));
722 array_push($n->cases
, $n2);
725 array_pop($x->stmtStack
);
729 $n = new JSNode($this->t
);
731 $this->t
->mustMatch(OP_LEFT_PAREN
);
733 if (($tt = $this->t
->peek()) != OP_SEMICOLON
)
735 $x->inForLoopInit
= true;
736 if ($tt == KEYWORD_VAR ||
$tt == KEYWORD_CONST
)
739 $n2 = $this->Variables($x);
743 $n2 = $this->Expression($x);
745 $x->inForLoopInit
= false;
748 if ($n2 && $this->t
->match(KEYWORD_IN
))
750 $n->type
= JS_FOR_IN
;
751 if ($n2->type
== KEYWORD_VAR
)
753 if (count($n2->treeNodes
) != 1)
755 throw $this->t
->SyntaxError(
756 'Invalid for..in left-hand side',
762 // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
763 $n->iterator
= $n2->treeNodes
[0];
772 $n->object = $this->Expression($x);
776 $n->setup
= $n2 ?
$n2 : null;
777 $this->t
->mustMatch(OP_SEMICOLON
);
778 $n->condition
= $this->t
->peek() == OP_SEMICOLON ?
null : $this->Expression($x);
779 $this->t
->mustMatch(OP_SEMICOLON
);
780 $n->update
= $this->t
->peek() == OP_RIGHT_PAREN ?
null : $this->Expression($x);
783 $this->t
->mustMatch(OP_RIGHT_PAREN
);
784 $n->body
= $this->nest($x, $n);
788 $n = new JSNode($this->t
);
790 $n->condition
= $this->ParenExpression($x);
791 $n->body
= $this->nest($x, $n);
795 $n = new JSNode($this->t
);
797 $n->body
= $this->nest($x, $n, KEYWORD_WHILE
);
798 $n->condition
= $this->ParenExpression($x);
799 if (!$x->ecmaStrictMode
)
801 // <script language="JavaScript"> (without version hints) may need
802 // automatic semicolon insertion without a newline after do-while.
803 // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
804 $this->t
->match(OP_SEMICOLON
);
810 case KEYWORD_CONTINUE
:
811 $n = new JSNode($this->t
);
813 if ($this->t
->peekOnSameLine() == TOKEN_IDENTIFIER
)
816 $n->label
= $this->t
->currentToken()->value
;
827 throw $this->t
->newSyntaxError('Label not found');
829 while ($ss[$i]->label
!= $label);
836 throw $this->t
->newSyntaxError('Invalid ' . $tt);
838 while (!$ss[$i]->isLoop
&& ($tt != KEYWORD_BREAK ||
$ss[$i]->type
!= KEYWORD_SWITCH
));
841 $n->target
= $ss[$i];
845 $n = new JSNode($this->t
);
846 $n->tryBlock
= $this->Block($x);
847 $n->catchClauses
= array();
849 while ($this->t
->match(KEYWORD_CATCH
))
851 $n2 = new JSNode($this->t
);
852 $this->t
->mustMatch(OP_LEFT_PAREN
);
853 $n2->varName
= $this->t
->mustMatch(TOKEN_IDENTIFIER
)->value
;
855 if ($this->t
->match(KEYWORD_IF
))
857 if ($x->ecmaStrictMode
)
858 throw $this->t
->newSyntaxError('Illegal catch guard');
860 if (count($n->catchClauses
) && !end($n->catchClauses
)->guard
)
861 throw $this->t
->newSyntaxError('Guarded catch after unguarded');
863 $n2->guard
= $this->Expression($x);
870 $this->t
->mustMatch(OP_RIGHT_PAREN
);
871 $n2->block
= $this->Block($x);
872 array_push($n->catchClauses
, $n2);
875 if ($this->t
->match(KEYWORD_FINALLY
))
876 $n->finallyBlock
= $this->Block($x);
878 if (!count($n->catchClauses
) && !$n->finallyBlock
)
879 throw $this->t
->newSyntaxError('Invalid try statement');
883 case KEYWORD_FINALLY
:
884 throw $this->t
->newSyntaxError($tt +
' without preceding try');
887 $n = new JSNode($this->t
);
888 $n->exception
= $this->Expression($x);
893 throw $this->t
->newSyntaxError('Invalid return');
895 $n = new JSNode($this->t
);
896 $tt = $this->t
->peekOnSameLine();
897 if ($tt != TOKEN_END
&& $tt != TOKEN_NEWLINE
&& $tt != OP_SEMICOLON
&& $tt != OP_RIGHT_CURLY
)
898 $n->value
= $this->Expression($x);
904 $n = new JSNode($this->t
);
905 $n->object = $this->ParenExpression($x);
906 $n->body
= $this->nest($x, $n);
911 $n = $this->Variables($x);
914 case TOKEN_CONDCOMMENT_START
:
915 case TOKEN_CONDCOMMENT_END
:
916 $n = new JSNode($this->t
);
919 case KEYWORD_DEBUGGER
:
920 $n = new JSNode($this->t
);
925 $n = new JSNode($this->t
, OP_SEMICOLON
);
926 $n->expression
= null;
930 if ($tt == TOKEN_IDENTIFIER
)
932 $this->t
->scanOperand
= false;
933 $tt = $this->t
->peek();
934 $this->t
->scanOperand
= true;
937 $label = $this->t
->currentToken()->value
;
939 for ($i = count($ss) - 1; $i >= 0; --$i)
941 if ($ss[$i]->label
== $label)
942 throw $this->t
->newSyntaxError('Duplicate label');
946 $n = new JSNode($this->t
, JS_LABEL
);
948 $n->statement
= $this->nest($x, $n);
954 $n = new JSNode($this->t
, OP_SEMICOLON
);
956 $n->expression
= $this->Expression($x);
957 $n->end
= $n->expression
->end
;
961 if ($this->t
->lineno
== $this->t
->currentToken()->lineno
)
963 $tt = $this->t
->peekOnSameLine();
964 if ($tt != TOKEN_END
&& $tt != TOKEN_NEWLINE
&& $tt != OP_SEMICOLON
&& $tt != OP_RIGHT_CURLY
)
965 throw $this->t
->newSyntaxError('Missing ; before statement');
968 $this->t
->match(OP_SEMICOLON
);
973 private function FunctionDefinition($x, $requireName, $functionForm)
975 $f = new JSNode($this->t
);
977 if ($f->type
!= KEYWORD_FUNCTION
)
978 $f->type
= ($f->value
== 'get') ? JS_GETTER
: JS_SETTER
;
980 if ($this->t
->match(TOKEN_IDENTIFIER
))
981 $f->name
= $this->t
->currentToken()->value
;
982 elseif ($requireName)
983 throw $this->t
->newSyntaxError('Missing function identifier');
985 $this->t
->mustMatch(OP_LEFT_PAREN
);
986 $f->params
= array();
988 while (($tt = $this->t
->get()) != OP_RIGHT_PAREN
)
990 if ($tt != TOKEN_IDENTIFIER
)
991 throw $this->t
->newSyntaxError('Missing formal parameter');
993 array_push($f->params
, $this->t
->currentToken()->value
);
995 if ($this->t
->peek() != OP_RIGHT_PAREN
)
996 $this->t
->mustMatch(OP_COMMA
);
999 $this->t
->mustMatch(OP_LEFT_CURLY
);
1001 $x2 = new JSCompilerContext(true);
1002 $f->body
= $this->Script($x2);
1004 $this->t
->mustMatch(OP_RIGHT_CURLY
);
1005 $f->end
= $this->t
->currentToken()->end
;
1007 $f->functionForm
= $functionForm;
1008 if ($functionForm == DECLARED_FORM
)
1009 array_push($x->funDecls
, $f);
1014 private function Variables($x)
1016 $n = new JSNode($this->t
);
1020 $this->t
->mustMatch(TOKEN_IDENTIFIER
);
1022 $n2 = new JSNode($this->t
);
1023 $n2->name
= $n2->value
;
1025 if ($this->t
->match(OP_ASSIGN
))
1027 if ($this->t
->currentToken()->assignOp
)
1028 throw $this->t
->newSyntaxError('Invalid variable initialization');
1030 $n2->initializer
= $this->Expression($x, OP_COMMA
);
1033 $n2->readOnly
= $n->type
== KEYWORD_CONST
;
1036 array_push($x->varDecls
, $n2);
1038 while ($this->t
->match(OP_COMMA
));
1043 private function Expression($x, $stop=false)
1045 $operators = array();
1046 $operands = array();
1049 $bl = $x->bracketLevel
;
1050 $cl = $x->curlyLevel
;
1051 $pl = $x->parenLevel
;
1052 $hl = $x->hookLevel
;
1054 while (($tt = $this->t
->get()) != TOKEN_END
)
1057 $x->bracketLevel
== $bl &&
1058 $x->curlyLevel
== $cl &&
1059 $x->parenLevel
== $pl &&
1060 $x->hookLevel
== $hl
1063 // Stop only if tt matches the optional stop parameter, and that
1064 // token is not quoted by some kind of bracket.
1071 // NB: cannot be empty, Statement handled that.
1075 if ($this->t
->scanOperand
)
1078 while ( !empty($operators) &&
1079 $this->opPrecedence
[end($operators)->type
] > $this->opPrecedence
[$tt]
1081 $this->reduce($operators, $operands);
1083 array_push($operators, new JSNode($this->t
));
1086 $this->t
->scanOperand
= true;
1087 $n = $this->Expression($x);
1089 if (!$this->t
->match(OP_COLON
))
1093 array_push($operands, $n);
1100 throw $this->t
->newSyntaxError('Invalid label');
1104 if ($this->t
->scanOperand
)
1107 // Use >, not >=, for right-associative ASSIGN
1108 while ( !empty($operators) &&
1109 $this->opPrecedence
[end($operators)->type
] > $this->opPrecedence
[$tt]
1111 $this->reduce($operators, $operands);
1113 array_push($operators, new JSNode($this->t
));
1114 end($operands)->assignOp
= $this->t
->currentToken()->assignOp
;
1115 $this->t
->scanOperand
= true;
1119 // An in operator should not be parsed if we're parsing the head of
1120 // a for (...) loop, unless it is in the then part of a conditional
1121 // expression, or parenthesized somehow.
1122 if ($x->inForLoopInit
&& !$x->hookLevel
&&
1123 !$x->bracketLevel
&& !$x->curlyLevel
&&
1129 // A comma operator should not be parsed if we're parsing the then part
1130 // of a conditional expression unless it's parenthesized somehow.
1131 if ($tt == OP_COMMA
&& $x->hookLevel
&&
1132 !$x->bracketLevel
&& !$x->curlyLevel
&&
1136 // Treat comma as left-associative so reduce can fold left-heavy
1137 // COMMA trees into a single array.
1142 case OP_BITWISE_XOR
:
1143 case OP_BITWISE_AND
:
1144 case OP_EQ
: case OP_NE
: case OP_STRICT_EQ
: case OP_STRICT_NE
:
1145 case OP_LT
: case OP_LE
: case OP_GE
: case OP_GT
:
1146 case KEYWORD_INSTANCEOF
:
1147 case OP_LSH
: case OP_RSH
: case OP_URSH
:
1148 case OP_PLUS
: case OP_MINUS
:
1149 case OP_MUL
: case OP_DIV
: case OP_MOD
:
1151 if ($this->t
->scanOperand
)
1154 while ( !empty($operators) &&
1155 $this->opPrecedence
[end($operators)->type
] >= $this->opPrecedence
[$tt]
1157 $this->reduce($operators, $operands);
1161 $this->t
->mustMatch(TOKEN_IDENTIFIER
);
1162 array_push($operands, new JSNode($this->t
, OP_DOT
, array_pop($operands), new JSNode($this->t
)));
1166 array_push($operators, new JSNode($this->t
));
1167 $this->t
->scanOperand
= true;
1171 case KEYWORD_DELETE
: case KEYWORD_VOID
: case KEYWORD_TYPEOF
:
1172 case OP_NOT
: case OP_BITWISE_NOT
: case OP_UNARY_PLUS
: case OP_UNARY_MINUS
:
1174 if (!$this->t
->scanOperand
)
1177 array_push($operators, new JSNode($this->t
));
1180 case OP_INCREMENT
: case OP_DECREMENT
:
1181 if ($this->t
->scanOperand
)
1183 array_push($operators, new JSNode($this->t
)); // prefix increment or decrement
1187 // Don't cross a line boundary for postfix {in,de}crement.
1188 $t = $this->t
->tokens
[($this->t
->tokenIndex +
$this->t
->lookahead
- 1) & 3];
1189 if ($t && $t->lineno
!= $this->t
->lineno
)
1192 if (!empty($operators))
1194 // Use >, not >=, so postfix has higher precedence than prefix.
1195 while ($this->opPrecedence
[end($operators)->type
] > $this->opPrecedence
[$tt])
1196 $this->reduce($operators, $operands);
1199 $n = new JSNode($this->t
, $tt, array_pop($operands));
1201 array_push($operands, $n);
1205 case KEYWORD_FUNCTION
:
1206 if (!$this->t
->scanOperand
)
1209 array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM
));
1210 $this->t
->scanOperand
= false;
1213 case KEYWORD_NULL
: case KEYWORD_THIS
: case KEYWORD_TRUE
: case KEYWORD_FALSE
:
1214 case TOKEN_IDENTIFIER
: case TOKEN_NUMBER
: case TOKEN_STRING
: case TOKEN_REGEXP
:
1215 if (!$this->t
->scanOperand
)
1218 array_push($operands, new JSNode($this->t
));
1219 $this->t
->scanOperand
= false;
1222 case TOKEN_CONDCOMMENT_START
:
1223 case TOKEN_CONDCOMMENT_END
:
1224 if ($this->t
->scanOperand
)
1225 array_push($operators, new JSNode($this->t
));
1227 array_push($operands, new JSNode($this->t
));
1230 case OP_LEFT_BRACKET
:
1231 if ($this->t
->scanOperand
)
1233 // Array initialiser. Parse using recursive descent, as the
1234 // sub-grammar here is not an operator grammar.
1235 $n = new JSNode($this->t
, JS_ARRAY_INIT
);
1236 while (($tt = $this->t
->peek()) != OP_RIGHT_BRACKET
)
1238 if ($tt == OP_COMMA
)
1245 $n->addNode($this->Expression($x, OP_COMMA
));
1246 if (!$this->t
->match(OP_COMMA
))
1250 $this->t
->mustMatch(OP_RIGHT_BRACKET
);
1251 array_push($operands, $n);
1252 $this->t
->scanOperand
= false;
1256 // Property indexing operator.
1257 array_push($operators, new JSNode($this->t
, JS_INDEX
));
1258 $this->t
->scanOperand
= true;
1263 case OP_RIGHT_BRACKET
:
1264 if ($this->t
->scanOperand ||
$x->bracketLevel
== $bl)
1267 while ($this->reduce($operators, $operands)->type
!= JS_INDEX
)
1274 if (!$this->t
->scanOperand
)
1277 // Object initialiser. As for array initialisers (see above),
1278 // parse using recursive descent.
1280 $n = new JSNode($this->t
, JS_OBJECT_INIT
);
1281 while (!$this->t
->match(OP_RIGHT_CURLY
))
1285 $tt = $this->t
->get();
1286 $tv = $this->t
->currentToken()->value
;
1287 if (($tv == 'get' ||
$tv == 'set') && $this->t
->peek() == TOKEN_IDENTIFIER
)
1289 if ($x->ecmaStrictMode
)
1290 throw $this->t
->newSyntaxError('Illegal property accessor');
1292 $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM
));
1298 case TOKEN_IDENTIFIER
:
1301 $id = new JSNode($this->t
);
1304 case OP_RIGHT_CURLY
:
1305 if ($x->ecmaStrictMode
)
1306 throw $this->t
->newSyntaxError('Illegal trailing ,');
1310 throw $this->t
->newSyntaxError('Invalid property name');
1313 $this->t
->mustMatch(OP_COLON
);
1314 $n->addNode(new JSNode($this->t
, JS_PROPERTY_INIT
, $id, $this->Expression($x, OP_COMMA
)));
1317 while ($this->t
->match(OP_COMMA
));
1319 $this->t
->mustMatch(OP_RIGHT_CURLY
);
1323 array_push($operands, $n);
1324 $this->t
->scanOperand
= false;
1328 case OP_RIGHT_CURLY
:
1329 if (!$this->t
->scanOperand
&& $x->curlyLevel
!= $cl)
1330 throw new Exception('PANIC: right curly botch');
1334 if ($this->t
->scanOperand
)
1336 array_push($operators, new JSNode($this->t
, JS_GROUP
));
1340 while ( !empty($operators) &&
1341 $this->opPrecedence
[end($operators)->type
] > $this->opPrecedence
[KEYWORD_NEW
]
1343 $this->reduce($operators, $operands);
1345 // Handle () now, to regularize the n-ary case for n > 0.
1346 // We must set scanOperand in case there are arguments and
1347 // the first one is a regexp or unary+/-.
1348 $n = end($operators);
1349 $this->t
->scanOperand
= true;
1350 if ($this->t
->match(OP_RIGHT_PAREN
))
1352 if ($n && $n->type
== KEYWORD_NEW
)
1354 array_pop($operators);
1355 $n->addNode(array_pop($operands));
1359 $n = new JSNode($this->t
, JS_CALL
, array_pop($operands), new JSNode($this->t
, JS_LIST
));
1362 array_push($operands, $n);
1363 $this->t
->scanOperand
= false;
1367 if ($n && $n->type
== KEYWORD_NEW
)
1368 $n->type
= JS_NEW_WITH_ARGS
;
1370 array_push($operators, new JSNode($this->t
, JS_CALL
));
1376 case OP_RIGHT_PAREN
:
1377 if ($this->t
->scanOperand ||
$x->parenLevel
== $pl)
1380 while (($tt = $this->reduce($operators, $operands)->type
) != JS_GROUP
&&
1381 $tt != JS_CALL
&& $tt != JS_NEW_WITH_ARGS
1387 if ($tt != JS_GROUP
)
1389 $n = end($operands);
1390 if ($n->treeNodes
[1]->type
!= OP_COMMA
)
1391 $n->treeNodes
[1] = new JSNode($this->t
, JS_LIST
, $n->treeNodes
[1]);
1393 $n->treeNodes
[1]->type
= JS_LIST
;
1399 // Automatic semicolon insertion means we may scan across a newline
1400 // and into the beginning of another statement. If so, break out of
1401 // the while loop and let the t.scanOperand logic handle errors.
1407 if ($x->hookLevel
!= $hl)
1408 throw $this->t
->newSyntaxError('Missing : in conditional expression');
1410 if ($x->parenLevel
!= $pl)
1411 throw $this->t
->newSyntaxError('Missing ) in parenthetical');
1413 if ($x->bracketLevel
!= $bl)
1414 throw $this->t
->newSyntaxError('Missing ] in index expression');
1416 if ($this->t
->scanOperand
)
1417 throw $this->t
->newSyntaxError('Missing operand');
1419 // Resume default mode, scanning for operands, not operators.
1420 $this->t
->scanOperand
= true;
1423 while (count($operators))
1424 $this->reduce($operators, $operands);
1426 return array_pop($operands);
1429 private function ParenExpression($x)
1431 $this->t
->mustMatch(OP_LEFT_PAREN
);
1432 $n = $this->Expression($x);
1433 $this->t
->mustMatch(OP_RIGHT_PAREN
);
1438 // Statement stack and nested statement handler.
1439 private function nest($x, $node, $end = false)
1441 array_push($x->stmtStack
, $node);
1442 $n = $this->statement($x);
1443 array_pop($x->stmtStack
);
1446 $this->t
->mustMatch($end);
1451 private function reduce(&$operators, &$operands)
1453 $n = array_pop($operators);
1455 $arity = $this->opArity
[$op];
1456 $c = count($operands);
1459 // Flatten left-associative trees
1462 $left = $operands[$c - 2];
1463 if ($left->type
== $op)
1465 $right = array_pop($operands);
1466 $left->addNode($right);
1473 // Always use push to add operands to n, to update start and end
1474 $a = array_splice($operands, $c - $arity);
1475 for ($i = 0; $i < $arity; $i++
)
1476 $n->addNode($a[$i]);
1478 // Include closing bracket or postfix operator in [start,end]
1479 $te = $this->t
->currentToken()->end
;
1483 array_push($operands, $n);
1489 class JSCompilerContext
1491 public $inFunction = false;
1492 public $inForLoopInit = false;
1493 public $ecmaStrictMode = false;
1494 public $bracketLevel = 0;
1495 public $curlyLevel = 0;
1496 public $parenLevel = 0;
1497 public $hookLevel = 0;
1499 public $stmtStack = array();
1500 public $funDecls = array();
1501 public $varDecls = array();
1503 public function __construct($inFunction)
1505 $this->inFunction
= $inFunction;
1517 public $treeNodes = array();
1518 public $funDecls = array();
1519 public $varDecls = array();
1521 public function __construct($t, $type=0)
1523 if ($token = $t->currentToken())
1525 $this->type
= $type ?
$type : $token->type
;
1526 $this->value
= $token->value
;
1527 $this->lineno
= $token->lineno
;
1528 $this->start
= $token->start
;
1529 $this->end
= $token->end
;
1533 $this->type
= $type;
1534 $this->lineno
= $t->lineno
;
1537 if (($numargs = func_num_args()) > 2)
1539 $args = func_get_args();
1540 for ($i = 2; $i < $numargs; $i++
)
1541 $this->addNode($args[$i]);
1545 // we don't want to bloat our object with all kind of specific properties, so we use overloading
1546 public function __set($name, $value)
1548 $this->$name = $value;
1551 public function __get($name)
1553 if (isset($this->$name))
1554 return $this->$name;
1559 public function addNode($node)
1563 if ($node->start
< $this->start
)
1564 $this->start
= $node->start
;
1565 if ($this->end
< $node->end
)
1566 $this->end
= $node->end
;
1569 $this->treeNodes
[] = $node;
1575 private $cursor = 0;
1578 public $tokens = array();
1579 public $tokenIndex = 0;
1580 public $lookahead = 0;
1581 public $scanNewlines = false;
1582 public $scanOperand = true;
1587 private $keywords = array(
1589 'case', 'catch', 'const', 'continue',
1590 'debugger', 'default', 'delete', 'do',
1592 'false', 'finally', 'for', 'function',
1593 'if', 'in', 'instanceof',
1597 'this', 'throw', 'true', 'try', 'typeof',
1602 private $opTypeNames = array(
1609 '|' => 'BITWISE_OR',
1610 '^' => 'BITWISE_XOR',
1611 '&' => 'BITWISE_AND',
1612 '===' => 'STRICT_EQ',
1615 '!==' => 'STRICT_NE',
1624 '++' => 'INCREMENT',
1625 '--' => 'DECREMENT',
1632 '~' => 'BITWISE_NOT',
1634 '[' => 'LEFT_BRACKET',
1635 ']' => 'RIGHT_BRACKET',
1636 '{' => 'LEFT_CURLY',
1637 '}' => 'RIGHT_CURLY',
1638 '(' => 'LEFT_PAREN',
1639 ')' => 'RIGHT_PAREN',
1640 '@*/' => 'CONDCOMMENT_END'
1643 private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
1646 public function __construct()
1648 $this->opRegExp
= '#^(' . implode('|', array_map('preg_quote', array_keys($this->opTypeNames
))) . ')#';
1650 // this is quite a hidden yet convenient place to create the defines for operators and keywords
1651 foreach ($this->opTypeNames
as $operand => $name)
1652 define('OP_' . $name, $operand);
1654 define('OP_UNARY_PLUS', 'U+');
1655 define('OP_UNARY_MINUS', 'U-');
1657 foreach ($this->keywords
as $keyword)
1658 define('KEYWORD_' . strtoupper($keyword), $keyword);
1661 public function init($source, $filename = '', $lineno = 1)
1663 $this->source
= $source;
1664 $this->filename
= $filename ?
$filename : '[inline]';
1665 $this->lineno
= $lineno;
1668 $this->tokens
= array();
1669 $this->tokenIndex
= 0;
1670 $this->lookahead
= 0;
1671 $this->scanNewlines
= false;
1672 $this->scanOperand
= true;
1675 public function getInput($chunksize)
1678 return substr($this->source
, $this->cursor
, $chunksize);
1680 return substr($this->source
, $this->cursor
);
1683 public function isDone()
1685 return $this->peek() == TOKEN_END
;
1688 public function match($tt)
1690 return $this->get() == $tt ||
$this->unget();
1693 public function mustMatch($tt)
1695 if (!$this->match($tt))
1696 throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
1698 return $this->currentToken();
1701 public function peek()
1703 if ($this->lookahead
)
1705 $next = $this->tokens
[($this->tokenIndex +
$this->lookahead
) & 3];
1706 if ($this->scanNewlines
&& $next->lineno
!= $this->lineno
)
1707 $tt = TOKEN_NEWLINE
;
1720 public function peekOnSameLine()
1722 $this->scanNewlines
= true;
1723 $tt = $this->peek();
1724 $this->scanNewlines
= false;
1729 public function currentToken()
1731 if (!empty($this->tokens
))
1732 return $this->tokens
[$this->tokenIndex
];
1735 public function get($chunksize = 1000)
1737 while($this->lookahead
)
1740 $this->tokenIndex
= ($this->tokenIndex +
1) & 3;
1741 $token = $this->tokens
[$this->tokenIndex
];
1742 if ($token->type
!= TOKEN_NEWLINE ||
$this->scanNewlines
)
1743 return $token->type
;
1746 $conditional_comment = false;
1748 // strip whitespace and comments
1751 $input = $this->getInput($chunksize);
1753 // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
1754 $re = $this->scanNewlines ?
'/^[ \r\t]+/' : '/^\s+/';
1755 if (preg_match($re, $input, $match))
1757 $spaces = $match[0];
1758 $spacelen = strlen($spaces);
1759 $this->cursor +
= $spacelen;
1760 if (!$this->scanNewlines
)
1761 $this->lineno +
= substr_count($spaces, "\n");
1763 if ($spacelen == $chunksize)
1764 continue; // complete chunk contained whitespace
1766 $input = $this->getInput($chunksize);
1767 if ($input == '' ||
$input[0] != '/')
1772 if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
1777 // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
1782 // check if this is a conditional (JScript) comment
1783 if (!empty($match[1]))
1785 $match[0] = '/*' . $match[1];
1786 $conditional_comment = true;
1791 $this->cursor +
= strlen($match[0]);
1792 $this->lineno +
= substr_count($match[0], "\n");
1801 elseif ($conditional_comment)
1803 $tt = TOKEN_CONDCOMMENT_START
;
1809 case '0': case '1': case '2': case '3': case '4':
1810 case '5': case '6': case '7': case '8': case '9':
1811 if (preg_match('/^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+/', $input, $match))
1815 else if (preg_match('/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/', $input, $match))
1817 // this should always match because of \d+
1824 if (preg_match('/^"(?:\\\\(?:.|\r?\n)|[^\\\\"\r\n]+)*"|^\'(?:\\\\(?:.|\r?\n)|[^\\\\\'\r\n]+)*\'/', $input, $match))
1831 return $this->get(null); // retry with a full chunk fetch
1833 throw $this->newSyntaxError('Unterminated string literal');
1838 if ($this->scanOperand
&& preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
1856 // should always match
1857 preg_match($this->opRegExp
, $input, $match);
1859 if (in_array($op, $this->assignOps
) && $input[strlen($op)] == '=')
1867 if ($this->scanOperand
)
1870 $tt = OP_UNARY_PLUS
;
1871 elseif ($op == OP_MINUS
)
1872 $tt = OP_UNARY_MINUS
;
1879 if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
1897 // these are all single
1898 $match = array($input[0]);
1903 // check end of conditional comment
1904 if (substr($input, 0, 3) == '@*/')
1906 $match = array('@*/');
1907 $tt = TOKEN_CONDCOMMENT_END
;
1910 throw $this->newSyntaxError('Illegal token');
1914 if ($this->scanNewlines
)
1916 $match = array("\n");
1917 $tt = TOKEN_NEWLINE
;
1920 throw $this->newSyntaxError('Illegal token');
1924 // FIXME: add support for unicode and unicode escape sequence \uHHHH
1925 if (preg_match('/^[$\w]+/', $input, $match))
1927 $tt = in_array($match[0], $this->keywords
) ?
$match[0] : TOKEN_IDENTIFIER
;
1930 throw $this->newSyntaxError('Illegal token');
1934 $this->tokenIndex
= ($this->tokenIndex +
1) & 3;
1936 if (!isset($this->tokens
[$this->tokenIndex
]))
1937 $this->tokens
[$this->tokenIndex
] = new JSToken();
1939 $token = $this->tokens
[$this->tokenIndex
];
1942 if ($tt == OP_ASSIGN
)
1943 $token->assignOp
= $op;
1945 $token->start
= $this->cursor
;
1947 $token->value
= $match[0];
1948 $this->cursor +
= strlen($match[0]);
1950 $token->end
= $this->cursor
;
1951 $token->lineno
= $this->lineno
;
1956 public function unget()
1958 if (++
$this->lookahead
== 4)
1959 throw $this->newSyntaxError('PANIC: too much lookahead!');
1961 $this->tokenIndex
= ($this->tokenIndex
- 1) & 3;
1964 public function newSyntaxError($m)
1966 return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename
. '\' on line ' . $this->lineno
);