[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/libs/ -> jsminplus.php (source)

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


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1