Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/minify/lib/JSMinPlus.php
Go to the documentation of this file.
00001 <?php
00002 
00025 /* ***** BEGIN LICENSE BLOCK *****
00026  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00027  *
00028  * The contents of this file are subject to the Mozilla Public License Version
00029  * 1.1 (the "License"); you may not use this file except in compliance with
00030  * the License. You may obtain a copy of the License at
00031  * http://www.mozilla.org/MPL/
00032  *
00033  * Software distributed under the License is distributed on an "AS IS" basis,
00034  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00035  * for the specific language governing rights and limitations under the
00036  * License.
00037  *
00038  * The Original Code is the Narcissus JavaScript engine.
00039  *
00040  * The Initial Developer of the Original Code is
00041  * Brendan Eich <brendan@mozilla.org>.
00042  * Portions created by the Initial Developer are Copyright (C) 2004
00043  * the Initial Developer. All Rights Reserved.
00044  *
00045  * Contributor(s): Tino Zijdel <crisp@tweakers.net>
00046  * PHP port, modifications and minifier routine are (C) 2009
00047  *
00048  * Alternatively, the contents of this file may be used under the terms of
00049  * either the GNU General Public License Version 2 or later (the "GPL"), or
00050  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00051  * in which case the provisions of the GPL or the LGPL are applicable instead
00052  * of those above. If you wish to allow use of your version of this file only
00053  * under the terms of either the GPL or the LGPL, and not to allow others to
00054  * use your version of this file under the terms of the MPL, indicate your
00055  * decision by deleting the provisions above and replace them with the notice
00056  * and other provisions required by the GPL or the LGPL. If you do not delete
00057  * the provisions above, a recipient may use your version of this file under
00058  * the terms of any one of the MPL, the GPL or the LGPL.
00059  *
00060  * ***** END LICENSE BLOCK ***** */
00061 
00062 define('TOKEN_END', 1);
00063 define('TOKEN_NUMBER', 2);
00064 define('TOKEN_IDENTIFIER', 3);
00065 define('TOKEN_STRING', 4);
00066 define('TOKEN_REGEXP', 5);
00067 define('TOKEN_NEWLINE', 6);
00068 define('TOKEN_CONDCOMMENT_MULTILINE', 7);
00069 
00070 define('JS_SCRIPT', 100);
00071 define('JS_BLOCK', 101);
00072 define('JS_LABEL', 102);
00073 define('JS_FOR_IN', 103);
00074 define('JS_CALL', 104);
00075 define('JS_NEW_WITH_ARGS', 105);
00076 define('JS_INDEX', 106);
00077 define('JS_ARRAY_INIT', 107);
00078 define('JS_OBJECT_INIT', 108);
00079 define('JS_PROPERTY_INIT', 109);
00080 define('JS_GETTER', 110);
00081 define('JS_SETTER', 111);
00082 define('JS_GROUP', 112);
00083 define('JS_LIST', 113);
00084 
00085 define('DECLARED_FORM', 0);
00086 define('EXPRESSED_FORM', 1);
00087 define('STATEMENT_FORM', 2);
00088 
00089 class JSMinPlus
00090 {
00091         private $parser;
00092         private $reserved = array(
00093                 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
00094                 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
00095                 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
00096                 'void', 'while', 'with',
00097                 // Words reserved for future use
00098                 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
00099                 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
00100                 'implements', 'import', 'int', 'interface', 'long', 'native',
00101                 'package', 'private', 'protected', 'public', 'short', 'static',
00102                 'super', 'synchronized', 'throws', 'transient', 'volatile',
00103                 // These are not reserved, but should be taken into account
00104                 // in isValidIdentifier (See jslint source code)
00105                 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
00106         );
00107 
00108         private function __construct()
00109         {
00110                 $this->parser = new JSParser();
00111         }
00112 
00113         public static function minify($js, $filename='')
00114         {
00115                 static $instance;
00116 
00117                 // this is a singleton
00118                 if(!$instance)
00119                         $instance = new JSMinPlus();
00120 
00121                 return $instance->min($js, $filename);
00122         }
00123 
00124         private function min($js, $filename)
00125         {
00126                 try
00127                 {
00128                         $n = $this->parser->parse($js, $filename, 1);
00129                         return $this->parseTree($n);
00130                 }
00131                 catch(Exception $e)
00132                 {
00133                         echo $e->getMessage() . "\n";
00134                 }
00135 
00136                 return false;
00137         }
00138 
00139         private function parseTree($n, $noBlockGrouping = false)
00140         {
00141                 $s = '';
00142 
00143                 switch ($n->type)
00144                 {
00145                         case KEYWORD_FUNCTION:
00146                                 $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
00147                                 $params = $n->params;
00148                                 for ($i = 0, $j = count($params); $i < $j; $i++)
00149                                         $s .= ($i ? ',' : '') . $params[$i];
00150                                 $s .= '){' . $this->parseTree($n->body, true) . '}';
00151                         break;
00152 
00153                         case JS_SCRIPT:
00154                                 // we do nothing with funDecls or varDecls
00155                                 $noBlockGrouping = true;
00156                         // fall through
00157                         case JS_BLOCK:
00158                                 $childs = $n->treeNodes;
00159                                 for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
00160                                 {
00161                                         $t = $this->parseTree($childs[$i]);
00162                                         if (strlen($t))
00163                                         {
00164                                                 if ($c)
00165                                                 {
00166                                                         if ($childs[$i]->type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
00167                                                                 $s .= "\n"; // put declared functions on a new line
00168                                                         else
00169                                                                 $s .= ';';
00170                                                 }
00171 
00172                                                 $s .= $t;
00173 
00174                                                 $c++;
00175                                         }
00176                                 }
00177 
00178                                 if ($c > 1 && !$noBlockGrouping)
00179                                 {
00180                                         $s = '{' . $s . '}';
00181                                 }
00182                         break;
00183 
00184                         case KEYWORD_IF:
00185                                 $s = 'if(' . $this->parseTree($n->condition) . ')';
00186                                 $thenPart = $this->parseTree($n->thenPart);
00187                                 $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
00188 
00189                                 // quite a rancid hack to see if we should enclose the thenpart in brackets
00190                                 if ($thenPart[0] != '{')
00191                                 {
00192                                         if (strpos($thenPart, 'if(') !== false)
00193                                                 $thenPart = '{' . $thenPart . '}';
00194                                         elseif ($elsePart)
00195                                                 $thenPart .= ';';
00196                                 }
00197 
00198                                 $s .= $thenPart;
00199 
00200                                 if ($elsePart)
00201                                 {
00202                                         $s .= 'else';
00203 
00204                                         if ($elsePart[0] != '{')
00205                                                 $s .= ' ';
00206 
00207                                         $s .= $elsePart;
00208                                 }
00209                         break;
00210 
00211                         case KEYWORD_SWITCH:
00212                                 $s = 'switch(' . $this->parseTree($n->discriminant) . '){';
00213                                 $cases = $n->cases;
00214                                 for ($i = 0, $j = count($cases); $i < $j; $i++)
00215                                 {
00216                                         $case = $cases[$i];
00217                                         if ($case->type == KEYWORD_CASE)
00218                                                 $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':';
00219                                         else
00220                                                 $s .= 'default:';
00221 
00222                                         $statement = $this->parseTree($case->statements);
00223                                         if ($statement)
00224                                                 $s .= $statement . ';';
00225                                 }
00226                                 $s = rtrim($s, ';') . '}';
00227                         break;
00228 
00229                         case KEYWORD_FOR:
00230                                 $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
00231                                         . ';' . ($n->condition ? $this->parseTree($n->condition) : '')
00232                                         . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')'
00233                                         . $this->parseTree($n->body);
00234                         break;
00235 
00236                         case KEYWORD_WHILE:
00237                                 $s = 'while(' . $this->parseTree($n->condition) . ')' . $this->parseTree($n->body);
00238                         break;
00239 
00240                         case JS_FOR_IN:
00241                                 $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
00242                         break;
00243 
00244                         case KEYWORD_DO:
00245                                 $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')';
00246                         break;
00247 
00248                         case KEYWORD_BREAK:
00249                         case KEYWORD_CONTINUE:
00250                                 $s = $n->value . ($n->label ? ' ' . $n->label : '');
00251                         break;
00252 
00253                         case KEYWORD_TRY:
00254                                 $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}';
00255                                 $catchClauses = $n->catchClauses;
00256                                 for ($i = 0, $j = count($catchClauses); $i < $j; $i++)
00257                                 {
00258                                         $t = $catchClauses[$i];
00259                                         $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}';
00260                                 }
00261                                 if ($n->finallyBlock)
00262                                         $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}';
00263                         break;
00264 
00265                         case KEYWORD_THROW:
00266                                 $s = 'throw ' . $this->parseTree($n->exception);
00267                         break;
00268 
00269                         case KEYWORD_RETURN:
00270                                 $s = 'return' . ($n->value ? ' ' . $this->parseTree($n->value) : '');
00271                         break;
00272 
00273                         case KEYWORD_WITH:
00274                                 $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
00275                         break;
00276 
00277                         case KEYWORD_VAR:
00278                         case KEYWORD_CONST:
00279                                 $s = $n->value . ' ';
00280                                 $childs = $n->treeNodes;
00281                                 for ($i = 0, $j = count($childs); $i < $j; $i++)
00282                                 {
00283                                         $t = $childs[$i];
00284                                         $s .= ($i ? ',' : '') . $t->name;
00285                                         $u = $t->initializer;
00286                                         if ($u)
00287                                                 $s .= '=' . $this->parseTree($u);
00288                                 }
00289                         break;
00290 
00291                         case KEYWORD_DEBUGGER:
00292                                 throw new Exception('NOT IMPLEMENTED: DEBUGGER');
00293                         break;
00294 
00295                         case TOKEN_CONDCOMMENT_MULTILINE:
00296                                 $s = $n->value . ' ';
00297                                 $childs = $n->treeNodes;
00298                                 for ($i = 0, $j = count($childs); $i < $j; $i++)
00299                                         $s .= $this->parseTree($childs[$i]);
00300                         break;
00301 
00302                         case OP_SEMICOLON:
00303                                 if ($expression = $n->expression)
00304                                         $s = $this->parseTree($expression);
00305                         break;
00306 
00307                         case JS_LABEL:
00308                                 $s = $n->label . ':' . $this->parseTree($n->statement);
00309                         break;
00310 
00311                         case OP_COMMA:
00312                                 $childs = $n->treeNodes;
00313                                 for ($i = 0, $j = count($childs); $i < $j; $i++)
00314                                         $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
00315                         break;
00316 
00317                         case OP_ASSIGN:
00318                                 $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]);
00319                         break;
00320 
00321                         case OP_HOOK:
00322                                 $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]);
00323                         break;
00324 
00325                         case OP_OR: case OP_AND:
00326                         case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND:
00327                         case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
00328                         case OP_LT: case OP_LE: case OP_GE: case OP_GT:
00329                         case OP_LSH: case OP_RSH: case OP_URSH:
00330                         case OP_MUL: case OP_DIV: case OP_MOD:
00331                                 $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]);
00332                         break;
00333 
00334                         case OP_PLUS:
00335                         case OP_MINUS:
00336                                 $s = $this->parseTree($n->treeNodes[0]) . $n->type;
00337                                 $nextTokenType = $n->treeNodes[1]->type;
00338                                 if (    $nextTokenType == OP_PLUS || $nextTokenType == OP_MINUS ||
00339                                         $nextTokenType == OP_INCREMENT || $nextTokenType == OP_DECREMENT ||
00340                                         $nextTokenType == OP_UNARY_PLUS || $nextTokenType == OP_UNARY_MINUS
00341                                 )
00342                                         $s .= ' ';
00343                                 $s .= $this->parseTree($n->treeNodes[1]);
00344                         break;
00345 
00346                         case KEYWORD_IN:
00347                                 $s = $this->parseTree($n->treeNodes[0]) . ' in ' . $this->parseTree($n->treeNodes[1]);
00348                         break;
00349 
00350                         case KEYWORD_INSTANCEOF:
00351                                 $s = $this->parseTree($n->treeNodes[0]) . ' instanceof ' . $this->parseTree($n->treeNodes[1]);
00352                         break;
00353 
00354                         case KEYWORD_DELETE:
00355                                 $s = 'delete ' . $this->parseTree($n->treeNodes[0]);
00356                         break;
00357 
00358                         case KEYWORD_VOID:
00359                                 $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
00360                         break;
00361 
00362                         case KEYWORD_TYPEOF:
00363                                 $s = 'typeof ' . $this->parseTree($n->treeNodes[0]);
00364                         break;
00365 
00366                         case OP_NOT:
00367                         case OP_BITWISE_NOT:
00368                         case OP_UNARY_PLUS:
00369                         case OP_UNARY_MINUS:
00370                                 $s = $n->value . $this->parseTree($n->treeNodes[0]);
00371                         break;
00372 
00373                         case OP_INCREMENT:
00374                         case OP_DECREMENT:
00375                                 if ($n->postfix)
00376                                         $s = $this->parseTree($n->treeNodes[0]) . $n->value;
00377                                 else
00378                                         $s = $n->value . $this->parseTree($n->treeNodes[0]);
00379                         break;
00380 
00381                         case OP_DOT:
00382                                 $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]);
00383                         break;
00384 
00385                         case JS_INDEX:
00386                                 $s = $this->parseTree($n->treeNodes[0]);
00387                                 // See if we can replace named index with a dot saving 3 bytes
00388                                 if (    $n->treeNodes[0]->type == TOKEN_IDENTIFIER &&
00389                                         $n->treeNodes[1]->type == TOKEN_STRING &&
00390                                         $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))
00391                                 )
00392                                         $s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
00393                                 else
00394                                         $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']';
00395                         break;
00396 
00397                         case JS_LIST:
00398                                 $childs = $n->treeNodes;
00399                                 for ($i = 0, $j = count($childs); $i < $j; $i++)
00400                                         $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
00401                         break;
00402 
00403                         case JS_CALL:
00404                                 $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
00405                         break;
00406 
00407                         case KEYWORD_NEW:
00408                         case JS_NEW_WITH_ARGS:
00409                                 $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')';
00410                         break;
00411 
00412                         case JS_ARRAY_INIT:
00413                                 $s = '[';
00414                                 $childs = $n->treeNodes;
00415                                 for ($i = 0, $j = count($childs); $i < $j; $i++)
00416                                 {
00417                                         $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
00418                                 }
00419                                 $s .= ']';
00420                         break;
00421 
00422                         case JS_OBJECT_INIT:
00423                                 $s = '{';
00424                                 $childs = $n->treeNodes;
00425                                 for ($i = 0, $j = count($childs); $i < $j; $i++)
00426                                 {
00427                                         $t = $childs[$i];
00428                                         if ($i)
00429                                                 $s .= ',';
00430                                         if ($t->type == JS_PROPERTY_INIT)
00431                                         {
00432                                                 // Ditch the quotes when the index is a valid identifier
00433                                                 if (    $t->treeNodes[0]->type == TOKEN_STRING &&
00434                                                         $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))
00435                                                 )
00436                                                         $s .= substr($t->treeNodes[0]->value, 1, -1);
00437                                                 else
00438                                                         $s .= $t->treeNodes[0]->value;
00439 
00440                                                 $s .= ':' . $this->parseTree($t->treeNodes[1]);
00441                                         }
00442                                         else
00443                                         {
00444                                                 $s .= $t->type == JS_GETTER ? 'get' : 'set';
00445                                                 $s .= ' ' . $t->name . '(';
00446                                                 $params = $t->params;
00447                                                 for ($i = 0, $j = count($params); $i < $j; $i++)
00448                                                         $s .= ($i ? ',' : '') . $params[$i];
00449                                                 $s .= '){' . $this->parseTree($t->body, true) . '}';
00450                                         }
00451                                 }
00452                                 $s .= '}';
00453                         break;
00454 
00455                         case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
00456                         case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
00457                                 $s = $n->value;
00458                         break;
00459 
00460                         case JS_GROUP:
00461                                 $s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
00462                         break;
00463 
00464                         default:
00465                                 throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
00466                 }
00467 
00468                 return $s;
00469         }
00470 
00471         private function isValidIdentifier($string)
00472         {
00473                 return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
00474         }
00475 }
00476 
00477 class JSParser
00478 {
00479         private $t;
00480 
00481         private $opPrecedence = array(
00482                 ';' => 0,
00483                 ',' => 1,
00484                 '=' => 2, '?' => 2, ':' => 2,
00485                 // The above all have to have the same precedence, see bug 330975.
00486                 '||' => 4,
00487                 '&&' => 5,
00488                 '|' => 6,
00489                 '^' => 7,
00490                 '&' => 8,
00491                 '==' => 9, '!=' => 9, '===' => 9, '!==' => 9,
00492                 '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,
00493                 '<<' => 11, '>>' => 11, '>>>' => 11,
00494                 '+' => 12, '-' => 12,
00495                 '*' => 13, '/' => 13, '%' => 13,
00496                 'delete' => 14, 'void' => 14, 'typeof' => 14,
00497                 '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,
00498                 '++' => 15, '--' => 15,
00499                 'new' => 16,
00500                 '.' => 17,
00501                 JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0,
00502                 JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0
00503         );
00504 
00505         private $opArity = array(
00506                 ',' => -2,
00507                 '=' => 2,
00508                 '?' => 3,
00509                 '||' => 2,
00510                 '&&' => 2,
00511                 '|' => 2,
00512                 '^' => 2,
00513                 '&' => 2,
00514                 '==' => 2, '!=' => 2, '===' => 2, '!==' => 2,
00515                 '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,
00516                 '<<' => 2, '>>' => 2, '>>>' => 2,
00517                 '+' => 2, '-' => 2,
00518                 '*' => 2, '/' => 2, '%' => 2,
00519                 'delete' => 1, 'void' => 1, 'typeof' => 1,
00520                 '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,
00521                 '++' => 1, '--' => 1,
00522                 'new' => 1,
00523                 '.' => 2,
00524                 JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,
00525                 JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,
00526                 TOKEN_CONDCOMMENT_MULTILINE => 1
00527         );
00528 
00529         public function __construct()
00530         {
00531                 $this->t = new JSTokenizer();
00532         }
00533 
00534         public function parse($s, $f, $l)
00535         {
00536                 // initialize tokenizer
00537                 $this->t->init($s, $f, $l);
00538 
00539                 $x = new JSCompilerContext(false);
00540                 $n = $this->Script($x);
00541                 if (!$this->t->isDone())
00542                         throw $this->t->newSyntaxError('Syntax error');
00543 
00544                 return $n;
00545         }
00546 
00547         private function Script($x)
00548         {
00549                 $n = $this->Statements($x);
00550                 $n->type = JS_SCRIPT;
00551                 $n->funDecls = $x->funDecls;
00552                 $n->varDecls = $x->varDecls;
00553 
00554                 return $n;
00555         }
00556 
00557         private function Statements($x)
00558         {
00559                 $n = new JSNode($this->t, JS_BLOCK);
00560                 array_push($x->stmtStack, $n);
00561 
00562                 while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY)
00563                         $n->addNode($this->Statement($x));
00564 
00565                 array_pop($x->stmtStack);
00566 
00567                 return $n;
00568         }
00569 
00570         private function Block($x)
00571         {
00572                 $this->t->mustMatch(OP_LEFT_CURLY);
00573                 $n = $this->Statements($x);
00574                 $this->t->mustMatch(OP_RIGHT_CURLY);
00575 
00576                 return $n;
00577         }
00578 
00579         private function Statement($x)
00580         {
00581                 $tt = $this->t->get();
00582                 $n2 = null;
00583 
00584                 // Cases for statements ending in a right curly return early, avoiding the
00585                 // common semicolon insertion magic after this switch.
00586                 switch ($tt)
00587                 {
00588                         case KEYWORD_FUNCTION:
00589                                 return $this->FunctionDefinition(
00590                                         $x,
00591                                         true,
00592                                         count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM
00593                                 );
00594                         break;
00595 
00596                         case OP_LEFT_CURLY:
00597                                 $n = $this->Statements($x);
00598                                 $this->t->mustMatch(OP_RIGHT_CURLY);
00599                         return $n;
00600 
00601                         case KEYWORD_IF:
00602                                 $n = new JSNode($this->t);
00603                                 $n->condition = $this->ParenExpression($x);
00604                                 array_push($x->stmtStack, $n);
00605                                 $n->thenPart = $this->Statement($x);
00606                                 $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null;
00607                                 array_pop($x->stmtStack);
00608                         return $n;
00609 
00610                         case KEYWORD_SWITCH:
00611                                 $n = new JSNode($this->t);
00612                                 $this->t->mustMatch(OP_LEFT_PAREN);
00613                                 $n->discriminant = $this->Expression($x);
00614                                 $this->t->mustMatch(OP_RIGHT_PAREN);
00615                                 $n->cases = array();
00616                                 $n->defaultIndex = -1;
00617 
00618                                 array_push($x->stmtStack, $n);
00619 
00620                                 $this->t->mustMatch(OP_LEFT_CURLY);
00621 
00622                                 while (($tt = $this->t->get()) != OP_RIGHT_CURLY)
00623                                 {
00624                                         switch ($tt)
00625                                         {
00626                                                 case KEYWORD_DEFAULT:
00627                                                         if ($n->defaultIndex >= 0)
00628                                                                 throw $this->t->newSyntaxError('More than one switch default');
00629                                                         // FALL THROUGH
00630                                                 case KEYWORD_CASE:
00631                                                         $n2 = new JSNode($this->t);
00632                                                         if ($tt == KEYWORD_DEFAULT)
00633                                                                 $n->defaultIndex = count($n->cases);
00634                                                         else
00635                                                                 $n2->caseLabel = $this->Expression($x, OP_COLON);
00636                                                                 break;
00637                                                 default:
00638                                                         throw $this->t->newSyntaxError('Invalid switch case');
00639                                         }
00640 
00641                                         $this->t->mustMatch(OP_COLON);
00642                                         $n2->statements = new JSNode($this->t, JS_BLOCK);
00643                                         while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY)
00644                                                 $n2->statements->addNode($this->Statement($x));
00645 
00646                                         array_push($n->cases, $n2);
00647                                 }
00648 
00649                                 array_pop($x->stmtStack);
00650                         return $n;
00651 
00652                         case KEYWORD_FOR:
00653                                 $n = new JSNode($this->t);
00654                                 $n->isLoop = true;
00655                                 $this->t->mustMatch(OP_LEFT_PAREN);
00656 
00657                                 if (($tt = $this->t->peek()) != OP_SEMICOLON)
00658                                 {
00659                                         $x->inForLoopInit = true;
00660                                         if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST)
00661                                         {
00662                                                 $this->t->get();
00663                                                 $n2 = $this->Variables($x);
00664                                         }
00665                                         else
00666                                         {
00667                                                 $n2 = $this->Expression($x);
00668                                         }
00669                                         $x->inForLoopInit = false;
00670                                 }
00671 
00672                                 if ($n2 && $this->t->match(KEYWORD_IN))
00673                                 {
00674                                         $n->type = JS_FOR_IN;
00675                                         if ($n2->type == KEYWORD_VAR)
00676                                         {
00677                                                 if (count($n2->treeNodes) != 1)
00678                                                 {
00679                                                         throw $this->t->SyntaxError(
00680                                                                 'Invalid for..in left-hand side',
00681                                                                 $this->t->filename,
00682                                                                 $n2->lineno
00683                                                         );
00684                                                 }
00685 
00686                                                 // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
00687                                                 $n->iterator = $n2->treeNodes[0];
00688                                                 $n->varDecl = $n2;
00689                                         }
00690                                         else
00691                                         {
00692                                                 $n->iterator = $n2;
00693                                                 $n->varDecl = null;
00694                                         }
00695 
00696                                         $n->object = $this->Expression($x);
00697                                 }
00698                                 else
00699                                 {
00700                                         $n->setup = $n2 ? $n2 : null;
00701                                         $this->t->mustMatch(OP_SEMICOLON);
00702                                         $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x);
00703                                         $this->t->mustMatch(OP_SEMICOLON);
00704                                         $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x);
00705                                 }
00706 
00707                                 $this->t->mustMatch(OP_RIGHT_PAREN);
00708                                 $n->body = $this->nest($x, $n);
00709                         return $n;
00710 
00711                         case KEYWORD_WHILE:
00712                                 $n = new JSNode($this->t);
00713                                 $n->isLoop = true;
00714                                 $n->condition = $this->ParenExpression($x);
00715                                 $n->body = $this->nest($x, $n);
00716                         return $n;
00717 
00718                         case KEYWORD_DO:
00719                                 $n = new JSNode($this->t);
00720                                 $n->isLoop = true;
00721                                 $n->body = $this->nest($x, $n, KEYWORD_WHILE);
00722                                 $n->condition = $this->ParenExpression($x);
00723                                 if (!$x->ecmaStrictMode)
00724                                 {
00725                                         // <script language="JavaScript"> (without version hints) may need
00726                                         // automatic semicolon insertion without a newline after do-while.
00727                                         // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
00728                                         $this->t->match(OP_SEMICOLON);
00729                                         return $n;
00730                                 }
00731                         break;
00732 
00733                         case KEYWORD_BREAK:
00734                         case KEYWORD_CONTINUE:
00735                                 $n = new JSNode($this->t);
00736 
00737                                 if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER)
00738                                 {
00739                                         $this->t->get();
00740                                         $n->label = $this->t->currentToken()->value;
00741                                 }
00742 
00743                                 $ss = $x->stmtStack;
00744                                 $i = count($ss);
00745                                 $label = $n->label;
00746                                 if ($label)
00747                                 {
00748                                         do
00749                                         {
00750                                                 if (--$i < 0)
00751                                                         throw $this->t->newSyntaxError('Label not found');
00752                                         }
00753                                         while ($ss[$i]->label != $label);
00754                                 }
00755                                 else
00756                                 {
00757                                         do
00758                                         {
00759                                                 if (--$i < 0)
00760                                                         throw $this->t->newSyntaxError('Invalid ' . $tt);
00761                                         }
00762                                         while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH));
00763                                 }
00764 
00765                                 $n->target = $ss[$i];
00766                         break;
00767 
00768                         case KEYWORD_TRY:
00769                                 $n = new JSNode($this->t);
00770                                 $n->tryBlock = $this->Block($x);
00771                                 $n->catchClauses = array();
00772 
00773                                 while ($this->t->match(KEYWORD_CATCH))
00774                                 {
00775                                         $n2 = new JSNode($this->t);
00776                                         $this->t->mustMatch(OP_LEFT_PAREN);
00777                                         $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
00778 
00779                                         if ($this->t->match(KEYWORD_IF))
00780                                         {
00781                                                 if ($x->ecmaStrictMode)
00782                                                         throw $this->t->newSyntaxError('Illegal catch guard');
00783 
00784                                                 if (count($n->catchClauses) && !end($n->catchClauses)->guard)
00785                                                         throw $this->t->newSyntaxError('Guarded catch after unguarded');
00786 
00787                                                 $n2->guard = $this->Expression($x);
00788                                         }
00789                                         else
00790                                         {
00791                                                 $n2->guard = null;
00792                                         }
00793 
00794                                         $this->t->mustMatch(OP_RIGHT_PAREN);
00795                                         $n2->block = $this->Block($x);
00796                                         array_push($n->catchClauses, $n2);
00797                                 }
00798 
00799                                 if ($this->t->match(KEYWORD_FINALLY))
00800                                         $n->finallyBlock = $this->Block($x);
00801 
00802                                 if (!count($n->catchClauses) && !$n->finallyBlock)
00803                                         throw $this->t->newSyntaxError('Invalid try statement');
00804                         return $n;
00805 
00806                         case KEYWORD_CATCH:
00807                         case KEYWORD_FINALLY:
00808                                 throw $this->t->newSyntaxError($tt + ' without preceding try');
00809 
00810                         case KEYWORD_THROW:
00811                                 $n = new JSNode($this->t);
00812                                 $n->exception = $this->Expression($x);
00813                         break;
00814 
00815                         case KEYWORD_RETURN:
00816                                 if (!$x->inFunction)
00817                                         throw $this->t->newSyntaxError('Invalid return');
00818 
00819                                 $n = new JSNode($this->t);
00820                                 $tt = $this->t->peekOnSameLine();
00821                                 if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
00822                                         $n->value = $this->Expression($x);
00823                                 else
00824                                         $n->value = null;
00825                         break;
00826 
00827                         case KEYWORD_WITH:
00828                                 $n = new JSNode($this->t);
00829                                 $n->object = $this->ParenExpression($x);
00830                                 $n->body = $this->nest($x, $n);
00831                         return $n;
00832 
00833                         case KEYWORD_VAR:
00834                         case KEYWORD_CONST:
00835                                 $n = $this->Variables($x);
00836                         break;
00837 
00838                         case TOKEN_CONDCOMMENT_MULTILINE:
00839                                 $n = new JSNode($this->t);
00840                         return $n;
00841 
00842                         case KEYWORD_DEBUGGER:
00843                                 $n = new JSNode($this->t);
00844                         break;
00845 
00846                         case TOKEN_NEWLINE:
00847                         case OP_SEMICOLON:
00848                                 $n = new JSNode($this->t, OP_SEMICOLON);
00849                                 $n->expression = null;
00850                         return $n;
00851 
00852                         default:
00853                                 if ($tt == TOKEN_IDENTIFIER)
00854                                 {
00855                                         $this->t->scanOperand = false;
00856                                         $tt = $this->t->peek();
00857                                         $this->t->scanOperand = true;
00858                                         if ($tt == OP_COLON)
00859                                         {
00860                                                 $label = $this->t->currentToken()->value;
00861                                                 $ss = $x->stmtStack;
00862                                                 for ($i = count($ss) - 1; $i >= 0; --$i)
00863                                                 {
00864                                                         if ($ss[$i]->label == $label)
00865                                                                 throw $this->t->newSyntaxError('Duplicate label');
00866                                                 }
00867 
00868                                                 $this->t->get();
00869                                                 $n = new JSNode($this->t, JS_LABEL);
00870                                                 $n->label = $label;
00871                                                 $n->statement = $this->nest($x, $n);
00872 
00873                                                 return $n;
00874                                         }
00875                                 }
00876 
00877                                 $n = new JSNode($this->t, OP_SEMICOLON);
00878                                 $this->t->unget();
00879                                 $n->expression = $this->Expression($x);
00880                                 $n->end = $n->expression->end;
00881                         break;
00882                 }
00883 
00884                 if ($this->t->lineno == $this->t->currentToken()->lineno)
00885                 {
00886                         $tt = $this->t->peekOnSameLine();
00887                         if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
00888                                 throw $this->t->newSyntaxError('Missing ; before statement');
00889                 }
00890 
00891                 $this->t->match(OP_SEMICOLON);
00892 
00893                 return $n;
00894         }
00895 
00896         private function FunctionDefinition($x, $requireName, $functionForm)
00897         {
00898                 $f = new JSNode($this->t);
00899 
00900                 if ($f->type != KEYWORD_FUNCTION)
00901                         $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
00902 
00903                 if ($this->t->match(TOKEN_IDENTIFIER))
00904                         $f->name = $this->t->currentToken()->value;
00905                 elseif ($requireName)
00906                         throw $this->t->newSyntaxError('Missing function identifier');
00907 
00908                 $this->t->mustMatch(OP_LEFT_PAREN);
00909                         $f->params = array();
00910 
00911                 while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
00912                 {
00913                         if ($tt != TOKEN_IDENTIFIER)
00914                                 throw $this->t->newSyntaxError('Missing formal parameter');
00915 
00916                         array_push($f->params, $this->t->currentToken()->value);
00917 
00918                         if ($this->t->peek() != OP_RIGHT_PAREN)
00919                                 $this->t->mustMatch(OP_COMMA);
00920                 }
00921 
00922                 $this->t->mustMatch(OP_LEFT_CURLY);
00923 
00924                 $x2 = new JSCompilerContext(true);
00925                 $f->body = $this->Script($x2);
00926 
00927                 $this->t->mustMatch(OP_RIGHT_CURLY);
00928                 $f->end = $this->t->currentToken()->end;
00929 
00930                 $f->functionForm = $functionForm;
00931                 if ($functionForm == DECLARED_FORM)
00932                         array_push($x->funDecls, $f);
00933 
00934                 return $f;
00935         }
00936 
00937         private function Variables($x)
00938         {
00939                 $n = new JSNode($this->t);
00940 
00941                 do
00942                 {
00943                         $this->t->mustMatch(TOKEN_IDENTIFIER);
00944 
00945                         $n2 = new JSNode($this->t);
00946                         $n2->name = $n2->value;
00947 
00948                         if ($this->t->match(OP_ASSIGN))
00949                         {
00950                                 if ($this->t->currentToken()->assignOp)
00951                                         throw $this->t->newSyntaxError('Invalid variable initialization');
00952 
00953                                 $n2->initializer = $this->Expression($x, OP_COMMA);
00954                         }
00955 
00956                         $n2->readOnly = $n->type == KEYWORD_CONST;
00957 
00958                         $n->addNode($n2);
00959                         array_push($x->varDecls, $n2);
00960                 }
00961                 while ($this->t->match(OP_COMMA));
00962 
00963                 return $n;
00964         }
00965 
00966         private function Expression($x, $stop=false)
00967         {
00968                 $operators = array();
00969                 $operands = array();
00970                 $n = false;
00971 
00972                 $bl = $x->bracketLevel;
00973                 $cl = $x->curlyLevel;
00974                 $pl = $x->parenLevel;
00975                 $hl = $x->hookLevel;
00976 
00977                 while (($tt = $this->t->get()) != TOKEN_END)
00978                 {
00979                         if ($tt == $stop &&
00980                                 $x->bracketLevel == $bl &&
00981                                 $x->curlyLevel == $cl &&
00982                                 $x->parenLevel == $pl &&
00983                                 $x->hookLevel == $hl
00984                         )
00985                         {
00986                                 // Stop only if tt matches the optional stop parameter, and that
00987                                 // token is not quoted by some kind of bracket.
00988                                 break;
00989                         }
00990 
00991                         switch ($tt)
00992                         {
00993                                 case OP_SEMICOLON:
00994                                         // NB: cannot be empty, Statement handled that.
00995                                         break 2;
00996 
00997                                 case OP_ASSIGN:
00998                                 case OP_HOOK:
00999                                 case OP_COLON:
01000                                         if ($this->t->scanOperand)
01001                                                 break 2;
01002 
01003                                         // Use >, not >=, for right-associative ASSIGN and HOOK/COLON.
01004                                         while ( !empty($operators) &&
01005                                                 (       $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] ||
01006                                                         ($tt == OP_COLON && end($operators)->type == OP_ASSIGN)
01007                                                 )
01008                                         )
01009                                                 $this->reduce($operators, $operands);
01010 
01011                                         if ($tt == OP_COLON)
01012                                         {
01013                                                 $n = end($operators);
01014                                                 if ($n->type != OP_HOOK)
01015                                                         throw $this->t->newSyntaxError('Invalid label');
01016 
01017                                                 --$x->hookLevel;
01018                                         }
01019                                         else
01020                                         {
01021                                                 array_push($operators, new JSNode($this->t));
01022                                                 if ($tt == OP_ASSIGN)
01023                                                         end($operands)->assignOp = $this->t->currentToken()->assignOp;
01024                                                 else
01025                                                         ++$x->hookLevel;
01026                                         }
01027 
01028                                         $this->t->scanOperand = true;
01029                                 break;
01030 
01031                                 case KEYWORD_IN:
01032                                         // An in operator should not be parsed if we're parsing the head of
01033                                         // a for (...) loop, unless it is in the then part of a conditional
01034                                         // expression, or parenthesized somehow.
01035                                         if ($x->inForLoopInit && !$x->hookLevel &&
01036                                                 !$x->bracketLevel && !$x->curlyLevel &&
01037                                                 !$x->parenLevel
01038                                         )
01039                                         {
01040                                                 break 2;
01041                                         }
01042                                 // FALL THROUGH
01043                                 case OP_COMMA:
01044                                         // Treat comma as left-associative so reduce can fold left-heavy
01045                                         // COMMA trees into a single array.
01046                                         // FALL THROUGH
01047                                 case OP_OR:
01048                                 case OP_AND:
01049                                 case OP_BITWISE_OR:
01050                                 case OP_BITWISE_XOR:
01051                                 case OP_BITWISE_AND:
01052                                 case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
01053                                 case OP_LT: case OP_LE: case OP_GE: case OP_GT:
01054                                 case KEYWORD_INSTANCEOF:
01055                                 case OP_LSH: case OP_RSH: case OP_URSH:
01056                                 case OP_PLUS: case OP_MINUS:
01057                                 case OP_MUL: case OP_DIV: case OP_MOD:
01058                                 case OP_DOT:
01059                                         if ($this->t->scanOperand)
01060                                                 break 2;
01061 
01062                                         while ( !empty($operators) &&
01063                                                 $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
01064                                         )
01065                                                 $this->reduce($operators, $operands);
01066 
01067                                         if ($tt == OP_DOT)
01068                                         {
01069                                                 $this->t->mustMatch(TOKEN_IDENTIFIER);
01070                                                 array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
01071                                         }
01072                                         else
01073                                         {
01074                                                 array_push($operators, new JSNode($this->t));
01075                                                 $this->t->scanOperand = true;
01076                                         }
01077                                 break;
01078 
01079                                 case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
01080                                 case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
01081                                 case KEYWORD_NEW:
01082                                         if (!$this->t->scanOperand)
01083                                                 break 2;
01084 
01085                                         array_push($operators, new JSNode($this->t));
01086                                 break;
01087 
01088                                 case OP_INCREMENT: case OP_DECREMENT:
01089                                         if ($this->t->scanOperand)
01090                                         {
01091                                                 array_push($operators, new JSNode($this->t));  // prefix increment or decrement
01092                                         }
01093                                         else
01094                                         {
01095                                                 // Don't cross a line boundary for postfix {in,de}crement.
01096                                                 $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
01097                                                 if ($t && $t->lineno != $this->t->lineno)
01098                                                         break 2;
01099 
01100                                                 if (!empty($operators))
01101                                                 {
01102                                                         // Use >, not >=, so postfix has higher precedence than prefix.
01103                                                         while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
01104                                                                 $this->reduce($operators, $operands);
01105                                                 }
01106 
01107                                                 $n = new JSNode($this->t, $tt, array_pop($operands));
01108                                                 $n->postfix = true;
01109                                                 array_push($operands, $n);
01110                                         }
01111                                 break;
01112 
01113                                 case KEYWORD_FUNCTION:
01114                                         if (!$this->t->scanOperand)
01115                                                 break 2;
01116 
01117                                         array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
01118                                         $this->t->scanOperand = false;
01119                                 break;
01120 
01121                                 case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
01122                                 case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
01123                                         if (!$this->t->scanOperand)
01124                                                 break 2;
01125 
01126                                         array_push($operands, new JSNode($this->t));
01127                                         $this->t->scanOperand = false;
01128                                 break;
01129 
01130                                 case TOKEN_CONDCOMMENT_MULTILINE:
01131                                         if ($this->t->scanOperand)
01132                                                 array_push($operators, new JSNode($this->t));
01133                                         else
01134                                                 array_push($operands, new JSNode($this->t));
01135                                 break;
01136 
01137                                 case OP_LEFT_BRACKET:
01138                                         if ($this->t->scanOperand)
01139                                         {
01140                                                 // Array initialiser.  Parse using recursive descent, as the
01141                                                 // sub-grammar here is not an operator grammar.
01142                                                 $n = new JSNode($this->t, JS_ARRAY_INIT);
01143                                                 while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
01144                                                 {
01145                                                         if ($tt == OP_COMMA)
01146                                                         {
01147                                                                 $this->t->get();
01148                                                                 $n->addNode(null);
01149                                                                 continue;
01150                                                         }
01151 
01152                                                         $n->addNode($this->Expression($x, OP_COMMA));
01153                                                         if (!$this->t->match(OP_COMMA))
01154                                                                 break;
01155                                                 }
01156 
01157                                                 $this->t->mustMatch(OP_RIGHT_BRACKET);
01158                                                 array_push($operands, $n);
01159                                                 $this->t->scanOperand = false;
01160                                         }
01161                                         else
01162                                         {
01163                                                 // Property indexing operator.
01164                                                 array_push($operators, new JSNode($this->t, JS_INDEX));
01165                                                 $this->t->scanOperand = true;
01166                                                 ++$x->bracketLevel;
01167                                         }
01168                                 break;
01169 
01170                                 case OP_RIGHT_BRACKET:
01171                                         if ($this->t->scanOperand || $x->bracketLevel == $bl)
01172                                                 break 2;
01173 
01174                                         while ($this->reduce($operators, $operands)->type != JS_INDEX)
01175                                                 continue;
01176 
01177                                         --$x->bracketLevel;
01178                                 break;
01179 
01180                                 case OP_LEFT_CURLY:
01181                                         if (!$this->t->scanOperand)
01182                                                 break 2;
01183 
01184                                         // Object initialiser.  As for array initialisers (see above),
01185                                         // parse using recursive descent.
01186                                         ++$x->curlyLevel;
01187                                         $n = new JSNode($this->t, JS_OBJECT_INIT);
01188                                         while (!$this->t->match(OP_RIGHT_CURLY))
01189                                         {
01190                                                 do
01191                                                 {
01192                                                         $tt = $this->t->get();
01193                                                         $tv = $this->t->currentToken()->value;
01194                                                         if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
01195                                                         {
01196                                                                 if ($x->ecmaStrictMode)
01197                                                                         throw $this->t->newSyntaxError('Illegal property accessor');
01198 
01199                                                                 $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
01200                                                         }
01201                                                         else
01202                                                         {
01203                                                                 switch ($tt)
01204                                                                 {
01205                                                                         case TOKEN_IDENTIFIER:
01206                                                                         case TOKEN_NUMBER:
01207                                                                         case TOKEN_STRING:
01208                                                                                 $id = new JSNode($this->t);
01209                                                                         break;
01210 
01211                                                                         case OP_RIGHT_CURLY:
01212                                                                                 if ($x->ecmaStrictMode)
01213                                                                                         throw $this->t->newSyntaxError('Illegal trailing ,');
01214                                                                         break 3;
01215 
01216                                                                         default:
01217                                                                                 throw $this->t->newSyntaxError('Invalid property name');
01218                                                                 }
01219 
01220                                                                 $this->t->mustMatch(OP_COLON);
01221                                                                 $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
01222                                                         }
01223                                                 }
01224                                                 while ($this->t->match(OP_COMMA));
01225 
01226                                                 $this->t->mustMatch(OP_RIGHT_CURLY);
01227                                                 break;
01228                                         }
01229 
01230                                         array_push($operands, $n);
01231                                         $this->t->scanOperand = false;
01232                                         --$x->curlyLevel;
01233                                 break;
01234 
01235                                 case OP_RIGHT_CURLY:
01236                                         if (!$this->t->scanOperand && $x->curlyLevel != $cl)
01237                                                 throw new Exception('PANIC: right curly botch');
01238                                 break 2;
01239 
01240                                 case OP_LEFT_PAREN:
01241                                         if ($this->t->scanOperand)
01242                                         {
01243                                                 array_push($operators, new JSNode($this->t, JS_GROUP));
01244                                         }
01245                                         else
01246                                         {
01247                                                 while ( !empty($operators) &&
01248                                                         $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
01249                                                 )
01250                                                         $this->reduce($operators, $operands);
01251 
01252                                                 // Handle () now, to regularize the n-ary case for n > 0.
01253                                                 // We must set scanOperand in case there are arguments and
01254                                                 // the first one is a regexp or unary+/-.
01255                                                 $n = end($operators);
01256                                                 $this->t->scanOperand = true;
01257                                                 if ($this->t->match(OP_RIGHT_PAREN))
01258                                                 {
01259                                                         if ($n && $n->type == KEYWORD_NEW)
01260                                                         {
01261                                                                 array_pop($operators);
01262                                                                 $n->addNode(array_pop($operands));
01263                                                         }
01264                                                         else
01265                                                         {
01266                                                                 $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
01267                                                         }
01268 
01269                                                         array_push($operands, $n);
01270                                                         $this->t->scanOperand = false;
01271                                                         break;
01272                                                 }
01273 
01274                                                 if ($n && $n->type == KEYWORD_NEW)
01275                                                         $n->type = JS_NEW_WITH_ARGS;
01276                                                 else
01277                                                         array_push($operators, new JSNode($this->t, JS_CALL));
01278                                         }
01279 
01280                                         ++$x->parenLevel;
01281                                 break;
01282 
01283                                 case OP_RIGHT_PAREN:
01284                                         if ($this->t->scanOperand || $x->parenLevel == $pl)
01285                                                 break 2;
01286 
01287                                         while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
01288                                                 $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
01289                                         )
01290                                         {
01291                                                 continue;
01292                                         }
01293 
01294                                         if ($tt != JS_GROUP)
01295                                         {
01296                                                 $n = end($operands);
01297                                                 if ($n->treeNodes[1]->type != OP_COMMA)
01298                                                         $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
01299                                                 else
01300                                                         $n->treeNodes[1]->type = JS_LIST;
01301                                         }
01302 
01303                                         --$x->parenLevel;
01304                                 break;
01305 
01306                                 // Automatic semicolon insertion means we may scan across a newline
01307                                 // and into the beginning of another statement.  If so, break out of
01308                                 // the while loop and let the t.scanOperand logic handle errors.
01309                                 default:
01310                                         break 2;
01311                         }
01312                 }
01313 
01314                 if ($x->hookLevel != $hl)
01315                         throw $this->t->newSyntaxError('Missing : after ?');
01316 
01317                 if ($x->parenLevel != $pl)
01318                         throw $this->t->newSyntaxError('Missing ) in parenthetical');
01319 
01320                 if ($x->bracketLevel != $bl)
01321                         throw $this->t->newSyntaxError('Missing ] in index expression');
01322 
01323                 if ($this->t->scanOperand)
01324                         throw $this->t->newSyntaxError('Missing operand');
01325 
01326                 // Resume default mode, scanning for operands, not operators.
01327                 $this->t->scanOperand = true;
01328                 $this->t->unget();
01329 
01330                 while (count($operators))
01331                         $this->reduce($operators, $operands);
01332 
01333                 return array_pop($operands);
01334         }
01335 
01336         private function ParenExpression($x)
01337         {
01338                 $this->t->mustMatch(OP_LEFT_PAREN);
01339                 $n = $this->Expression($x);
01340                 $this->t->mustMatch(OP_RIGHT_PAREN);
01341 
01342                 return $n;
01343         }
01344 
01345         // Statement stack and nested statement handler.
01346         private function nest($x, $node, $end = false)
01347         {
01348                 array_push($x->stmtStack, $node);
01349                 $n = $this->statement($x);
01350                 array_pop($x->stmtStack);
01351 
01352                 if ($end)
01353                         $this->t->mustMatch($end);
01354 
01355                 return $n;
01356         }
01357 
01358         private function reduce(&$operators, &$operands)
01359         {
01360                 $n = array_pop($operators);
01361                 $op = $n->type;
01362                 $arity = $this->opArity[$op];
01363                 $c = count($operands);
01364                 if ($arity == -2)
01365                 {
01366                         // Flatten left-associative trees
01367                         if ($c >= 2)
01368                         {
01369                                 $left = $operands[$c - 2];
01370                                 if ($left->type == $op)
01371                                 {
01372                                         $right = array_pop($operands);
01373                                         $left->addNode($right);
01374                                         return $left;
01375                                 }
01376                         }
01377                         $arity = 2;
01378                 }
01379 
01380                 // Always use push to add operands to n, to update start and end
01381                 $a = array_splice($operands, $c - $arity);
01382                 for ($i = 0; $i < $arity; $i++)
01383                         $n->addNode($a[$i]);
01384 
01385                 // Include closing bracket or postfix operator in [start,end]
01386                 $te = $this->t->currentToken()->end;
01387                 if ($n->end < $te)
01388                         $n->end = $te;
01389 
01390                 array_push($operands, $n);
01391 
01392                 return $n;
01393         }
01394 }
01395 
01396 class JSCompilerContext
01397 {
01398         public $inFunction = false;
01399         public $inForLoopInit = false;
01400         public $ecmaStrictMode = false;
01401         public $bracketLevel = 0;
01402         public $curlyLevel = 0;
01403         public $parenLevel = 0;
01404         public $hookLevel = 0;
01405 
01406         public $stmtStack = array();
01407         public $funDecls = array();
01408         public $varDecls = array();
01409 
01410         public function __construct($inFunction)
01411         {
01412                 $this->inFunction = $inFunction;
01413         }
01414 }
01415 
01416 class JSNode
01417 {
01418         private $type;
01419         private $value;
01420         private $lineno;
01421         private $start;
01422         private $end;
01423 
01424         public $treeNodes = array();
01425         public $funDecls = array();
01426         public $varDecls = array();
01427 
01428         public function __construct($t, $type=0)
01429         {
01430                 if ($token = $t->currentToken())
01431                 {
01432                         $this->type = $type ? $type : $token->type;
01433                         $this->value = $token->value;
01434                         $this->lineno = $token->lineno;
01435                         $this->start = $token->start;
01436                         $this->end = $token->end;
01437                 }
01438                 else
01439                 {
01440                         $this->type = $type;
01441                         $this->lineno = $t->lineno;
01442                 }
01443 
01444                 if (($numargs = func_num_args()) > 2)
01445                 {
01446                         $args = func_get_args();;
01447                         for ($i = 2; $i < $numargs; $i++)
01448                                 $this->addNode($args[$i]);
01449                 }
01450         }
01451 
01452         // we don't want to bloat our object with all kind of specific properties, so we use overloading
01453         public function __set($name, $value)
01454         {
01455                 $this->$name = $value;
01456         }
01457 
01458         public function __get($name)
01459         {
01460                 if (isset($this->$name))
01461                         return $this->$name;
01462 
01463                 return null;
01464         }
01465 
01466         public function addNode($node)
01467         {
01468                 $this->treeNodes[] = $node;
01469         }
01470 }
01471 
01472 class JSTokenizer
01473 {
01474         private $cursor = 0;
01475         private $source;
01476 
01477         public $tokens = array();
01478         public $tokenIndex = 0;
01479         public $lookahead = 0;
01480         public $scanNewlines = false;
01481         public $scanOperand = true;
01482 
01483         public $filename;
01484         public $lineno;
01485 
01486         private $keywords = array(
01487                 'break',
01488                 'case', 'catch', 'const', 'continue',
01489                 'debugger', 'default', 'delete', 'do',
01490                 'else', 'enum',
01491                 'false', 'finally', 'for', 'function',
01492                 'if', 'in', 'instanceof',
01493                 'new', 'null',
01494                 'return',
01495                 'switch',
01496                 'this', 'throw', 'true', 'try', 'typeof',
01497                 'var', 'void',
01498                 'while', 'with'
01499         );
01500 
01501         private $opTypeNames = array(
01502                 ';'     => 'SEMICOLON',
01503                 ','     => 'COMMA',
01504                 '?'     => 'HOOK',
01505                 ':'     => 'COLON',
01506                 '||'    => 'OR',
01507                 '&&'    => 'AND',
01508                 '|'     => 'BITWISE_OR',
01509                 '^'     => 'BITWISE_XOR',
01510                 '&'     => 'BITWISE_AND',
01511                 '==='   => 'STRICT_EQ',
01512                 '=='    => 'EQ',
01513                 '='     => 'ASSIGN',
01514                 '!=='   => 'STRICT_NE',
01515                 '!='    => 'NE',
01516                 '<<'    => 'LSH',
01517                 '<='    => 'LE',
01518                 '<'     => 'LT',
01519                 '>>>'   => 'URSH',
01520                 '>>'    => 'RSH',
01521                 '>='    => 'GE',
01522                 '>'     => 'GT',
01523                 '++'    => 'INCREMENT',
01524                 '--'    => 'DECREMENT',
01525                 '+'     => 'PLUS',
01526                 '-'     => 'MINUS',
01527                 '*'     => 'MUL',
01528                 '/'     => 'DIV',
01529                 '%'     => 'MOD',
01530                 '!'     => 'NOT',
01531                 '~'     => 'BITWISE_NOT',
01532                 '.'     => 'DOT',
01533                 '['     => 'LEFT_BRACKET',
01534                 ']'     => 'RIGHT_BRACKET',
01535                 '{'     => 'LEFT_CURLY',
01536                 '}'     => 'RIGHT_CURLY',
01537                 '('     => 'LEFT_PAREN',
01538                 ')'     => 'RIGHT_PAREN',
01539                 '@*/'   => 'CONDCOMMENT_END'
01540         );
01541 
01542         private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
01543         private $opRegExp;
01544 
01545         public function __construct()
01546         {
01547                 $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', array_keys($this->opTypeNames))) . ')#';
01548 
01549                 // this is quite a hidden yet convenient place to create the defines for operators and keywords
01550                 foreach ($this->opTypeNames as $operand => $name)
01551                         define('OP_' . $name, $operand);
01552 
01553                 define('OP_UNARY_PLUS', 'U+');
01554                 define('OP_UNARY_MINUS', 'U-');
01555 
01556                 foreach ($this->keywords as $keyword)
01557                         define('KEYWORD_' . strtoupper($keyword), $keyword);
01558         }
01559 
01560         public function init($source, $filename = '', $lineno = 1)
01561         {
01562                 $this->source = $source;
01563                 $this->filename = $filename ? $filename : '[inline]';
01564                 $this->lineno = $lineno;
01565 
01566                 $this->cursor = 0;
01567                 $this->tokens = array();
01568                 $this->tokenIndex = 0;
01569                 $this->lookahead = 0;
01570                 $this->scanNewlines = false;
01571                 $this->scanOperand = true;
01572         }
01573 
01574         public function getInput($chunksize)
01575         {
01576                 if ($chunksize)
01577                         return substr($this->source, $this->cursor, $chunksize);
01578 
01579                 return substr($this->source, $this->cursor);
01580         }
01581 
01582         public function isDone()
01583         {
01584                 return $this->peek() == TOKEN_END;
01585         }
01586 
01587         public function match($tt)
01588         {
01589                 return $this->get() == $tt || $this->unget();
01590         }
01591 
01592         public function mustMatch($tt)
01593         {
01594                 if (!$this->match($tt))
01595                         throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
01596 
01597                 return $this->currentToken();
01598         }
01599 
01600         public function peek()
01601         {
01602                 if ($this->lookahead)
01603                 {
01604                         $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
01605                         if ($this->scanNewlines && $next->lineno != $this->lineno)
01606                                 $tt = TOKEN_NEWLINE;
01607                         else
01608                                 $tt = $next->type;
01609                 }
01610                 else
01611                 {
01612                         $tt = $this->get();
01613                         $this->unget();
01614                 }
01615 
01616                 return $tt;
01617         }
01618 
01619         public function peekOnSameLine()
01620         {
01621                 $this->scanNewlines = true;
01622                 $tt = $this->peek();
01623                 $this->scanNewlines = false;
01624 
01625                 return $tt;
01626         }
01627 
01628         public function currentToken()
01629         {
01630                 if (!empty($this->tokens))
01631                         return $this->tokens[$this->tokenIndex];
01632         }
01633 
01634         public function get($chunksize = 1000)
01635         {
01636                 while($this->lookahead)
01637                 {
01638                         $this->lookahead--;
01639                         $this->tokenIndex = ($this->tokenIndex + 1) & 3;
01640                         $token = $this->tokens[$this->tokenIndex];
01641                         if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
01642                                 return $token->type;
01643                 }
01644 
01645                 $conditional_comment = false;
01646 
01647                 // strip whitespace and comments
01648                 while(true)
01649                 {
01650                         $input = $this->getInput($chunksize);
01651 
01652                         // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
01653                         $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
01654                         if (preg_match($re, $input, $match))
01655                         {
01656                                 $spaces = $match[0];
01657                                 $spacelen = strlen($spaces);
01658                                 $this->cursor += $spacelen;
01659                                 if (!$this->scanNewlines)
01660                                         $this->lineno += substr_count($spaces, "\n");
01661 
01662                                 if ($spacelen == $chunksize)
01663                                         continue; // complete chunk contained whitespace
01664 
01665                                 $input = $this->getInput($chunksize);
01666                                 if ($input == '' || $input[0] != '/')
01667                                         break;
01668                         }
01669 
01670                         // Comments
01671                         if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?(?:.|\n)*?\*\/|\/.*)/', $input, $match))
01672                         {
01673                                 if (!$chunksize)
01674                                         break;
01675 
01676                                 // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
01677                                 $chunksize = null;
01678                                 continue;
01679                         }
01680 
01681                         // check if this is a conditional (JScript) comment
01682                         if (!empty($match[1]))
01683                         {
01684                                 //$match[0] = '/*' . $match[1];
01685                                 $conditional_comment = true;
01686                                 break;
01687                         }
01688                         else
01689                         {
01690                                 $this->cursor += strlen($match[0]);
01691                                 $this->lineno += substr_count($match[0], "\n");
01692                         }
01693                 }
01694 
01695                 if ($input == '')
01696                 {
01697                         $tt = TOKEN_END;
01698                         $match = array('');
01699                 }
01700                 elseif ($conditional_comment)
01701                 {
01702                         $tt = TOKEN_CONDCOMMENT_MULTILINE;
01703                 }
01704                 else
01705                 {
01706                         switch ($input[0])
01707                         {
01708                                 case '0': case '1': case '2': case '3': case '4':
01709                                 case '5': case '6': case '7': case '8': case '9':
01710                                         if (preg_match('/^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+/', $input, $match))
01711                                         {
01712                                                 $tt = TOKEN_NUMBER;
01713                                         }
01714                                         elseif (preg_match('/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/', $input, $match))
01715                                         {
01716                                                 // this should always match because of \d+
01717                                                 $tt = TOKEN_NUMBER;
01718                                         }
01719                                 break;
01720 
01721                                 case '"':
01722                                 case "'":
01723                                         if (preg_match('/^"(?:\\\\(?:.|\r?\n)|[^\\\\"\r\n])*"|^\'(?:\\\\(?:.|\r?\n)|[^\\\\\'\r\n])*\'/', $input, $match))
01724                                         {
01725                                                 $tt = TOKEN_STRING;
01726                                         }
01727                                         else
01728                                         {
01729                                                 if ($chunksize)
01730                                                         return $this->get(null); // retry with a full chunk fetch
01731 
01732                                                 throw $this->newSyntaxError('Unterminated string literal');
01733                                         }
01734                                 break;
01735 
01736                                 case '/':
01737                                         if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
01738                                         {
01739                                                 $tt = TOKEN_REGEXP;
01740                                                 break;
01741                                         }
01742                                 // fall through
01743 
01744                                 case '|':
01745                                 case '^':
01746                                 case '&':
01747                                 case '<':
01748                                 case '>':
01749                                 case '+':
01750                                 case '-':
01751                                 case '*':
01752                                 case '%':
01753                                 case '=':
01754                                 case '!':
01755                                         // should always match
01756                                         preg_match($this->opRegExp, $input, $match);
01757                                         $op = $match[0];
01758                                         if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
01759                                         {
01760                                                 $tt = OP_ASSIGN;
01761                                                 $match[0] .= '=';
01762                                         }
01763                                         else
01764                                         {
01765                                                 $tt = $op;
01766                                                 if ($this->scanOperand)
01767                                                 {
01768                                                         if ($op == OP_PLUS)
01769                                                                 $tt = OP_UNARY_PLUS;
01770                                                         elseif ($op == OP_MINUS)
01771                                                                 $tt = OP_UNARY_MINUS;
01772                                                 }
01773                                                 $op = null;
01774                                         }
01775                                 break;
01776 
01777                                 case '.':
01778                                         if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
01779                                         {
01780                                                 $tt = TOKEN_NUMBER;
01781                                                 break;
01782                                         }
01783                                 // fall through
01784 
01785                                 case ';':
01786                                 case ',':
01787                                 case '?':
01788                                 case ':':
01789                                 case '~':
01790                                 case '[':
01791                                 case ']':
01792                                 case '{':
01793                                 case '}':
01794                                 case '(':
01795                                 case ')':
01796                                         // these are all single
01797                                         $match = array($input[0]);
01798                                         $tt = $input[0];
01799                                 break;
01800 
01801                                 case '@':
01802                                         throw $this->newSyntaxError('Illegal token');
01803                                 break;
01804 
01805                                 case "\n":
01806                                         if ($this->scanNewlines)
01807                                         {
01808                                                 $match = array("\n");
01809                                                 $tt = TOKEN_NEWLINE;
01810                                         }
01811                                         else
01812                                                 throw $this->newSyntaxError('Illegal token');
01813                                 break;
01814 
01815                                 default:
01816                                         // FIXME: add support for unicode and unicode escape sequence \uHHHH
01817                                         if (preg_match('/^[$\w]+/', $input, $match))
01818                                         {
01819                                                 $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
01820                                         }
01821                                         else
01822                                                 throw $this->newSyntaxError('Illegal token');
01823                         }
01824                 }
01825 
01826                 $this->tokenIndex = ($this->tokenIndex + 1) & 3;
01827 
01828                 if (!isset($this->tokens[$this->tokenIndex]))
01829                         $this->tokens[$this->tokenIndex] = new JSToken();
01830 
01831                 $token = $this->tokens[$this->tokenIndex];
01832                 $token->type = $tt;
01833 
01834                 if ($tt == OP_ASSIGN)
01835                         $token->assignOp = $op;
01836 
01837                 $token->start = $this->cursor;
01838 
01839                 $token->value = $match[0];
01840                 $this->cursor += strlen($match[0]);
01841 
01842                 $token->end = $this->cursor;
01843                 $token->lineno = $this->lineno;
01844 
01845                 return $tt;
01846         }
01847 
01848         public function unget()
01849         {
01850                 if (++$this->lookahead == 4)
01851                         throw $this->newSyntaxError('PANIC: too much lookahead!');
01852 
01853                 $this->tokenIndex = ($this->tokenIndex - 1) & 3;
01854         }
01855 
01856         public function newSyntaxError($m)
01857         {
01858                 return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
01859         }
01860 }
01861 
01862 class JSToken
01863 {
01864         public $type;
01865         public $value;
01866         public $start;
01867         public $end;
01868         public $lineno;
01869         public $assignOp;
01870 }
01871 
01872 ?>
 All Data Structures Namespaces Files Functions Variables Enumerations