Same name in other branches
  1. 5.0.x advagg_js_minify/jsminplus.inc \JSMinPlus::parseTree()
  2. 6.0.x advagg_js_minify/jsminplus.inc \JSMinPlus::parseTree()
  3. 7.x-2.x advagg_js_compress/jsminplus.inc \JSMinPlus::parseTree()
  4. 8.x-2.x advagg_js_minify/jsminplus.inc \JSMinPlus::parseTree()
  5. 8.x-3.x advagg_js_minify/jsminplus.inc \JSMinPlus::parseTree()
  6. 8.x-4.x advagg_js_minify/jsminplus.inc \JSMinPlus::parseTree()
1 call to JSMinPlus::parseTree()
JSMinPlus::min in advagg_js_compress/jsminplus.inc

File

advagg_js_compress/jsminplus.inc, line 273

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;
}