You are here

private function JSParser::Expression in Advanced CSS/JS Aggregation 8.4

Same name and namespace in other branches
  1. 8.2 advagg_js_minify/jsminplus.inc \JSParser::Expression()
  2. 8.3 advagg_js_minify/jsminplus.inc \JSParser::Expression()
  3. 6 advagg_js_compress/jsminplus.inc \JSParser::Expression()
  4. 7.2 advagg_js_compress/jsminplus.inc \JSParser::Expression()
  5. 7 advagg_js_compress/jsminplus.inc \JSParser::Expression()
3 calls to JSParser::Expression()
JSParser::ParenExpression in advagg_js_minify/jsminplus.inc
JSParser::Statement in advagg_js_minify/jsminplus.inc
JSParser::Variables in advagg_js_minify/jsminplus.inc

File

advagg_js_minify/jsminplus.inc, line 1304
JSMinPlus version 1.4

Class

JSParser

Code

private function Expression($x, $stop = false) {
  $operators = array();
  $operands = array();
  $n = false;
  $bl = $x->bracketLevel;
  $cl = $x->curlyLevel;
  $pl = $x->parenLevel;
  $hl = $x->hookLevel;
  while (($tt = $this->t
    ->get()) != TOKEN_END) {
    if ($tt == $stop && $x->bracketLevel == $bl && $x->curlyLevel == $cl && $x->parenLevel == $pl && $x->hookLevel == $hl) {

      // Stop only if tt matches the optional stop parameter, and that
      // token is not quoted by some kind of bracket.
      break;
    }
    switch ($tt) {
      case OP_SEMICOLON:

        // NB: cannot be empty, Statement handled that.
        break 2;
      case OP_HOOK:
        if ($this->t->scanOperand) {
          break 2;
        }
        while (!empty($operators) && $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]) {
          $this
            ->reduce($operators, $operands);
        }
        array_push($operators, new JSNode($this->t));
        ++$x->hookLevel;
        $this->t->scanOperand = true;
        $n = $this
          ->Expression($x);
        if (!$this->t
          ->match(OP_COLON)) {
          break 2;
        }
        --$x->hookLevel;
        array_push($operands, $n);
        break;
      case OP_COLON:
        if ($x->hookLevel) {
          break 2;
        }
        throw $this->t
          ->newSyntaxError('Invalid label');
        break;
      case OP_ASSIGN:
        if ($this->t->scanOperand) {
          break 2;
        }

        // Use >, not >=, for right-associative ASSIGN
        while (!empty($operators) && $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]) {
          $this
            ->reduce($operators, $operands);
        }
        array_push($operators, new JSNode($this->t));
        end($operands)->assignOp = $this->t
          ->currentToken()->assignOp;
        $this->t->scanOperand = true;
        break;
      case KEYWORD_IN:

        // An in operator should not be parsed if we're parsing the head of
        // a for (...) loop, unless it is in the then part of a conditional
        // expression, or parenthesized somehow.
        if ($x->inForLoopInit && !$x->hookLevel && !$x->bracketLevel && !$x->curlyLevel && !$x->parenLevel) {
          break 2;
        }

      // FALL THROUGH
      case OP_COMMA:

        // A comma operator should not be parsed if we're parsing the then part
        // of a conditional expression unless it's parenthesized somehow.
        if ($tt == OP_COMMA && $x->hookLevel && !$x->bracketLevel && !$x->curlyLevel && !$x->parenLevel) {
          break 2;
        }

      // Treat comma as left-associative so reduce can fold left-heavy
      // COMMA trees into a single array.
      // FALL THROUGH
      case OP_OR:
      case OP_AND:
      case OP_BITWISE_OR:
      case OP_BITWISE_XOR:
      case OP_BITWISE_AND:
      case OP_EQ:
      case OP_NE:
      case OP_STRICT_EQ:
      case OP_STRICT_NE:
      case OP_LT:
      case OP_LE:
      case OP_GE:
      case OP_GT:
      case KEYWORD_INSTANCEOF:
      case OP_LSH:
      case OP_RSH:
      case OP_URSH:
      case OP_PLUS:
      case OP_MINUS:
      case OP_MUL:
      case OP_DIV:
      case OP_MOD:
      case OP_DOT:
        if ($this->t->scanOperand) {
          break 2;
        }
        while (!empty($operators) && $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]) {
          $this
            ->reduce($operators, $operands);
        }
        if ($tt == OP_DOT) {
          $this->t
            ->mustMatch(TOKEN_IDENTIFIER, true);
          array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
        }
        else {
          array_push($operators, new JSNode($this->t));
          $this->t->scanOperand = true;
        }
        break;
      case KEYWORD_DELETE:
      case KEYWORD_VOID:
      case KEYWORD_TYPEOF:
      case OP_NOT:
      case OP_BITWISE_NOT:
      case OP_UNARY_PLUS:
      case OP_UNARY_MINUS:
      case KEYWORD_NEW:
        if (!$this->t->scanOperand) {
          break 2;
        }
        array_push($operators, new JSNode($this->t));
        break;
      case OP_INCREMENT:
      case OP_DECREMENT:
        if ($this->t->scanOperand) {
          array_push($operators, new JSNode($this->t));

          // prefix increment or decrement
        }
        else {

          // Don't cross a line boundary for postfix {in,de}crement.
          $t = $this->t->tokens[$this->t->tokenIndex + $this->t->lookahead - 1 & 3];
          if ($t && $t->lineno != $this->t->lineno) {
            break 2;
          }
          if (!empty($operators)) {

            // Use >, not >=, so postfix has higher precedence than prefix.
            while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]) {
              $this
                ->reduce($operators, $operands);
            }
          }
          $n = new JSNode($this->t, $tt, array_pop($operands));
          $n->postfix = true;
          array_push($operands, $n);
        }
        break;
      case KEYWORD_FUNCTION:
        if (!$this->t->scanOperand) {
          break 2;
        }
        array_push($operands, $this
          ->FunctionDefinition($x, false, EXPRESSED_FORM));
        $this->t->scanOperand = false;
        break;
      case KEYWORD_NULL:
      case KEYWORD_THIS:
      case KEYWORD_TRUE:
      case KEYWORD_FALSE:
      case TOKEN_IDENTIFIER:
      case TOKEN_NUMBER:
      case TOKEN_STRING:
      case TOKEN_REGEXP:
        if (!$this->t->scanOperand) {
          break 2;
        }
        array_push($operands, new JSNode($this->t));
        $this->t->scanOperand = false;
        break;
      case TOKEN_CONDCOMMENT_START:
      case TOKEN_CONDCOMMENT_END:
        if ($this->t->scanOperand) {
          array_push($operators, new JSNode($this->t));
        }
        else {
          array_push($operands, new JSNode($this->t));
        }
        break;
      case OP_LEFT_BRACKET:
        if ($this->t->scanOperand) {

          // Array initialiser.  Parse using recursive descent, as the
          // sub-grammar here is not an operator grammar.
          $n = new JSNode($this->t, JS_ARRAY_INIT);
          while (($tt = $this->t
            ->peek()) != OP_RIGHT_BRACKET) {
            if ($tt == OP_COMMA) {
              $this->t
                ->get();
              $n
                ->addNode(null);
              continue;
            }
            $n
              ->addNode($this
              ->Expression($x, OP_COMMA));
            if (!$this->t
              ->match(OP_COMMA)) {
              break;
            }
          }
          $this->t
            ->mustMatch(OP_RIGHT_BRACKET);
          array_push($operands, $n);
          $this->t->scanOperand = false;
        }
        else {

          // Property indexing operator.
          array_push($operators, new JSNode($this->t, JS_INDEX));
          $this->t->scanOperand = true;
          ++$x->bracketLevel;
        }
        break;
      case OP_RIGHT_BRACKET:
        if ($this->t->scanOperand || $x->bracketLevel == $bl) {
          break 2;
        }
        while ($this
          ->reduce($operators, $operands)->type != JS_INDEX) {
          continue;
        }
        --$x->bracketLevel;
        break;
      case OP_LEFT_CURLY:
        if (!$this->t->scanOperand) {
          break 2;
        }

        // Object initialiser.  As for array initialisers (see above),
        // parse using recursive descent.
        ++$x->curlyLevel;
        $n = new JSNode($this->t, JS_OBJECT_INIT);
        while (!$this->t
          ->match(OP_RIGHT_CURLY)) {
          do {
            $tt = $this->t
              ->get();
            $tv = $this->t
              ->currentToken()->value;
            if (($tv == 'get' || $tv == 'set') && $this->t
              ->peek() == TOKEN_IDENTIFIER) {
              if ($x->ecmaStrictMode) {
                throw $this->t
                  ->newSyntaxError('Illegal property accessor');
              }
              $n
                ->addNode($this
                ->FunctionDefinition($x, true, EXPRESSED_FORM));
            }
            else {
              switch ($tt) {
                case TOKEN_IDENTIFIER:
                case TOKEN_NUMBER:
                case TOKEN_STRING:
                  $id = new JSNode($this->t);
                  break;
                case OP_RIGHT_CURLY:
                  if ($x->ecmaStrictMode) {
                    throw $this->t
                      ->newSyntaxError('Illegal trailing ,');
                  }
                  break 3;
                default:
                  throw $this->t
                    ->newSyntaxError('Invalid property name');
              }
              $this->t
                ->mustMatch(OP_COLON);
              $n
                ->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this
                ->Expression($x, OP_COMMA)));
            }
          } while ($this->t
            ->match(OP_COMMA));
          $this->t
            ->mustMatch(OP_RIGHT_CURLY);
          break;
        }
        array_push($operands, $n);
        $this->t->scanOperand = false;
        --$x->curlyLevel;
        break;
      case OP_RIGHT_CURLY:
        if (!$this->t->scanOperand && $x->curlyLevel != $cl) {
          throw new Exception('PANIC: right curly botch');
        }
        break 2;
      case OP_LEFT_PAREN:
        if ($this->t->scanOperand) {
          array_push($operators, new JSNode($this->t, JS_GROUP));
        }
        else {
          while (!empty($operators) && $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]) {
            $this
              ->reduce($operators, $operands);
          }

          // Handle () now, to regularize the n-ary case for n > 0.
          // We must set scanOperand in case there are arguments and
          // the first one is a regexp or unary+/-.
          $n = end($operators);
          $this->t->scanOperand = true;
          if ($this->t
            ->match(OP_RIGHT_PAREN)) {
            if ($n && $n->type == KEYWORD_NEW) {
              array_pop($operators);
              $n
                ->addNode(array_pop($operands));
            }
            else {
              $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
            }
            array_push($operands, $n);
            $this->t->scanOperand = false;
            break;
          }
          if ($n && $n->type == KEYWORD_NEW) {
            $n->type = JS_NEW_WITH_ARGS;
          }
          else {
            array_push($operators, new JSNode($this->t, JS_CALL));
          }
        }
        ++$x->parenLevel;
        break;
      case OP_RIGHT_PAREN:
        if ($this->t->scanOperand || $x->parenLevel == $pl) {
          break 2;
        }
        while (($tt = $this
          ->reduce($operators, $operands)->type) != JS_GROUP && $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS) {
          continue;
        }
        if ($tt != JS_GROUP) {
          $n = end($operands);
          if ($n->treeNodes[1]->type != OP_COMMA) {
            $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
          }
          else {
            $n->treeNodes[1]->type = JS_LIST;
          }
        }
        --$x->parenLevel;
        break;

      // Automatic semicolon insertion means we may scan across a newline
      // and into the beginning of another statement.  If so, break out of
      // the while loop and let the t.scanOperand logic handle errors.
      default:
        break 2;
    }
  }
  if ($x->hookLevel != $hl) {
    throw $this->t
      ->newSyntaxError('Missing : in conditional expression');
  }
  if ($x->parenLevel != $pl) {
    throw $this->t
      ->newSyntaxError('Missing ) in parenthetical');
  }
  if ($x->bracketLevel != $bl) {
    throw $this->t
      ->newSyntaxError('Missing ] in index expression');
  }
  if ($this->t->scanOperand) {
    throw $this->t
      ->newSyntaxError('Missing operand');
  }

  // Resume default mode, scanning for operands, not operators.
  $this->t->scanOperand = true;
  $this->t
    ->unget();
  while (count($operators)) {
    $this
      ->reduce($operators, $operands);
  }
  return array_pop($operands);
}