/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Norris Boyd
 *   Igor Bukanov
 *   Ethan Hugg
 *   Bob Jervis
 *   Terry Lucas
 *   Milen Nankov
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package org.mozilla.javascript;

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

/**
 * This class allows the creation of nodes, and follows the Factory pattern.
 *
 * @see Node
 * @author Mike McCabe
 * @author Norris Boyd
 */
final class IRFactory
{
    IRFactory(Parser parser)
    {
        this.parser = parser;
    }

    ScriptOrFnNode createScript()
    {
        return new ScriptOrFnNode(Token.SCRIPT);
    }

    /**
     * Script (for associating file/url names with toplevel scripts.)
     */
    void initScript(ScriptOrFnNode scriptNode, Node body)
    {
        Node children = body.getFirstChild();
        if (children != null) { scriptNode.addChildrenToBack(children); }
    }

    /**
     * Leaf
     */
    Node createLeaf(int nodeType)
    {
        return new Node(nodeType);
    }

    /**
     * Statement leaf nodes.
     */

    Node createSwitch(Node expr, int lineno)
    {
        //
        // The switch will be rewritten from:
        //
        // switch (expr) {
        //   case test1: statements1;
        //   ...
        //   default: statementsDefault;
        //   ...
        //   case testN: statementsN;
        // }
        //
        // to:
        //
        // {
        //     switch (expr) {
        //       case test1: goto label1;
        //       ...
        //       case testN: goto labelN;
        //     }
        //     goto labelDefault;
        //   label1:
        //     statements1;
        //   ...
        //   labelDefault:
        //     statementsDefault;
        //   ...
        //   labelN:
        //     statementsN;
        //   breakLabel:
        // }
        //
        // where inside switch each "break;" without label will be replaced
        // by "goto breakLabel".
        //
        // If the original switch does not have the default label, then
        // the transformed code would contain after the switch instead of
        //     goto labelDefault;
        // the following goto:
        //     goto breakLabel;
        //

        Node.Jump switchNode = new Node.Jump(Token.SWITCH, expr, lineno);
        Node block = new Node(Token.BLOCK, switchNode);
        return block;
    }

    /**
     * If caseExpression argument is null it indicate default label.
     */
    void addSwitchCase(Node switchBlock, Node caseExpression, Node statements)
    {
        if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug();
        Node.Jump switchNode = (Node.Jump)switchBlock.getFirstChild();
        if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug();

        Node gotoTarget = Node.newTarget();
        if (caseExpression != null) {
            Node.Jump caseNode = new Node.Jump(Token.CASE, caseExpression);
            caseNode.target = gotoTarget;
            switchNode.addChildToBack(caseNode);
        } else {
            switchNode.setDefault(gotoTarget);
        }
        switchBlock.addChildToBack(gotoTarget);
        switchBlock.addChildToBack(statements);
    }

    void closeSwitch(Node switchBlock)
    {
        if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug();
        Node.Jump switchNode = (Node.Jump)switchBlock.getFirstChild();
        if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug();

        Node switchBreakTarget = Node.newTarget();
        // switchNode.target is only used by NodeTransformer
        // to detect switch end
        switchNode.target = switchBreakTarget;

        Node defaultTarget = switchNode.getDefault();
        if (defaultTarget == null) {
            defaultTarget = switchBreakTarget;
        }

        switchBlock.addChildAfter(makeJump(Token.GOTO, defaultTarget),
                                  switchNode);
        switchBlock.addChildToBack(switchBreakTarget);
    }

    Node createVariables(int token, int lineno)
    {
        return new Node(token, lineno);
    }

    Node createExprStatement(Node expr, int lineno)
    {
        int type;
        if (parser.insideFunction()) {
            type = Token.EXPR_VOID;
        } else {
            type = Token.EXPR_RESULT;
        }
        return new Node(type, expr, lineno);
    }

    Node createExprStatementNoReturn(Node expr, int lineno)
    {
        return new Node(Token.EXPR_VOID, expr, lineno);
    }

