JSParser

The W3 Total Cache JSParser class.

Defined (1)

The class is defined in the following location(s).

/lib/Minify/JSMinPlus.php  
  1. class JSParser 
  2. private $t; 
  3. private $minifier; 
  4.  
  5. private $opPrecedence = array( 
  6. ';' => 0,  
  7. ', ' => 1,  
  8. '=' => 2, '?' => 2, ':' => 2,  
  9. // The above all have to have the same precedence, see bug 330975 
  10. '||' => 4,  
  11. '&&' => 5,  
  12. '|' => 6,  
  13. '^' => 7,  
  14. '&' => 8,  
  15. '==' => 9, '!=' => 9, '===' => 9, '!==' => 9,  
  16. '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,  
  17. '<<' => 11, '>>' => 11, '>>>' => 11,  
  18. '+' => 12, '-' => 12,  
  19. '*' => 13, '/' => 13, '%' => 13,  
  20. 'delete' => 14, 'void' => 14, 'typeof' => 14,  
  21. '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,  
  22. '++' => 15, '--' => 15,  
  23. 'new' => 16,  
  24. '.' => 17,  
  25. JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0,  
  26. JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0 
  27. ); 
  28.  
  29. private $opArity = array( 
  30. ', ' => -2,  
  31. '=' => 2,  
  32. '?' => 3,  
  33. '||' => 2,  
  34. '&&' => 2,  
  35. '|' => 2,  
  36. '^' => 2,  
  37. '&' => 2,  
  38. '==' => 2, '!=' => 2, '===' => 2, '!==' => 2,  
  39. '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,  
  40. '<<' => 2, '>>' => 2, '>>>' => 2,  
  41. '+' => 2, '-' => 2,  
  42. '*' => 2, '/' => 2, '%' => 2,  
  43. 'delete' => 1, 'void' => 1, 'typeof' => 1,  
  44. '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,  
  45. '++' => 1, '--' => 1,  
  46. 'new' => 1,  
  47. '.' => 2,  
  48. JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,  
  49. JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,  
  50. TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1 
  51. ); 
  52.  
  53. public function __construct($minifier=null) 
  54. $this->minifier = $minifier; 
  55. $this->t = new JSTokenizer(); 
  56.  
  57. public function parse($s, $f, $l) 
  58. // initialize tokenizer 
  59. $this->t->init($s, $f, $l); 
  60.  
  61. $x = new JSCompilerContext(false); 
  62. $n = $this->Script($x); 
  63. if (!$this->t->isDone()) 
  64. throw $this->t->newSyntaxError('Syntax error'); 
  65.  
  66. return $n; 
  67.  
  68. private function Script($x) 
  69. $n = $this->Statements($x); 
  70. $n->type = JS_SCRIPT; 
  71. $n->funDecls = $x->funDecls; 
  72. $n->varDecls = $x->varDecls; 
  73.  
  74. // minify by scope 
  75. if ($this->minifier) 
  76. $n->value = $this->minifier->parseTree($n); 
  77.  
  78. // clear tree from node to save memory 
  79. $n->treeNodes = null; 
  80. $n->funDecls = null; 
  81. $n->varDecls = null; 
  82.  
  83. $n->type = JS_MINIFIED; 
  84.  
  85. return $n; 
  86.  
  87. private function Statements($x) 
  88. $n = new JSNode($this->t, JS_BLOCK); 
  89. array_push($x->stmtStack, $n); 
  90.  
  91. while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY) 
  92. $n->addNode($this->Statement($x)); 
  93.  
  94. array_pop($x->stmtStack); 
  95.  
  96. return $n; 
  97.  
  98. private function Block($x) 
  99. $this->t->mustMatch(OP_LEFT_CURLY); 
  100. $n = $this->Statements($x); 
  101. $this->t->mustMatch(OP_RIGHT_CURLY); 
  102.  
  103. return $n; 
  104.  
  105. private function Statement($x) 
  106. $tt = $this->t->get(); 
  107. $n2 = null; 
  108.  
  109. // Cases for statements ending in a right curly return early, avoiding the 
  110. // common semicolon insertion magic after this switch. 
  111. switch ($tt) 
  112. case KEYWORD_FUNCTION: 
  113. return $this->FunctionDefinition( 
  114. $x,  
  115. true,  
  116. count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM 
  117. ); 
  118. break; 
  119.  
  120. case OP_LEFT_CURLY: 
  121. $n = $this->Statements($x); 
  122. $this->t->mustMatch(OP_RIGHT_CURLY); 
  123. return $n; 
  124.  
  125. case KEYWORD_IF: 
  126. $n = new JSNode($this->t); 
  127. $n->condition = $this->ParenExpression($x); 
  128. array_push($x->stmtStack, $n); 
  129. $n->thenPart = $this->Statement($x); 
  130. $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null; 
  131. array_pop($x->stmtStack); 
  132. return $n; 
  133.  
  134. case KEYWORD_SWITCH: 
  135. $n = new JSNode($this->t); 
  136. $this->t->mustMatch(OP_LEFT_PAREN); 
  137. $n->discriminant = $this->Expression($x); 
  138. $this->t->mustMatch(OP_RIGHT_PAREN); 
  139. $n->cases = array(); 
  140. $n->defaultIndex = -1; 
  141.  
  142. array_push($x->stmtStack, $n); 
  143.  
  144. $this->t->mustMatch(OP_LEFT_CURLY); 
  145.  
  146. while (($tt = $this->t->get()) != OP_RIGHT_CURLY) 
  147. switch ($tt) 
  148. case KEYWORD_DEFAULT: 
  149. if ($n->defaultIndex >= 0) 
  150. throw $this->t->newSyntaxError('More than one switch default'); 
  151. // FALL THROUGH 
  152. case KEYWORD_CASE: 
  153. $n2 = new JSNode($this->t); 
  154. if ($tt == KEYWORD_DEFAULT) 
  155. $n->defaultIndex = count($n->cases); 
  156. else 
  157. $n2->caseLabel = $this->Expression($x, OP_COLON); 
  158. break; 
  159. default: 
  160. throw $this->t->newSyntaxError('Invalid switch case'); 
  161.  
  162. $this->t->mustMatch(OP_COLON); 
  163. $n2->statements = new JSNode($this->t, JS_BLOCK); 
  164. while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY) 
  165. $n2->statements->addNode($this->Statement($x)); 
  166.  
  167. array_push($n->cases, $n2); 
  168.  
  169. array_pop($x->stmtStack); 
  170. return $n; 
  171.  
  172. case KEYWORD_FOR: 
  173. $n = new JSNode($this->t); 
  174. $n->isLoop = true; 
  175. $this->t->mustMatch(OP_LEFT_PAREN); 
  176.  
  177. if (($tt = $this->t->peek()) != OP_SEMICOLON) 
  178. $x->inForLoopInit = true; 
  179. if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST) 
  180. $this->t->get(); 
  181. $n2 = $this->Variables($x); 
  182. else 
  183. $n2 = $this->Expression($x); 
  184. $x->inForLoopInit = false; 
  185.  
  186. if ($n2 && $this->t->match(KEYWORD_IN)) 
  187. $n->type = JS_FOR_IN; 
  188. if ($n2->type == KEYWORD_VAR) 
  189. if (count($n2->treeNodes) != 1) 
  190. throw $this->t->SyntaxError( 
  191. 'Invalid for..in left-hand side',  
  192. $this->t->filename,  
  193. $n2->lineno 
  194. ); 
  195.  
  196. // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name. 
  197. $n->iterator = $n2->treeNodes[0]; 
  198. $n->varDecl = $n2; 
  199. else 
  200. $n->iterator = $n2; 
  201. $n->varDecl = null; 
  202.  
  203. $n->object = $this->Expression($x); 
  204. else 
  205. $n->setup = $n2 ? $n2 : null; 
  206. $this->t->mustMatch(OP_SEMICOLON); 
  207. $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x); 
  208. $this->t->mustMatch(OP_SEMICOLON); 
  209. $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x); 
  210.  
  211. $this->t->mustMatch(OP_RIGHT_PAREN); 
  212. $n->body = $this->nest($x, $n); 
  213. return $n; 
  214.  
  215. case KEYWORD_WHILE: 
  216. $n = new JSNode($this->t); 
  217. $n->isLoop = true; 
  218. $n->condition = $this->ParenExpression($x); 
  219. $n->body = $this->nest($x, $n); 
  220. return $n; 
  221.  
  222. case KEYWORD_DO: 
  223. $n = new JSNode($this->t); 
  224. $n->isLoop = true; 
  225. $n->body = $this->nest($x, $n, KEYWORD_WHILE); 
  226. $n->condition = $this->ParenExpression($x); 
  227. if (!$x->ecmaStrictMode) 
  228. // <script language="JavaScript"> (without version hints) may need 
  229. // automatic semicolon insertion without a newline after do-while. 
  230. // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945. 
  231. $this->t->match(OP_SEMICOLON); 
  232. return $n; 
  233. break; 
  234.  
  235. case KEYWORD_BREAK: 
  236. case KEYWORD_CONTINUE: 
  237. $n = new JSNode($this->t); 
  238.  
  239. if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER) 
  240. $this->t->get(); 
  241. $n->label = $this->t->currentToken()->value; 
  242.  
  243. $ss = $x->stmtStack; 
  244. $i = count($ss); 
  245. $label = $n->label; 
  246. if ($label) 
  247. do 
  248. if (--$i < 0) 
  249. throw $this->t->newSyntaxError('Label not found'); 
  250. while ($ss[$i]->label != $label); 
  251. else 
  252. do 
  253. if (--$i < 0) 
  254. throw $this->t->newSyntaxError('Invalid ' . $tt); 
  255. while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH)); 
  256.  
  257. $n->target = $ss[$i]; 
  258. break; 
  259.  
  260. case KEYWORD_TRY: 
  261. $n = new JSNode($this->t); 
  262. $n->tryBlock = $this->Block($x); 
  263. $n->catchClauses = array(); 
  264.  
  265. while ($this->t->match(KEYWORD_CATCH)) 
  266. $n2 = new JSNode($this->t); 
  267. $this->t->mustMatch(OP_LEFT_PAREN); 
  268. $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value; 
  269.  
  270. if ($this->t->match(KEYWORD_IF)) 
  271. if ($x->ecmaStrictMode) 
  272. throw $this->t->newSyntaxError('Illegal catch guard'); 
  273.  
  274. if (count($n->catchClauses) && !end($n->catchClauses)->guard) 
  275. throw $this->t->newSyntaxError('Guarded catch after unguarded'); 
  276.  
  277. $n2->guard = $this->Expression($x); 
  278. else 
  279. $n2->guard = null; 
  280.  
  281. $this->t->mustMatch(OP_RIGHT_PAREN); 
  282. $n2->block = $this->Block($x); 
  283. array_push($n->catchClauses, $n2); 
  284.  
  285. if ($this->t->match(KEYWORD_FINALLY)) 
  286. $n->finallyBlock = $this->Block($x); 
  287.  
  288. if (!count($n->catchClauses) && !$n->finallyBlock) 
  289. throw $this->t->newSyntaxError('Invalid try statement'); 
  290. return $n; 
  291.  
  292. case KEYWORD_CATCH: 
  293. case KEYWORD_FINALLY: 
  294. throw $this->t->newSyntaxError($tt + ' without preceding try'); 
  295.  
  296. case KEYWORD_THROW: 
  297. $n = new JSNode($this->t); 
  298. $n->value = $this->Expression($x); 
  299. break; 
  300.  
  301. case KEYWORD_RETURN: 
  302. if (!$x->inFunction) 
  303. throw $this->t->newSyntaxError('Invalid return'); 
  304.  
  305. $n = new JSNode($this->t); 
  306. $tt = $this->t->peekOnSameLine(); 
  307. if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY) 
  308. $n->value = $this->Expression($x); 
  309. else 
  310. $n->value = null; 
  311. break; 
  312.  
  313. case KEYWORD_WITH: 
  314. $n = new JSNode($this->t); 
  315. $n->object = $this->ParenExpression($x); 
  316. $n->body = $this->nest($x, $n); 
  317. return $n; 
  318.  
  319. case KEYWORD_VAR: 
  320. case KEYWORD_CONST: 
  321. $n = $this->Variables($x); 
  322. break; 
  323.  
  324. case TOKEN_CONDCOMMENT_START: 
  325. case TOKEN_CONDCOMMENT_END: 
  326. $n = new JSNode($this->t); 
  327. return $n; 
  328.  
  329. case KEYWORD_DEBUGGER: 
  330. $n = new JSNode($this->t); 
  331. break; 
  332.  
  333. case TOKEN_NEWLINE: 
  334. case OP_SEMICOLON: 
  335. $n = new JSNode($this->t, OP_SEMICOLON); 
  336. $n->expression = null; 
  337. return $n; 
  338.  
  339. default: 
  340. if ($tt == TOKEN_IDENTIFIER) 
  341. $this->t->scanOperand = false; 
  342. $tt = $this->t->peek(); 
  343. $this->t->scanOperand = true; 
  344. if ($tt == OP_COLON) 
  345. $label = $this->t->currentToken()->value; 
  346. $ss = $x->stmtStack; 
  347. for ($i = count($ss) - 1; $i >= 0; --$i) 
  348. if ($ss[$i]->label == $label) 
  349. throw $this->t->newSyntaxError('Duplicate label'); 
  350.  
  351. $this->t->get(); 
  352. $n = new JSNode($this->t, JS_LABEL); 
  353. $n->label = $label; 
  354. $n->statement = $this->nest($x, $n); 
  355.  
  356. return $n; 
  357.  
  358. $n = new JSNode($this->t, OP_SEMICOLON); 
  359. $this->t->unget(); 
  360. $n->expression = $this->Expression($x); 
  361. $n->end = $n->expression->end; 
  362. break; 
  363.  
  364. if ($this->t->lineno == $this->t->currentToken()->lineno) 
  365. $tt = $this->t->peekOnSameLine(); 
  366. if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY) 
  367. throw $this->t->newSyntaxError('Missing ; before statement'); 
  368.  
  369. $this->t->match(OP_SEMICOLON); 
  370.  
  371. return $n; 
  372.  
  373. private function FunctionDefinition($x, $requireName, $functionForm) 
  374. $f = new JSNode($this->t); 
  375.  
  376. if ($f->type != KEYWORD_FUNCTION) 
  377. $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER; 
  378.  
  379. if ($this->t->match(TOKEN_IDENTIFIER)) 
  380. $f->name = $this->t->currentToken()->value; 
  381. elseif ($requireName) 
  382. throw $this->t->newSyntaxError('Missing function identifier'); 
  383.  
  384. $this->t->mustMatch(OP_LEFT_PAREN); 
  385. $f->params = array(); 
  386.  
  387. while (($tt = $this->t->get()) != OP_RIGHT_PAREN) 
  388. if ($tt != TOKEN_IDENTIFIER) 
  389. throw $this->t->newSyntaxError('Missing formal parameter'); 
  390.  
  391. array_push($f->params, $this->t->currentToken()->value); 
  392.  
  393. if ($this->t->peek() != OP_RIGHT_PAREN) 
  394. $this->t->mustMatch(OP_COMMA); 
  395.  
  396. $this->t->mustMatch(OP_LEFT_CURLY); 
  397.  
  398. $x2 = new JSCompilerContext(true); 
  399. $f->body = $this->Script($x2); 
  400.  
  401. $this->t->mustMatch(OP_RIGHT_CURLY); 
  402. $f->end = $this->t->currentToken()->end; 
  403.  
  404. $f->functionForm = $functionForm; 
  405. if ($functionForm == DECLARED_FORM) 
  406. array_push($x->funDecls, $f); 
  407.  
  408. return $f; 
  409.  
  410. private function Variables($x) 
  411. $n = new JSNode($this->t); 
  412.  
  413. do 
  414. $this->t->mustMatch(TOKEN_IDENTIFIER); 
  415.  
  416. $n2 = new JSNode($this->t); 
  417. $n2->name = $n2->value; 
  418.  
  419. if ($this->t->match(OP_ASSIGN)) 
  420. if ($this->t->currentToken()->assignOp) 
  421. throw $this->t->newSyntaxError('Invalid variable initialization'); 
  422.  
  423. $n2->initializer = $this->Expression($x, OP_COMMA); 
  424.  
  425. $n2->readOnly = $n->type == KEYWORD_CONST; 
  426.  
  427. $n->addNode($n2); 
  428. array_push($x->varDecls, $n2); 
  429. while ($this->t->match(OP_COMMA)); 
  430.  
  431. return $n; 
  432.  
  433. private function Expression($x, $stop=false) 
  434. $operators = array(); 
  435. $operands = array(); 
  436. $n = false; 
  437.  
  438. $bl = $x->bracketLevel; 
  439. $cl = $x->curlyLevel; 
  440. $pl = $x->parenLevel; 
  441. $hl = $x->hookLevel; 
  442.  
  443. while (($tt = $this->t->get()) != TOKEN_END) 
  444. if ($tt == $stop && 
  445. $x->bracketLevel == $bl && 
  446. $x->curlyLevel == $cl && 
  447. $x->parenLevel == $pl && 
  448. $x->hookLevel == $hl 
  449. // Stop only if tt matches the optional stop parameter, and that 
  450. // token is not quoted by some kind of bracket. 
  451. break; 
  452.  
  453. switch ($tt) 
  454. case OP_SEMICOLON: 
  455. // NB: cannot be empty, Statement handled that. 
  456. break 2; 
  457.  
  458. case OP_HOOK: 
  459. if ($this->t->scanOperand) 
  460. break 2; 
  461.  
  462. while ( !empty($operators) && 
  463. $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] 
  464. $this->reduce($operators, $operands); 
  465.  
  466. array_push($operators, new JSNode($this->t)); 
  467.  
  468. ++$x->hookLevel; 
  469. $this->t->scanOperand = true; 
  470. $n = $this->Expression($x); 
  471.  
  472. if (!$this->t->match(OP_COLON)) 
  473. break 2; 
  474.  
  475. --$x->hookLevel; 
  476. array_push($operands, $n); 
  477. break; 
  478.  
  479. case OP_COLON: 
  480. if ($x->hookLevel) 
  481. break 2; 
  482.  
  483. throw $this->t->newSyntaxError('Invalid label'); 
  484. break; 
  485.  
  486. case OP_ASSIGN: 
  487. if ($this->t->scanOperand) 
  488. break 2; 
  489.  
  490. // Use >, not >=, for right-associative ASSIGN 
  491. while ( !empty($operators) && 
  492. $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt] 
  493. $this->reduce($operators, $operands); 
  494.  
  495. array_push($operators, new JSNode($this->t)); 
  496. end($operands)->assignOp = $this->t->currentToken()->assignOp; 
  497. $this->t->scanOperand = true; 
  498. break; 
  499.  
  500. case KEYWORD_IN: 
  501. // An in operator should not be parsed if we're parsing the head of 
  502. // a for (...) loop, unless it is in the then part of a conditional 
  503. // expression, or parenthesized somehow. 
  504. if ($x->inForLoopInit && !$x->hookLevel && 
  505. !$x->bracketLevel && !$x->curlyLevel && 
  506. !$x->parenLevel 
  507. break 2; 
  508. // FALL THROUGH 
  509. case OP_COMMA: 
  510. // A comma operator should not be parsed if we're parsing the then part 
  511. // of a conditional expression unless it's parenthesized somehow. 
  512. if ($tt == OP_COMMA && $x->hookLevel && 
  513. !$x->bracketLevel && !$x->curlyLevel && 
  514. !$x->parenLevel 
  515. break 2; 
  516. // Treat comma as left-associative so reduce can fold left-heavy 
  517. // COMMA trees into a single array. 
  518. // FALL THROUGH 
  519. case OP_OR: 
  520. case OP_AND: 
  521. case OP_BITWISE_OR: 
  522. case OP_BITWISE_XOR: 
  523. case OP_BITWISE_AND: 
  524. case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE: 
  525. case OP_LT: case OP_LE: case OP_GE: case OP_GT: 
  526. case KEYWORD_INSTANCEOF: 
  527. case OP_LSH: case OP_RSH: case OP_URSH: 
  528. case OP_PLUS: case OP_MINUS: 
  529. case OP_MUL: case OP_DIV: case OP_MOD: 
  530. case OP_DOT: 
  531. if ($this->t->scanOperand) 
  532. break 2; 
  533.  
  534. while ( !empty($operators) && 
  535. $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt] 
  536. $this->reduce($operators, $operands); 
  537.  
  538. if ($tt == OP_DOT) 
  539. $this->t->mustMatch(TOKEN_IDENTIFIER); 
  540. array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t))); 
  541. else 
  542. array_push($operators, new JSNode($this->t)); 
  543. $this->t->scanOperand = true; 
  544. break; 
  545.  
  546. case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF: 
  547. case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS: 
  548. case KEYWORD_NEW: 
  549. if (!$this->t->scanOperand) 
  550. break 2; 
  551.  
  552. array_push($operators, new JSNode($this->t)); 
  553. break; 
  554.  
  555. case OP_INCREMENT: case OP_DECREMENT: 
  556. if ($this->t->scanOperand) 
  557. array_push($operators, new JSNode($this->t)); // prefix increment or decrement 
  558. else 
  559. // Don't cross a line boundary for postfix {in, de}crement. 
  560. $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3]; 
  561. if ($t && $t->lineno != $this->t->lineno) 
  562. break 2; 
  563.  
  564. if (!empty($operators)) 
  565. // Use >, not >=, so postfix has higher precedence than prefix. 
  566. while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]) 
  567. $this->reduce($operators, $operands); 
  568.  
  569. $n = new JSNode($this->t, $tt, array_pop($operands)); 
  570. $n->postfix = true; 
  571. array_push($operands, $n); 
  572. break; 
  573.  
  574. case KEYWORD_FUNCTION: 
  575. if (!$this->t->scanOperand) 
  576. break 2; 
  577.  
  578. array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM)); 
  579. $this->t->scanOperand = false; 
  580. break; 
  581.  
  582. case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE: 
  583. case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP: 
  584. if (!$this->t->scanOperand) 
  585. break 2; 
  586.  
  587. array_push($operands, new JSNode($this->t)); 
  588. $this->t->scanOperand = false; 
  589. break; 
  590.  
  591. case TOKEN_CONDCOMMENT_START: 
  592. case TOKEN_CONDCOMMENT_END: 
  593. if ($this->t->scanOperand) 
  594. array_push($operators, new JSNode($this->t)); 
  595. else 
  596. array_push($operands, new JSNode($this->t)); 
  597. break; 
  598.  
  599. case OP_LEFT_BRACKET: 
  600. if ($this->t->scanOperand) 
  601. // Array initialiser. Parse using recursive descent, as the 
  602. // sub-grammar here is not an operator grammar. 
  603. $n = new JSNode($this->t, JS_ARRAY_INIT); 
  604. while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET) 
  605. if ($tt == OP_COMMA) 
  606. $this->t->get(); 
  607. $n->addNode(null); 
  608. continue; 
  609.  
  610. $n->addNode($this->Expression($x, OP_COMMA)); 
  611. if (!$this->t->match(OP_COMMA)) 
  612. break; 
  613.  
  614. $this->t->mustMatch(OP_RIGHT_BRACKET); 
  615. array_push($operands, $n); 
  616. $this->t->scanOperand = false; 
  617. else 
  618. // Property indexing operator. 
  619. array_push($operators, new JSNode($this->t, JS_INDEX)); 
  620. $this->t->scanOperand = true; 
  621. ++$x->bracketLevel; 
  622. break; 
  623.  
  624. case OP_RIGHT_BRACKET: 
  625. if ($this->t->scanOperand || $x->bracketLevel == $bl) 
  626. break 2; 
  627.  
  628. while ($this->reduce($operators, $operands)->type != JS_INDEX) 
  629. continue; 
  630.  
  631. --$x->bracketLevel; 
  632. break; 
  633.  
  634. case OP_LEFT_CURLY: 
  635. if (!$this->t->scanOperand) 
  636. break 2; 
  637.  
  638. // Object initialiser. As for array initialisers (see above),  
  639. // parse using recursive descent. 
  640. ++$x->curlyLevel; 
  641. $n = new JSNode($this->t, JS_OBJECT_INIT); 
  642. while (!$this->t->match(OP_RIGHT_CURLY)) 
  643. do 
  644. $tt = $this->t->get(); 
  645. $tv = $this->t->currentToken()->value; 
  646. if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER) 
  647. if ($x->ecmaStrictMode) 
  648. throw $this->t->newSyntaxError('Illegal property accessor'); 
  649.  
  650. $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM)); 
  651. else 
  652. switch ($tt) 
  653. case TOKEN_IDENTIFIER: 
  654. case TOKEN_NUMBER: 
  655. case TOKEN_STRING: 
  656. $id = new JSNode($this->t); 
  657. break; 
  658.  
  659. case OP_RIGHT_CURLY: 
  660. if ($x->ecmaStrictMode) 
  661. throw $this->t->newSyntaxError('Illegal trailing , '); 
  662. break 3; 
  663.  
  664. default: 
  665. throw $this->t->newSyntaxError('Invalid property name'); 
  666.  
  667. $this->t->mustMatch(OP_COLON); 
  668. $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA))); 
  669. while ($this->t->match(OP_COMMA)); 
  670.  
  671. $this->t->mustMatch(OP_RIGHT_CURLY); 
  672. break; 
  673.  
  674. array_push($operands, $n); 
  675. $this->t->scanOperand = false; 
  676. --$x->curlyLevel; 
  677. break; 
  678.  
  679. case OP_RIGHT_CURLY: 
  680. if (!$this->t->scanOperand && $x->curlyLevel != $cl) 
  681. throw new Exception('PANIC: right curly botch'); 
  682. break 2; 
  683.  
  684. case OP_LEFT_PAREN: 
  685. if ($this->t->scanOperand) 
  686. array_push($operators, new JSNode($this->t, JS_GROUP)); 
  687. else 
  688. while ( !empty($operators) && 
  689. $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW] 
  690. $this->reduce($operators, $operands); 
  691.  
  692. // Handle () now, to regularize the n-ary case for n > 0. 
  693. // We must set scanOperand in case there are arguments and 
  694. // the first one is a regexp or unary+/-. 
  695. $n = end($operators); 
  696. $this->t->scanOperand = true; 
  697. if ($this->t->match(OP_RIGHT_PAREN)) 
  698. if ($n && $n->type == KEYWORD_NEW) 
  699. array_pop($operators); 
  700. $n->addNode(array_pop($operands)); 
  701. else 
  702. $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST)); 
  703.  
  704. array_push($operands, $n); 
  705. $this->t->scanOperand = false; 
  706. break; 
  707.  
  708. if ($n && $n->type == KEYWORD_NEW) 
  709. $n->type = JS_NEW_WITH_ARGS; 
  710. else 
  711. array_push($operators, new JSNode($this->t, JS_CALL)); 
  712.  
  713. ++$x->parenLevel; 
  714. break; 
  715.  
  716. case OP_RIGHT_PAREN: 
  717. if ($this->t->scanOperand || $x->parenLevel == $pl) 
  718. break 2; 
  719.  
  720. while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP && 
  721. $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS 
  722. continue; 
  723.  
  724. if ($tt != JS_GROUP) 
  725. $n = end($operands); 
  726. if ($n->treeNodes[1]->type != OP_COMMA) 
  727. $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]); 
  728. else 
  729. $n->treeNodes[1]->type = JS_LIST; 
  730.  
  731. --$x->parenLevel; 
  732. break; 
  733.  
  734. // Automatic semicolon insertion means we may scan across a newline 
  735. // and into the beginning of another statement. If so, break out of 
  736. // the while loop and let the t.scanOperand logic handle errors. 
  737. default: 
  738. break 2; 
  739.  
  740. if ($x->hookLevel != $hl) 
  741. throw $this->t->newSyntaxError('Missing : in conditional expression'); 
  742.  
  743. if ($x->parenLevel != $pl) 
  744. throw $this->t->newSyntaxError('Missing ) in parenthetical'); 
  745.  
  746. if ($x->bracketLevel != $bl) 
  747. throw $this->t->newSyntaxError('Missing ] in index expression'); 
  748.  
  749. if ($this->t->scanOperand) 
  750. throw $this->t->newSyntaxError('Missing operand'); 
  751.  
  752. // Resume default mode, scanning for operands, not operators. 
  753. $this->t->scanOperand = true; 
  754. $this->t->unget(); 
  755.  
  756. while (count($operators)) 
  757. $this->reduce($operators, $operands); 
  758.  
  759. return array_pop($operands); 
  760.  
  761. private function ParenExpression($x) 
  762. $this->t->mustMatch(OP_LEFT_PAREN); 
  763. $n = $this->Expression($x); 
  764. $this->t->mustMatch(OP_RIGHT_PAREN); 
  765.  
  766. return $n; 
  767.  
  768. // Statement stack and nested statement handler. 
  769. private function nest($x, $node, $end = false) 
  770. array_push($x->stmtStack, $node); 
  771. $n = $this->statement($x); 
  772. array_pop($x->stmtStack); 
  773.  
  774. if ($end) 
  775. $this->t->mustMatch($end); 
  776.  
  777. return $n; 
  778.  
  779. private function reduce(&$operators, &$operands) 
  780. $n = array_pop($operators); 
  781. $op = $n->type; 
  782. $arity = $this->opArity[$op]; 
  783. $c = count($operands); 
  784. if ($arity == -2) 
  785. // Flatten left-associative trees 
  786. if ($c >= 2) 
  787. $left = $operands[$c - 2]; 
  788. if ($left->type == $op) 
  789. $right = array_pop($operands); 
  790. $left->addNode($right); 
  791. return $left; 
  792. $arity = 2; 
  793.  
  794. // Always use push to add operands to n, to update start and end 
  795. $a = array_splice($operands, $c - $arity); 
  796. for ($i = 0; $i < $arity; $i++) 
  797. $n->addNode($a[$i]); 
  798.  
  799. // Include closing bracket or postfix operator in [start, end] 
  800. $te = $this->t->currentToken()->end; 
  801. if ($n->end < $te) 
  802. $n->end = $te; 
  803.  
  804. array_push($operands, $n); 
  805.  
  806. return $n;