All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mozilla.javascript.IRFactory Maven / Gradle / Ivy

Go to download

Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.

The newest version!
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript;

import java.util.ArrayList;
import java.util.List;
import org.mozilla.javascript.ast.ArrayComprehension;
import org.mozilla.javascript.ast.ArrayComprehensionLoop;
import org.mozilla.javascript.ast.ArrayLiteral;
import org.mozilla.javascript.ast.Assignment;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.BigIntLiteral;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.BreakStatement;
import org.mozilla.javascript.ast.CatchClause;
import org.mozilla.javascript.ast.ConditionalExpression;
import org.mozilla.javascript.ast.ContinueStatement;
import org.mozilla.javascript.ast.DestructuringForm;
import org.mozilla.javascript.ast.DoLoop;
import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.EmptyExpression;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.ForInLoop;
import org.mozilla.javascript.ast.ForLoop;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.GeneratorExpression;
import org.mozilla.javascript.ast.GeneratorExpressionLoop;
import org.mozilla.javascript.ast.IfStatement;
import org.mozilla.javascript.ast.InfixExpression;
import org.mozilla.javascript.ast.Jump;
import org.mozilla.javascript.ast.KeywordLiteral;
import org.mozilla.javascript.ast.Label;
import org.mozilla.javascript.ast.LabeledStatement;
import org.mozilla.javascript.ast.LetNode;
import org.mozilla.javascript.ast.Loop;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NewExpression;
import org.mozilla.javascript.ast.NumberLiteral;
import org.mozilla.javascript.ast.ObjectLiteral;
import org.mozilla.javascript.ast.ObjectProperty;
import org.mozilla.javascript.ast.ParenthesizedExpression;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.RegExpLiteral;
import org.mozilla.javascript.ast.ReturnStatement;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.SwitchCase;
import org.mozilla.javascript.ast.SwitchStatement;
import org.mozilla.javascript.ast.Symbol;
import org.mozilla.javascript.ast.TaggedTemplateLiteral;
import org.mozilla.javascript.ast.TemplateCharacters;
import org.mozilla.javascript.ast.TemplateLiteral;
import org.mozilla.javascript.ast.ThrowStatement;
import org.mozilla.javascript.ast.TryStatement;
import org.mozilla.javascript.ast.UnaryExpression;
import org.mozilla.javascript.ast.UpdateExpression;
import org.mozilla.javascript.ast.VariableDeclaration;
import org.mozilla.javascript.ast.VariableInitializer;
import org.mozilla.javascript.ast.WhileLoop;
import org.mozilla.javascript.ast.WithStatement;
import org.mozilla.javascript.ast.XmlDotQuery;
import org.mozilla.javascript.ast.XmlElemRef;
import org.mozilla.javascript.ast.XmlExpression;
import org.mozilla.javascript.ast.XmlFragment;
import org.mozilla.javascript.ast.XmlLiteral;
import org.mozilla.javascript.ast.XmlMemberGet;
import org.mozilla.javascript.ast.XmlPropRef;
import org.mozilla.javascript.ast.XmlRef;
import org.mozilla.javascript.ast.XmlString;
import org.mozilla.javascript.ast.Yield;

/**
 * This class rewrites the parse tree into an IR suitable for codegen.
 *
 * @see Node
 * @author Mike McCabe
 * @author Norris Boyd
 */
public final class IRFactory {
    private static final int LOOP_DO_WHILE = 0;
    private static final int LOOP_WHILE = 1;
    private static final int LOOP_FOR = 2;

    private static final int ALWAYS_TRUE_BOOLEAN = 1;
    private static final int ALWAYS_FALSE_BOOLEAN = -1;

    private Parser parser;
    private Decompiler decompiler = new Decompiler();

    public IRFactory(CompilerEnvirons env) {
        this(env, env.getErrorReporter());
    }

    public IRFactory(CompilerEnvirons env, ErrorReporter errorReporter) {
        parser = new Parser(env, errorReporter);
    }

    /**
     * Transforms the tree into a lower-level IR suitable for codegen. Optionally generates the
     * encoded source.
     */
    public ScriptNode transformTree(AstRoot root) {
        parser.currentScriptOrFn = root;
        parser.inUseStrictDirective = root.isInStrictMode();
        int sourceStartOffset = decompiler.getCurrentOffset();

        if (Token.printTrees) {
            System.out.println("IRFactory.transformTree");
            System.out.println(root.debugPrint());
        }
        ScriptNode script = (ScriptNode) transform(root);

        int sourceEndOffset = decompiler.getCurrentOffset();
        script.setEncodedSourceBounds(sourceStartOffset, sourceEndOffset);

        if (parser.compilerEnv.isGeneratingSource()) {
            script.setEncodedSource(decompiler.getEncodedSource());
        }

        decompiler = null;
        return script;
    }

