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

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

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 org.mozilla.javascript.ast.*;

import java.util.List;
import java.util.ArrayList;

/**
 * This class rewrites the parse tree into an IR suitable for codegen.
 *
 * @see Node
 * @author Mike McCabe
 * @author Norris Boyd
 */
public final class IRFactory extends Parser
{
    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 Decompiler decompiler = new Decompiler();

    public IRFactory() {
        super();
    }

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

    public IRFactory(CompilerEnvirons env, ErrorReporter errorReporter) {
        super(env, errorReporter);
    }

    /**
     * Transforms the tree into a lower-level IR suitable for codegen.
     * Optionally generates the encoded source.
     */
    public ScriptNode transformTree(AstRoot root) {
        currentScriptOrFn = root;
        this.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 (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.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:
              return node;
          case Token.FOR:
              if (node instanceof ForInLoop) {
                  return transformForInLoop((ForInLoop)node);
              } else {
                  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.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:
              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 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 = createScopeNode(Token.ARRAYCOMP, lineno);
        String arrayName = currentScriptOrFn.getNextTempName();
        pushScope(scopeNode);
        try {
            defineSymbol(Token.LET, arrayName, false);
            Node block = new Node(Token.BLOCK, lineno);
            Node newArray = createCallOrNew(Token.NEW, createName("Array"));
            Node init = new Node(Token.EXPR_VOID,
                                 createAssignment(Token.ASSIGN,
                                                  createName(arrayName),
                                                  newArray),
                                 lineno);
            block.addChildToBack(init);
            block.addChildToBack(arrayCompTransformHelper(node, arrayName));
            scopeNode.addChildToBack(block);
            scopeNode.addChildToBack(createName(arrayName));
            return scopeNode;
        } finally {
            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 = currentScriptOrFn.getNextTempName();
                defineSymbol(Token.LP, name, false);
                expr = createBinary(Token.COMMA,
                                    createAssignment(Token.ASSIGN,
                                                     iter,
                                                     createName(name)),
                                    expr);
            }
            Node init = createName(name);
            // Define as a let since we want the scope of the variable to
            // be restricted to the array comprehension
            defineSymbol(Token.LET, name, false);
            iterators[i] = init;

            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(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());
                pushScope(loop);
                pushed++;
                body = createForIn(Token.LET,
                                   loop,
                                   iterators[i],
                                   iteratedObjs[i],
                                   body,
                                   acl.isForEach());
            }
        } finally {
            for (int i = 0; i < pushed; i++) {
                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(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);
            array.putProp(Node.SKIP_INDEXES_PROP, skips);
        }
        return array;
    }

    private Node transformAssignment(Assignment node) {
        AstNode left = removeParens(node.getLeft());
        Node target = null;
        if (isDestructuring(left)) {
            decompile(left);
            target = left;
        } else {
            target = transform(left);
        }
        decompiler.addToken(node.getType());
        return createAssignment(node.getType(),
                                target,
                                transform(node.getRight()));
    }

    private Node transformBlock(AstNode node) {
        if (node instanceof Scope) {
            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) {
                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);
        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 {
            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);
        pushScope(loop);
        try {
            int declType = -1;
            AstNode iter = loop.getIterator();
            if (iter instanceof VariableDeclaration) {
                declType = ((VariableDeclaration)iter).getType();
            }
            Node lhs = transform(iter);
            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());
        } finally {
            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 = currentScope;
        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 {
            currentScope = savedScope;
        }
    }

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

        PerFunctionVariables savedVars = new PerFunctionVariables(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();
            ++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 {
            --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(currentScriptOrFn.getNextTempName());
        fn.setIsGenerator();
        fn.setFunctionType(FunctionNode.FUNCTION_EXPRESSION);
        fn.setRequiresActivation();
      
        int functionType = fn.getFunctionType();
        int start = decompiler.markFunctionStart(functionType);
        Node mexpr = decompileFunctionHeader(fn);
        int index = currentScriptOrFn.addFunction(fn);

        PerFunctionVariables savedVars = new PerFunctionVariables(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;
            ++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 {
            --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 = currentScriptOrFn.getNextTempName();
                defineSymbol(Token.LP, name, false);
                expr = createBinary(Token.COMMA,
                                    createAssignment(Token.ASSIGN,
                                                     iter,
                                                     createName(name)),
                                    expr);
            }
            Node init = createName(name);
            // Define as a let since we want the scope of the variable to
            // be restricted to the array comprehension
            defineSymbol(Token.LET, name, false);
            iterators[i] = init;

            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());
                pushScope(loop);
                pushed++;
                body = createForIn(Token.LET,
                                   loop,
                                   iterators[i],
                                   iteratedObjs[i],
                                   body,
                                   acl.isForEach());
            }
        } finally {
            for (int i = 0; i < pushed; i++) {
                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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy