You are here

public function JSMinPlus::parseTree in Javascript Aggregator 6

1 call to JSMinPlus::parseTree()
JSMinPlus::min in ./jsminplus.php

File

./jsminplus.php, line 224

Class

JSMinPlus

Code

public function parseTree($n, $noBlockGrouping = false) {
  $s = '';
  switch ($n->type) {
    case JS_MINIFIED:
      $s = $n->value;
      break;
    case JS_SCRIPT:

      // we do nothing yet with funDecls or varDecls
      $noBlockGrouping = true;

    // FALL THROUGH
    case JS_BLOCK:
      $childs = $n->treeNodes;
      $lastType = 0;
      for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++) {
        $type = $childs[$i]->type;
        $t = $this
          ->parseTree($childs[$i]);
        if (strlen($t)) {
          if ($c) {
            $s = rtrim($s, ';');
            if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM) {

              // put declared functions on a new line
              $s .= "\n";
            }
            elseif ($type == KEYWORD_VAR && $type == $lastType) {

              // mutiple var-statements can go into one
              $t = ',' . substr($t, 4);
            }
            else {

              // add terminator
              $s .= ';';
            }
          }
          $s .= $t;
          $c++;
          $lastType = $type;
        }
      }
      if ($c > 1 && !$noBlockGrouping) {
        $s = '{' . $s . '}';
      }
      break;
    case KEYWORD_FUNCTION:
      $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
      $params = $n->params;
      for ($i = 0, $j = count($params); $i < $j; $i++) {
        $s .= ($i ? ',' : '') . $params[$i];
      }
      $s .= '){' . $this
        ->parseTree($n->body, true) . '}';
      break;
    case KEYWORD_IF:
      $s = 'if(' . $this
        ->parseTree($n->condition) . ')';
      $thenPart = $this
        ->parseTree($n->thenPart);
      $elsePart = $n->elsePart ? $this
        ->parseTree($n->elsePart) : null;

      // empty if-statement
      if ($thenPart == '') {
        $thenPart = ';';
      }
      if ($elsePart) {

        // be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
        if ($thenPart != ';' && $thenPart[0] != '{') {
          $thenPart = '{' . $thenPart . '}';
        }
        $s .= $thenPart . 'else';

        // we could check for more, but that hardly ever applies so go for performance
        if ($elsePart[0] != '{') {
          $s .= ' ';
        }
        $s .= $elsePart;
      }
      else {
        $s .= $thenPart;
      }
      break;
    case KEYWORD_SWITCH:
      $s = 'switch(' . $this
        ->parseTree($n->discriminant) . '){';
      $cases = $n->cases;
      for ($i = 0, $j = count($cases); $i < $j; $i++) {
        $case = $cases[$i];
        if ($case->type == KEYWORD_CASE) {
          $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this
            ->parseTree($case->caseLabel) . ':';
        }
        else {
          $s .= 'default:';
        }
        $statement = $this
          ->parseTree($case->statements, true);
        if ($statement) {
          $s .= $statement;

          // no terminator for last statement
          if ($i + 1 < $j) {
            $s .= ';';
          }
        }
      }
      $s .= '}';
      break;
    case KEYWORD_FOR:
      $s = 'for(' . ($n->setup ? $this
        ->parseTree($n->setup) : '') . ';' . ($n->condition ? $this
        ->parseTree($n->condition) : '') . ';' . ($n->update ? $this
        ->parseTree($n->update) : '') . ')';
      $body = $this
        ->parseTree($n->body);
      if ($body == '') {
        $body = ';';
      }
      $s .= $body;
      break;
    case KEYWORD_WHILE:
      $s = 'while(' . $this
        ->parseTree($n->condition) . ')';
      $body = $this
        ->parseTree($n->body);
      if ($body == '') {
        $body = ';';
      }
      $s .= $body;
      break;
    case JS_FOR_IN:
      $s = 'for(' . ($n->varDecl ? $this
        ->parseTree($n->varDecl) : $this
        ->parseTree($n->iterator)) . ' in ' . $this
        ->parseTree($n->object) . ')';
      $body = $this
        ->parseTree($n->body);
      if ($body == '') {
        $body = ';';
      }
      $s .= $body;
      break;
    case KEYWORD_DO:
      $s = 'do{' . $this
        ->parseTree($n->body, true) . '}while(' . $this
        ->parseTree($n->condition) . ')';
      break;
    case KEYWORD_BREAK:
    case KEYWORD_CONTINUE:
      $s = $n->value . ($n->label ? ' ' . $n->label : '');
      break;
    case KEYWORD_TRY:
      $s = 'try{' . $this
        ->parseTree($n->tryBlock, true) . '}';
      $catchClauses = $n->catchClauses;
      for ($i = 0, $j = count($catchClauses); $i < $j; $i++) {
        $t = $catchClauses[$i];
        $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this
          ->parseTree($t->guard) : '') . '){' . $this
          ->parseTree($t->block, true) . '}';
      }
      if ($n->finallyBlock) {
        $s .= 'finally{' . $this
          ->parseTree($n->finallyBlock, true) . '}';
      }
      break;
    case KEYWORD_THROW:
    case KEYWORD_RETURN:
      $s = $n->type;
      if ($n->value) {
        $t = $this
          ->parseTree($n->value);
        if (strlen($t)) {
          if ($this
            ->isWordChar($t[0]) || $t[0] == '\\') {
            $s .= ' ';
          }
          $s .= $t;
        }
      }
      break;
    case KEYWORD_WITH:
      $s = 'with(' . $this
        ->parseTree($n->object) . ')' . $this
        ->parseTree($n->body);
      break;
    case KEYWORD_VAR:
    case KEYWORD_CONST:
      $s = $n->value . ' ';
      $childs = $n->treeNodes;
      for ($i = 0, $j = count($childs); $i < $j; $i++) {
        $t = $childs[$i];
        $s .= ($i ? ',' : '') . $t->name;
        $u = $t->initializer;
        if ($u) {
          $s .= '=' . $this
            ->parseTree($u);
        }
      }
      break;
    case KEYWORD_IN:
    case KEYWORD_INSTANCEOF:
      $left = $this
        ->parseTree($n->treeNodes[0]);
      $right = $this
        ->parseTree($n->treeNodes[1]);
      $s = $left;
      if ($this
        ->isWordChar(substr($left, -1))) {
        $s .= ' ';
      }
      $s .= $n->type;
      if ($this
        ->isWordChar($right[0]) || $right[0] == '\\') {
        $s .= ' ';
      }
      $s .= $right;
      break;
    case KEYWORD_DELETE:
    case KEYWORD_TYPEOF:
      $right = $this
        ->parseTree($n->treeNodes[0]);
      $s = $n->type;
      if ($this
        ->isWordChar($right[0]) || $right[0] == '\\') {
        $s .= ' ';
      }
      $s .= $right;
      break;
    case KEYWORD_VOID:
      $s = 'void(' . $this
        ->parseTree($n->treeNodes[0]) . ')';
      break;
    case KEYWORD_DEBUGGER:
      throw new Exception('NOT IMPLEMENTED: DEBUGGER');
      break;
    case TOKEN_CONDCOMMENT_START:
    case TOKEN_CONDCOMMENT_END:
      $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : '');
      $childs = $n->treeNodes;
      for ($i = 0, $j = count($childs); $i < $j; $i++) {
        $s .= $this
          ->parseTree($childs[$i]);
      }
      break;
    case OP_SEMICOLON:
      if ($expression = $n->expression) {
        $s = $this
          ->parseTree($expression);
      }
      break;
    case JS_LABEL:
      $s = $n->label . ':' . $this
        ->parseTree($n->statement);
      break;
    case OP_COMMA:
      $childs = $n->treeNodes;
      for ($i = 0, $j = count($childs); $i < $j; $i++) {
        $s .= ($i ? ',' : '') . $this
          ->parseTree($childs[$i]);
      }
      break;
    case OP_ASSIGN:
      $s = $this
        ->parseTree($n->treeNodes[0]) . $n->value . $this
        ->parseTree($n->treeNodes[1]);
      break;
    case OP_HOOK:
      $s = $this
        ->parseTree($n->treeNodes[0]) . '?' . $this
        ->parseTree($n->treeNodes[1]) . ':' . $this
        ->parseTree($n->treeNodes[2]);
      break;
    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 OP_LSH:
    case OP_RSH:
    case OP_URSH:
    case OP_MUL:
    case OP_DIV:
    case OP_MOD:
      $s = $this
        ->parseTree($n->treeNodes[0]) . $n->type . $this
        ->parseTree($n->treeNodes[1]);
      break;
    case OP_PLUS:
    case OP_MINUS:
      $left = $this
        ->parseTree($n->treeNodes[0]);
      $right = $this
        ->parseTree($n->treeNodes[1]);
      switch ($n->treeNodes[1]->type) {
        case OP_PLUS:
        case OP_MINUS:
        case OP_INCREMENT:
        case OP_DECREMENT:
        case OP_UNARY_PLUS:
        case OP_UNARY_MINUS:
          $s = $left . $n->type . ' ' . $right;
          break;
        case TOKEN_STRING:

          //combine concatted strings with same quotestyle
          if ($n->type == OP_PLUS && substr($left, -1) == $right[0]) {
            $s = substr($left, 0, -1) . substr($right, 1);
            break;
          }

        // FALL THROUGH
        default:
          $s = $left . $n->type . $right;
      }
      break;
    case OP_NOT:
    case OP_BITWISE_NOT:
    case OP_UNARY_PLUS:
    case OP_UNARY_MINUS:
      $s = $n->value . $this
        ->parseTree($n->treeNodes[0]);
      break;
    case OP_INCREMENT:
    case OP_DECREMENT:
      if ($n->postfix) {
        $s = $this
          ->parseTree($n->treeNodes[0]) . $n->value;
      }
      else {
        $s = $n->value . $this
          ->parseTree($n->treeNodes[0]);
      }
      break;
    case OP_DOT:
      $s = $this
        ->parseTree($n->treeNodes[0]) . '.' . $this
        ->parseTree($n->treeNodes[1]);
      break;
    case JS_INDEX:
      $s = $this
        ->parseTree($n->treeNodes[0]);

      // See if we can replace named index with a dot saving 3 bytes
      if ($n->treeNodes[0]->type == TOKEN_IDENTIFIER && $n->treeNodes[1]->type == TOKEN_STRING && $this
        ->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))) {
        $s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
      }
      else {
        $s .= '[' . $this
          ->parseTree($n->treeNodes[1]) . ']';
      }
      break;
    case JS_LIST:
      $childs = $n->treeNodes;
      for ($i = 0, $j = count($childs); $i < $j; $i++) {
        $s .= ($i ? ',' : '') . $this
          ->parseTree($childs[$i]);
      }
      break;
    case JS_CALL:
      $s = $this
        ->parseTree($n->treeNodes[0]) . '(' . $this
        ->parseTree($n->treeNodes[1]) . ')';
      break;
    case KEYWORD_NEW:
    case JS_NEW_WITH_ARGS:
      $s = 'new ' . $this
        ->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this
        ->parseTree($n->treeNodes[1]) : '') . ')';
      break;
    case JS_ARRAY_INIT:
      $s = '[';
      $childs = $n->treeNodes;
      for ($i = 0, $j = count($childs); $i < $j; $i++) {
        $s .= ($i ? ',' : '') . $this
          ->parseTree($childs[$i]);
      }
      $s .= ']';
      break;
    case JS_OBJECT_INIT:
      $s = '{';
      $childs = $n->treeNodes;
      for ($i = 0, $j = count($childs); $i < $j; $i++) {
        $t = $childs[$i];
        if ($i) {
          $s .= ',';
        }
        if ($t->type == JS_PROPERTY_INIT) {

          // Ditch the quotes when the index is a valid identifier
          if ($t->treeNodes[0]->type == TOKEN_STRING && $this
            ->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))) {
            $s .= substr($t->treeNodes[0]->value, 1, -1);
          }
          else {
            $s .= $t->treeNodes[0]->value;
          }
          $s .= ':' . $this
            ->parseTree($t->treeNodes[1]);
        }
        else {
          $s .= $t->type == JS_GETTER ? 'get' : 'set';
          $s .= ' ' . $t->name . '(';
          $params = $t->params;
          for ($i = 0, $j = count($params); $i < $j; $i++) {
            $s .= ($i ? ',' : '') . $params[$i];
          }
          $s .= '){' . $this
            ->parseTree($t->body, true) . '}';
        }
      }
      $s .= '}';
      break;
    case TOKEN_NUMBER:
      $s = $n->value;
      if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m)) {
        $s = $m[1] . 'e' . strlen($m[2]);
      }
      break;
    case KEYWORD_NULL:
    case KEYWORD_THIS:
    case KEYWORD_TRUE:
    case KEYWORD_FALSE:
    case TOKEN_IDENTIFIER:
    case TOKEN_STRING:
    case TOKEN_REGEXP:
      $s = $n->value;
      break;
    case JS_GROUP:
      if (in_array($n->treeNodes[0]->type, array(
        JS_ARRAY_INIT,
        JS_OBJECT_INIT,
        JS_GROUP,
        TOKEN_NUMBER,
        TOKEN_STRING,
        TOKEN_REGEXP,
        TOKEN_IDENTIFIER,
        KEYWORD_NULL,
        KEYWORD_THIS,
        KEYWORD_TRUE,
        KEYWORD_FALSE,
      ))) {
        $s = $this
          ->parseTree($n->treeNodes[0]);
      }
      else {
        $s = '(' . $this
          ->parseTree($n->treeNodes[0]) . ')';
      }
      break;
    default:
      throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
  }
  return $s;
}