MODX Revolution PHP Cross Reference Content Management Systems

Source: /manager/min/lib/JSMinPlus.php - 1872 lines - 47248 bytes - Summary - Text - Print

Description: JSMinPlus version 1.1

   1  <?php
   2  
   3  /**
   4   * JSMinPlus version 1.1
   5   *
   6   * Minifies a javascript file using a javascript parser
   7   *
   8   * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript)
   9   * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine)
  10   * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/
  11   * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716
  12   *
  13   * Tino Zijdel <crisp@tweakers.net>
  14   *
  15   * Usage: $minified = JSMinPlus::minify($script [, $filename])
  16   *
  17   * Versionlog (see also changelog.txt):
  18   * 12-04-2009 - some small bugfixes and performance improvements
  19   * 09-04-2009 - initial open sourced version 1.0
  20   *
  21   * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip
  22   *
  23   */
  24  
  25  /* ***** BEGIN LICENSE BLOCK *****
  26   * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  27   *
  28   * The contents of this file are subject to the Mozilla Public License Version
  29   * 1.1 (the "License"); you may not use this file except in compliance with
  30   * the License. You may obtain a copy of the License at
  31   * http://www.mozilla.org/MPL/
  32   *
  33   * Software distributed under the License is distributed on an "AS IS" basis,
  34   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  35   * for the specific language governing rights and limitations under the
  36   * License.
  37   *
  38   * The Original Code is the Narcissus JavaScript engine.
  39   *
  40   * The Initial Developer of the Original Code is
  41   * Brendan Eich <brendan@mozilla.org>.
  42   * Portions created by the Initial Developer are Copyright (C) 2004
  43   * the Initial Developer. All Rights Reserved.
  44   *
  45   * Contributor(s): Tino Zijdel <crisp@tweakers.net>
  46   * PHP port, modifications and minifier routine are (C) 2009
  47   *
  48   * Alternatively, the contents of this file may be used under the terms of
  49   * either the GNU General Public License Version 2 or later (the "GPL"), or
  50   * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  51   * in which case the provisions of the GPL or the LGPL are applicable instead
  52   * of those above. If you wish to allow use of your version of this file only
  53   * under the terms of either the GPL or the LGPL, and not to allow others to
  54   * use your version of this file under the terms of the MPL, indicate your
  55   * decision by deleting the provisions above and replace them with the notice
  56   * and other provisions required by the GPL or the LGPL. If you do not delete
  57   * the provisions above, a recipient may use your version of this file under
  58   * the terms of any one of the MPL, the GPL or the LGPL.
  59   *
  60   * ***** END LICENSE BLOCK ***** */
  61  
  62  define('TOKEN_END', 1);
  63  define('TOKEN_NUMBER', 2);
  64  define('TOKEN_IDENTIFIER', 3);
  65  define('TOKEN_STRING', 4);
  66  define('TOKEN_REGEXP', 5);
  67  define('TOKEN_NEWLINE', 6);
  68  define('TOKEN_CONDCOMMENT_MULTILINE', 7);
  69  
  70  define('JS_SCRIPT', 100);
  71  define('JS_BLOCK', 101);
  72  define('JS_LABEL', 102);
  73  define('JS_FOR_IN', 103);
  74  define('JS_CALL', 104);
  75  define('JS_NEW_WITH_ARGS', 105);
  76  define('JS_INDEX', 106);
  77  define('JS_ARRAY_INIT', 107);
  78  define('JS_OBJECT_INIT', 108);
  79  define('JS_PROPERTY_INIT', 109);
  80  define('JS_GETTER', 110);
  81  define('JS_SETTER', 111);
  82  define('JS_GROUP', 112);
  83  define('JS_LIST', 113);
  84  
  85  define('DECLARED_FORM', 0);
  86  define('EXPRESSED_FORM', 1);
  87  define('STATEMENT_FORM', 2);
  88  
  89  class JSMinPlus
  90  {
  91      private $parser;
  92      private $reserved = array(
  93          'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
  94          'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
  95          'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
  96          'void', 'while', 'with',
  97          // Words reserved for future use
  98          'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
  99          'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
 100          'implements', 'import', 'int', 'interface', 'long', 'native',
 101          'package', 'private', 'protected', 'public', 'short', 'static',
 102          'super', 'synchronized', 'throws', 'transient', 'volatile',
 103          // These are not reserved, but should be taken into account
 104          // in isValidIdentifier (See jslint source code)
 105          'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
 106      );
 107  
 108  	private function __construct()
 109      {
 110          $this->parser = new JSParser();
 111      }
 112  
 113  	public static function minify($js, $filename='')
 114      {
 115          static $instance;
 116  
 117          // this is a singleton
 118          if(!$instance)
 119              $instance = new JSMinPlus();
 120  
 121          return $instance->min($js, $filename);
 122      }
 123  
 124  	private function min($js, $filename)
 125      {
 126          try
 127          {
 128              $n = $this->parser->parse($js, $filename, 1);
 129              return $this->parseTree($n);
 130          }
 131          catch(Exception $e)
 132          {
 133              echo $e->getMessage() . "\n";
 134          }
 135  
 136          return false;
 137      }
 138  
 139  	private function parseTree($n, $noBlockGrouping = false)
 140      {
 141          $s = '';
 142  
 143          switch ($n->type)
 144          {
 145              case KEYWORD_FUNCTION:
 146                  $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
 147                  $params = $n->params;
 148                  for ($i = 0, $j = count($params); $i < $j; $i++)
 149                      $s .= ($i ? ',' : '') . $params[$i];
 150                  $s .= '){' . $this->parseTree($n->body, true) . '}';
 151              break;
 152  
 153              case JS_SCRIPT:
 154                  // we do nothing with funDecls or varDecls
 155                  $noBlockGrouping = true;
 156              // fall through
 157              case JS_BLOCK:
 158                  $childs = $n->treeNodes;
 159                  for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
 160                  {
 161                      $t = $this->parseTree($childs[$i]);
 162                      if (strlen($t))
 163                      {
 164                          if ($c)
 165                          {
 166                              if ($childs[$i]->type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
 167                                  $s .= "\n"; // put declared functions on a new line
 168                              else
 169                                  $s .= ';';
 170                          }
 171  
 172                          $s .= $t;
 173  
 174                          $c++;
 175                      }
 176                  }
 177  
 178                  if ($c > 1 && !$noBlockGrouping)
 179                  {
 180                      $s = '{' . $s . '}';
 181                  }
 182              break;
 183  
 184              case KEYWORD_IF:
 185                  $s = 'if(' . $this->parseTree($n->condition) . ')';
 186                  $thenPart = $this->parseTree($n->thenPart);
 187                  $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
 188  
 189                  // quite a rancid hack to see if we should enclose the thenpart in brackets
 190                  if ($thenPart[0] != '{')
 191                  {
 192                      if (strpos($thenPart, 'if(') !== false)
 193                          $thenPart = '{' . $thenPart . '}';
 194                      elseif ($elsePart)
 195                          $thenPart .= ';';
 196                  }
 197  
 198                  $s .= $thenPart;
 199  
 200                  if ($elsePart)
 201                  {
 202                      $s .= 'else';
 203  
 204                      if ($elsePart[0] != '{')
 205                          $s .= ' ';
 206  
 207                      $s .= $elsePart;
 208                  }
 209              break;
 210  
 211              case KEYWORD_SWITCH:
 212                  $s = 'switch(' . $this->parseTree($n->discriminant) . '){';
 213                  $cases = $n->cases;
 214                  for ($i = 0, $j = count($cases); $i < $j; $i++)
 215                  {
 216                      $case = $cases[$i];
 217                      if ($case->type == KEYWORD_CASE)
 218                          $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':';
 219                      else
 220                          $s .= 'default:';
 221  
 222                      $statement = $this->parseTree($case->statements);
 223                      if ($statement)
 224                          $s .= $statement . ';';
 225                  }
 226                  $s = rtrim($s, ';') . '}';
 227              break;
 228  
 229              case KEYWORD_FOR:
 230                  $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
 231                      . ';' . ($n->condition ? $this->parseTree($n->condition) : '')
 232                      . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')'
 233                      . $this->parseTree($n->body);
 234              break;
 235  
 236              case KEYWORD_WHILE:
 237                  $s = 'while(' . $this->parseTree($n->condition) . ')' . $this->parseTree($n->body);
 238              break;
 239  
 240              case JS_FOR_IN:
 241                  $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
 242              break;
 243  
 244              case KEYWORD_DO:
 245                  $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')';
 246              break;
 247  
 248              case KEYWORD_BREAK:
 249              case KEYWORD_CONTINUE:
 250                  $s = $n->value . ($n->label ? ' ' . $n->label : '');
 251              break;
 252  
 253              case KEYWORD_TRY:
 254                  $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}';
 255                  $catchClauses = $n->catchClauses;
 256                  for ($i = 0, $j = count($catchClauses); $i < $j; $i++)
 257                  {
 258                      $t = $catchClauses[$i];
 259                      $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}';
 260                  }
 261                  if ($n->finallyBlock)
 262                      $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}';
 263              break;
 264  
 265              case KEYWORD_THROW:
 266                  $s = 'throw ' . $this->parseTree($n->exception);
 267              break;
 268  
 269              case KEYWORD_RETURN:
 270                  $s = 'return' . ($n->value ? ' ' . $this->parseTree($n->value) : '');
 271              break;
 272  
 273              case KEYWORD_WITH:
 274                  $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
 275              break;
 276  
 277              case KEYWORD_VAR:
 278              case KEYWORD_CONST:
 279                  $s = $n->value . ' ';
 280                  $childs = $n->treeNodes;
 281                  for ($i = 0, $j = count($childs); $i < $j; $i++)
 282                  {
 283                      $t = $childs[$i];
 284                      $s .= ($i ? ',' : '') . $t->name;
 285                      $u = $t->initializer;
 286                      if ($u)
 287                          $s .= '=' . $this->parseTree($u);
 288                  }
 289              break;
 290  
 291              case KEYWORD_DEBUGGER:
 292                  throw new Exception('NOT IMPLEMENTED: DEBUGGER');
 293              break;
 294  
 295              case TOKEN_CONDCOMMENT_MULTILINE:
 296                  $s = $n->value . ' ';
 297                  $childs = $n->treeNodes;
 298                  for ($i = 0, $j = count($childs); $i < $j; $i++)
 299                      $s .= $this->parseTree($childs[$i]);
 300              break;
 301  
 302              case OP_SEMICOLON:
 303                  if ($expression = $n->expression)
 304                      $s = $this->parseTree($expression);
 305              break;
 306  
 307              case JS_LABEL:
 308                  $s = $n->label . ':' . $this->parseTree($n->statement);
 309              break;
 310  
 311              case OP_COMMA:
 312                  $childs = $n->treeNodes;
 313                  for ($i = 0, $j = count($childs); $i < $j; $i++)
 314                      $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
 315              break;
 316  
 317              case OP_ASSIGN:
 318                  $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]);
 319              break;
 320  
 321              case OP_HOOK:
 322                  $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]);
 323              break;
 324  
 325              case OP_OR: case OP_AND:
 326              case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND:
 327              case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
 328              case OP_LT: case OP_LE: case OP_GE: case OP_GT:
 329              case OP_LSH: case OP_RSH: case OP_URSH:
 330              case OP_MUL: case OP_DIV: case OP_MOD:
 331                  $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]);
 332              break;
 333  
 334              case OP_PLUS:
 335              case OP_MINUS:
 336                  $s = $this->parseTree($n->treeNodes[0]) . $n->type;
 337                  $nextTokenType = $n->treeNodes[1]->type;
 338                  if (    $nextTokenType == OP_PLUS || $nextTokenType == OP_MINUS ||
 339                      $nextTokenType == OP_INCREMENT || $nextTokenType == OP_DECREMENT ||
 340                      $nextTokenType == OP_UNARY_PLUS || $nextTokenType == OP_UNARY_MINUS
 341                  )
 342                      $s .= ' ';
 343                  $s .= $this->parseTree($n->treeNodes[1]);
 344              break;
 345  
 346              case KEYWORD_IN:
 347                  $s = $this->parseTree($n->treeNodes[0]) . ' in ' . $this->parseTree($n->treeNodes[1]);
 348              break;
 349  
 350              case KEYWORD_INSTANCEOF:
 351                  $s = $this->parseTree($n->treeNodes[0]) . ' instanceof ' . $this->parseTree($n->treeNodes[1]);
 352              break;
 353  
 354              case KEYWORD_DELETE:
 355                  $s = 'delete ' . $this->parseTree($n->treeNodes[0]);
 356              break;
 357  
 358              case KEYWORD_VOID:
 359                  $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
 360              break;
 361  
 362              case KEYWORD_TYPEOF:
 363                  $s = 'typeof ' . $this->parseTree($n->treeNodes[0]);
 364              break;
 365  
 366              case OP_NOT:
 367              case OP_BITWISE_NOT:
 368              case OP_UNARY_PLUS:
 369              case OP_UNARY_MINUS:
 370                  $s = $n->value . $this->parseTree($n->treeNodes[0]);
 371              break;
 372  
 373              case OP_INCREMENT:
 374              case OP_DECREMENT:
 375                  if ($n->postfix)
 376                      $s = $this->parseTree($n->treeNodes[0]) . $n->value;
 377                  else
 378                      $s = $n->value . $this->parseTree($n->treeNodes[0]);
 379              break;
 380  
 381              case OP_DOT:
 382                  $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]);
 383              break;
 384  
 385              case JS_INDEX:
 386                  $s = $this->parseTree($n->treeNodes[0]);
 387                  // See if we can replace named index with a dot saving 3 bytes
 388                  if (    $n->treeNodes[0]->type == TOKEN_IDENTIFIER &&
 389                      $n->treeNodes[1]->type == TOKEN_STRING &&
 390                      $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))
 391                  )
 392                      $s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
 393                  else
 394                      $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']';
 395              break;
 396  
 397              case JS_LIST:
 398                  $childs = $n->treeNodes;
 399                  for ($i = 0, $j = count($childs); $i < $j; $i++)
 400                      $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
 401              break;
 402  
 403              case JS_CALL:
 404                  $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
 405              break;
 406  
 407              case KEYWORD_NEW:
 408              case JS_NEW_WITH_ARGS:
 409                  $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')';
 410              break;
 411  
 412              case JS_ARRAY_INIT:
 413                  $s = '[';
 414                  $childs = $n->treeNodes;
 415                  for ($i = 0, $j = count($childs); $i < $j; $i++)
 416                  {
 417                      $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
 418                  }
 419                  $s .= ']';
 420              break;
 421  
 422              case JS_OBJECT_INIT:
 423                  $s = '{';
 424                  $childs = $n->treeNodes;
 425                  for ($i = 0, $j = count($childs); $i < $j; $i++)
 426                  {
 427                      $t = $childs[$i];
 428                      if ($i)
 429                          $s .= ',';
 430                      if ($t->type == JS_PROPERTY_INIT)
 431                      {
 432                          // Ditch the quotes when the index is a valid identifier
 433                          if (    $t->treeNodes[0]->type == TOKEN_STRING &&
 434                              $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))
 435                          )
 436                              $s .= substr($t->treeNodes[0]->value, 1, -1);
 437                          else
 438                              $s .= $t->treeNodes[0]->value;
 439  
 440                          $s .= ':' . $this->parseTree($t->treeNodes[1]);
 441                      }
 442                      else
 443                      {
 444                          $s .= $t->type == JS_GETTER ? 'get' : 'set';
 445                          $s .= ' ' . $t->name . '(';
 446                          $params = $t->params;
 447                          for ($i = 0, $j = count($params); $i < $j; $i++)
 448                              $s .= ($i ? ',' : '') . $params[$i];
 449                          $s .= '){' . $this->parseTree($t->body, true) . '}';
 450                      }
 451                  }
 452                  $s .= '}';
 453              break;
 454  
 455              case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
 456              case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
 457                  $s = $n->value;
 458              break;
 459  
 460              case JS_GROUP:
 461                  $s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
 462              break;
 463  
 464              default:
 465                  throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
 466          }
 467  
 468          return $s;
 469      }
 470  
 471  	private function isValidIdentifier($string)
 472      {
 473          return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
 474      }
 475  }
 476  
 477  class JSParser
 478  {
 479      private $t;
 480  
 481      private $opPrecedence = array(
 482          ';' => 0,
 483          ',' => 1,
 484          '=' => 2, '?' => 2, ':' => 2,
 485          // The above all have to have the same precedence, see bug 330975.
 486          '||' => 4,
 487          '&&' => 5,
 488          '|' => 6,
 489          '^' => 7,
 490          '&' => 8,
 491          '==' => 9, '!=' => 9, '===' => 9, '!==' => 9,
 492          '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,
 493          '<<' => 11, '>>' => 11, '>>>' => 11,
 494          '+' => 12, '-' => 12,
 495          '*' => 13, '/' => 13, '%' => 13,
 496          'delete' => 14, 'void' => 14, 'typeof' => 14,
 497          '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,
 498          '++' => 15, '--' => 15,
 499          'new' => 16,
 500          '.' => 17,
 501          JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0,
 502          JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0
 503      );
 504  
 505      private $opArity = array(
 506          ',' => -2,
 507          '=' => 2,
 508          '?' => 3,
 509          '||' => 2,
 510          '&&' => 2,
 511          '|' => 2,
 512          '^' => 2,
 513          '&' => 2,
 514          '==' => 2, '!=' => 2, '===' => 2, '!==' => 2,
 515          '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,
 516          '<<' => 2, '>>' => 2, '>>>' => 2,
 517          '+' => 2, '-' => 2,
 518          '*' => 2, '/' => 2, '%' => 2,
 519          'delete' => 1, 'void' => 1, 'typeof' => 1,
 520          '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,
 521          '++' => 1, '--' => 1,
 522          'new' => 1,
 523          '.' => 2,
 524          JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,
 525          JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,
 526          TOKEN_CONDCOMMENT_MULTILINE => 1
 527      );
 528  
 529  	public function __construct()
 530      {
 531          $this->t = new JSTokenizer();
 532      }
 533  
 534  	public function parse($s, $f, $l)
 535      {
 536          // initialize tokenizer
 537          $this->t->init($s, $f, $l);
 538  
 539          $x = new JSCompilerContext(false);
 540          $n = $this->Script($x);
 541          if (!$this->t->isDone())
 542              throw $this->t->newSyntaxError('Syntax error');
 543  
 544          return $n;
 545      }
 546  
 547  	private function Script($x)
 548      {
 549          $n = $this->Statements($x);
 550          $n->type = JS_SCRIPT;
 551          $n->funDecls = $x->funDecls;
 552          $n->varDecls = $x->varDecls;
 553  
 554          return $n;
 555      }
 556  
 557  	private function Statements($x)
 558      {
 559          $n = new JSNode($this->t, JS_BLOCK);
 560          array_push($x->stmtStack, $n);
 561  
 562          while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY)
 563              $n->addNode($this->Statement($x));
 564  
 565          array_pop($x->stmtStack);
 566  
 567          return $n;
 568      }
 569  
 570  	private function Block($x)
 571      {
 572          $this->t->mustMatch(OP_LEFT_CURLY);
 573          $n = $this->Statements($x);
 574          $this->t->mustMatch(OP_RIGHT_CURLY);
 575  
 576          return $n;
 577      }
 578  
 579  	private function Statement($x)
 580      {
 581          $tt = $this->t->get();
 582          $n2 = null;
 583  
 584          // Cases for statements ending in a right curly return early, avoiding the
 585          // common semicolon insertion magic after this switch.
 586          switch ($tt)
 587          {
 588              case KEYWORD_FUNCTION:
 589                  return $this->FunctionDefinition(
 590                      $x,
 591                      true,
 592                      count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM
 593                  );
 594              break;
 595  
 596              case OP_LEFT_CURLY:
 597                  $n = $this->Statements($x);
 598                  $this->t->mustMatch(OP_RIGHT_CURLY);
 599              return $n;
 600  
 601              case KEYWORD_IF:
 602                  $n = new JSNode($this->t);
 603                  $n->condition = $this->ParenExpression($x);
 604                  array_push($x->stmtStack, $n);
 605                  $n->thenPart = $this->Statement($x);
 606                  $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null;
 607                  array_pop($x->stmtStack);
 608              return $n;
 609  
 610              case KEYWORD_SWITCH:
 611                  $n = new JSNode($this->t);
 612                  $this->t->mustMatch(OP_LEFT_PAREN);
 613                  $n->discriminant = $this->Expression($x);
 614                  $this->t->mustMatch(OP_RIGHT_PAREN);
 615                  $n->cases = array();
 616                  $n->defaultIndex = -1;
 617  
 618                  array_push($x->stmtStack, $n);
 619  
 620                  $this->t->mustMatch(OP_LEFT_CURLY);
 621  
 622                  while (($tt = $this->t->get()) != OP_RIGHT_CURLY)
 623                  {
 624                      switch ($tt)
 625                      {
 626                          case KEYWORD_DEFAULT:
 627                              if ($n->defaultIndex >= 0)
 628                                  throw $this->t->newSyntaxError('More than one switch default');
 629                              // FALL THROUGH
 630                          case KEYWORD_CASE:
 631                              $n2 = new JSNode($this->t);
 632                              if ($tt == KEYWORD_DEFAULT)
 633                                  $n->defaultIndex = count($n->cases);
 634                              else
 635                                  $n2->caseLabel = $this->Expression($x, OP_COLON);
 636                                  break;
 637                          default:
 638                              throw $this->t->newSyntaxError('Invalid switch case');
 639                      }
 640  
 641                      $this->t->mustMatch(OP_COLON);
 642                      $n2->statements = new JSNode($this->t, JS_BLOCK);
 643                      while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY)
 644                          $n2->statements->addNode($this->Statement($x));
 645  
 646                      array_push($n->cases, $n2);
 647                  }
 648  
 649                  array_pop($x->stmtStack);
 650              return $n;
 651  
 652              case KEYWORD_FOR:
 653                  $n = new JSNode($this->t);
 654                  $n->isLoop = true;
 655                  $this->t->mustMatch(OP_LEFT_PAREN);
 656  
 657                  if (($tt = $this->t->peek()) != OP_SEMICOLON)
 658                  {
 659                      $x->inForLoopInit = true;
 660                      if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST)
 661                      {
 662                          $this->t->get();
 663                          $n2 = $this->Variables($x);
 664                      }
 665                      else
 666                      {
 667                          $n2 = $this->Expression($x);
 668                      }
 669                      $x->inForLoopInit = false;
 670                  }
 671  
 672                  if ($n2 && $this->t->match(KEYWORD_IN))
 673                  {
 674                      $n->type = JS_FOR_IN;
 675                      if ($n2->type == KEYWORD_VAR)
 676                      {
 677                          if (count($n2->treeNodes) != 1)
 678                          {
 679                              throw $this->t->SyntaxError(
 680                                  'Invalid for..in left-hand side',
 681                                  $this->t->filename,
 682                                  $n2->lineno
 683                              );
 684                          }
 685  
 686                          // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
 687                          $n->iterator = $n2->treeNodes[0];
 688                          $n->varDecl = $n2;
 689                      }
 690                      else
 691                      {
 692                          $n->iterator = $n2;
 693                          $n->varDecl = null;
 694                      }
 695  
 696                      $n->object = $this->Expression($x);
 697                  }
 698                  else
 699                  {
 700                      $n->setup = $n2 ? $n2 : null;
 701                      $this->t->mustMatch(OP_SEMICOLON);
 702                      $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x);
 703                      $this->t->mustMatch(OP_SEMICOLON);
 704                      $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x);
 705                  }
 706  
 707                  $this->t->mustMatch(OP_RIGHT_PAREN);
 708                  $n->body = $this->nest($x, $n);
 709              return $n;
 710  
 711              case KEYWORD_WHILE:
 712                      $n = new JSNode($this->t);
 713                      $n->isLoop = true;
 714                      $n->condition = $this->ParenExpression($x);
 715                      $n->body = $this->nest($x, $n);
 716              return $n;
 717  
 718              case KEYWORD_DO:
 719                  $n = new JSNode($this->t);
 720                  $n->isLoop = true;
 721                  $n->body = $this->nest($x, $n, KEYWORD_WHILE);
 722                  $n->condition = $this->ParenExpression($x);
 723                  if (!$x->ecmaStrictMode)
 724                  {
 725                      // <script language="JavaScript"> (without version hints) may need
 726                      // automatic semicolon insertion without a newline after do-while.
 727                      // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
 728                      $this->t->match(OP_SEMICOLON);
 729                      return $n;
 730                  }
 731              break;
 732  
 733              case KEYWORD_BREAK:
 734              case KEYWORD_CONTINUE:
 735                  $n = new JSNode($this->t);
 736  
 737                  if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER)
 738                  {
 739                      $this->t->get();
 740                      $n->label = $this->t->currentToken()->value;
 741                  }
 742  
 743                  $ss = $x->stmtStack;
 744                  $i = count($ss);
 745                  $label = $n->label;
 746                  if ($label)
 747                  {
 748                      do
 749                      {
 750                          if (--$i < 0)
 751                              throw $this->t->newSyntaxError('Label not found');
 752                      }
 753                      while ($ss[$i]->label != $label);
 754                  }
 755                  else
 756                  {
 757                      do
 758                      {
 759                          if (--$i < 0)
 760                              throw $this->t->newSyntaxError('Invalid ' . $tt);
 761                      }
 762                      while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH));
 763                  }
 764  
 765                  $n->target = $ss[$i];
 766              break;
 767  
 768              case KEYWORD_TRY:
 769                  $n = new JSNode($this->t);
 770                  $n->tryBlock = $this->Block($x);
 771                  $n->catchClauses = array();
 772  
 773                  while ($this->t->match(KEYWORD_CATCH))
 774                  {
 775                      $n2 = new JSNode($this->t);
 776                      $this->t->mustMatch(OP_LEFT_PAREN);
 777                      $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
 778  
 779                      if ($this->t->match(KEYWORD_IF))
 780                      {
 781                          if ($x->ecmaStrictMode)
 782                              throw $this->t->newSyntaxError('Illegal catch guard');
 783  
 784                          if (count($n->catchClauses) && !end($n->catchClauses)->guard)
 785                              throw $this->t->newSyntaxError('Guarded catch after unguarded');
 786  
 787                          $n2->guard = $this->Expression($x);
 788                      }
 789                      else
 790                      {
 791                          $n2->guard = null;
 792                      }
 793  
 794                      $this->t->mustMatch(OP_RIGHT_PAREN);
 795                      $n2->block = $this->Block($x);
 796                      array_push($n->catchClauses, $n2);
 797                  }
 798  
 799                  if ($this->t->match(KEYWORD_FINALLY))
 800                      $n->finallyBlock = $this->Block($x);
 801  
 802                  if (!count($n->catchClauses) && !$n->finallyBlock)
 803                      throw $this->t->newSyntaxError('Invalid try statement');
 804              return $n;
 805  
 806              case KEYWORD_CATCH:
 807              case KEYWORD_FINALLY:
 808                  throw $this->t->newSyntaxError($tt + ' without preceding try');
 809  
 810              case KEYWORD_THROW:
 811                  $n = new JSNode($this->t);
 812                  $n->exception = $this->Expression($x);
 813              break;
 814  
 815              case KEYWORD_RETURN:
 816                  if (!$x->inFunction)
 817                      throw $this->t->newSyntaxError('Invalid return');
 818  
 819                  $n = new JSNode($this->t);
 820                  $tt = $this->t->peekOnSameLine();
 821                  if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
 822                      $n->value = $this->Expression($x);
 823                  else
 824                      $n->value = null;
 825              break;
 826  
 827              case KEYWORD_WITH:
 828                  $n = new JSNode($this->t);
 829                  $n->object = $this->ParenExpression($x);
 830                  $n->body = $this->nest($x, $n);
 831              return $n;
 832  
 833              case KEYWORD_VAR:
 834              case KEYWORD_CONST:
 835                      $n = $this->Variables($x);
 836              break;
 837  
 838              case TOKEN_CONDCOMMENT_MULTILINE:
 839                  $n = new JSNode($this->t);
 840              return $n;
 841  
 842              case KEYWORD_DEBUGGER:
 843                  $n = new JSNode($this->t);
 844              break;
 845  
 846              case TOKEN_NEWLINE:
 847              case OP_SEMICOLON:
 848                  $n = new JSNode($this->t, OP_SEMICOLON);
 849                  $n->expression = null;
 850              return $n;
 851  
 852              default:
 853                  if ($tt == TOKEN_IDENTIFIER)
 854                  {
 855                      $this->t->scanOperand = false;
 856                      $tt = $this->t->peek();
 857                      $this->t->scanOperand = true;
 858                      if ($tt == OP_COLON)
 859                      {
 860                          $label = $this->t->currentToken()->value;
 861                          $ss = $x->stmtStack;
 862                          for ($i = count($ss) - 1; $i >= 0; --$i)
 863                          {
 864                              if ($ss[$i]->label == $label)
 865                                  throw $this->t->newSyntaxError('Duplicate label');
 866                          }
 867  
 868                          $this->t->get();
 869                          $n = new JSNode($this->t, JS_LABEL);
 870                          $n->label = $label;
 871                          $n->statement = $this->nest($x, $n);
 872  
 873                          return $n;
 874                      }
 875                  }
 876  
 877                  $n = new JSNode($this->t, OP_SEMICOLON);
 878                  $this->t->unget();
 879                  $n->expression = $this->Expression($x);
 880                  $n->end = $n->expression->end;
 881              break;
 882          }
 883  
 884          if ($this->t->lineno == $this->t->currentToken()->lineno)
 885          {
 886              $tt = $this->t->peekOnSameLine();
 887              if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
 888                  throw $this->t->newSyntaxError('Missing ; before statement');
 889          }
 890  
 891          $this->t->match(OP_SEMICOLON);
 892  
 893          return $n;
 894      }
 895  
 896  	private function FunctionDefinition($x, $requireName, $functionForm)
 897      {
 898          $f = new JSNode($this->t);
 899  
 900          if ($f->type != KEYWORD_FUNCTION)
 901              $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
 902  
 903          if ($this->t->match(TOKEN_IDENTIFIER))
 904              $f->name = $this->t->currentToken()->value;
 905          elseif ($requireName)
 906              throw $this->t->newSyntaxError('Missing function identifier');
 907  
 908          $this->t->mustMatch(OP_LEFT_PAREN);
 909              $f->params = array();
 910  
 911          while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
 912          {
 913              if ($tt != TOKEN_IDENTIFIER)
 914                  throw $this->t->newSyntaxError('Missing formal parameter');
 915  
 916              array_push($f->params, $this->t->currentToken()->value);
 917  
 918              if ($this->t->peek() != OP_RIGHT_PAREN)
 919                  $this->t->mustMatch(OP_COMMA);
 920          }
 921  
 922          $this->t->mustMatch(OP_LEFT_CURLY);
 923  
 924          $x2 = new JSCompilerContext(true);
 925          $f->body = $this->Script($x2);
 926  
 927          $this->t->mustMatch(OP_RIGHT_CURLY);
 928          $f->end = $this->t->currentToken()->end;
 929  
 930          $f->functionForm = $functionForm;
 931          if ($functionForm == DECLARED_FORM)
 932              array_push($x->funDecls, $f);
 933  
 934          return $f;
 935      }
 936  
 937  	private function Variables($x)
 938      {
 939          $n = new JSNode($this->t);
 940  
 941          do
 942          {
 943              $this->t->mustMatch(TOKEN_IDENTIFIER);
 944  
 945              $n2 = new JSNode($this->t);
 946              $n2->name = $n2->value;
 947  
 948              if ($this->t->match(OP_ASSIGN))
 949              {
 950                  if ($this->t->currentToken()->assignOp)
 951                      throw $this->t->newSyntaxError('Invalid variable initialization');
 952  
 953                  $n2->initializer = $this->Expression($x, OP_COMMA);
 954              }
 955  
 956              $n2->readOnly = $n->type == KEYWORD_CONST;
 957  
 958              $n->addNode($n2);
 959              array_push($x->varDecls, $n2);
 960          }
 961          while ($this->t->match(OP_COMMA));
 962  
 963          return $n;
 964      }
 965  
 966  	private function Expression($x, $stop=false)
 967      {
 968          $operators = array();
 969          $operands = array();
 970          $n = false;
 971  
 972          $bl = $x->bracketLevel;
 973          $cl = $x->curlyLevel;
 974          $pl = $x->parenLevel;
 975          $hl = $x->hookLevel;
 976  
 977          while (($tt = $this->t->get()) != TOKEN_END)
 978          {
 979              if ($tt == $stop &&
 980                  $x->bracketLevel == $bl &&
 981                  $x->curlyLevel == $cl &&
 982                  $x->parenLevel == $pl &&
 983                  $x->hookLevel == $hl
 984              )
 985              {
 986                  // Stop only if tt matches the optional stop parameter, and that
 987                  // token is not quoted by some kind of bracket.
 988                  break;
 989              }
 990  
 991              switch ($tt)
 992              {
 993                  case OP_SEMICOLON:
 994                      // NB: cannot be empty, Statement handled that.
 995                      break 2;
 996  
 997                  case OP_ASSIGN:
 998                  case OP_HOOK:
 999                  case OP_COLON:
1000                      if ($this->t->scanOperand)
1001                          break 2;
1002  
1003                      // Use >, not >=, for right-associative ASSIGN and HOOK/COLON.
1004                      while (    !empty($operators) &&
1005                          (    $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] ||
1006                              ($tt == OP_COLON && end($operators)->type == OP_ASSIGN)
1007                          )
1008                      )
1009                          $this->reduce($operators, $operands);
1010  
1011                      if ($tt == OP_COLON)
1012                      {
1013                          $n = end($operators);
1014                          if ($n->type != OP_HOOK)
1015                              throw $this->t->newSyntaxError('Invalid label');
1016  
1017                          --$x->hookLevel;
1018                      }
1019                      else
1020                      {
1021                          array_push($operators, new JSNode($this->t));
1022                          if ($tt == OP_ASSIGN)
1023                              end($operands)->assignOp = $this->t->currentToken()->assignOp;
1024                          else
1025                              ++$x->hookLevel;
1026                      }
1027  
1028                      $this->t->scanOperand = true;
1029                  break;
1030  
1031                  case KEYWORD_IN:
1032                      // An in operator should not be parsed if we're parsing the head of
1033                      // a for (...) loop, unless it is in the then part of a conditional
1034                      // expression, or parenthesized somehow.
1035                      if ($x->inForLoopInit && !$x->hookLevel &&
1036                          !$x->bracketLevel && !$x->curlyLevel &&
1037                          !$x->parenLevel
1038                      )
1039                      {
1040                          break 2;
1041                      }
1042                  // FALL THROUGH
1043                  case OP_COMMA:
1044                      // Treat comma as left-associative so reduce can fold left-heavy
1045                      // COMMA trees into a single array.
1046                      // FALL THROUGH
1047                  case OP_OR:
1048                  case OP_AND:
1049                  case OP_BITWISE_OR:
1050                  case OP_BITWISE_XOR:
1051                  case OP_BITWISE_AND:
1052                  case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
1053                  case OP_LT: case OP_LE: case OP_GE: case OP_GT:
1054                  case KEYWORD_INSTANCEOF:
1055                  case OP_LSH: case OP_RSH: case OP_URSH:
1056                  case OP_PLUS: case OP_MINUS:
1057                  case OP_MUL: case OP_DIV: case OP_MOD:
1058                  case OP_DOT:
1059                      if ($this->t->scanOperand)
1060                          break 2;
1061  
1062                      while (    !empty($operators) &&
1063                          $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1064                      )
1065                          $this->reduce($operators, $operands);
1066  
1067                      if ($tt == OP_DOT)
1068                      {
1069                          $this->t->mustMatch(TOKEN_IDENTIFIER);
1070                          array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
1071                      }
1072                      else
1073                      {
1074                          array_push($operators, new JSNode($this->t));
1075                          $this->t->scanOperand = true;
1076                      }
1077                  break;
1078  
1079                  case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
1080                  case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1081                  case KEYWORD_NEW:
1082                      if (!$this->t->scanOperand)
1083                          break 2;
1084  
1085                      array_push($operators, new JSNode($this->t));
1086                  break;
1087  
1088                  case OP_INCREMENT: case OP_DECREMENT:
1089                      if ($this->t->scanOperand)
1090                      {
1091                          array_push($operators, new JSNode($this->t));  // prefix increment or decrement
1092                      }
1093                      else
1094                      {
1095                          // Don't cross a line boundary for postfix {in,de}crement.
1096                          $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
1097                          if ($t && $t->lineno != $this->t->lineno)
1098                              break 2;
1099  
1100                          if (!empty($operators))
1101                          {
1102                              // Use >, not >=, so postfix has higher precedence than prefix.
1103                              while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
1104                                  $this->reduce($operators, $operands);
1105                          }
1106  
1107                          $n = new JSNode($this->t, $tt, array_pop($operands));
1108                          $n->postfix = true;
1109                          array_push($operands, $n);
1110                      }
1111                  break;
1112  
1113                  case KEYWORD_FUNCTION:
1114                      if (!$this->t->scanOperand)
1115                          break 2;
1116  
1117                      array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
1118                      $this->t->scanOperand = false;
1119                  break;
1120  
1121                  case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
1122                  case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
1123                      if (!$this->t->scanOperand)
1124                          break 2;
1125  
1126                      array_push($operands, new JSNode($this->t));
1127                      $this->t->scanOperand = false;
1128                  break;
1129  
1130                  case TOKEN_CONDCOMMENT_MULTILINE:
1131                      if ($this->t->scanOperand)
1132                          array_push($operators, new JSNode($this->t));
1133                      else
1134                          array_push($operands, new JSNode($this->t));
1135                  break;
1136  
1137                  case OP_LEFT_BRACKET:
1138                      if ($this->t->scanOperand)
1139                      {
1140                          // Array initialiser.  Parse using recursive descent, as the
1141                          // sub-grammar here is not an operator grammar.
1142                          $n = new JSNode($this->t, JS_ARRAY_INIT);
1143                          while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
1144                          {
1145                              if ($tt == OP_COMMA)
1146                              {
1147                                  $this->t->get();
1148                                  $n->addNode(null);
1149                                  continue;
1150                              }
1151  
1152                              $n->addNode($this->Expression($x, OP_COMMA));
1153                              if (!$this->t->match(OP_COMMA))
1154                                  break;
1155                          }
1156  
1157                          $this->t->mustMatch(OP_RIGHT_BRACKET);
1158                          array_push($operands, $n);
1159                          $this->t->scanOperand = false;
1160                      }
1161                      else
1162                      {
1163                          // Property indexing operator.
1164                          array_push($operators, new JSNode($this->t, JS_INDEX));
1165                          $this->t->scanOperand = true;
1166                          ++$x->bracketLevel;
1167                      }
1168                  break;
1169  
1170                  case OP_RIGHT_BRACKET:
1171                      if ($this->t->scanOperand || $x->bracketLevel == $bl)
1172                          break 2;
1173  
1174                      while ($this->reduce($operators, $operands)->type != JS_INDEX)
1175                          continue;
1176  
1177                      --$x->bracketLevel;
1178                  break;
1179  
1180                  case OP_LEFT_CURLY:
1181                      if (!$this->t->scanOperand)
1182                          break 2;
1183  
1184                      // Object initialiser.  As for array initialisers (see above),
1185                      // parse using recursive descent.
1186                      ++$x->curlyLevel;
1187                      $n = new JSNode($this->t, JS_OBJECT_INIT);
1188                      while (!$this->t->match(OP_RIGHT_CURLY))
1189                      {
1190                          do
1191                          {
1192                              $tt = $this->t->get();
1193                              $tv = $this->t->currentToken()->value;
1194                              if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
1195                              {
1196                                  if ($x->ecmaStrictMode)
1197                                      throw $this->t->newSyntaxError('Illegal property accessor');
1198  
1199                                  $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
1200                              }
1201                              else
1202                              {
1203                                  switch ($tt)
1204                                  {
1205                                      case TOKEN_IDENTIFIER:
1206                                      case TOKEN_NUMBER:
1207                                      case TOKEN_STRING:
1208                                          $id = new JSNode($this->t);
1209                                      break;
1210  
1211                                      case OP_RIGHT_CURLY:
1212                                          if ($x->ecmaStrictMode)
1213                                              throw $this->t->newSyntaxError('Illegal trailing ,');
1214                                      break 3;
1215  
1216                                      default:
1217                                          throw $this->t->newSyntaxError('Invalid property name');
1218                                  }
1219  
1220                                  $this->t->mustMatch(OP_COLON);
1221                                  $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
1222                              }
1223                          }
1224                          while ($this->t->match(OP_COMMA));
1225  
1226                          $this->t->mustMatch(OP_RIGHT_CURLY);
1227                          break;
1228                      }
1229  
1230                      array_push($operands, $n);
1231                      $this->t->scanOperand = false;
1232                      --$x->curlyLevel;
1233                  break;
1234  
1235                  case OP_RIGHT_CURLY:
1236                      if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1237                          throw new Exception('PANIC: right curly botch');
1238                  break 2;
1239  
1240                  case OP_LEFT_PAREN:
1241                      if ($this->t->scanOperand)
1242                      {
1243                          array_push($operators, new JSNode($this->t, JS_GROUP));
1244                      }
1245                      else
1246                      {
1247                          while (    !empty($operators) &&
1248                              $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
1249                          )
1250                              $this->reduce($operators, $operands);
1251  
1252                          // Handle () now, to regularize the n-ary case for n > 0.
1253                          // We must set scanOperand in case there are arguments and
1254                          // the first one is a regexp or unary+/-.
1255                          $n = end($operators);
1256                          $this->t->scanOperand = true;
1257                          if ($this->t->match(OP_RIGHT_PAREN))
1258                          {
1259                              if ($n && $n->type == KEYWORD_NEW)
1260                              {
1261                                  array_pop($operators);
1262                                  $n->addNode(array_pop($operands));
1263                              }
1264                              else
1265                              {
1266                                  $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
1267                              }
1268  
1269                              array_push($operands, $n);
1270                              $this->t->scanOperand = false;
1271                              break;
1272                          }
1273  
1274                          if ($n && $n->type == KEYWORD_NEW)
1275                              $n->type = JS_NEW_WITH_ARGS;
1276                          else
1277                              array_push($operators, new JSNode($this->t, JS_CALL));
1278                      }
1279  
1280                      ++$x->parenLevel;
1281                  break;
1282  
1283                  case OP_RIGHT_PAREN:
1284                      if ($this->t->scanOperand || $x->parenLevel == $pl)
1285                          break 2;
1286  
1287                      while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
1288                          $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
1289                      )
1290                      {
1291                          continue;
1292                      }
1293  
1294                      if ($tt != JS_GROUP)
1295                      {
1296                          $n = end($operands);
1297                          if ($n->treeNodes[1]->type != OP_COMMA)
1298                              $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
1299                          else
1300                              $n->treeNodes[1]->type = JS_LIST;
1301                      }
1302  
1303                      --$x->parenLevel;
1304                  break;
1305  
1306                  // Automatic semicolon insertion means we may scan across a newline
1307                  // and into the beginning of another statement.  If so, break out of
1308                  // the while loop and let the t.scanOperand logic handle errors.
1309                  default:
1310                      break 2;
1311              }
1312          }
1313  
1314          if ($x->hookLevel != $hl)
1315              throw $this->t->newSyntaxError('Missing : after ?');
1316  
1317          if ($x->parenLevel != $pl)
1318              throw $this->t->newSyntaxError('Missing ) in parenthetical');
1319  
1320          if ($x->bracketLevel != $bl)
1321              throw $this->t->newSyntaxError('Missing ] in index expression');
1322  
1323          if ($this->t->scanOperand)
1324              throw $this->t->newSyntaxError('Missing operand');
1325  
1326          // Resume default mode, scanning for operands, not operators.
1327          $this->t->scanOperand = true;
1328          $this->t->unget();
1329  
1330          while (count($operators))
1331              $this->reduce($operators, $operands);
1332  
1333          return array_pop($operands);
1334      }
1335  
1336  	private function ParenExpression($x)
1337      {
1338          $this->t->mustMatch(OP_LEFT_PAREN);
1339          $n = $this->Expression($x);
1340          $this->t->mustMatch(OP_RIGHT_PAREN);
1341  
1342          return $n;
1343      }
1344  
1345      // Statement stack and nested statement handler.
1346  	private function nest($x, $node, $end = false)
1347      {
1348          array_push($x->stmtStack, $node);
1349          $n = $this->statement($x);
1350          array_pop($x->stmtStack);
1351  
1352          if ($end)
1353              $this->t->mustMatch($end);
1354  
1355          return $n;
1356      }
1357  
1358  	private function reduce(&$operators, &$operands)
1359      {
1360          $n = array_pop($operators);
1361          $op = $n->type;
1362          $arity = $this->opArity[$op];
1363          $c = count($operands);
1364          if ($arity == -2)
1365          {
1366              // Flatten left-associative trees
1367              if ($c >= 2)
1368              {
1369                  $left = $operands[$c - 2];
1370                  if ($left->type == $op)
1371                  {
1372                      $right = array_pop($operands);
1373                      $left->addNode($right);
1374                      return $left;
1375                  }
1376              }
1377              $arity = 2;
1378          }
1379  
1380          // Always use push to add operands to n, to update start and end
1381          $a = array_splice($operands, $c - $arity);
1382          for ($i = 0; $i < $arity; $i++)
1383              $n->addNode($a[$i]);
1384  
1385          // Include closing bracket or postfix operator in [start,end]
1386          $te = $this->t->currentToken()->end;
1387          if ($n->end < $te)
1388              $n->end = $te;
1389  
1390          array_push($operands, $n);
1391  
1392          return $n;
1393      }
1394  }
1395  
1396  class JSCompilerContext
1397  {
1398      public $inFunction = false;
1399      public $inForLoopInit = false;
1400      public $ecmaStrictMode = false;
1401      public $bracketLevel = 0;
1402      public $curlyLevel = 0;
1403      public $parenLevel = 0;
1404      public $hookLevel = 0;
1405  
1406      public $stmtStack = array();
1407      public $funDecls = array();
1408      public $varDecls = array();
1409  
1410  	public function __construct($inFunction)
1411      {
1412          $this->inFunction = $inFunction;
1413      }
1414  }
1415  
1416  class JSNode
1417  {
1418      private $type;
1419      private $value;
1420      private $lineno;
1421      private $start;
1422      private $end;
1423  
1424      public $treeNodes = array();
1425      public $funDecls = array();
1426      public $varDecls = array();
1427  
1428  	public function __construct($t, $type=0)
1429      {
1430          if ($token = $t->currentToken())
1431          {
1432              $this->type = $type ? $type : $token->type;
1433              $this->value = $token->value;
1434              $this->lineno = $token->lineno;
1435              $this->start = $token->start;
1436              $this->end = $token->end;
1437          }
1438          else
1439          {
1440              $this->type = $type;
1441              $this->lineno = $t->lineno;
1442          }
1443  
1444          if (($numargs = func_num_args()) > 2)
1445          {
1446              $args = func_get_args();;
1447              for ($i = 2; $i < $numargs; $i++)
1448                  $this->addNode($args[$i]);
1449          }
1450      }
1451  
1452      // we don't want to bloat our object with all kind of specific properties, so we use overloading
1453  	public function __set($name, $value)
1454      {
1455          $this->$name = $value;
1456      }
1457  
1458  	public function __get($name)
1459      {
1460          if (isset($this->$name))
1461              return $this->$name;
1462  
1463          return null;
1464      }
1465  
1466  	public function addNode($node)
1467      {
1468          $this->treeNodes[] = $node;
1469      }
1470  }
1471  
1472  class JSTokenizer
1473  {
1474      private $cursor = 0;
1475      private $source;
1476  
1477      public $tokens = array();
1478      public $tokenIndex = 0;
1479      public $lookahead = 0;
1480      public $scanNewlines = false;
1481      public $scanOperand = true;
1482  
1483      public $filename;
1484      public $lineno;
1485  
1486      private $keywords = array(
1487          'break',
1488          'case', 'catch', 'const', 'continue',
1489          'debugger', 'default', 'delete', 'do',
1490          'else', 'enum',
1491          'false', 'finally', 'for', 'function',
1492          'if', 'in', 'instanceof',
1493          'new', 'null',
1494          'return',
1495          'switch',
1496          'this', 'throw', 'true', 'try', 'typeof',
1497          'var', 'void',
1498          'while', 'with'
1499      );
1500  
1501      private $opTypeNames = array(
1502          ';'    => 'SEMICOLON',
1503          ','    => 'COMMA',
1504          '?'    => 'HOOK',
1505          ':'    => 'COLON',
1506          '||'    => 'OR',
1507          '&&'    => 'AND',
1508          '|'    => 'BITWISE_OR',
1509          '^'    => 'BITWISE_XOR',
1510          '&'    => 'BITWISE_AND',
1511          '==='    => 'STRICT_EQ',
1512          '=='    => 'EQ',
1513          '='    => 'ASSIGN',
1514          '!=='    => 'STRICT_NE',
1515          '!='    => 'NE',
1516          '<<'    => 'LSH',
1517          '<='    => 'LE',
1518          '<'    => 'LT',
1519          '>>>'    => 'URSH',
1520          '>>'    => 'RSH',
1521          '>='    => 'GE',
1522          '>'    => 'GT',
1523          '++'    => 'INCREMENT',
1524          '--'    => 'DECREMENT',
1525          '+'    => 'PLUS',
1526          '-'    => 'MINUS',
1527          '*'    => 'MUL',
1528          '/'    => 'DIV',
1529          '%'    => 'MOD',
1530          '!'    => 'NOT',
1531          '~'    => 'BITWISE_NOT',
1532          '.'    => 'DOT',
1533          '['    => 'LEFT_BRACKET',
1534          ']'    => 'RIGHT_BRACKET',
1535          '{'    => 'LEFT_CURLY',
1536          '}'    => 'RIGHT_CURLY',
1537          '('    => 'LEFT_PAREN',
1538          ')'    => 'RIGHT_PAREN',
1539          '@*/'    => 'CONDCOMMENT_END'
1540      );
1541  
1542      private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
1543      private $opRegExp;
1544  
1545  	public function __construct()
1546      {
1547          $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', array_keys($this->opTypeNames))) . ')#';
1548  
1549          // this is quite a hidden yet convenient place to create the defines for operators and keywords
1550          foreach ($this->opTypeNames as $operand => $name)
1551              define('OP_' . $name, $operand);
1552  
1553          define('OP_UNARY_PLUS', 'U+');
1554          define('OP_UNARY_MINUS', 'U-');
1555  
1556          foreach ($this->keywords as $keyword)
1557              define('KEYWORD_' . strtoupper($keyword), $keyword);
1558      }
1559  
1560  	public function init($source, $filename = '', $lineno = 1)
1561      {
1562          $this->source = $source;
1563          $this->filename = $filename ? $filename : '[inline]';
1564          $this->lineno = $lineno;
1565  
1566          $this->cursor = 0;
1567          $this->tokens = array();
1568          $this->tokenIndex = 0;
1569          $this->lookahead = 0;
1570          $this->scanNewlines = false;
1571          $this->scanOperand = true;
1572      }
1573  
1574  	public function getInput($chunksize)
1575      {
1576          if ($chunksize)
1577              return substr($this->source, $this->cursor, $chunksize);
1578  
1579          return substr($this->source, $this->cursor);
1580      }
1581  
1582  	public function isDone()
1583      {
1584          return $this->peek() == TOKEN_END;
1585      }
1586  
1587  	public function match($tt)
1588      {
1589          return $this->get() == $tt || $this->unget();
1590      }
1591  
1592  	public function mustMatch($tt)
1593      {
1594              if (!$this->match($tt))
1595              throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
1596  
1597          return $this->currentToken();
1598      }
1599  
1600  	public function peek()
1601      {
1602          if ($this->lookahead)
1603          {
1604              $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
1605              if ($this->scanNewlines && $next->lineno != $this->lineno)
1606                  $tt = TOKEN_NEWLINE;
1607              else
1608                  $tt = $next->type;
1609          }
1610          else
1611          {
1612              $tt = $this->get();
1613              $this->unget();
1614          }
1615  
1616          return $tt;
1617      }
1618  
1619  	public function peekOnSameLine()
1620      {
1621          $this->scanNewlines = true;
1622          $tt = $this->peek();
1623          $this->scanNewlines = false;
1624  
1625          return $tt;
1626      }
1627  
1628  	public function currentToken()
1629      {
1630          if (!empty($this->tokens))
1631              return $this->tokens[$this->tokenIndex];
1632      }
1633  
1634  	public function get($chunksize = 1000)
1635      {
1636          while($this->lookahead)
1637          {
1638              $this->lookahead--;
1639              $this->tokenIndex = ($this->tokenIndex + 1) & 3;
1640              $token = $this->tokens[$this->tokenIndex];
1641              if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
1642                  return $token->type;
1643          }
1644  
1645          $conditional_comment = false;
1646  
1647          // strip whitespace and comments
1648          while(true)
1649          {
1650              $input = $this->getInput($chunksize);
1651  
1652              // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
1653              $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
1654              if (preg_match($re, $input, $match))
1655              {
1656                  $spaces = $match[0];
1657                  $spacelen = strlen($spaces);
1658                  $this->cursor += $spacelen;
1659                  if (!$this->scanNewlines)
1660                      $this->lineno += substr_count($spaces, "\n");
1661  
1662                  if ($spacelen == $chunksize)
1663                      continue; // complete chunk contained whitespace
1664  
1665                  $input = $this->getInput($chunksize);
1666                  if ($input == '' || $input[0] != '/')
1667                      break;
1668              }
1669  
1670              // Comments
1671              if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?(?:.|\n)*?\*\/|\/.*)/', $input, $match))
1672              {
1673                  if (!$chunksize)
1674                      break;
1675  
1676                  // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
1677                  $chunksize = null;
1678                  continue;
1679              }
1680  
1681              // check if this is a conditional (JScript) comment
1682              if (!empty($match[1]))
1683              {
1684                  //$match[0] = '/*' . $match[1];
1685                  $conditional_comment = true;
1686                  break;
1687              }
1688              else
1689              {
1690                  $this->cursor += strlen($match[0]);
1691                  $this->lineno += substr_count($match[0], "\n");
1692              }
1693          }
1694  
1695          if ($input == '')
1696          {
1697              $tt = TOKEN_END;
1698              $match = array('');
1699          }
1700          elseif ($conditional_comment)
1701          {
1702              $tt = TOKEN_CONDCOMMENT_MULTILINE;
1703          }
1704          else
1705          {
1706              switch ($input[0])
1707              {
1708                  case '0': case '1': case '2': case '3': case '4':
1709                  case '5': case '6': case '7': case '8': case '9':
1710                      if (preg_match('/^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+/', $input, $match))
1711                      {
1712                          $tt = TOKEN_NUMBER;
1713                      }
1714                      elseif (preg_match('/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/', $input, $match))
1715                      {
1716                          // this should always match because of \d+
1717                          $tt = TOKEN_NUMBER;
1718                      }
1719                  break;
1720  
1721                  case '"':
1722                  case "'":
1723                      if (preg_match('/^"(?:\\\\(?:.|\r?\n)|[^\\\\"\r\n])*"|^\'(?:\\\\(?:.|\r?\n)|[^\\\\\'\r\n])*\'/', $input, $match))
1724                      {
1725                          $tt = TOKEN_STRING;
1726                      }
1727                      else
1728                      {
1729                          if ($chunksize)
1730                              return $this->get(null); // retry with a full chunk fetch
1731  
1732                          throw $this->newSyntaxError('Unterminated string literal');
1733                      }
1734                  break;
1735  
1736                  case '/':
1737                      if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
1738                      {
1739                          $tt = TOKEN_REGEXP;
1740                          break;
1741                      }
1742                  // fall through
1743  
1744                  case '|':
1745                  case '^':
1746                  case '&':
1747                  case '<':
1748                  case '>':
1749                  case '+':
1750                  case '-':
1751                  case '*':
1752                  case '%':
1753                  case '=':
1754                  case '!':
1755                      // should always match
1756                      preg_match($this->opRegExp, $input, $match);
1757                      $op = $match[0];
1758                      if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
1759                      {
1760                          $tt = OP_ASSIGN;
1761                          $match[0] .= '=';
1762                      }
1763                      else
1764                      {
1765                          $tt = $op;
1766                          if ($this->scanOperand)
1767                          {
1768                              if ($op == OP_PLUS)
1769                                  $tt = OP_UNARY_PLUS;
1770                              elseif ($op == OP_MINUS)
1771                                  $tt = OP_UNARY_MINUS;
1772                          }
1773                          $op = null;
1774                      }
1775                  break;
1776  
1777                  case '.':
1778                      if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
1779                      {
1780                          $tt = TOKEN_NUMBER;
1781                          break;
1782                      }
1783                  // fall through
1784  
1785                  case ';':
1786                  case ',':
1787                  case '?':
1788                  case ':':
1789                  case '~':
1790                  case '[':
1791                  case ']':
1792                  case '{':
1793                  case '}':
1794                  case '(':
1795                  case ')':
1796                      // these are all single
1797                      $match = array($input[0]);
1798                      $tt = $input[0];
1799                  break;
1800  
1801                  case '@':
1802                      throw $this->newSyntaxError('Illegal token');
1803                  break;
1804  
1805                  case "\n":
1806                      if ($this->scanNewlines)
1807                      {
1808                          $match = array("\n");
1809                          $tt = TOKEN_NEWLINE;
1810                      }
1811                      else
1812                          throw $this->newSyntaxError('Illegal token');
1813                  break;
1814  
1815                  default:
1816                      // FIXME: add support for unicode and unicode escape sequence \uHHHH
1817                      if (preg_match('/^[$\w]+/', $input, $match))
1818                      {
1819                          $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
1820                      }
1821                      else
1822                          throw $this->newSyntaxError('Illegal token');
1823              }
1824          }
1825  
1826          $this->tokenIndex = ($this->tokenIndex + 1) & 3;
1827  
1828          if (!isset($this->tokens[$this->tokenIndex]))
1829              $this->tokens[$this->tokenIndex] = new JSToken();
1830  
1831          $token = $this->tokens[$this->tokenIndex];
1832          $token->type = $tt;
1833  
1834          if ($tt == OP_ASSIGN)
1835              $token->assignOp = $op;
1836  
1837          $token->start = $this->cursor;
1838  
1839          $token->value = $match[0];
1840          $this->cursor += strlen($match[0]);
1841  
1842          $token->end = $this->cursor;
1843          $token->lineno = $this->lineno;
1844  
1845          return $tt;
1846      }
1847  
1848  	public function unget()
1849      {
1850          if (++$this->lookahead == 4)
1851              throw $this->newSyntaxError('PANIC: too much lookahead!');
1852  
1853          $this->tokenIndex = ($this->tokenIndex - 1) & 3;
1854      }
1855  
1856  	public function newSyntaxError($m)
1857      {
1858          return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
1859      }
1860  }
1861  
1862  class JSToken
1863  {
1864      public $type;
1865      public $value;
1866      public $start;
1867      public $end;
1868      public $lineno;
1869      public $assignOp;
1870  }
1871  
1872  ?>

title

Description

title

Description

title

Description

title

title

Body