    // Might want to convert this to polymorphism - move transform*
    // functions into the AstNode subclasses.  OTOH that would make
    // IR transformation part of the public AST API - desirable?
    // Another possibility:  create AstTransformer interface and adapter.
    public Node transform(AstNode node) {
        switch (node.getType()) {
            case Token.ARRAYCOMP:
                return transformArrayComp((ArrayComprehension) node);
            case Token.ARRAYLIT:
                return transformArrayLiteral((ArrayLiteral) node);
            case Token.BIGINT:
                return transformBigInt((BigIntLiteral) node);
            case Token.BLOCK:
                return transformBlock(node);
            case Token.BREAK:
                return transformBreak((BreakStatement) node);
            case Token.CALL:
                return transformFunctionCall((FunctionCall) node);
            case Token.CONTINUE:
                return transformContinue((ContinueStatement) node);
            case Token.DO:
                return transformDoLoop((DoLoop) node);
            case Token.EMPTY:
            case Token.COMMENT:
                return node;
            case Token.FOR:
                if (node instanceof ForInLoop) {
                    return transformForInLoop((ForInLoop) node);
                }
                return transformForLoop((ForLoop) node);
            case Token.FUNCTION:
                return transformFunction((FunctionNode) node);
            case Token.GENEXPR:
                return transformGenExpr((GeneratorExpression) node);
            case Token.GETELEM:
                return transformElementGet((ElementGet) node);
            case Token.GETPROP:
                return transformPropertyGet((PropertyGet) node);
            case Token.HOOK:
                return transformCondExpr((ConditionalExpression) node);
            case Token.IF:
                return transformIf((IfStatement) node);

            case Token.TRUE:
            case Token.FALSE:
            case Token.THIS:
            case Token.NULL:
            case Token.DEBUGGER:
                return transformLiteral(node);

            case Token.NAME:
                return transformName((Name) node);
            case Token.NUMBER:
                return transformNumber((NumberLiteral) node);
            case Token.NEW:
                return transformNewExpr((NewExpression) node);
            case Token.OBJECTLIT:
                return transformObjectLiteral((ObjectLiteral) node);
            case Token.TEMPLATE_LITERAL:
                return transformTemplateLiteral((TemplateLiteral) node);
            case Token.TAGGED_TEMPLATE_LITERAL:
                return transformTemplateLiteralCall((TaggedTemplateLiteral) node);
            case Token.REGEXP:
                return transformRegExp((RegExpLiteral) node);
            case Token.RETURN:
                return transformReturn((ReturnStatement) node);
            case Token.SCRIPT:
                return transformScript((ScriptNode) node);
            case Token.STRING:
                return transformString((StringLiteral) node);
            case Token.SWITCH:
                return transformSwitch((SwitchStatement) node);
            case Token.THROW:
                return transformThrow((ThrowStatement) node);
            case Token.TRY:
                return transformTry((TryStatement) node);
            case Token.WHILE:
                return transformWhileLoop((WhileLoop) node);
            case Token.WITH:
                return transformWith((WithStatement) node);
            case Token.YIELD:
            case Token.YIELD_STAR:
                return transformYield((Yield) node);
            default:
                if (node instanceof ExpressionStatement) {
                    return transformExprStmt((ExpressionStatement) node);
                }
                if (node instanceof Assignment) {
                    return transformAssignment((Assignment) node);
                }
                if (node instanceof UnaryExpression) {
                    return transformUnary((UnaryExpression) node);
                }
                if (node instanceof UpdateExpression) {
                    return transformUpdate((UpdateExpression) node);
                }
                if (node instanceof XmlMemberGet) {
                    return transformXmlMemberGet((XmlMemberGet) node);
                }
                if (node instanceof InfixExpression) {
                    return transformInfix((InfixExpression) node);
                }
                if (node instanceof VariableDeclaration) {
                    return transformVariables((VariableDeclaration) node);
                }
                if (node instanceof ParenthesizedExpression) {
                    return transformParenExpr((ParenthesizedExpression) node);
                }
                if (node instanceof LabeledStatement) {
                    return transformLabeledStatement((LabeledStatement) node);
                }
                if (node instanceof LetNode) {
                    return transformLetNode((LetNode) node);
                }
                if (node instanceof XmlRef) {
                    return transformXmlRef((XmlRef) node);
                }
                if (node instanceof XmlLiteral) {
                    return transformXmlLiteral((XmlLiteral) node);
                }
                throw new IllegalArgumentException("Can't transform: " + node);
        }
    }

    private Node transformArrayComp(ArrayComprehension node) {
        // An array comprehension expression such as
        //
        //   [expr for (x in foo) for each ([y, z] in bar) if (cond)]
        //
        // is rewritten approximately as
        //
        // new Scope(ARRAYCOMP) {
        //   new Node(BLOCK) {
        //     let tmp1 = new Array;
        //     for (let x in foo) {
        //       for each (let tmp2 in bar) {
        //         if (cond) {
        //           tmp1.push([y, z] = tmp2, expr);
        //         }
        //       }
        //     }
        //   }
        //   createName(tmp1)
        // }

        int lineno = node.getLineno();
        Scope scopeNode = parser.createScopeNode(Token.ARRAYCOMP, lineno);
        String arrayName = parser.currentScriptOrFn.getNextTempName();
        parser.pushScope(scopeNode);
        try {
            parser.defineSymbol(Token.LET, arrayName, false);
            Node block = new Node(Token.BLOCK, lineno);
            Node newArray = createCallOrNew(Token.NEW, parser.createName("Array"));
            Node init =
                    new Node(
                            Token.EXPR_VOID,
                            createAssignment(Token.ASSIGN, parser.createName(arrayName), newArray),
                            lineno);
            block.addChildToBack(init);
            block.addChildToBack(arrayCompTransformHelper(node, arrayName));
            scopeNode.addChildToBack(block);
            scopeNode.addChildToBack(parser.createName(arrayName));
            return scopeNode;
        } finally {
            parser.popScope();
        }
    }

    private Node arrayCompTransformHelper(ArrayComprehension node, String arrayName) {
        decompiler.addToken(Token.LB);
        int lineno = node.getLineno();
        Node expr = transform(node.getResult());

        List loops = node.getLoops();
        int numLoops = loops.size();

        // Walk through loops, collecting and defining their iterator symbols.
        Node[] iterators = new Node[numLoops];
        Node[] iteratedObjs = new Node[numLoops];

        for (int i = 0; i < numLoops; i++) {
            ArrayComprehensionLoop acl = loops.get(i);
            decompiler.addName(" ");
            decompiler.addToken(Token.FOR);
            if (acl.isForEach()) {
                decompiler.addName("each ");
            }
            decompiler.addToken(Token.LP);

            AstNode iter = acl.getIterator();
            String name = null;
            if (iter.getType() == Token.NAME) {
                name = iter.getString();
                decompiler.addName(name);
            } else {
                // destructuring assignment
                decompile(iter);
                name = parser.currentScriptOrFn.getNextTempName();
                parser.defineSymbol(Token.LP, name, false);
                expr =
                        createBinary(
                                Token.COMMA,
                                createAssignment(Token.ASSIGN, iter, parser.createName(name)),
                                expr);
            }
            Node init = parser.createName(name);
            // Define as a let since we want the scope of the variable to
            // be restricted to the array comprehension
            parser.defineSymbol(Token.LET, name, false);
            iterators[i] = init;

            if (acl.isForOf()) {
                decompiler.addName("of ");
            } else {
                decompiler.addToken(Token.IN);
            }
            iteratedObjs[i] = transform(acl.getIteratedObject());
            decompiler.addToken(Token.RP);
        }

        // generate code for tmpArray.push(body)
        Node call =
                createCallOrNew(
                        Token.CALL,
                        createPropertyGet(parser.createName(arrayName), null, "push", 0));

        Node body = new Node(Token.EXPR_VOID, call, lineno);

        if (node.getFilter() != null) {
            decompiler.addName(" ");
            decompiler.addToken(Token.IF);
            decompiler.addToken(Token.LP);
            body = createIf(transform(node.getFilter()), body, null, lineno);
            decompiler.addToken(Token.RP);
        }

        // Now walk loops in reverse to build up the body statement.
        int pushed = 0;
        try {
            for (int i = numLoops - 1; i >= 0; i--) {
                ArrayComprehensionLoop acl = loops.get(i);
                Scope loop =
                        createLoopNode(
                                null, // no label
                                acl.getLineno());
                parser.pushScope(loop);
                pushed++;
                body =
                        createForIn(
                                Token.LET,
                                loop,
                                iterators[i],
                                iteratedObjs[i],
                                body,
                                acl.isForEach(),
                                acl.isForOf());
            }
        } finally {
            for (int i = 0; i < pushed; i++) {
                parser.popScope();
            }
        }

        decompiler.addToken(Token.RB);

        // Now that we've accumulated any destructuring forms,
        // add expr to the call node; it's pushed on each iteration.
        call.addChildToBack(expr);
        return body;
    }

    private Node transformArrayLiteral(ArrayLiteral node) {
        if (node.isDestructuring()) {
            return node;
        }
        decompiler.addToken(Token.LB);
        List elems = node.getElements();
        Node array = new Node(Token.ARRAYLIT);
        List skipIndexes = null;
        for (int i = 0; i < elems.size(); ++i) {
            AstNode elem = elems.get(i);
            if (elem.getType() != Token.EMPTY) {
                array.addChildToBack(transform(elem));
            } else {
                if (skipIndexes == null) {
                    skipIndexes = new ArrayList<>();
                }
                skipIndexes.add(Integer.valueOf(i));
            }
            if (i < elems.size() - 1) decompiler.addToken(Token.COMMA);
        }
        decompiler.addToken(Token.RB);
        array.putIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, node.getDestructuringLength());
        if (skipIndexes != null) {
            int[] skips = new int[skipIndexes.size()];
            for (int i = 0; i < skipIndexes.size(); i++) skips[i] = skipIndexes.get(i).intValue();
            array.putProp(Node.SKIP_INDEXES_PROP, skips);
        }
        return array;
    }

    private Node transformAssignment(Assignment node) {
        AstNode right = node.getRight();
        AstNode left = parser.removeParens(node.getLeft());
        left = transformAssignmentLeft(node, left, right);

        Node target = null;
        if (isDestructuring(left)) {
            decompile(left);
            target = left;
        } else {
            target = transform(left);
        }

        decompiler.addToken(node.getType());
        return createAssignment(node.getType(), target, transform(right));
    }

    private AstNode transformAssignmentLeft(Assignment node, AstNode left, AstNode right) {
        if (right.getType() == Token.NULL
                && node.getType() == Token.ASSIGN
                && left instanceof Name
                && right instanceof KeywordLiteral) {

            String identifier = ((Name) left).getIdentifier();
            for (AstNode p = node.getParent(); p != null; p = p.getParent()) {
                if (p instanceof FunctionNode) {
                    Name functionName = ((FunctionNode) p).getFunctionName();
                    if (functionName != null && functionName.getIdentifier().equals(identifier)) {
                        PropertyGet propertyGet = new PropertyGet();
                        KeywordLiteral thisKeyword = new KeywordLiteral();
                        thisKeyword.setType(Token.THIS);
                        propertyGet.setLeft(thisKeyword);
                        propertyGet.setRight(left);
                        node.setLeft(propertyGet);
                        return propertyGet;
                    }
                }
            }
        }
        return left;
    }

    private Node transformBigInt(BigIntLiteral node) {
        decompiler.addBigInt(node.getBigInt());
        return node;
    }

    private Node transformBlock(AstNode node) {
        if (node instanceof Scope) {
            parser.pushScope((Scope) node);
        }
        try {
            List kids = new ArrayList<>();
            for (Node kid : node) {
                kids.add(transform((AstNode) kid));
            }
            node.removeChildren();
            for (Node kid : kids) {
                node.addChildToBack(kid);
            }
            return node;
        } finally {
            if (node instanceof Scope) {
                parser.popScope();
            }
        }
    }

    private Node transformBreak(BreakStatement node) {
        decompiler.addToken(Token.BREAK);
        if (node.getBreakLabel() != null) {
            decompiler.addName(node.getBreakLabel().getIdentifier());
        }
        decompiler.addEOL(Token.SEMI);
        return node;
    }

    private Node transformCondExpr(ConditionalExpression node) {
        Node test = transform(node.getTestExpression());
        decompiler.addToken(Token.HOOK);
        Node ifTrue = transform(node.getTrueExpression());
        decompiler.addToken(Token.COLON);
        Node ifFalse = transform(node.getFalseExpression());
        return createCondExpr(test, ifTrue, ifFalse);
    }

    private Node transformContinue(ContinueStatement node) {
        decompiler.addToken(Token.CONTINUE);
        if (node.getLabel() != null) {
            decompiler.addName(node.getLabel().getIdentifier());
        }
        decompiler.addEOL(Token.SEMI);
        return node;
    }

    private Node transformDoLoop(DoLoop loop) {
        loop.setType(Token.LOOP);
        parser.pushScope(loop);
        try {
            decompiler.addToken(Token.DO);
            decompiler.addEOL(Token.LC);
            Node body = transform(loop.getBody());
            decompiler.addToken(Token.RC);
            decompiler.addToken(Token.WHILE);
            decompiler.addToken(Token.LP);
            Node cond = transform(loop.getCondition());
            decompiler.addToken(Token.RP);
            decompiler.addEOL(Token.SEMI);
            return createLoop(loop, LOOP_DO_WHILE, body, cond, null, null);
        } finally {
            parser.popScope();
        }
    }

    private Node transformElementGet(ElementGet node) {
        // OPT: could optimize to createPropertyGet
        // iff elem is string that can not be number
        Node target = transform(node.getTarget());
        decompiler.addToken(Token.LB);
        Node element = transform(node.getElement());
        decompiler.addToken(Token.RB);
        return new Node(Token.GETELEM, target, element);
    }

    private Node transformExprStmt(ExpressionStatement node) {
        Node expr = transform(node.getExpression());
        decompiler.addEOL(Token.SEMI);
        return new Node(node.getType(), expr, node.getLineno());
    }

    private Node transformForInLoop(ForInLoop loop) {
        decompiler.addToken(Token.FOR);
        if (loop.isForEach()) decompiler.addName("each ");
        decompiler.addToken(Token.LP);

        loop.setType(Token.LOOP);
        parser.pushScope(loop);
        try {
            int declType = -1;
            AstNode iter = loop.getIterator();
            if (iter instanceof VariableDeclaration) {
                declType = iter.getType();
            }
            Node lhs = transform(iter);
            if (loop.isForOf()) {
                decompiler.addName("of ");
            } else {
                decompiler.addToken(Token.IN);
            }
            Node obj = transform(loop.getIteratedObject());
            decompiler.addToken(Token.RP);
            decompiler.addEOL(Token.LC);
            Node body = transform(loop.getBody());
            decompiler.addEOL(Token.RC);
            return createForIn(declType, loop, lhs, obj, body, loop.isForEach(), loop.isForOf());
        } finally {
            parser.popScope();
        }
    }

    private Node transformForLoop(ForLoop loop) {
        decompiler.addToken(Token.FOR);
        decompiler.addToken(Token.LP);
        loop.setType(Token.LOOP);
        // XXX: Can't use pushScope/popScope here since 'createFor' may split
        // the scope
        Scope savedScope = parser.currentScope;
        parser.currentScope = loop;
        try {
            Node init = transform(loop.getInitializer());
            decompiler.addToken(Token.SEMI);
            Node test = transform(loop.getCondition());
            decompiler.addToken(Token.SEMI);
            Node incr = transform(loop.getIncrement());
            decompiler.addToken(Token.RP);
            decompiler.addEOL(Token.LC);
            Node body = transform(loop.getBody());
            decompiler.addEOL(Token.RC);
            return createFor(loop, init, test, incr, body);
        } finally {
            parser.currentScope = savedScope;
        }
    }

    private Node transformFunction(FunctionNode fn) {
        int functionType = fn.getFunctionType();
        int start = decompiler.markFunctionStart(functionType, fn.isES6Generator());
        Node mexpr = decompileFunctionHeader(fn);
        int index = parser.currentScriptOrFn.addFunction(fn);

        Parser.PerFunctionVariables savedVars = parser.createPerFunctionVariables(fn);
        try {
            // If we start needing to record much more codegen metadata during
            // function parsing, we should lump it all into a helper class.
            Node destructuring = (Node) fn.getProp(Node.DESTRUCTURING_PARAMS);
            fn.removeProp(Node.DESTRUCTURING_PARAMS);

            int lineno = fn.getBody().getLineno();
            ++parser.nestingOfFunction; // only for body, not params
            Node body = transform(fn.getBody());

            if (!fn.isExpressionClosure()) {
                decompiler.addToken(Token.RC);
            }
            fn.setEncodedSourceBounds(start, decompiler.markFunctionEnd(start));

            if (functionType != FunctionNode.FUNCTION_EXPRESSION && !fn.isExpressionClosure()) {
                // Add EOL only if function is not part of expression
                // since it gets SEMI + EOL from Statement in that case
                decompiler.addToken(Token.EOL);
            }

            if (destructuring != null) {
                body.addChildToFront(new Node(Token.EXPR_VOID, destructuring, lineno));
            }

            int syntheticType = fn.getFunctionType();
            Node pn = initFunction(fn, index, body, syntheticType);
            if (mexpr != null) {
                pn = createAssignment(Token.ASSIGN, mexpr, pn);
                if (syntheticType != FunctionNode.FUNCTION_EXPRESSION) {
                    pn = createExprStatementNoReturn(pn, fn.getLineno());
                }
            }
            return pn;

        } finally {
            --parser.nestingOfFunction;
            savedVars.restore();
        }
    }

    private Node transformFunctionCall(FunctionCall node) {
        Node call = createCallOrNew(Token.CALL, transform(node.getTarget()));
        call.setLineno(node.getLineno());
        decompiler.addToken(Token.LP);
        List args = node.getArguments();
        for (int i = 0; i < args.size(); i++) {
            AstNode arg = args.get(i);
            call.addChildToBack(transform(arg));
            if (i < args.size() - 1) {
                decompiler.addToken(Token.COMMA);
            }
        }
        decompiler.addToken(Token.RP);
        return call;
    }

    private Node transformGenExpr(GeneratorExpression node) {
        Node pn;

        FunctionNode fn = new FunctionNode();
        fn.setSourceName(parser.currentScriptOrFn.getNextTempName());
        fn.setIsGenerator();
        fn.setFunctionType(FunctionNode.FUNCTION_EXPRESSION);
        fn.setRequiresActivation();

        int functionType = fn.getFunctionType();
        int start = decompiler.markFunctionStart(functionType, false);
        Node mexpr = decompileFunctionHeader(fn);
        int index = parser.currentScriptOrFn.addFunction(fn);

        Parser.PerFunctionVariables savedVars = parser.createPerFunctionVariables(fn);
        try {
            // If we start needing to record much more codegen metadata during
            // function parsing, we should lump it all into a helper class.
            Node destructuring = (Node) fn.getProp(Node.DESTRUCTURING_PARAMS);
            fn.removeProp(Node.DESTRUCTURING_PARAMS);

            int lineno = node.lineno;
            ++parser.nestingOfFunction; // only for body, not params
            Node body = genExprTransformHelper(node);

            if (!fn.isExpressionClosure()) {
                decompiler.addToken(Token.RC);
            }
            fn.setEncodedSourceBounds(start, decompiler.markFunctionEnd(start));

            if (functionType != FunctionNode.FUNCTION_EXPRESSION && !fn.isExpressionClosure()) {
                // Add EOL only if function is not part of expression
                // since it gets SEMI + EOL from Statement in that case
                decompiler.addToken(Token.EOL);
            }

            if (destructuring != null) {
                body.addChildToFront(new Node(Token.EXPR_VOID, destructuring, lineno));
            }

            int syntheticType = fn.getFunctionType();
            pn = initFunction(fn, index, body, syntheticType);
            if (mexpr != null) {
                pn = createAssignment(Token.ASSIGN, mexpr, pn);
                if (syntheticType != FunctionNode.FUNCTION_EXPRESSION) {
                    pn = createExprStatementNoReturn(pn, fn.getLineno());
                }
            }
        } finally {
            --parser.nestingOfFunction;
            savedVars.restore();
        }

        Node call = createCallOrNew(Token.CALL, pn);
        call.setLineno(node.getLineno());
        decompiler.addToken(Token.LP);
        decompiler.addToken(Token.RP);
        return call;
    }

    private Node genExprTransformHelper(GeneratorExpression node) {
        decompiler.addToken(Token.LP);
        int lineno = node.getLineno();
        Node expr = transform(node.getResult());

        List loops = node.getLoops();
        int numLoops = loops.size();

        // Walk through loops, collecting and defining their iterator symbols.
        Node[] iterators = new Node[numLoops];
        Node[] iteratedObjs = new Node[numLoops];

        for (int i = 0; i < numLoops; i++) {
            GeneratorExpressionLoop acl = loops.get(i);
            decompiler.addName(" ");
            decompiler.addToken(Token.FOR);
            decompiler.addToken(Token.LP);

            AstNode iter = acl.getIterator();
            String name = null;
            if (iter.getType() == Token.NAME) {
                name = iter.getString();
                decompiler.addName(name);
            } else {
                // destructuring assignment
                decompile(iter);
                name = parser.currentScriptOrFn.getNextTempName();
                parser.defineSymbol(Token.LP, name, false);
                expr =
                        createBinary(
                                Token.COMMA,
                                createAssignment(Token.ASSIGN, iter, parser.createName(name)),
                                expr);
            }
            Node init = parser.createName(name);
            // Define as a let since we want the scope of the variable to
            // be restricted to the array comprehension
            parser.defineSymbol(Token.LET, name, false);
            iterators[i] = init;

            if (acl.isForOf()) {
                decompiler.addName("of ");
            } else {
                decompiler.addToken(Token.IN);
            }
            iteratedObjs[i] = transform(acl.getIteratedObject());
            decompiler.addToken(Token.RP);
        }

        // generate code for tmpArray.push(body)
        Node yield = new Node(Token.YIELD, expr, node.getLineno());

        Node body = new Node(Token.EXPR_VOID, yield, lineno);

        if (node.getFilter() != null) {
            decompiler.addName(" ");
            decompiler.addToken(Token.IF);
            decompiler.addToken(Token.LP);
            body = createIf(transform(node.getFilter()), body, null, lineno);
            decompiler.addToken(Token.RP);
        }

        // Now walk loops in reverse to build up the body statement.
        int pushed = 0;
        try {
            for (int i = numLoops - 1; i >= 0; i--) {
                GeneratorExpressionLoop acl = loops.get(i);
                Scope loop =
                        createLoopNode(
                                null, // no label
                                acl.getLineno());
                parser.pushScope(loop);
                pushed++;
                body =
                        createForIn(
                                Token.LET,
                                loop,
                                iterators[i],
                                iteratedObjs[i],
                                body,
                                acl.isForEach(),
                                acl.isForOf());
            }
        } finally {
            for (int i = 0; i < pushed; i++) {
                parser.popScope();
            }
        }

        decompiler.addToken(Token.RP);

        return body;
    }

    private Node transformIf(IfStatement n) {
        decompiler.addToken(Token.IF);
        decompiler.addToken(Token.LP);
        Node cond = transform(n.getCondition());
        decompiler.addToken(Token.RP);
        decompiler.addEOL(Token.LC);
        Node ifTrue = transform(n.getThenPart());
        Node ifFalse = null;
        if (n.getElsePart() != null) {
            decompiler.addToken(Token.RC);
            decompiler.addToken(Token.ELSE);
            decompiler.addEOL(Token.LC);
            ifFalse = transform(n.getElsePart());
        }
        decompiler.addEOL(Token.RC);
        return createIf(cond, ifTrue, ifFalse, n.getLineno());
    }

    private Node transformInfix(InfixExpression node) {
        Node left = transform(node.getLeft());
        decompiler.addToken(node.getType());
        Node right = transform(node.getRight());
        if (node instanceof XmlDotQuery) {
            decompiler.addToken(Token.RP);
        }
        return createBinary(node.getType(), left, right);
    }

    private Node transformLabeledStatement(LabeledStatement ls) {
        Label label = ls.getFirstLabel();
        List

... and a goto to GOTO around these handlers. */ private Node createTryCatchFinally( Node tryBlock, Node catchBlocks, Node finallyBlock, int lineno) { boolean hasFinally = (finallyBlock != null) && (finallyBlock.getType() != Token.BLOCK || finallyBlock.hasChildren()); // short circuit if (tryBlock.getType() == Token.BLOCK && !tryBlock.hasChildren() && !hasFinally) { return tryBlock; } boolean hasCatch = catchBlocks.hasChildren(); // short circuit if (!hasFinally && !hasCatch) { // bc finally might be an empty block... return tryBlock; } Node handlerBlock = new Node(Token.LOCAL_BLOCK); Jump pn = new Jump(Token.TRY, tryBlock, lineno); pn.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); if (hasCatch) { // jump around catch code Node endCatch = Node.newTarget(); pn.addChildToBack(makeJump(Token.GOTO, endCatch)); // make a TARGET for the catch that the tcf node knows about Node catchTarget = Node.newTarget(); pn.target = catchTarget; // mark it pn.addChildToBack(catchTarget); // // Given // // try { // tryBlock; // } catch (e if condition1) { // something1; // ... // // } catch (e if conditionN) { // somethingN; // } catch (e) { // somethingDefault; // } // // rewrite as // // try { // tryBlock; // goto after_catch: // } catch (x) { // with (newCatchScope(e, x)) { // if (condition1) { // something1; // goto after_catch; // } // } // ... // with (newCatchScope(e, x)) { // if (conditionN) { // somethingN; // goto after_catch; // } // } // with (newCatchScope(e, x)) { // somethingDefault; // goto after_catch; // } // } // after_catch: // // If there is no default catch, then the last with block // arround "somethingDefault;" is replaced by "rethrow;" // It is assumed that catch handler generation will store // exeception object in handlerBlock register // Block with local for exception scope objects Node catchScopeBlock = new Node(Token.LOCAL_BLOCK); // expects catchblocks children to be (cond block) pairs. Node cb = catchBlocks.getFirstChild(); boolean hasDefault = false; int scopeIndex = 0; while (cb != null) { int catchLineNo = cb.getLineno(); Node name = cb.getFirstChild(); Node cond = name.getNext(); Node catchStatement = cond.getNext(); cb.removeChild(name); cb.removeChild(cond); cb.removeChild(catchStatement); // Add goto to the catch statement to jump out of catch // but prefix it with LEAVEWITH since try..catch produces // "with"code in order to limit the scope of the exception // object. catchStatement.addChildToBack(new Node(Token.LEAVEWITH)); catchStatement.addChildToBack(makeJump(Token.GOTO, endCatch)); // Create condition "if" when present Node condStmt; if (cond.getType() == Token.EMPTY) { condStmt = catchStatement; hasDefault = true; } else { condStmt = createIf(cond, catchStatement, null, catchLineNo); } // Generate code to create the scope object and store // it in catchScopeBlock register Node catchScope = new Node(Token.CATCH_SCOPE, name, createUseLocal(handlerBlock)); catchScope.putProp(Node.LOCAL_BLOCK_PROP, catchScopeBlock); catchScope.putIntProp(Node.CATCH_SCOPE_PROP, scopeIndex); catchScopeBlock.addChildToBack(catchScope); // Add with statement based on catch scope object catchScopeBlock.addChildToBack( createWith(createUseLocal(catchScopeBlock), condStmt, catchLineNo)); // move to next cb cb = cb.getNext(); ++scopeIndex; } pn.addChildToBack(catchScopeBlock); if (!hasDefault) { // Generate code to rethrow if no catch clause was executed Node rethrow = new Node(Token.RETHROW); rethrow.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); pn.addChildToBack(rethrow); } pn.addChildToBack(endCatch); } if (hasFinally) { Node finallyTarget = Node.newTarget(); pn.setFinally(finallyTarget); // add jsr finally to the try block pn.addChildToBack(makeJump(Token.JSR, finallyTarget)); // jump around finally code Node finallyEnd = Node.newTarget(); pn.addChildToBack(makeJump(Token.GOTO, finallyEnd)); pn.addChildToBack(finallyTarget); Node fBlock = new Node(Token.FINALLY, finallyBlock); fBlock.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); pn.addChildToBack(fBlock); pn.addChildToBack(finallyEnd); } handlerBlock.addChildToBack(pn); return handlerBlock; } private Node createWith(Node obj, Node body, int lineno) { parser.setRequiresActivation(); Node result = new Node(Token.BLOCK, lineno); result.addChildToBack(new Node(Token.ENTERWITH, obj)); Node bodyNode = new Node(Token.WITH, body, lineno); result.addChildrenToBack(bodyNode); result.addChildToBack(new Node(Token.LEAVEWITH)); return result; } private static Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno) { int condStatus = isAlwaysDefinedBoolean(cond); if (condStatus == ALWAYS_TRUE_BOOLEAN) { return ifTrue; } else if (condStatus == ALWAYS_FALSE_BOOLEAN) { if (ifFalse != null) { return ifFalse; } // Replace if (false) xxx by empty block return new Node(Token.BLOCK, lineno); } Node result = new Node(Token.BLOCK, lineno); Node ifNotTarget = Node.newTarget(); Jump IFNE = new Jump(Token.IFNE, cond); IFNE.target = ifNotTarget; result.addChildToBack(IFNE); result.addChildrenToBack(ifTrue); if (ifFalse != null) { Node endTarget = Node.newTarget(); result.addChildToBack(makeJump(Token.GOTO, endTarget)); result.addChildToBack(ifNotTarget); result.addChildrenToBack(ifFalse); result.addChildToBack(endTarget); } else { result.addChildToBack(ifNotTarget); } return result; } private static Node createCondExpr(Node cond, Node ifTrue, Node ifFalse) { int condStatus = isAlwaysDefinedBoolean(cond); if (condStatus == ALWAYS_TRUE_BOOLEAN) { return ifTrue; } else if (condStatus == ALWAYS_FALSE_BOOLEAN) { return ifFalse; } return new Node(Token.HOOK, cond, ifTrue, ifFalse); } private static Node createUnary(int nodeType, Node child) { int childType = child.getType(); switch (nodeType) { case Token.DELPROP: { Node n; if (childType == Token.NAME) { // Transform Delete(Name "a") // to Delete(Bind("a"), String("a")) child.setType(Token.BINDNAME); Node right = Node.newString(child.getString()); n = new Node(nodeType, child, right); } else if (childType == Token.GETPROP || childType == Token.GETELEM) { Node left = child.getFirstChild(); Node right = child.getLastChild(); child.removeChild(left); child.removeChild(right); n = new Node(nodeType, left, right); } else if (childType == Token.GET_REF) { Node ref = child.getFirstChild(); child.removeChild(ref); n = new Node(Token.DEL_REF, ref); } else { // Always evaluate delete operand, see ES5 11.4.1 & bug #726121 n = new Node(nodeType, new Node(Token.TRUE), child); } return n; } case Token.TYPEOF: if (childType == Token.NAME) { child.setType(Token.TYPEOFNAME); return child; } break; case Token.BITNOT: if (childType == Token.NUMBER) { int value = ScriptRuntime.toInt32(child.getDouble()); child.setDouble(~value); return child; } break; case Token.NEG: if (childType == Token.NUMBER) { child.setDouble(-child.getDouble()); return child; } break; case Token.NOT: { int status = isAlwaysDefinedBoolean(child); if (status != 0) { int type; if (status == ALWAYS_TRUE_BOOLEAN) { type = Token.FALSE; } else { type = Token.TRUE; } if (childType == Token.TRUE || childType == Token.FALSE) { child.setType(type); return child; } return new Node(type); } break; } } return new Node(nodeType, child); } private Node createCallOrNew(int nodeType, Node child) { int type = Node.NON_SPECIALCALL; if (child.getType() == Token.NAME) { String name = child.getString(); if (name.equals("eval")) { type = Node.SPECIALCALL_EVAL; } else if (name.equals("With")) { type = Node.SPECIALCALL_WITH; } } else if (child.getType() == Token.GETPROP) { String name = child.getLastChild().getString(); if (name.equals("eval")) { type = Node.SPECIALCALL_EVAL; } } Node node = new Node(nodeType, child); if (type != Node.NON_SPECIALCALL) { // Calls to these functions require activation objects. parser.setRequiresActivation(); node.putIntProp(Node.SPECIALCALL_PROP, type); } return node; } private static Node createIncDec(int nodeType, boolean post, Node child) { child = makeReference(child); int childType = child.getType(); switch (childType) { case Token.NAME: case Token.GETPROP: case Token.GETELEM: case Token.GET_REF: { Node n = new Node(nodeType, child); int incrDecrMask = 0; if (nodeType == Token.DEC) { incrDecrMask |= Node.DECR_FLAG; } if (post) { incrDecrMask |= Node.POST_FLAG; } n.putIntProp(Node.INCRDECR_PROP, incrDecrMask); return n; } } throw Kit.codeBug(); } private Node createPropertyGet( Node target, String namespace, String name, int memberTypeFlags) { if (namespace == null && memberTypeFlags == 0) { if (target == null) { return parser.createName(name); } parser.checkActivationName(name, Token.GETPROP); if (ScriptRuntime.isSpecialProperty(name)) { Node ref = new Node(Token.REF_SPECIAL, target); ref.putProp(Node.NAME_PROP, name); return new Node(Token.GET_REF, ref); } return new Node(Token.GETPROP, target, Node.newString(name)); } Node elem = Node.newString(name); memberTypeFlags |= Node.PROPERTY_FLAG; return createMemberRefGet(target, namespace, elem, memberTypeFlags); } /** * @param target the node before the LB * @param namespace optional namespace * @param elem the node in the brackets * @param memberTypeFlags E4X flags */ private Node createElementGet(Node target, String namespace, Node elem, int memberTypeFlags) { // OPT: could optimize to createPropertyGet // iff elem is string that can not be number if (namespace == null && memberTypeFlags == 0) { // stand-alone [aaa] as primary expression is array literal // declaration and should not come here! if (target == null) throw Kit.codeBug(); return new Node(Token.GETELEM, target, elem); } return createMemberRefGet(target, namespace, elem, memberTypeFlags); } private Node createMemberRefGet(Node target, String namespace, Node elem, int memberTypeFlags) { Node nsNode = null; if (namespace != null) { // See 11.1.2 in ECMA 357 if (namespace.equals("*")) { nsNode = new Node(Token.NULL); } else { nsNode = parser.createName(namespace); } } Node ref; if (target == null) { if (namespace == null) { ref = new Node(Token.REF_NAME, elem); } else { ref = new Node(Token.REF_NS_NAME, nsNode, elem); } } else { if (namespace == null) { ref = new Node(Token.REF_MEMBER, target, elem); } else { ref = new Node(Token.REF_NS_MEMBER, target, nsNode, elem); } } if (memberTypeFlags != 0) { ref.putIntProp(Node.MEMBER_TYPE_PROP, memberTypeFlags); } return new Node(Token.GET_REF, ref); } private static Node createBinary(int nodeType, Node left, Node right) { switch (nodeType) { case Token.ADD: // numerical addition and string concatenation if (left.type == Token.STRING) { String s2; if (right.type == Token.STRING) { s2 = right.getString(); } else if (right.type == Token.NUMBER) { s2 = ScriptRuntime.numberToString(right.getDouble(), 10); } else { break; } String s1 = left.getString(); left.setString(s1.concat(s2)); return left; } else if (left.type == Token.NUMBER) { if (right.type == Token.NUMBER) { left.setDouble(left.getDouble() + right.getDouble()); return left; } else if (right.type == Token.STRING) { String s1, s2; s1 = ScriptRuntime.numberToString(left.getDouble(), 10); s2 = right.getString(); right.setString(s1.concat(s2)); return right; } } // can't do anything if we don't know both types - since // 0 + object is supposed to call toString on the object and do // string concantenation rather than addition break; case Token.SUB: // numerical subtraction if (left.type == Token.NUMBER) { double ld = left.getDouble(); if (right.type == Token.NUMBER) { // both numbers left.setDouble(ld - right.getDouble()); return left; } else if (ld == 0.0) { // first 0: 0-x -> -x return new Node(Token.NEG, right); } } else if (right.type == Token.NUMBER) { if (right.getDouble() == 0.0) { // second 0: x - 0 -> +x // can not make simply x because x - 0 must be number return new Node(Token.POS, left); } } break; case Token.MUL: // numerical multiplication if (left.type == Token.NUMBER) { double ld = left.getDouble(); if (right.type == Token.NUMBER) { // both numbers left.setDouble(ld * right.getDouble()); return left; } else if (ld == 1.0) { // first 1: 1 * x -> +x return new Node(Token.POS, right); } } else if (right.type == Token.NUMBER) { if (right.getDouble() == 1.0) { // second 1: x * 1 -> +x // can not make simply x because x - 0 must be number return new Node(Token.POS, left); } } // can't do x*0: Infinity * 0 gives NaN, not 0 break; case Token.DIV: // number division if (right.type == Token.NUMBER) { double rd = right.getDouble(); if (left.type == Token.NUMBER) { // both constants -- just divide, trust Java to handle x/0 left.setDouble(left.getDouble() / rd); return left; } else if (rd == 1.0) { // second 1: x/1 -> +x // not simply x to force number convertion return new Node(Token.POS, left); } } break; case Token.AND: { // Since x && y gives x, not false, when Boolean(x) is false, // and y, not Boolean(y), when Boolean(x) is true, x && y // can only be simplified if x is defined. See bug 309957. int leftStatus = isAlwaysDefinedBoolean(left); if (leftStatus == ALWAYS_FALSE_BOOLEAN) { // if the first one is false, just return it return left; } else if (leftStatus == ALWAYS_TRUE_BOOLEAN) { // if first is true, set to second return right; } break; } case Token.OR: { // Since x || y gives x, not true, when Boolean(x) is true, // and y, not Boolean(y), when Boolean(x) is false, x || y // can only be simplified if x is defined. See bug 309957. int leftStatus = isAlwaysDefinedBoolean(left); if (leftStatus == ALWAYS_TRUE_BOOLEAN) { // if the first one is true, just return it return left; } else if (leftStatus == ALWAYS_FALSE_BOOLEAN) { // if first is false, set to second return right; } break; } } return new Node(nodeType, left, right); } private Node createAssignment(int assignType, Node left, Node right) { Node ref = makeReference(left); if (ref == null) { if (left.getType() == Token.ARRAYLIT || left.getType() == Token.OBJECTLIT) { if (assignType != Token.ASSIGN) { parser.reportError("msg.bad.destruct.op"); return right; } return parser.createDestructuringAssignment(-1, left, right); } parser.reportError("msg.bad.assign.left"); return right; } left = ref; int assignOp; switch (assignType) { case Token.ASSIGN: return parser.simpleAssignment(left, right); case Token.ASSIGN_BITOR: assignOp = Token.BITOR; break; case Token.ASSIGN_BITXOR: assignOp = Token.BITXOR; break; case Token.ASSIGN_BITAND: assignOp = Token.BITAND; break; case Token.ASSIGN_LSH: assignOp = Token.LSH; break; case Token.ASSIGN_RSH: assignOp = Token.RSH; break; case Token.ASSIGN_URSH: assignOp = Token.URSH; break; case Token.ASSIGN_ADD: assignOp = Token.ADD; break; case Token.ASSIGN_SUB: assignOp = Token.SUB; break; case Token.ASSIGN_MUL: assignOp = Token.MUL; break; case Token.ASSIGN_DIV: assignOp = Token.DIV; break; case Token.ASSIGN_MOD: assignOp = Token.MOD; break; case Token.ASSIGN_EXP: assignOp = Token.EXP; break; default: throw Kit.codeBug(); } int nodeType = left.getType(); switch (nodeType) { case Token.NAME: { Node op = new Node(assignOp, left, right); Node lvalueLeft = Node.newString(Token.BINDNAME, left.getString()); return new Node(Token.SETNAME, lvalueLeft, op); } case Token.GETPROP: case Token.GETELEM: { Node obj = left.getFirstChild(); Node id = left.getLastChild(); int type = nodeType == Token.GETPROP ? Token.SETPROP_OP : Token.SETELEM_OP; Node opLeft = new Node(Token.USE_STACK); Node op = new Node(assignOp, opLeft, right); return new Node(type, obj, id, op); } case Token.GET_REF: { ref = left.getFirstChild(); parser.checkMutableReference(ref); Node opLeft = new Node(Token.USE_STACK); Node op = new Node(assignOp, opLeft, right); return new Node(Token.SET_REF_OP, ref, op); } } throw Kit.codeBug(); } private static Node createUseLocal(Node localBlock) { if (Token.LOCAL_BLOCK != localBlock.getType()) throw Kit.codeBug(); Node result = new Node(Token.LOCAL_LOAD); result.putProp(Node.LOCAL_BLOCK_PROP, localBlock); return result; } private static Jump makeJump(int type, Node target) { Jump n = new Jump(type); n.target = target; return n; } private static Node makeReference(Node node) { int type = node.getType(); switch (type) { case Token.NAME: case Token.GETPROP: case Token.GETELEM: case Token.GET_REF: return node; case Token.CALL: node.setType(Token.REF_CALL); return new Node(Token.GET_REF, node); } // Signal caller to report error return null; } // Check if Node always mean true or false in boolean context private static int isAlwaysDefinedBoolean(Node node) { switch (node.getType()) { case Token.FALSE: case Token.NULL: return ALWAYS_FALSE_BOOLEAN; case Token.TRUE: return ALWAYS_TRUE_BOOLEAN; case Token.NUMBER: { double num = node.getDouble(); if (!Double.isNaN(num) && num != 0.0) { return ALWAYS_TRUE_BOOLEAN; } return ALWAYS_FALSE_BOOLEAN; } } return 0; } // Check if node is the target of a destructuring bind. boolean isDestructuring(Node n) { return n instanceof DestructuringForm && ((DestructuringForm) n).isDestructuring(); } Node decompileFunctionHeader(FunctionNode fn) { Node mexpr = null; if (fn.getFunctionName() != null) { decompiler.addName(fn.getName()); } else if (fn.getMemberExprNode() != null) { mexpr = transform(fn.getMemberExprNode()); } boolean isArrow = fn.getFunctionType() == FunctionNode.ARROW_FUNCTION; boolean noParen = isArrow && fn.getLp() == -1; if (!noParen) { decompiler.addToken(Token.LP); } List params = fn.getParams(); int last = params.size() - 1; for (int i = 0; i <= last; i++) { if (i > 0) { decompiler.addToken(Token.COMMA); } if (fn.hasRestParameter() && i == last) { decompiler.addToken(Token.DOTDOTDOT); } decompile(params.get(i)); } if (!noParen) { decompiler.addToken(Token.RP); } if (isArrow) { decompiler.addToken(Token.ARROW); } if (!fn.isExpressionClosure()) { decompiler.addEOL(Token.LC); } return mexpr; } void decompile(AstNode node) { switch (node.getType()) { case Token.ARRAYLIT: decompileArrayLiteral((ArrayLiteral) node); break; case Token.OBJECTLIT: decompileObjectLiteral((ObjectLiteral) node); break; case Token.STRING: decompiler.addString(((StringLiteral) node).getValue()); break; case Token.NAME: decompiler.addName(((Name) node).getIdentifier()); break; case Token.NUMBER: decompiler.addNumber(((NumberLiteral) node).getNumber()); break; case Token.BIGINT: decompiler.addBigInt(node.getBigInt()); break; case Token.GETPROP: decompilePropertyGet((PropertyGet) node); break; case Token.EMPTY: break; case Token.GETELEM: decompileElementGet((ElementGet) node); break; case Token.THIS: decompiler.addToken(node.getType()); break; default: Kit.codeBug("unexpected token: " + Token.typeToName(node.getType())); } } // used for destructuring forms, since we don't transform() them void decompileArrayLiteral(ArrayLiteral node) { decompiler.addToken(Token.LB); List elems = node.getElements(); int size = elems.size(); for (int i = 0; i < size; i++) { AstNode elem = elems.get(i); decompile(elem); if (i < size - 1) { decompiler.addToken(Token.COMMA); } } decompiler.addToken(Token.RB); } // only used for destructuring forms void decompileObjectLiteral(ObjectLiteral node) { decompiler.addToken(Token.LC); List props = node.getElements(); int size = props.size(); for (int i = 0; i < size; i++) { ObjectProperty prop = props.get(i); boolean shorthandPropertyName = Boolean.TRUE.equals(prop.getProp(Node.SHORTHAND_PROPERTY_NAME)); decompile(prop.getLeft()); if (!shorthandPropertyName) { decompiler.addToken(Token.COLON); decompile(prop.getRight()); } if (i < size - 1) { decompiler.addToken(Token.COMMA); } } decompiler.addToken(Token.RC); } // only used for destructuring forms void decompilePropertyGet(PropertyGet node) { decompile(node.getTarget()); decompiler.addToken(Token.DOT); decompile(node.getProperty()); } // only used for destructuring forms void decompileElementGet(ElementGet node) { decompile(node.getTarget()); decompiler.addToken(Token.LB); decompile(node.getElement()); decompiler.addToken(Token.RB); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy