679 private $opPrecedence = array(
682 '=' => 2,
'?' => 2,
':' => 2,
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,
703 private $opArity = array(
712 '==' => 2,
'!=' => 2,
'===' => 2,
'!==' => 2,
713 '<' => 2,
'<=' => 2,
'>=' => 2,
'>' => 2,
'in' => 2,
'instanceof' => 2,
714 '<<' => 2,
'>>' => 2,
'>>>' => 2,
716 '*' => 2,
'/' => 2,
'%' => 2,
717 'delete' => 1,
'void' => 1,
'typeof' => 1,
718 '!' => 1,
'~' => 1,
'U+' => 1,
'U-' => 1,
719 '++' => 1,
'--' => 1,
729 $this->minifier = $minifier;
736 $this->t->init($s, $f, $l);
739 $n = $this->Script($x);
740 if (!$this->t->isDone())
741 throw $this->t->newSyntaxError(
'Syntax error');
746 private function Script($x)
748 $n = $this->Statements($x);
750 $n->funDecls = $x->funDecls;
751 $n->varDecls = $x->varDecls;
756 $n->value = $this->minifier->parseTree($n);
759 $n->treeNodes =
null;
769 private function Statements($x)
772 array_push($x->stmtStack, $n);
775 $n->addNode($this->Statement($x));
777 array_pop($x->stmtStack);
782 private function Block($x)
785 $n = $this->Statements($x);
791 private function Statement($x)
793 $tt = $this->t->get();
801 return $this->FunctionDefinition(
809 $n = $this->Statements($x);
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);
823 $n =
new JSNode($this->t);
825 $n->discriminant = $this->Expression($x);
828 $n->defaultIndex = -1;
830 array_push($x->stmtStack, $n);
839 if ($n->defaultIndex >= 0)
840 throw $this->t->newSyntaxError(
'More than one switch default');
843 $n2 =
new JSNode($this->t);
845 $n->defaultIndex = count($n->cases);
847 $n2->caseLabel = $this->Expression($x,
OP_COLON);
850 throw $this->t->newSyntaxError(
'Invalid switch case');
856 $n2->statements->addNode($this->Statement($x));
858 array_push($n->cases, $n2);
861 array_pop($x->stmtStack);
865 $n =
new JSNode($this->t);
871 $x->inForLoopInit =
true;
875 $n2 = $this->Variables($x);
879 $n2 = $this->Expression($x);
881 $x->inForLoopInit =
false;
889 if (count($n2->treeNodes) != 1)
891 throw $this->t->SyntaxError(
892 'Invalid for..in left-hand side',
899 $n->iterator = $n2->treeNodes[0];
908 $n->object = $this->Expression($x);
912 $n->setup = $n2 ?:
null;
914 $n->condition = $this->t->peek() ==
OP_SEMICOLON ? null : $this->Expression($x);
916 $n->update = $this->t->peek() ==
OP_RIGHT_PAREN ? null : $this->Expression($x);
920 $n->body = $this->nest($x, $n);
924 $n =
new JSNode($this->t);
926 $n->condition = $this->ParenExpression($x);
927 $n->body = $this->nest($x, $n);
931 $n =
new JSNode($this->t);
934 $n->condition = $this->ParenExpression($x);
935 if (!$x->ecmaStrictMode)
947 $n =
new JSNode($this->t);
952 $n->label = $this->t->currentToken()->value;
963 throw $this->t->newSyntaxError(
'Label not found');
965 while ($ss[$i]->label != $label);
972 throw $this->t->newSyntaxError(
'Invalid ' . $tt);
979 $n =
new JSNode($this->t);
980 $n->tryBlock = $this->Block($x);
981 $n->catchClauses = array();
985 $n2 =
new JSNode($this->t);
991 if ($x->ecmaStrictMode)
992 throw $this->t->newSyntaxError(
'Illegal catch guard');
994 if (count($n->catchClauses) && !end($n->catchClauses)->guard)
995 throw $this->t->newSyntaxError(
'Guarded catch after unguarded');
997 $n2->guard = $this->Expression($x);
1005 $n2->block = $this->Block($x);
1006 array_push($n->catchClauses, $n2);
1010 $n->finallyBlock = $this->Block($x);
1012 if (!count($n->catchClauses) && !$n->finallyBlock)
1013 throw $this->t->newSyntaxError(
'Invalid try statement');
1018 throw $this->t->newSyntaxError($tt .
' without preceding try');
1021 $n =
new JSNode($this->t);
1022 $n->value = $this->Expression($x);
1026 if (!$x->inFunction)
1027 throw $this->t->newSyntaxError(
'Invalid return');
1029 $n =
new JSNode($this->t);
1030 $tt = $this->t->peekOnSameLine();
1032 $n->value = $this->Expression($x);
1038 $n =
new JSNode($this->t);
1039 $n->object = $this->ParenExpression($x);
1040 $n->body = $this->nest($x, $n);
1045 $n = $this->Variables($x);
1050 $n =
new JSNode($this->t);
1054 $n =
new JSNode($this->t);
1065 $this->t->scanOperand =
false;
1066 $tt = $this->t->peek();
1067 $this->t->scanOperand =
true;
1070 $label = $this->t->currentToken()->value;
1071 $ss = $x->stmtStack;
1072 for ($i = count($ss) - 1; $i >= 0; --$i)
1074 if ($ss[$i]->label == $label)
1075 throw $this->t->newSyntaxError(
'Duplicate label');
1081 $n->statement = $this->nest($x, $n);
1089 $n->expression = $this->Expression($x);
1090 $n->end = $n->expression->end;
1094 if ($this->t->lineno == $this->t->currentToken()->lineno)
1096 $tt = $this->t->peekOnSameLine();
1098 throw $this->t->newSyntaxError(
'Missing ; before statement');
1106 private function FunctionDefinition($x, $requireName, $functionForm)
1108 $f =
new JSNode($this->t);
1114 $f->name = $this->t->currentToken()->value;
1115 elseif ($requireName)
1116 throw $this->t->newSyntaxError(
'Missing function identifier');
1119 $f->params = array();
1124 throw $this->t->newSyntaxError(
'Missing formal parameter');
1126 array_push($f->params, $this->t->currentToken()->value);
1135 $f->body = $this->Script($x2);
1138 $f->end = $this->t->currentToken()->end;
1140 $f->functionForm = $functionForm;
1142 array_push($x->funDecls, $f);
1147 private function Variables($x)
1149 $n =
new JSNode($this->t);
1155 $n2 =
new JSNode($this->t);
1156 $n2->name = $n2->value;
1160 if ($this->t->currentToken()->assignOp)
1161 throw $this->t->newSyntaxError(
'Invalid variable initialization');
1163 $n2->initializer = $this->Expression($x,
OP_COMMA);
1169 array_push($x->varDecls, $n2);
1176 private function Expression($x, $stop=
false)
1178 $operators = array();
1179 $operands = array();
1182 $bl = $x->bracketLevel;
1183 $cl = $x->curlyLevel;
1184 $pl = $x->parenLevel;
1185 $hl = $x->hookLevel;
1187 while (($tt = $this->t->get()) !=
TOKEN_END)
1190 $x->bracketLevel == $bl &&
1191 $x->curlyLevel == $cl &&
1192 $x->parenLevel == $pl &&
1193 $x->hookLevel == $hl
1208 if ($this->t->scanOperand)
1211 while ( !empty($operators) &&
1212 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1214 $this->reduce($operators, $operands);
1216 array_push($operators,
new JSNode($this->t));
1219 $this->t->scanOperand =
true;
1220 $n = $this->Expression($x);
1226 array_push($operands, $n);
1233 throw $this->t->newSyntaxError(
'Invalid label');
1237 if ($this->t->scanOperand)
1241 while ( !empty($operators) &&
1242 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1244 $this->reduce($operators, $operands);
1246 array_push($operators,
new JSNode($this->t));
1247 end($operands)->assignOp = $this->t->currentToken()->assignOp;
1248 $this->t->scanOperand =
true;
1255 if ($x->inForLoopInit && !$x->hookLevel &&
1256 !$x->bracketLevel && !$x->curlyLevel &&
1264 if ($tt ==
OP_COMMA && $x->hookLevel &&
1265 !$x->bracketLevel && !$x->curlyLevel &&
1284 if ($this->t->scanOperand)
1287 while ( !empty($operators) &&
1288 $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1290 $this->reduce($operators, $operands);
1294 $tt = $this->t->get();
1296 throw $this->t->newSyntaxError(
"Unexpected token; token identifier or keyword expected.");
1298 array_push($operands,
new JSNode($this->t,
OP_DOT, array_pop($operands),
new JSNode($this->t)));
1302 array_push($operators,
new JSNode($this->t));
1303 $this->t->scanOperand =
true;
1310 if (!$this->t->scanOperand)
1313 array_push($operators,
new JSNode($this->t));
1317 if ($this->t->scanOperand)
1319 array_push($operators,
new JSNode($this->t));
1324 $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
1325 if ($t && $t->lineno != $this->t->lineno)
1328 if (!empty($operators))
1331 while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
1332 $this->reduce($operators, $operands);
1335 $n =
new JSNode($this->t, $tt, array_pop($operands));
1337 array_push($operands, $n);
1342 if (!$this->t->scanOperand)
1345 array_push($operands, $this->FunctionDefinition($x,
false,
EXPRESSED_FORM));
1346 $this->t->scanOperand =
false;
1351 if (!$this->t->scanOperand)
1354 array_push($operands,
new JSNode($this->t));
1355 $this->t->scanOperand =
false;
1360 if ($this->t->scanOperand)
1361 array_push($operators,
new JSNode($this->t));
1363 array_push($operands,
new JSNode($this->t));
1367 if ($this->t->scanOperand)
1381 $n->addNode($this->Expression($x,
OP_COMMA));
1387 array_push($operands, $n);
1388 $this->t->scanOperand =
false;
1394 $this->t->scanOperand =
true;
1400 if ($this->t->scanOperand || $x->bracketLevel == $bl)
1403 while ($this->reduce($operators, $operands)->type !=
JS_INDEX)
1410 if (!$this->t->scanOperand)
1421 $tt = $this->t->get();
1422 $tv = $this->t->currentToken()->value;
1423 if (($tv ==
'get' || $tv ==
'set') && $this->t->peek() ==
TOKEN_IDENTIFIER)
1425 if ($x->ecmaStrictMode)
1426 throw $this->t->newSyntaxError(
'Illegal property accessor');
1428 $n->addNode($this->FunctionDefinition($x,
true,
EXPRESSED_FORM));
1434 if ($this->isKeyword($tt))
1442 $id =
new JSNode($this->t);
1446 if ($x->ecmaStrictMode)
1447 throw $this->t->newSyntaxError(
'Illegal trailing ,');
1451 throw $this->t->newSyntaxError(
'Invalid property name');
1464 array_push($operands, $n);
1465 $this->t->scanOperand =
false;
1470 if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1471 throw new Exception(
'PANIC: right curly botch');
1475 if ($this->t->scanOperand)
1481 while ( !empty($operators) &&
1482 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[
KEYWORD_NEW]
1484 $this->reduce($operators, $operands);
1489 $n = end($operators);
1490 $this->t->scanOperand =
true;
1495 array_pop($operators);
1496 $n->addNode(array_pop($operands));
1503 array_push($operands, $n);
1504 $this->t->scanOperand =
false;
1518 if ($this->t->scanOperand || $x->parenLevel == $pl)
1521 while (($tt = $this->reduce($operators, $operands)->type) !=
JS_GROUP &&
1530 $n = end($operands);
1531 if ($n->treeNodes[1]->type !=
OP_COMMA)
1532 $n->treeNodes[1] =
new JSNode($this->t,
JS_LIST, $n->treeNodes[1]);
1534 $n->treeNodes[1]->type =
JS_LIST;
1548 if ($x->hookLevel != $hl)
1549 throw $this->t->newSyntaxError(
'Missing : in conditional expression');
1551 if ($x->parenLevel != $pl)
1552 throw $this->t->newSyntaxError(
'Missing ) in parenthetical');
1554 if ($x->bracketLevel != $bl)
1555 throw $this->t->newSyntaxError(
'Missing ] in index expression');
1557 if ($this->t->scanOperand)
1558 throw $this->t->newSyntaxError(
'Missing operand');
1561 $this->t->scanOperand =
true;
1564 while (count($operators))
1565 $this->reduce($operators, $operands);
1567 return array_pop($operands);
1570 private function ParenExpression($x)
1573 $n = $this->Expression($x);
1580 private function nest($x, $node, $end =
false)
1582 array_push($x->stmtStack, $node);
1583 $n = $this->Statement($x);
1584 array_pop($x->stmtStack);
1587 $this->t->mustMatch($end);
1592 private function reduce(&$operators, &$operands)
1594 $n = array_pop($operators);
1596 $arity = $this->opArity[$op];
1597 $c = count($operands);
1603 $left = $operands[$c - 2];
1604 if ($left->type == $op)
1606 $right = array_pop($operands);
1607 $left->addNode($right);
1615 $a = array_splice($operands, $c - $arity);
1616 for ($i = 0; $i < $arity; $i++)
1617 $n->addNode($a[$i]);
1620 $te = $this->t->currentToken()->end;
1624 array_push($operands, $n);
1629 private function isKeyword($tt)