    Node createDefaultNamespace(Node expr, int lineno)
    {
        // default xml namespace requires activation
        setRequiresActivation();
        Node n = createUnary(Token.DEFAULTNAMESPACE, expr);
        Node result = createExprStatement(n, lineno);
        return result;
    }

    /**
     * Name
     */
    Node createName(String name)
    {
        checkActivationName(name, Token.NAME);
        return Node.newString(Token.NAME, name);
    }
    
    private Node createName(int type, String name, Node child)
    {
        Node result = createName(name);
        result.setType(type);
        if (child != null)
            result.addChildToBack(child);
        return result;
    }

    /**
     * String (for literals)
     */
    Node createString(String string)
    {
        return Node.newString(string);
    }

    /**
     * Number (for literals)
     */
    Node createNumber(double number)
    {
        return Node.newNumber(number);
    }

    /**
     * Catch clause of try/catch/finally
     * @param varName the name of the variable to bind to the exception
     * @param catchCond the condition under which to catch the exception.
     *                  May be null if no condition is given.
     * @param stmts the statements in the catch clause
     * @param lineno the starting line number of the catch clause
     */
    Node createCatch(String varName, Node catchCond, Node stmts, int lineno)
    {
        if (catchCond == null) {
            catchCond = new Node(Token.EMPTY);
        }
        return new Node(Token.CATCH, createName(varName),
                        catchCond, stmts, lineno);
    }

    /**
     * Throw
     */
    Node createThrow(Node expr, int lineno)
    {
        return new Node(Token.THROW, expr, lineno);
    }

    /**
     * Return
     */
    Node createReturn(Node expr, int lineno)
    {
        return expr == null
            ? new Node(Token.RETURN, lineno)
            : new Node(Token.RETURN, expr, lineno);
    }

    /**
     * Debugger
     */
    Node createDebugger(int lineno)
    {
        return new Node(Token.DEBUGGER,  lineno);
    }
  
    /**
     * Label
     */
    Node createLabel(int lineno)
    {
        return new Node.Jump(Token.LABEL, lineno);
    }

    Node getLabelLoop(Node label)
    {
        return ((Node.Jump)label).getLoop();
    }

    /**
     * Label
     */
    Node createLabeledStatement(Node labelArg, Node statement)
    {
        Node.Jump label = (Node.Jump)labelArg;

        // Make a target and put it _after_ the statement
        // node.  And in the LABEL node, so breaks get the
        // right target.

        Node breakTarget = Node.newTarget();
        Node block = new Node(Token.BLOCK, label, statement, breakTarget);
        label.target = breakTarget;

        return block;
    }

    /**
     * Break (possibly labeled)
     */
    Node createBreak(Node breakStatement, int lineno)
    {
        Node.Jump n = new Node.Jump(Token.BREAK, lineno);
        Node.Jump jumpStatement;
        int t = breakStatement.getType();
        if (t == Token.LOOP || t == Token.LABEL) {
            jumpStatement = (Node.Jump)breakStatement;
        } else if (t == Token.BLOCK
                   && breakStatement.getFirstChild().getType() == Token.SWITCH)
        {
            jumpStatement = (Node.Jump)breakStatement.getFirstChild();
        } else {
            throw Kit.codeBug();
        }
        n.setJumpStatement(jumpStatement);
        return n;
    }

    /**
     * Continue (possibly labeled)
     */
    Node createContinue(Node loop, int lineno)
    {
        if (loop.getType() != Token.LOOP) Kit.codeBug();
        Node.Jump n = new Node.Jump(Token.CONTINUE, lineno);
        n.setJumpStatement((Node.Jump)loop);
        return n;
    }

    /**
     * Statement block
     * Creates the empty statement block
     * Must make subsequent calls to add statements to the node
     */
    Node createBlock(int lineno)
    {
        return new Node(Token.BLOCK, lineno);
    }

