/* -*- 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.AstRoot;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.ast.Jump;
import org.mozilla.javascript.ast.FunctionNode;

/**
 * Generates bytecode for the Interpreter.
 */
class CodeGenerator extends Icode {

    private static final int MIN_LABEL_TABLE_SIZE = 32;
    private static final int MIN_FIXUP_TABLE_SIZE = 40;

    private CompilerEnvirons compilerEnv;

    private boolean itsInFunctionFlag;
    private boolean itsInTryFlag;

    private InterpreterData itsData;

    private ScriptNode scriptOrFn;
    private int iCodeTop;
    private int stackDepth;
    private int lineNumber;
    private int doubleTableTop;

    private ObjToIntMap strings = new ObjToIntMap(20);
    private int localTop;
    private int[] labelTable;
    private int labelTableTop;

    // fixupTable[i] = (label_index << 32) | fixup_site
    private long[] fixupTable;
    private int fixupTableTop;
    private ObjArray literalIds = new ObjArray();

    private int exceptionTableTop;

    // ECF_ or Expression Context Flags constants: for now only TAIL
    private static final int ECF_TAIL = 1 << 0;

    public InterpreterData compile(CompilerEnvirons compilerEnv,
                                   ScriptNode tree,
                                   String encodedSource,
                                   boolean returnFunction)
    {
        this.compilerEnv = compilerEnv;

        if (Token.printTrees) {
            System.out.println("before transform:");
            System.out.println(tree.toStringTree(tree));
        }

        new NodeTransformer().transform(tree);

        if (Token.printTrees) {
            System.out.println("after transform:");
            System.out.println(tree.toStringTree(tree));
        }

        if (returnFunction) {
            scriptOrFn = tree.getFunctionNode(0);
        } else {
            scriptOrFn = tree;
        }
        itsData = new InterpreterData(compilerEnv.getLanguageVersion(),
                                      scriptOrFn.getSourceName(),
                                      encodedSource,
                                      ((AstRoot)tree).isInStrictMode());
        itsData.topLevel = true;

        if (returnFunction) {
            generateFunctionICode();
        } else {
            generateICodeFromTree(scriptOrFn);
        }
        return itsData;
    }

    private void generateFunctionICode()
    {
        itsInFunctionFlag = true;

        FunctionNode theFunction = (FunctionNode)scriptOrFn;

        itsData.itsFunctionType = theFunction.getFunctionType();
        itsData.itsNeedsActivation = theFunction.requiresActivation();
        if (theFunction.getFunctionName() != null) {
            itsData.itsName = theFunction.getName();
        }
        if (theFunction.isGenerator()) {
          addIcode(Icode_GENERATOR);
          addUint16(theFunction.getBaseLineno() & 0xFFFF);
        }

        generateICodeFromTree(theFunction.getLastChild());
    }

    private void generateICodeFromTree(Node tree)
    {
        generateNestedFunctions();

        generateRegExpLiterals();

        visitStatement(tree, 0);
        fixLabelGotos();
        // add RETURN_RESULT only to scripts as function always ends with RETURN
        if (itsData.itsFunctionType == 0) {
            addToken(Token.RETURN_RESULT);
        }

        if (itsData.itsICode.length != iCodeTop) {
            // Make itsData.itsICode length exactly iCodeTop to save memory
            // and catch bugs with jumps beyond icode as early as possible
            byte[] tmp = new byte[iCodeTop];
            System.arraycopy(itsData.itsICode, 0, tmp, 0, iCodeTop);
            itsData.itsICode = tmp;
        }
        if (strings.size() == 0) {
            itsData.itsStringTable = null;
        } else {
            itsData.itsStringTable = new String[strings.size()];
            ObjToIntMap.Iterator iter = strings.newIterator();
            for (iter.start(); !iter.done(); iter.next()) {
                String str = (String)iter.getKey();
                int index = iter.getValue();
                if (itsData.itsStringTable[index] != null) Kit.codeBug();
                itsData.itsStringTable[index] = str;
            }
        }
        if (doubleTableTop == 0) {
            itsData.itsDoubleTable = null;
        } else if (itsData.itsDoubleTable.length != doubleTableTop) {
            double[] tmp = new double[doubleTableTop];
            System.arraycopy(itsData.itsDoubleTable, 0, tmp, 0,
                             doubleTableTop);
            itsData.itsDoubleTable = tmp;
        }
        if (exceptionTableTop != 0
            && itsData.itsExceptionTable.length != exceptionTableTop)
        {
            int[] tmp = new int[exceptionTableTop];
            System.arraycopy(itsData.itsExceptionTable, 0, tmp, 0,
                             exceptionTableTop);
            itsData.itsExceptionTable = tmp;
        }

        itsData.itsMaxVars = scriptOrFn.getParamAndVarCount();
        // itsMaxFrameArray: interpret method needs this amount for its
        // stack and sDbl arrays
        itsData.itsMaxFrameArray = itsData.itsMaxVars
                                   + itsData.itsMaxLocals
                                   + itsData.itsMaxStack;

        itsData.argNames = scriptOrFn.getParamAndVarNames();
        itsData.argIsConst = scriptOrFn.getParamAndVarConst();
        itsData.argCount = scriptOrFn.getParamCount();

        itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart();
        itsData.encodedSourceEnd = scriptOrFn.getEncodedSourceEnd();

        if (literalIds.size() != 0) {
            itsData.literalIds = literalIds.toArray();
        }

        if (Token.printICode) Interpreter.dumpICode(itsData);
    }

    private void generateNestedFunctions()
    {
        int functionCount = scriptOrFn.getFunctionCount();
        if (functionCount == 0) return;

        InterpreterData[] array = new InterpreterData[functionCount];
        for (int i = 0; i != functionCount; i++) {
            FunctionNode fn = scriptOrFn.getFunctionNode(i);
            CodeGenerator gen = new CodeGenerator();
            gen.compilerEnv = compilerEnv;
            gen.scriptOrFn = fn;
            gen.itsData = new InterpreterData(itsData);
            gen.generateFunctionICode();
            array[i] = gen.itsData;
        }
        itsData.itsNestedFunctions = array;
    }

    private void generateRegExpLiterals()
    {
        int N = scriptOrFn.getRegexpCount();
        if (N == 0) return;

        Context cx = Context.getContext();
        RegExpProxy rep = ScriptRuntime.checkRegExpProxy(cx);
        Object[] array = new Object[N];
        for (int i = 0; i != N; i++) {
            String string = scriptOrFn.getRegexpString(i);
            String flags = scriptOrFn.getRegexpFlags(i);
            array[i] = rep.compileRegExp(cx, string, flags);
        }
        itsData.itsRegExpLiterals = array;
    }

    private void updateLineNumber(Node node)
    {
        int lineno = node.getLineno();
        if (lineno != lineNumber && lineno >= 0) {
            if (itsData.firstLinePC < 0) {
                itsData.firstLinePC = lineno;
            }
            lineNumber = lineno;
            addIcode(Icode_LINE);
            addUint16(lineno & 0xFFFF);
        }
    }

    private RuntimeException badTree(Node node)
    {
        throw new RuntimeException(node.toString());
    }

    private void visitStatement(Node node, int initialStackDepth)
    {
        int type = node.getType();
        Node child = node.getFirstChild();
        switch (type) {

          case Token.FUNCTION:
            {
                int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
                int fnType = scriptOrFn.getFunctionNode(fnIndex).
                                 getFunctionType();
                // Only function expressions or function expression
                // statements need closure code creating new function
                // object on stack as function statements are initialized
                // at script/function start.
                // In addition, function expressions can not be present here
                // at statement level, they must only be present as expressions.
                if (fnType == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
                    addIndexOp(Icode_CLOSURE_STMT, fnIndex);
                } else {
                    if (fnType != FunctionNode.FUNCTION_STATEMENT) {
                        throw Kit.codeBug();
                    }
                }
                // For function statements or function expression statements
                // in scripts, we need to ensure that the result of the script
                // is the function if it is the last statement in the script.
                // For example, eval("function () {}") should return a
                // function, not undefined.
                if (!itsInFunctionFlag) {
                    addIndexOp(Icode_CLOSURE_EXPR, fnIndex);
                    stackChange(1);
                    addIcode(Icode_POP_RESULT);
                    stackChange(-1);
                }
            }
            break;

          case Token.LABEL:
          case Token.LOOP:
          case Token.BLOCK:
          case Token.EMPTY:
          case Token.WITH:
            updateLineNumber(node);
          case Token.SCRIPT:
            // fall through
            while (child != null) {
                visitStatement(child, initialStackDepth);
                child = child.getNext();
            }
            break;

          case Token.ENTERWITH:
            visitExpression(child, 0);
            addToken(Token.ENTERWITH);
            stackChange(-1);
            break;

          case Token.LEAVEWITH:
            addToken(Token.LEAVEWITH);
            break;

          case Token.LOCAL_BLOCK:
            {
                int local = allocLocal();
                node.putIntProp(Node.LOCAL_PROP, local);
                updateLineNumber(node);
                while (child != null) {
                    visitStatement(child, initialStackDepth);
                    child = child.getNext();
                }
                addIndexOp(Icode_LOCAL_CLEAR, local);
                releaseLocal(local);
            }
            break;

          case Token.DEBUGGER:
            addIcode(Icode_DEBUGGER);
            break;

          case Token.SWITCH:
            updateLineNumber(node);
            // See comments in IRFactory.createSwitch() for description
            // of SWITCH node
            {
                visitExpression(child, 0);
                for (Jump caseNode = (Jump)child.getNext();
                     caseNode != null;
                     caseNode = (Jump)caseNode.getNext())
                {
                    if (caseNode.getType() != Token.CASE)
                        throw badTree(caseNode);
                    Node test = caseNode.getFirstChild();
                    addIcode(Icode_DUP);
                    stackChange(1);
                    visitExpression(test, 0);
                    addToken(Token.SHEQ);
                    stackChange(-1);
                    // If true, Icode_IFEQ_POP will jump and remove case
                    // value from stack
                    addGoto(caseNode.target, Icode_IFEQ_POP);
                    stackChange(-1);
                }
                addIcode(Icode_POP);
                stackChange(-1);
            }
            break;

          case Token.TARGET:
            markTargetLabel(node);
            break;

          case Token.IFEQ :
          case Token.IFNE :
            {
                Node target = ((Jump)node).target;
                visitExpression(child, 0);
                addGoto(target, type);
                stackChange(-1);
            }
            break;

          case Token.GOTO:
            {
                Node target = ((Jump)node).target;
                addGoto(target, type);
            }
            break;

          case Token.JSR:
            {
                Node target = ((Jump)node).target;
                addGoto(target, Icode_GOSUB);
            }
            break;

          case Token.FINALLY:
            {
                // Account for incomming GOTOSUB address
                stackChange(1);
                int finallyRegister = getLocalBlockRef(node);
                addIndexOp(Icode_STARTSUB, finallyRegister);
                stackChange(-1);
                while (child != null) {
                    visitStatement(child, initialStackDepth);
                    child = child.getNext();
                }
                addIndexOp(Icode_RETSUB, finallyRegister);
            }
            break;

          case Token.EXPR_VOID:
          case Token.EXPR_RESULT:
            updateLineNumber(node);
            visitExpression(child, 0);
            addIcode((type == Token.EXPR_VOID) ? Icode_POP : Icode_POP_RESULT);
            stackChange(-1);
            break;

          case Token.TRY:
            {
                Jump tryNode = (Jump)node;
                int exceptionObjectLocal = getLocalBlockRef(tryNode);
                int scopeLocal = allocLocal();

                addIndexOp(Icode_SCOPE_SAVE, scopeLocal);

                int tryStart = iCodeTop;
                boolean savedFlag = itsInTryFlag;
                itsInTryFlag = true;
                while (child != null) {
                    visitStatement(child, initialStackDepth);
                    child = child.getNext();
                }
                itsInTryFlag = savedFlag;

                Node catchTarget = tryNode.target;
                if (catchTarget != null) {
                    int catchStartPC
                        = labelTable[getTargetLabel(catchTarget)];
                    addExceptionHandler(
                        tryStart, catchStartPC, catchStartPC,
                        false, exceptionObjectLocal, scopeLocal);
                }
                Node finallyTarget = tryNode.getFinally();
                if (finallyTarget != null) {
                    int finallyStartPC
                        = labelTable[getTargetLabel(finallyTarget)];
                    addExceptionHandler(
                        tryStart, finallyStartPC, finallyStartPC,
                        true, exceptionObjectLocal, scopeLocal);
                }

                addIndexOp(Icode_LOCAL_CLEAR, scopeLocal);
                releaseLocal(scopeLocal);
            }
            break;

          case Token.CATCH_SCOPE:
            {
                int localIndex = getLocalBlockRef(node);
                int scopeIndex = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);
                String name = child.getString();
                child = child.getNext();
                visitExpression(child, 0); // load expression object
                addStringPrefix(name);
                addIndexPrefix(localIndex);
                addToken(Token.CATCH_SCOPE);
                addUint8(scopeIndex != 0 ? 1 : 0);
                stackChange(-1);
            }
            break;

          case Token.THROW:
            updateLineNumber(node);
            visitExpression(child, 0);
            addToken(Token.THROW);
            addUint16(lineNumber & 0xFFFF);
            stackChange(-1);
            break;

          case Token.RETHROW:
            updateLineNumber(node);
            addIndexOp(Token.RETHROW, getLocalBlockRef(node));
            break;

          case Token.RETURN:
            updateLineNumber(node);
            if (node.getIntProp(Node.GENERATOR_END_PROP, 0) != 0) {
                // We're in a generator, so change RETURN to GENERATOR_END
                addIcode(Icode_GENERATOR_END);
                addUint16(lineNumber & 0xFFFF);
            } else if (child != null) {
                visitExpression(child, ECF_TAIL);
                addToken(Token.RETURN);
                stackChange(-1);
            } else {
                addIcode(Icode_RETUNDEF);
            }
            break;

          case Token.RETURN_RESULT:
            updateLineNumber(node);
            addToken(Token.RETURN_RESULT);
            break;

          case Token.ENUM_INIT_KEYS:
          case Token.ENUM_INIT_VALUES:
          case Token.ENUM_INIT_ARRAY:
            visitExpression(child, 0);
            addIndexOp(type, getLocalBlockRef(node));
            stackChange(-1);
            break;

          case Icode_GENERATOR:
            break;

          default:
            throw badTree(node);
        }