    FunctionNode createFunction(String name)
    {
        return new FunctionNode(name);
    }

    Node initFunction(FunctionNode fnNode, int functionIndex,
                      Node statements, int functionType)
    {
        fnNode.itsFunctionType = functionType;
        fnNode.addChildToBack(statements);

        int functionCount = fnNode.getFunctionCount();
        if (functionCount != 0) {
            // Functions containing other functions require activation objects
            fnNode.itsNeedsActivation = true;
        }

        if (functionType == FunctionNode.FUNCTION_EXPRESSION) {
            String name = fnNode.getFunctionName();
            if (name != null && name.length() != 0) {
                // A function expression needs to have its name as a
                // variable (if it isn't already allocated as a variable).
                // See ECMA Ch. 13.  We add code to the beginning of the
                // function to initialize a local variable of the
                // function's name to the function value.
                Node setFn = new Node(Token.EXPR_VOID,
                                 new Node(Token.SETNAME,
                                     Node.newString(Token.BINDNAME, name),
                                     new Node(Token.THISFN)));
                statements.addChildrenToFront(setFn);
            }
        }

        // Add return to end if needed.
        Node lastStmt = statements.getLastChild();
        if (lastStmt == null || lastStmt.getType() != Token.RETURN) {
            statements.addChildToBack(new Node(Token.RETURN));
        }

        Node result = Node.newString(Token.FUNCTION,
                                     fnNode.getFunctionName());
        result.putIntProp(Node.FUNCTION_PROP, functionIndex);
        return result;
    }

    /**
     * Add a child to the back of the given node.  This function
     * breaks the Factory abstraction, but it removes a requirement
     * from implementors of Node.
     */
    void addChildToBack(Node parent, Node child)
    {
        parent.addChildToBack(child);
    }

    /**
     * Create a node that can be used to hold lexically scoped variable
     * definitions (via let declarations).
     * 
     * @param token the token of the node to create
     * @param lineno line number of source
     * @return the created node
     */
    Node createScopeNode(int token, int lineno) {
        return new Node.Scope(token, lineno);
    }

    /**
     * Create loop node. The parser will later call
     * createWhile|createDoWhile|createFor|createForIn
     * to finish loop generation.
     */
    Node createLoopNode(Node loopLabel, int lineno)
    {
        Node.Jump result = new Node.Scope(Token.LOOP, lineno);
        if (loopLabel != null) {
            ((Node.Jump)loopLabel).setLoop(result);
        }
        return result;
    }

    /**
     * While
     */
    Node createWhile(Node loop, Node cond, Node body)
    {
        return createLoop((Node.Jump)loop, LOOP_WHILE, body, cond,
                          null, null);
    }

    /**
     * DoWhile
     */
    Node createDoWhile(Node loop, Node body, Node cond)
    {
        return createLoop((Node.Jump)loop, LOOP_DO_WHILE, body, cond,
                          null, null);
    }

    /**
     * For
     */
    Node createFor(Node loop, Node init, Node test, Node incr, Node body)
    {
        if (init.getType() == Token.LET) {
            // rewrite "for (let i=s; i < N; i++)..." as 
            // "let (i=s) { for (; i < N; i++)..." so that "s" is evaluated
            // outside the scope of the for.
            Node.Scope let = Node.Scope.splitScope((Node.Scope)loop);
            let.setType(Token.LET);
            let.addChildrenToBack(init);
            let.addChildToBack(createLoop((Node.Jump)loop, LOOP_FOR, body, test,
                new Node(Token.EMPTY), incr));
            return let;
        }
        return createLoop((Node.Jump)loop, LOOP_FOR, body, test, init, incr);
    }

    private Node createLoop(Node.Jump loop, int loopType, Node body, Node cond,
                            Node init, Node incr)
    {
        Node bodyTarget = Node.newTarget();
        Node condTarget = Node.newTarget();
        if (loopType == LOOP_FOR && cond.getType() == Token.EMPTY) {
            cond = new Node(Token.TRUE);
        }
        Node.Jump IFEQ = new Node.Jump(Token.IFEQ, cond);
        IFEQ.target = bodyTarget;
        Node breakTarget = Node.newTarget();

        loop.addChildToBack(bodyTarget);
        loop.addChildrenToBack(body);
        if (loopType == LOOP_WHILE || loopType == LOOP_FOR) {
            // propagate lineno to condition
            loop.addChildrenToBack(new Node(Token.EMPTY, loop.getLineno()));
        }
        loop.addChildToBack(condTarget);
        loop.addChildToBack(IFEQ);
        loop.addChildToBack(breakTarget);

        loop.target = breakTarget;
        Node continueTarget = condTarget;

        if (loopType == LOOP_WHILE || loopType == LOOP_FOR) {
            // Just add a GOTO to the condition in the do..while
            loop.addChildToFront(makeJump(Token.GOTO, condTarget));

            if (loopType == LOOP_FOR) {
                int initType = init.getType();
                if (initType != Token.EMPTY) {
                    if (initType != Token.VAR && initType != Token.LET) {
                        init = new Node(Token.EXPR_VOID, init);
                    }
                    loop.addChildToFront(init);
                }
                Node incrTarget = Node.newTarget();
                loop.addChildAfter(incrTarget, body);
                if (incr.getType() != Token.EMPTY) {
                    incr = new Node(Token.EXPR_VOID, incr);
                    loop.addChildAfter(incr, incrTarget);
                }
                continueTarget = incrTarget;
            }
        }

        loop.setContinue(continueTarget);

        return loop;
    }

    /**
     * For .. In
     *
     */
    Node createForIn(int declType, Node loop, Node lhs, Node obj, Node body,
                     boolean isForEach)
    {
        int destructuring = -1;
        int destructuringLen = 0;
        Node lvalue;
        int type = lhs.getType();
        if (type == Token.VAR || type == Token.LET) {
            Node lastChild = lhs.getLastChild();
            if (lhs.getFirstChild() != lastChild) {
                /*
                 * check that there was only one variable given.
                 * we can't do this in the parser, because then the
                 * parser would have to know something about the
                 * 'init' node of the for-in loop.
                 */
                parser.reportError("msg.mult.index");
            }
            if (lastChild.getType() == Token.ARRAYLIT ||
                lastChild.getType() == Token.OBJECTLIT)
            {
                type = destructuring = lastChild.getType();
                lvalue = lastChild;
                destructuringLen = lastChild.getIntProp(
                    Node.DESTRUCTURING_ARRAY_LENGTH, 0);
            } else if (lastChild.getType() == Token.NAME) {
                lvalue = Node.newString(Token.NAME, lastChild.getString());
            } else {
                parser.reportError("msg.bad.for.in.lhs");
                return obj;
            }
        } else if (type == Token.ARRAYLIT || type == Token.OBJECTLIT) {
            destructuring = type;
            lvalue = lhs;
            destructuringLen = lhs.getIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, 0);
        } else {
            lvalue = makeReference(lhs);
            if (lvalue == null) {
                parser.reportError("msg.bad.for.in.lhs");
                return obj;
            }
        }

        Node localBlock = new Node(Token.LOCAL_BLOCK);
        int initType = (isForEach)           ? Token.ENUM_INIT_VALUES :
                       (destructuring != -1) ? Token.ENUM_INIT_ARRAY :
                                               Token.ENUM_INIT_KEYS;
        Node init = new Node(initType, obj);
        init.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
        Node cond = new Node(Token.ENUM_NEXT);
        cond.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
        Node id = new Node(Token.ENUM_ID);
        id.putProp(Node.LOCAL_BLOCK_PROP, localBlock);

        Node newBody = new Node(Token.BLOCK);
        Node assign;
        if (destructuring != -1) {
            assign = createDestructuringAssignment(declType, lvalue, id);
            if (!isForEach && (destructuring == Token.OBJECTLIT ||
                               destructuringLen != 2))
            {
                // destructuring assignment is only allowed in for..each or
                // with an array type of length 2 (to hold key and value)
                parser.reportError("msg.bad.for.in.destruct");
            }
        } else {
            assign = simpleAssignment(lvalue, id);
        }
        newBody.addChildToBack(new Node(Token.EXPR_VOID, assign));
        newBody.addChildToBack(body);

        loop = createWhile(loop, cond, newBody);
        loop.addChildToFront(init);
        if (type == Token.VAR || type == Token.LET)
            loop.addChildToFront(lhs);
        localBlock.addChildToBack(loop);

        return localBlock;
    }

    /**
     * Try/Catch/Finally
     *
     * The IRFactory tries to express as much as possible in the tree;
     * the responsibilities remaining for Codegen are to add the Java
     * handlers: (Either (but not both) of TARGET and FINALLY might not
     * be defined)

     * - a catch handler for javascript exceptions that unwraps the
     * exception onto the stack and GOTOes to the catch target

     * - a finally handler

     * ... and a goto to GOTO around these handlers.
     */
    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);
        Node.Jump pn = new Node.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;
    }

    /**
     * Throw, Return, Label, Break and Continue are defined in ASTFactory.
     */

    /**
     * With
     */
    Node createWith(Node obj, Node body, int lineno)
    {
        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;
    }

    /**
     * DOTQUERY
     */
    public Node createDotQuery (Node obj, Node body, int lineno)
    {
        setRequiresActivation();
        Node result = new Node(Token.DOTQUERY, obj, body, lineno);
        return result;
    }

    Node createArrayLiteral(ObjArray elems, int skipCount, int destructuringLen)
    {
        int length = elems.size();
        int[] skipIndexes = null;
        if (skipCount != 0) {
            skipIndexes = new int[skipCount];
        }
        Node array = new Node(Token.ARRAYLIT);
        for (int i = 0, j = 0; i != length; ++i) {
            Node elem = (Node)elems.get(i);
            if (elem != null) {
                array.addChildToBack(elem);
            } else {
                skipIndexes[j] = i;
                ++j;
            }
        }
        if (skipCount != 0) {
            array.putProp(Node.SKIP_INDEXES_PROP, skipIndexes);
        }
        array.putIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, destructuringLen);
        return array;
    }

    /**
     * Object Literals
     * <BR> createObjectLiteral rewrites its argument as object
     * creation plus object property entries, so later compiler
     * stages don't need to know about object literals.
     */
    Node createObjectLiteral(ObjArray elems)
    {
        int size = elems.size() / 2;
        Node object = new Node(Token.OBJECTLIT);
        Object[] properties;
        if (size == 0) {
            properties = ScriptRuntime.emptyArgs;
        } else {
            properties = new Object[size];
            for (int i = 0; i != size; ++i) {
                properties[i] = elems.get(2 * i);
                Node value = (Node)elems.get(2 * i + 1);
                object.addChildToBack(value);
            }
        }
        object.putProp(Node.OBJECT_IDS_PROP, properties);
        return object;
    }

    /**
     * Regular expressions
     */
    Node createRegExp(int regexpIndex)
    {
        Node n = new Node(Token.REGEXP);
        n.putIntProp(Node.REGEXP_PROP, regexpIndex);
        return n;
    }

    /**
     * If statement
     */
    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();
        Node.Jump IFNE = new Node.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;
    }

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

    /**
     * Unary
     */
    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 left = child;
                Node right = Node.newString(child.getString());
                n = new Node(nodeType, left, 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 {
                n = new Node(Token.TRUE);
            }
            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);
    }

    Node createYield(Node child, int lineno)
    {
      if (!parser.insideFunction()) {
        parser.reportError("msg.bad.yield");
      }
      setRequiresActivation();
      setIsGenerator();
      if (child != null)
        return new Node(Token.YIELD, child, lineno);
      else
        return new Node(Token.YIELD, lineno);
    }

    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.
            setRequiresActivation();
            node.putIntProp(Node.SPECIALCALL_PROP, type);
        }
        return node;
    }

    Node createIncDec(int nodeType, boolean post, Node child)
    {
        child = makeReference(child);
        if (child == null) {
            String msg;
            if (nodeType == Token.DEC) {
                msg = "msg.bad.decr";
            } else {
                msg = "msg.bad.incr";
            }
            parser.reportError(msg);
            return null;
        }

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

    Node createPropertyGet(Node target, String namespace, String name,
                           int memberTypeFlags)
    {
        if (namespace == null && memberTypeFlags == 0) {
            if (target == null) {
                return createName(name);
            }
            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, createString(name));
        }
        Node elem = createString(name);
        memberTypeFlags |= Node.PROPERTY_FLAG;
        return createMemberRefGet(target, namespace, elem, memberTypeFlags);
    }

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

    /**
     * Binary
     */
    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 simpleAssignment(Node left, Node right)
    {
        int nodeType = left.getType();
        switch (nodeType) {
          case Token.NAME:
            left.setType(Token.BINDNAME);
            return new Node(Token.SETNAME, left, right);

          case Token.GETPROP:
          case Token.GETELEM: {
            Node obj = left.getFirstChild();
            Node id = left.getLastChild();
            int type;
            if (nodeType == Token.GETPROP) {
                type = Token.SETPROP;
            } else {
                type = Token.SETELEM;
            }
            return new Node(type, obj, id, right);
          }
          case Token.GET_REF: {
            Node ref = left.getFirstChild();
            checkMutableReference(ref);
            return new Node(Token.SET_REF, ref, right);
          }
        }

        throw Kit.codeBug();
    }

    private void checkMutableReference(Node n)
    {
        int memberTypeFlags = n.getIntProp(Node.MEMBER_TYPE_PROP, 0);
        if ((memberTypeFlags & Node.DESCENDANTS_FLAG) != 0) {
            parser.reportError("msg.bad.assign.left");
        }
    }

    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 createDestructuringAssignment(-1, left, right);
            }
            parser.reportError("msg.bad.assign.left");
            return right;
        }
        left = ref;

        int assignOp;
        switch (assignType) {
          case Token.ASSIGN:
            return 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;
          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();
            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();
    }
    
    /**
     * Given a destructuring assignment with a left hand side parsed
     * as an array or object literal and a right hand side expression,
     * rewrite as a series of assignments to the variables defined in
     * left from property accesses to the expression on the right.
     * @param type declaration type: Token.VAR or Token.LET or -1
     * @param left array or object literal containing NAME nodes for
     *        variables to assign
     * @param right expression to assign from
     * @return expression that performs a series of assignments to
     *         the variables defined in left
     */
    Node createDestructuringAssignment(int type, Node left, Node right)
    {
        String tempName = parser.currentScriptOrFn.getNextTempName();
        Node result = destructuringAssignmentHelper(type, left, right,
            tempName);
        Node comma = result.getLastChild();
        comma.addChildToBack(createName(tempName));
        return result;
    }

    private Node destructuringAssignmentHelper(int variableType, Node left, 
                                               Node right, String tempName)
    {
        Node result = createScopeNode(Token.LETEXPR,
            parser.getCurrentLineNumber());
        result.addChildToFront(new Node(Token.LET,
            createName(Token.NAME, tempName, right)));
        try {
            parser.pushScope(result);
            parser.defineSymbol(Token.LET, true, tempName);
        } finally {
            parser.popScope();
        }
        Node comma = new Node(Token.COMMA);
        result.addChildToBack(comma);
        final int setOp = variableType == Token.CONST ? Token.SETCONST
        		                                      : Token.SETNAME;
        List<String> destructuringNames = new ArrayList<String>();
        boolean empty = true;
        int type = left.getType();
        if (type == Token.ARRAYLIT) {
            int index = 0;
            int[] skipIndices = (int[])left.getProp(Node.SKIP_INDEXES_PROP);
            int skip = 0;
            Node n = left.getFirstChild();
            for (;;) {
                if (skipIndices != null) {
                    while (skip < skipIndices.length && 
                           skipIndices[skip] == index) {
                        skip++;
                        index++;
                    }
                }
                if (n == null)
                    break;
                Node rightElem = new Node(Token.GETELEM,
                    createName(tempName), 
                    createNumber(index));
                if (n.getType() == Token.NAME) {
                    String name = n.getString();
                    comma.addChildToBack(new Node(setOp, 
                        createName(Token.BINDNAME, name, null),
                        rightElem));
                    if (variableType != -1) {
                        parser.defineSymbol(variableType, true, name);
                        destructuringNames.add(name);
                    }
                } else {
                    comma.addChildToBack(
                        destructuringAssignmentHelper(variableType, n,
                            rightElem,
                            parser.currentScriptOrFn.getNextTempName()));
                }
                index++;
                empty = false;
                n = n.getNext();
            }
        } else if (type == Token.OBJECTLIT) {
            int index = 0;
            Object[] propertyIds = (Object[])
                left.getProp(Node.OBJECT_IDS_PROP);
            for (Node n = left.getFirstChild(); n != null; n = n.getNext())
            {
                Object id = propertyIds[index];
                Node rightElem = id instanceof String 
                    ? new Node(Token.GETPROP,
                        createName(tempName), 
                        createString((String)id))
                    : new Node(Token.GETELEM,
                        createName(tempName), 
                        createNumber(((Number)id).intValue()));
                if (n.getType() == Token.NAME) {
                    String name = n.getString();
                    comma.addChildToBack(new Node(setOp, 
                        createName(Token.BINDNAME, name, null),
                        rightElem));
                    if (variableType != -1) {
                        parser.defineSymbol(variableType, true, name);
                        destructuringNames.add(name);
                    }
                } else {
                    comma.addChildToBack(
                        destructuringAssignmentHelper(variableType, n,
                            rightElem,
                            parser.currentScriptOrFn.getNextTempName()));
                }
                index++;
                empty = false;
            }
        } else if (type == Token.GETPROP || type == Token.GETELEM) {
            comma.addChildToBack(simpleAssignment(left, createName(tempName)));
        } else {
            parser.reportError("msg.bad.assign.left");
        }
        if (empty) {
            // Don't want a COMMA node with no children. Just add a zero.
            comma.addChildToBack(createNumber(0));
        }
        result.putProp(Node.DESTRUCTURING_NAMES, destructuringNames);
        return result;
    }

    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 Node.Jump makeJump(int type, Node target)
    {
        Node.Jump n = new Node.Jump(type);
        n.target = target;
        return n;
    }

    private 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 (num == num && num != 0.0) {
                return ALWAYS_TRUE_BOOLEAN;
            } else {
                return ALWAYS_FALSE_BOOLEAN;
            }
          }
        }
        return 0;
    }
    
    private void checkActivationName(String name, int token)
    {
        if (parser.insideFunction()) {
            boolean activation = false;
            if ("arguments".equals(name)
                || (parser.compilerEnv.activationNames != null
                    && parser.compilerEnv.activationNames.contains(name)))
            {
                activation = true;
            } else if ("length".equals(name)) {
                if (token == Token.GETPROP
                    && parser.compilerEnv.getLanguageVersion()
                       == Context.VERSION_1_2)
                {
                    // Use of "length" in 1.2 requires an activation object.
                    activation = true;
                }
            }
            if (activation) {
                setRequiresActivation();
            }
        }
    }

    private void setRequiresActivation()
    {
        if (parser.insideFunction()) {
            ((FunctionNode)parser.currentScriptOrFn).itsNeedsActivation = true;
        }
    }

    private void setIsGenerator()
    {
        if (parser.insideFunction()) {
            ((FunctionNode)parser.currentScriptOrFn).itsIsGenerator = true;
        }
    }

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