        if (stackDepth != initialStackDepth) {
            throw Kit.codeBug();
        }
    }

    private void visitExpression(Node node, int contextFlags)
    {
        int type = node.getType();
        Node child = node.getFirstChild();
        int savedStackDepth = stackDepth;
        switch (type) {

          case Token.FUNCTION:
            {
                int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
                FunctionNode fn = scriptOrFn.getFunctionNode(fnIndex);
                // See comments in visitStatement for Token.FUNCTION case
                if (fn.getFunctionType() != FunctionNode.FUNCTION_EXPRESSION) {
                    throw Kit.codeBug();
                }
                addIndexOp(Icode_CLOSURE_EXPR, fnIndex);
                stackChange(1);
            }
            break;

          case Token.LOCAL_LOAD:
            {
                int localIndex = getLocalBlockRef(node);
                addIndexOp(Token.LOCAL_LOAD, localIndex);
                stackChange(1);
            }
            break;

          case Token.COMMA:
            {
                Node lastChild = node.getLastChild();
                while (child != lastChild) {
                    visitExpression(child, 0);
                    addIcode(Icode_POP);
                    stackChange(-1);
                    child = child.getNext();
                }
                // Preserve tail context flag if any
                visitExpression(child, contextFlags & ECF_TAIL);
            }
            break;

          case Token.USE_STACK:
            // Indicates that stack was modified externally,
            // like placed catch object
            stackChange(1);
            break;

          case Token.REF_CALL:
          case Token.CALL:
          case Token.NEW:
            {
                if (type == Token.NEW) {
                    visitExpression(child, 0);
                } else {
                    generateCallFunAndThis(child);
                }
                int argCount = 0;
                while ((child = child.getNext()) != null) {
                    visitExpression(child, 0);
                    ++argCount;
                }
                int callType = node.getIntProp(Node.SPECIALCALL_PROP,
                                               Node.NON_SPECIALCALL);
                if (type != Token.REF_CALL && callType != Node.NON_SPECIALCALL) {
                    // embed line number and source filename
                    addIndexOp(Icode_CALLSPECIAL, argCount);
                    addUint8(callType);
                    addUint8(type == Token.NEW ? 1 : 0);
                    addUint16(lineNumber & 0xFFFF);
                } else {
                    // Only use the tail call optimization if we're not in a try
                    // or we're not generating debug info (since the
                    // optimization will confuse the debugger)
                    if (type == Token.CALL && (contextFlags & ECF_TAIL) != 0 &&
                        !compilerEnv.isGenerateDebugInfo() && !itsInTryFlag)
                    {
                        type = Icode_TAIL_CALL;
                    }
                    addIndexOp(type, argCount);
                }
                // adjust stack
                if (type == Token.NEW) {
                    // new: f, args -> result
                    stackChange(-argCount);
                } else {
                    // call: f, thisObj, args -> result
                    // ref_call: f, thisObj, args -> ref
                    stackChange(-1 - argCount);
                }
                if (argCount > itsData.itsMaxCalleeArgs) {
                    itsData.itsMaxCalleeArgs = argCount;
                }
            }
            break;

          case Token.AND:
          case Token.OR:
            {
                visitExpression(child, 0);
                addIcode(Icode_DUP);
                stackChange(1);
                int afterSecondJumpStart = iCodeTop;
                int jump = (type == Token.AND) ? Token.IFNE : Token.IFEQ;
                addGotoOp(jump);
                stackChange(-1);
                addIcode(Icode_POP);
                stackChange(-1);
                child = child.getNext();
                // Preserve tail context flag if any
                visitExpression(child, contextFlags & ECF_TAIL);
                resolveForwardGoto(afterSecondJumpStart);
            }
            break;

          case Token.HOOK:
            {
                Node ifThen = child.getNext();
                Node ifElse = ifThen.getNext();
                visitExpression(child, 0);
                int elseJumpStart = iCodeTop;
                addGotoOp(Token.IFNE);
                stackChange(-1);
                // Preserve tail context flag if any
                visitExpression(ifThen, contextFlags & ECF_TAIL);
                int afterElseJumpStart = iCodeTop;
                addGotoOp(Token.GOTO);
                resolveForwardGoto(elseJumpStart);
                stackDepth = savedStackDepth;
                // Preserve tail context flag if any
                visitExpression(ifElse, contextFlags & ECF_TAIL);
                resolveForwardGoto(afterElseJumpStart);
            }
            break;

          case Token.GETPROP:
          case Token.GETPROPNOWARN:
            visitExpression(child, 0);
            child = child.getNext();
            addStringOp(type, child.getString());
            break;

          case Token.DELPROP:
            boolean isName = child.getType() == Token.BINDNAME;
            visitExpression(child, 0);
            child = child.getNext();
            visitExpression(child, 0);
            if (isName) {
                // special handling for delete name
                addIcode(Icode_DELNAME);
            } else {
                addToken(Token.DELPROP);
            }
            stackChange(-1);
            break;

          case Token.GETELEM:
          case Token.BITAND:
          case Token.BITOR:
          case Token.BITXOR:
          case Token.LSH:
          case Token.RSH:
          case Token.URSH:
          case Token.ADD:
          case Token.SUB:
          case Token.MOD:
          case Token.DIV:
          case Token.MUL:
          case Token.EQ:
          case Token.NE:
          case Token.SHEQ:
          case Token.SHNE:
          case Token.IN:
          case Token.INSTANCEOF:
          case Token.LE:
          case Token.LT:
          case Token.GE:
          case Token.GT:
            visitExpression(child, 0);
            child = child.getNext();
            visitExpression(child, 0);
            addToken(type);
            stackChange(-1);
            break;

          case Token.POS:
          case Token.NEG:
          case Token.NOT:
          case Token.BITNOT:
          case Token.TYPEOF:
          case Token.VOID:
            visitExpression(child, 0);
            if (type == Token.VOID) {
                addIcode(Icode_POP);
                addIcode(Icode_UNDEF);
            } else {
                addToken(type);
            }
            break;

          case Token.GET_REF:
          case Token.DEL_REF:
            visitExpression(child, 0);
            addToken(type);
            break;

          case Token.SETPROP:
          case Token.SETPROP_OP:
            {
                visitExpression(child, 0);
                child = child.getNext();
                String property = child.getString();
                child = child.getNext();
                if (type == Token.SETPROP_OP) {
                    addIcode(Icode_DUP);
                    stackChange(1);
                    addStringOp(Token.GETPROP, property);
                    // Compensate for the following USE_STACK
                    stackChange(-1);
                }
                visitExpression(child, 0);
                addStringOp(Token.SETPROP, property);
                stackChange(-1);
            }
            break;

          case Token.SETELEM:
          case Token.SETELEM_OP:
            visitExpression(child, 0);
            child = child.getNext();
            visitExpression(child, 0);
            child = child.getNext();
            if (type == Token.SETELEM_OP) {
                addIcode(Icode_DUP2);
                stackChange(2);
                addToken(Token.GETELEM);
                stackChange(-1);
                // Compensate for the following USE_STACK
                stackChange(-1);
            }
            visitExpression(child, 0);
            addToken(Token.SETELEM);
            stackChange(-2);
            break;

          case Token.SET_REF:
          case Token.SET_REF_OP:
            visitExpression(child, 0);
            child = child.getNext();
            if (type == Token.SET_REF_OP) {
                addIcode(Icode_DUP);
                stackChange(1);
                addToken(Token.GET_REF);
                // Compensate for the following USE_STACK
                stackChange(-1);
            }
            visitExpression(child, 0);
            addToken(Token.SET_REF);
            stackChange(-1);
            break;

          case Token.STRICT_SETNAME:
          case Token.SETNAME:
            {
                String name = child.getString();
                visitExpression(child, 0);
                child = child.getNext();
                visitExpression(child, 0);
                addStringOp(type, name);
                stackChange(-1);
            }
            break;

          case Token.SETCONST:
            {
                String name = child.getString();
                visitExpression(child, 0);
                child = child.getNext();
                visitExpression(child, 0);
                addStringOp(Icode_SETCONST, name);
                stackChange(-1);
            }
            break;

          case Token.TYPEOFNAME:
            {
                int index = -1;
                // use typeofname if an activation frame exists
                // since the vars all exist there instead of in jregs
                if (itsInFunctionFlag && !itsData.itsNeedsActivation)
                    index = scriptOrFn.getIndexForNameNode(node);
                if (index == -1) {
                    addStringOp(Icode_TYPEOFNAME, node.getString());
                    stackChange(1);
                } else {
                    addVarOp(Token.GETVAR, index);
                    stackChange(1);
                    addToken(Token.TYPEOF);
                }
            }
            break;

          case Token.BINDNAME:
          case Token.NAME:
          case Token.STRING:
            addStringOp(type, node.getString());
            stackChange(1);
            break;

          case Token.INC:
          case Token.DEC:
            visitIncDec(node, child);
            break;

          case Token.NUMBER:
            {
                double num = node.getDouble();
                int inum = (int)num;
                if (inum == num) {
                    if (inum == 0) {
                        addIcode(Icode_ZERO);
                        // Check for negative zero
                        if (1.0 / num < 0.0) {
                            addToken(Token.NEG);
                        }
                    } else if (inum == 1) {
                        addIcode(Icode_ONE);
                    } else if ((short)inum == inum) {
                        addIcode(Icode_SHORTNUMBER);
                        // write short as uin16 bit pattern
                        addUint16(inum & 0xFFFF);
                    } else {
                        addIcode(Icode_INTNUMBER);
                        addInt(inum);
                    }
                } else {
                    int index = getDoubleIndex(num);
                    addIndexOp(Token.NUMBER, index);
                }
                stackChange(1);
            }
            break;

          case Token.GETVAR:
            {
                if (itsData.itsNeedsActivation) Kit.codeBug();
                int index = scriptOrFn.getIndexForNameNode(node);
                addVarOp(Token.GETVAR, index);
                stackChange(1);
            }
            break;

          case Token.SETVAR:
            {
                if (itsData.itsNeedsActivation) Kit.codeBug();
                int index = scriptOrFn.getIndexForNameNode(child);
                child = child.getNext();
                visitExpression(child, 0);
                addVarOp(Token.SETVAR, index);
            }
            break;

          case Token.SETCONSTVAR:
            {
                if (itsData.itsNeedsActivation) Kit.codeBug();
                int index = scriptOrFn.getIndexForNameNode(child);
                child = child.getNext();
                visitExpression(child, 0);
                addVarOp(Token.SETCONSTVAR, index);
            }
            break;

          case Token.NULL:
          case Token.THIS:
          case Token.THISFN:
          case Token.FALSE:
          case Token.TRUE:
            addToken(type);
            stackChange(1);
            break;

          case Token.ENUM_NEXT:
          case Token.ENUM_ID:
            addIndexOp(type, getLocalBlockRef(node));
            stackChange(1);
            break;

          case Token.REGEXP:
            {
                int index = node.getExistingIntProp(Node.REGEXP_PROP);
                addIndexOp(Token.REGEXP, index);
                stackChange(1);
            }
            break;

          case Token.ARRAYLIT:
          case Token.OBJECTLIT:
            visitLiteral(node, child);
            break;

          case Token.ARRAYCOMP:
            visitArrayComprehension(node, child, child.getNext());
            break;

          case Token.REF_SPECIAL:
            visitExpression(child, 0);
            addStringOp(type, (String)node.getProp(Node.NAME_PROP));
            break;

          case Token.REF_MEMBER:
          case Token.REF_NS_MEMBER:
          case Token.REF_NAME:
          case Token.REF_NS_NAME:
            {
                int memberTypeFlags = node.getIntProp(Node.MEMBER_TYPE_PROP, 0);
                // generate possible target, possible namespace and member
                int childCount = 0;
                do {
                    visitExpression(child, 0);
                    ++childCount;
                    child = child.getNext();
                } while (child != null);
                addIndexOp(type, memberTypeFlags);
                stackChange(1 - childCount);
            }
            break;

          case Token.DOTQUERY:
            {
                int queryPC;
                updateLineNumber(node);
                visitExpression(child, 0);
                addIcode(Icode_ENTERDQ);
                stackChange(-1);
                queryPC = iCodeTop;
                visitExpression(child.getNext(), 0);
                addBackwardGoto(Icode_LEAVEDQ, queryPC);
            }
            break;

          case Token.DEFAULTNAMESPACE :
          case Token.ESCXMLATTR :
          case Token.ESCXMLTEXT :
            visitExpression(child, 0);
            addToken(type);
            break;

          case Token.YIELD:
            if (child != null) {
                visitExpression(child, 0);
            } else {
                addIcode(Icode_UNDEF);
                stackChange(1);
            }
            addToken(Token.YIELD);
            addUint16(node.getLineno() & 0xFFFF);
            break;

          case Token.WITHEXPR: {
            Node enterWith = node.getFirstChild();
            Node with = enterWith.getNext();
            visitExpression(enterWith.getFirstChild(), 0);
            addToken(Token.ENTERWITH);
            stackChange(-1);
            visitExpression(with.getFirstChild(), 0);
            addToken(Token.LEAVEWITH);
            break;
          }

          default:
            throw badTree(node);
        }
        if (savedStackDepth + 1 != stackDepth) {
            Kit.codeBug();
        }
    }

    private void generateCallFunAndThis(Node left)
    {
        // Generate code to place on stack function and thisObj
        int type = left.getType();
        switch (type) {
          case Token.NAME: {
            String name = left.getString();
            // stack: ... -> ... function thisObj
            addStringOp(Icode_NAME_AND_THIS, name);
            stackChange(2);
            break;
          }
          case Token.GETPROP:
          case Token.GETELEM: {
            Node target = left.getFirstChild();
            visitExpression(target, 0);
            Node id = target.getNext();
            if (type == Token.GETPROP) {
                String property = id.getString();
                // stack: ... target -> ... function thisObj
                addStringOp(Icode_PROP_AND_THIS, property);
                stackChange(1);
            } else {
                visitExpression(id, 0);
                // stack: ... target id -> ... function thisObj
                addIcode(Icode_ELEM_AND_THIS);
            }
            break;
          }
          default:
            // Including Token.GETVAR
            visitExpression(left, 0);
            // stack: ... value -> ... function thisObj
            addIcode(Icode_VALUE_AND_THIS);
            stackChange(1);
            break;
        }
    }


    private void visitIncDec(Node node, Node child)
    {
        int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP);
        int childType = child.getType();
        switch (childType) {
          case Token.GETVAR : {
            if (itsData.itsNeedsActivation) Kit.codeBug();
            int i = scriptOrFn.getIndexForNameNode(child);
            addVarOp(Icode_VAR_INC_DEC, i);
            addUint8(incrDecrMask);
            stackChange(1);
            break;
          }
          case Token.NAME : {
            String name = child.getString();
            addStringOp(Icode_NAME_INC_DEC, name);
            addUint8(incrDecrMask);
            stackChange(1);
            break;
          }
          case Token.GETPROP : {
            Node object = child.getFirstChild();
            visitExpression(object, 0);
            String property = object.getNext().getString();
            addStringOp(Icode_PROP_INC_DEC, property);
            addUint8(incrDecrMask);
            break;
          }
          case Token.GETELEM : {
            Node object = child.getFirstChild();
            visitExpression(object, 0);
            Node index = object.getNext();
            visitExpression(index, 0);
            addIcode(Icode_ELEM_INC_DEC);
            addUint8(incrDecrMask);
            stackChange(-1);
            break;
          }
          case Token.GET_REF : {
            Node ref = child.getFirstChild();
            visitExpression(ref, 0);
            addIcode(Icode_REF_INC_DEC);
            addUint8(incrDecrMask);
            break;
          }
          default : {
            throw badTree(node);
          }
        }
    }

    private void visitLiteral(Node node, Node child)
    {
        int type = node.getType();
        int count;
        Object[] propertyIds = null;
        if (type == Token.ARRAYLIT) {
            count = 0;
            for (Node n = child; n != null; n = n.getNext()) {
                ++count;
            }
        } else if (type == Token.OBJECTLIT) {
            propertyIds = (Object[])node.getProp(Node.OBJECT_IDS_PROP);
            count = propertyIds.length;
        } else {
            throw badTree(node);
        }
        addIndexOp(Icode_LITERAL_NEW, count);
        stackChange(2);
        while (child != null) {
            int childType = child.getType();
            if (childType == Token.GET) {
                visitExpression(child.getFirstChild(), 0);
                addIcode(Icode_LITERAL_GETTER);
            } else if (childType == Token.SET) {
                visitExpression(child.getFirstChild(), 0);
                addIcode(Icode_LITERAL_SETTER);
            } else if (childType == Token.METHOD) {
                visitExpression(child.getFirstChild(), 0);
                addIcode(Icode_LITERAL_SET);
            } else {
                visitExpression(child, 0);
                addIcode(Icode_LITERAL_SET);
            }
            stackChange(-1);
            child = child.getNext();
        }
        if (type == Token.ARRAYLIT) {
            int[] skipIndexes = (int[])node.getProp(Node.SKIP_INDEXES_PROP);
            if (skipIndexes == null) {
                addToken(Token.ARRAYLIT);
            } else {
                int index = literalIds.size();
                literalIds.add(skipIndexes);
                addIndexOp(Icode_SPARE_ARRAYLIT, index);
            }
        } else {
            int index = literalIds.size();
            literalIds.add(propertyIds);
            addIndexOp(Token.OBJECTLIT, index);
        }
        stackChange(-1);
    }

    private void visitArrayComprehension(Node node, Node initStmt, Node expr)
    {
        // A bit of a hack: array comprehensions are implemented using
        // statement nodes for the iteration, yet they appear in an
        // expression context. So we pass the current stack depth to
        // visitStatement so it can check that the depth is not altered
        // by statements.
        visitStatement(initStmt, stackDepth);
        visitExpression(expr, 0);
    }

    private int getLocalBlockRef(Node node)
    {
        Node localBlock = (Node)node.getProp(Node.LOCAL_BLOCK_PROP);
        return localBlock.getExistingIntProp(Node.LOCAL_PROP);
    }

    private int getTargetLabel(Node target)
    {
        int label = target.labelId();
        if (label != -1) {
            return label;
        }
        label = labelTableTop;
        if (labelTable == null || label == labelTable.length) {
            if (labelTable == null) {
                labelTable = new int[MIN_LABEL_TABLE_SIZE];
            }else {
                int[] tmp = new int[labelTable.length * 2];
                System.arraycopy(labelTable, 0, tmp, 0, label);
                labelTable = tmp;
            }
        }
        labelTableTop = label + 1;
        labelTable[label] = -1;

        target.labelId(label);
        return label;
    }

    private void markTargetLabel(Node target)
    {
        int label = getTargetLabel(target);
        if (labelTable[label] != -1) {
            // Can mark label only once
            Kit.codeBug();
        }
        labelTable[label] = iCodeTop;
    }

    private void addGoto(Node target, int gotoOp)
    {
        int label = getTargetLabel(target);
        if (!(label < labelTableTop)) Kit.codeBug();
        int targetPC = labelTable[label];

        if (targetPC != -1) {
            addBackwardGoto(gotoOp, targetPC);
        } else {
            int gotoPC = iCodeTop;
            addGotoOp(gotoOp);
            int top = fixupTableTop;
            if (fixupTable == null || top == fixupTable.length) {
                if (fixupTable == null) {
                    fixupTable = new long[MIN_FIXUP_TABLE_SIZE];
                } else {
                    long[] tmp = new long[fixupTable.length * 2];
                    System.arraycopy(fixupTable, 0, tmp, 0, top);
                    fixupTable = tmp;
                }
            }
            fixupTableTop = top + 1;
            fixupTable[top] = ((long)label << 32) | gotoPC;
        }
    }

    private void fixLabelGotos()
    {
        for (int i = 0; i < fixupTableTop; i++) {
            long fixup = fixupTable[i];
            int label = (int)(fixup >> 32);
            int jumpSource = (int)fixup;
            int pc = labelTable[label];
            if (pc == -1) {
                // Unlocated label
                throw Kit.codeBug();
            }
            resolveGoto(jumpSource, pc);
        }
        fixupTableTop = 0;
    }

    private void addBackwardGoto(int gotoOp, int jumpPC)
    {
        int fromPC = iCodeTop;
        // Ensure that this is a jump backward
        if (fromPC <= jumpPC) throw Kit.codeBug();
        addGotoOp(gotoOp);
        resolveGoto(fromPC, jumpPC);
    }

    private void resolveForwardGoto(int fromPC)
    {
        // Ensure that forward jump skips at least self bytecode
        if (iCodeTop < fromPC + 3) throw Kit.codeBug();
        resolveGoto(fromPC, iCodeTop);
    }

    private void resolveGoto(int fromPC, int jumpPC)
    {
        int offset = jumpPC - fromPC;
        // Ensure that jumps do not overlap
        if (0 <= offset && offset <= 2) throw Kit.codeBug();
        int offsetSite = fromPC + 1;
        if (offset != (short)offset) {
            if (itsData.longJumps == null) {
                itsData.longJumps = new UintMap();
            }
            itsData.longJumps.put(offsetSite, jumpPC);
            offset = 0;
        }
        byte[] array = itsData.itsICode;
        array[offsetSite] = (byte)(offset >> 8);
        array[offsetSite + 1] = (byte)offset;
    }

    private void addToken(int token)
    {
        if (!Icode.validTokenCode(token)) throw Kit.codeBug();
        addUint8(token);
    }

    private void addIcode(int icode)
    {
        if (!Icode.validIcode(icode)) throw Kit.codeBug();
        // Write negative icode as uint8 bits
        addUint8(icode & 0xFF);
    }

    private void addUint8(int value)
    {
        if ((value & ~0xFF) != 0) throw Kit.codeBug();
        byte[] array = itsData.itsICode;
        int top = iCodeTop;
        if (top == array.length) {
            array = increaseICodeCapacity(1);
        }
        array[top] = (byte)value;
        iCodeTop = top + 1;
    }

    private void addUint16(int value)
    {
        if ((value & ~0xFFFF) != 0) throw Kit.codeBug();
        byte[] array = itsData.itsICode;
        int top = iCodeTop;
        if (top + 2 > array.length) {
            array = increaseICodeCapacity(2);
        }
        array[top] = (byte)(value >>> 8);
        array[top + 1] = (byte)value;
        iCodeTop = top + 2;
    }

    private void addInt(int i)
    {
        byte[] array = itsData.itsICode;
        int top = iCodeTop;
        if (top + 4 > array.length) {
            array = increaseICodeCapacity(4);
        }
        array[top] = (byte)(i >>> 24);
        array[top + 1] = (byte)(i >>> 16);
        array[top + 2] = (byte)(i >>> 8);
        array[top + 3] = (byte)i;
        iCodeTop = top + 4;
    }

    private int getDoubleIndex(double num)
    {
        int index = doubleTableTop;
        if (index == 0) {
            itsData.itsDoubleTable = new double[64];
        } else if (itsData.itsDoubleTable.length == index) {
            double[] na = new double[index * 2];
            System.arraycopy(itsData.itsDoubleTable, 0, na, 0, index);
            itsData.itsDoubleTable = na;
        }
        itsData.itsDoubleTable[index] = num;
        doubleTableTop = index + 1;
        return index;
    }

    private void addGotoOp(int gotoOp)
    {
        byte[] array = itsData.itsICode;
        int top = iCodeTop;
        if (top + 3 > array.length) {
            array = increaseICodeCapacity(3);
        }
        array[top] = (byte)gotoOp;
        // Offset would written later
        iCodeTop = top + 1 + 2;
    }

    private void addVarOp(int op, int varIndex)
    {
        switch (op) {
          case Token.SETCONSTVAR:
            if (varIndex < 128) {
                addIcode(Icode_SETCONSTVAR1);
                addUint8(varIndex);
                return;
            }
            addIndexOp(Icode_SETCONSTVAR, varIndex);
            return;
          case Token.GETVAR:
          case Token.SETVAR:
            if (varIndex < 128) {
                addIcode(op == Token.GETVAR ? Icode_GETVAR1 : Icode_SETVAR1);
                addUint8(varIndex);
                return;
            }
            // fallthrough
          case Icode_VAR_INC_DEC:
            addIndexOp(op, varIndex);
            return;
        }
        throw Kit.codeBug();
    }

    private void addStringOp(int op, String str)
    {
        addStringPrefix(str);
        if (Icode.validIcode(op)) {
            addIcode(op);
        } else {
            addToken(op);
        }
    }

    private void addIndexOp(int op, int index)
    {
        addIndexPrefix(index);
        if (Icode.validIcode(op)) {
            addIcode(op);
        } else {
            addToken(op);
        }
    }

    private void addStringPrefix(String str)
    {
        int index = strings.get(str, -1);
        if (index == -1) {
            index = strings.size();
            strings.put(str, index);
        }
        if (index < 4) {
            addIcode(Icode_REG_STR_C0 - index);
        } else if (index <= 0xFF) {
            addIcode(Icode_REG_STR1);
            addUint8(index);
         } else if (index <= 0xFFFF) {
            addIcode(Icode_REG_STR2);
            addUint16(index);
         } else {
            addIcode(Icode_REG_STR4);
            addInt(index);
        }
    }

    private void addIndexPrefix(int index)
    {
        if (index < 0) Kit.codeBug();
        if (index < 6) {
            addIcode(Icode_REG_IND_C0 - index);
        } else if (index <= 0xFF) {
            addIcode(Icode_REG_IND1);
            addUint8(index);
         } else if (index <= 0xFFFF) {
            addIcode(Icode_REG_IND2);
            addUint16(index);
         } else {
            addIcode(Icode_REG_IND4);
            addInt(index);
        }
    }

    private void addExceptionHandler(int icodeStart, int icodeEnd,
                                     int handlerStart, boolean isFinally,
                                     int exceptionObjectLocal, int scopeLocal)
    {
        int top = exceptionTableTop;
        int[] table = itsData.itsExceptionTable;
        if (table == null) {
            if (top != 0) Kit.codeBug();
            table = new int[Interpreter.EXCEPTION_SLOT_SIZE * 2];
            itsData.itsExceptionTable = table;
        } else if (table.length == top) {
            table = new int[table.length * 2];
            System.arraycopy(itsData.itsExceptionTable, 0, table, 0, top);
            itsData.itsExceptionTable = table;
        }
        table[top + Interpreter.EXCEPTION_TRY_START_SLOT]  = icodeStart;
        table[top + Interpreter.EXCEPTION_TRY_END_SLOT]    = icodeEnd;
        table[top + Interpreter.EXCEPTION_HANDLER_SLOT]    = handlerStart;
        table[top + Interpreter.EXCEPTION_TYPE_SLOT]     = isFinally ? 1 : 0;
        table[top + Interpreter.EXCEPTION_LOCAL_SLOT]    = exceptionObjectLocal;
        table[top + Interpreter.EXCEPTION_SCOPE_SLOT]    = scopeLocal;

        exceptionTableTop = top + Interpreter.EXCEPTION_SLOT_SIZE;
    }

    private byte[] increaseICodeCapacity(int extraSize)
    {
        int capacity = itsData.itsICode.length;
        int top = iCodeTop;
        if (top + extraSize <= capacity) throw Kit.codeBug();
        capacity *= 2;
        if (top + extraSize > capacity) {
            capacity = top + extraSize;
        }
        byte[] array = new byte[capacity];
        System.arraycopy(itsData.itsICode, 0, array, 0, top);
        itsData.itsICode = array;
        return array;
    }

    private void stackChange(int change)
    {
        if (change <= 0) {
            stackDepth += change;
        } else {
            int newDepth = stackDepth + change;
            if (newDepth > itsData.itsMaxStack) {
                itsData.itsMaxStack = newDepth;
            }
            stackDepth = newDepth;
        }
    }

    private int allocLocal()
    {
        int localSlot = localTop;
        ++localTop;
        if (localTop > itsData.itsMaxLocals) {
            itsData.itsMaxLocals = localTop;
        }
        return localSlot;
    }

    private void releaseLocal(int localSlot)
    {
        --localTop;
        if (localSlot != localTop) Kit.codeBug();
    }
}
