/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * 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.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * 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.
 */

package javassist.compiler;

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

import javassist.bytecode.Bytecode;
import javassist.bytecode.Opcode;
import javassist.compiler.ast.ASTList;
import javassist.compiler.ast.ASTree;
import javassist.compiler.ast.ArrayInit;
import javassist.compiler.ast.AssignExpr;
import javassist.compiler.ast.BinExpr;
import javassist.compiler.ast.CallExpr;
import javassist.compiler.ast.CastExpr;
import javassist.compiler.ast.CondExpr;
import javassist.compiler.ast.Declarator;
import javassist.compiler.ast.DoubleConst;
import javassist.compiler.ast.Expr;
import javassist.compiler.ast.FieldDecl;
import javassist.compiler.ast.InstanceOfExpr;
import javassist.compiler.ast.IntConst;
import javassist.compiler.ast.Keyword;
import javassist.compiler.ast.Member;
import javassist.compiler.ast.MethodDecl;
import javassist.compiler.ast.NewExpr;
import javassist.compiler.ast.Pair;
import javassist.compiler.ast.Stmnt;
import javassist.compiler.ast.StringL;
import javassist.compiler.ast.Symbol;
import javassist.compiler.ast.Variable;
import javassist.compiler.ast.Visitor;

/* The code generator is implemented by three files:
 * CodeGen.java, MemberCodeGen.java, and JvstCodeGen.
 * I just wanted to split a big file into three smaller ones.
 */

public abstract class CodeGen extends Visitor implements Opcode, TokenId {
    static final String javaLangObject = "java.lang.Object";
    static final String jvmJavaLangObject = "java/lang/Object";

    static final String javaLangString = "java.lang.String";
    static final String jvmJavaLangString = "java/lang/String";

    protected Bytecode bytecode;
    private int tempVar;
    TypeChecker typeChecker;

    /**
     * true if the last visited node is a return statement.
     */
    protected boolean hasReturned;

    /**
     * Must be true if compilation is for a static method.
     */
    public boolean inStaticMethod;

    protected List<Integer>  breakList, continueList;

    /**
     * doit() in ReturnHook is called from atReturn().
     */
    protected static abstract class ReturnHook {
        ReturnHook next;

        /**
         * Returns true if the generated code ends with return,
         * throw, or goto. 
         */
        protected abstract boolean doit(Bytecode b, int opcode);

        protected ReturnHook(CodeGen gen) {
            next = gen.returnHooks;
            gen.returnHooks = this;
        }

        protected void remove(CodeGen gen) {
            gen.returnHooks = next;
        }
    }

    protected ReturnHook returnHooks;

    /* The following fields are used by atXXX() methods
     * for returning the type of the compiled expression.
     */
    protected int exprType;     // VOID, NULL, CLASS, BOOLEAN, INT, ...
    protected int arrayDim;
    protected String className; // JVM-internal representation

    public CodeGen(Bytecode b) {
        bytecode = b;
        tempVar = -1;
        typeChecker = null;
        hasReturned = false;
        inStaticMethod = false;
        breakList = null;
        continueList = null;
        returnHooks = null;
    }

    public void setTypeChecker(TypeChecker checker) {
        typeChecker = checker;
    }

    protected static void fatal() throws CompileError {
        throw new CompileError("fatal");
    }

    public static boolean is2word(int type, int dim) {
        return dim == 0 && (type == DOUBLE || type == LONG); 
    }

    public int getMaxLocals() { return bytecode.getMaxLocals(); }

    public void setMaxLocals(int n) {
        bytecode.setMaxLocals(n);
    }

    protected void incMaxLocals(int size) {
        bytecode.incMaxLocals(size);
    }

    /**
     * Returns a local variable that single or double words can be
     * stored in.
     */
    protected int getTempVar() {
        if (tempVar < 0) {
            tempVar = getMaxLocals();
            incMaxLocals(2);
        }

        return tempVar;
    }

    protected int getLocalVar(Declarator d) {
        int v = d.getLocalVar();
        if (v < 0) {
            v = getMaxLocals(); // delayed variable allocation.
            d.setLocalVar(v);
            incMaxLocals(1);
        }

        return v;
    }

    /**
     * Returns the JVM-internal representation of this class name.
     */
    protected abstract String getThisName();

    /**
     * Returns the JVM-internal representation of this super class name.
     */
    protected abstract String getSuperName() throws CompileError;

    /* Converts a class name into a JVM-internal representation.
     *
     * It may also expand a simple class name to java.lang.*.
     * For example, this converts Object into java/lang/Object.
     */
    protected abstract String resolveClassName(ASTList name)
        throws CompileError;

    /* Expands a simple class name to java.lang.*.
     * For example, this converts Object into java/lang/Object.
     */
    protected abstract String resolveClassName(String jvmClassName)
        throws CompileError;

    /**
     * @param name      the JVM-internal representation.
     *                  name is not exapnded to java.lang.*.
     */
    protected static String toJvmArrayName(String name, int dim) {
        if (name == null)
            return null;

        if (dim == 0)
            return name;
        StringBuilder sbuf = new StringBuilder();
        int d = dim;
        while (d-- > 0)
            sbuf.append('[');

        sbuf.append('L');
        sbuf.append(name);
        sbuf.append(';');

        return sbuf.toString();
    }

    protected static String toJvmTypeName(int type, int dim) {
        char c = 'I';
        switch(type) {
        case BOOLEAN :
            c = 'Z';
            break;
        case BYTE :
            c = 'B';
            break;
        case CHAR :
            c = 'C';
            break;
        case SHORT :
            c = 'S';
            break;
        case INT :
            c = 'I';
            break;
        case LONG :
            c = 'J';
            break;
        case FLOAT :
            c = 'F';
            break;
        case DOUBLE :
            c = 'D';
            break;
        case VOID :
            c = 'V';
            break;
        }

        StringBuilder sbuf = new StringBuilder();
        while (dim-- > 0)
                sbuf.append('[');

        sbuf.append(c);
        return sbuf.toString();
    }

    public void compileExpr(ASTree expr) throws CompileError {
        doTypeCheck(expr);
        expr.accept(this);
    }

    public boolean compileBooleanExpr(boolean branchIf, ASTree expr)
        throws CompileError
    {
        doTypeCheck(expr);
        return booleanExpr(branchIf, expr);
    }

    public void doTypeCheck(ASTree expr) throws CompileError {
        if (typeChecker != null)
            expr.accept(typeChecker);
    }

    @Override
    public void atASTList(ASTList n) throws CompileError { fatal(); }

    @Override
    public void atPair(Pair n) throws CompileError { fatal(); }

    @Override
    public void atSymbol(Symbol n) throws CompileError { fatal(); }

    @Override
    public void atFieldDecl(FieldDecl field) throws CompileError {
        field.getInit().accept(this);
    }

    @Override
    public void atMethodDecl(MethodDecl method) throws CompileError {
        ASTList mods = method.getModifiers();
        setMaxLocals(1);
        while (mods != null) {
            Keyword k = (Keyword)mods.head();
            mods = mods.tail();
            if (k.get() == STATIC) {
                setMaxLocals(0);
                inStaticMethod = true;
            }
        }

        ASTList params = method.getParams();
        while (params != null) {
            atDeclarator((Declarator)params.head());
            params = params.tail();
        }

        Stmnt s = method.getBody();
        atMethodBody(s, method.isConstructor(),
                     method.getReturn().getType() == VOID);
    }

    /**
     * @param isCons	true if super() must be called.
     *			false if the method is a class initializer.
     */
    public void atMethodBody(Stmnt s, boolean isCons, boolean isVoid)
        throws CompileError
    {
        if (s == null)
            return;

        if (isCons && needsSuperCall(s))
            insertDefaultSuperCall();

        hasReturned = false;
        s.accept(this);
        if (!hasReturned)
            if (isVoid) {
                bytecode.addOpcode(Opcode.RETURN);
                hasReturned = true;
            }
            else
                throw new CompileError("no return statement");
    }

    private boolean needsSuperCall(Stmnt body) throws CompileError {
        if (body.getOperator() == BLOCK)
            body = (Stmnt)body.head();

        if (body != null && body.getOperator() == EXPR) {
            ASTree expr = body.head();
            if (expr != null && expr instanceof Expr
                && ((Expr)expr).getOperator() == CALL) {
                ASTree target = ((Expr)expr).head();
                if (target instanceof Keyword) {
                    int token = ((Keyword)target).get();
                    return token != THIS && token != SUPER;
                }
            }
        }

        return true;
    }

    protected abstract void insertDefaultSuperCall() throws CompileError;

    @Override
    public void atStmnt(Stmnt st) throws CompileError {
        if (st == null)
            return;     // empty

        int op = st.getOperator();
        if (op == EXPR) {
            ASTree expr = st.getLeft();
            doTypeCheck(expr);
            if (expr instanceof AssignExpr)
                atAssignExpr((AssignExpr)expr, false);
            else if (isPlusPlusExpr(expr)) {
                Expr e = (Expr)expr;
                atPlusPlus(e.getOperator(), e.oprand1(), e, false);
            }
            else {
                expr.accept(this);
                if (is2word(exprType, arrayDim))
                    bytecode.addOpcode(POP2);
                else if (exprType != VOID)
                    bytecode.addOpcode(POP);
            }
        }
        else if (op == DECL || op == BLOCK) {
            ASTList list = st;
            while (list != null) {
                ASTree h = list.head();
                list = list.tail();
                if (h != null)
                    h.accept(this);
            }
        }
        else if (op == IF)
            atIfStmnt(st);
        else if (op == WHILE || op == DO)
            atWhileStmnt(st, op == WHILE);
        else if (op == FOR)
            atForStmnt(st);
        else if (op == BREAK || op == CONTINUE)
            atBreakStmnt(st, op == BREAK);
        else if (op == TokenId.RETURN)
            atReturnStmnt(st);
        else if (op == THROW)
            atThrowStmnt(st);
        else if (op == TRY)
            atTryStmnt(st);
        else if (op == SWITCH)
            atSwitchStmnt(st);
        else if (op == SYNCHRONIZED)
            atSyncStmnt(st);
        else {
            // LABEL, SWITCH label stament might be null?.
            hasReturned = false;
            throw new CompileError(
                "sorry, not supported statement: TokenId " + op);
        }
    }

    private void atIfStmnt(Stmnt st) throws CompileError {
        ASTree expr = st.head();
        Stmnt thenp = (Stmnt)st.tail().head();
        Stmnt elsep = (Stmnt)st.tail().tail().head();
        if (compileBooleanExpr(false, expr)) {
            hasReturned = false;
            if (elsep != null)
                elsep.accept(this);

            return;
        }

        int pc = bytecode.currentPc();
        int pc2 = 0;
        bytecode.addIndex(0);   // correct later

        hasReturned = false;
        if (thenp != null)
            thenp.accept(this);

        boolean thenHasReturned = hasReturned;
        hasReturned = false;

        if (elsep != null && !thenHasReturned) {
            bytecode.addOpcode(Opcode.GOTO);
            pc2 = bytecode.currentPc();
            bytecode.addIndex(0);
        }

        bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
        if (elsep != null) {
            elsep.accept(this);
            if (!thenHasReturned)
                bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1);

            hasReturned = thenHasReturned && hasReturned;
        }
    }

    private void atWhileStmnt(Stmnt st, boolean notDo) throws CompileError {
        List<Integer>  prevBreakList = breakList;
        List<Integer>  prevContList = continueList;
        breakList = new ArrayList<Integer>();
        continueList = new ArrayList<Integer>();

        ASTree expr = st.head();
        Stmnt body = (Stmnt)st.tail();

        int pc = 0;
        if (notDo) {
            bytecode.addOpcode(Opcode.GOTO);
            pc = bytecode.currentPc();
            bytecode.addIndex(0);
        }

        int pc2 = bytecode.currentPc();
        if (body != null)
            body.accept(this);

        int pc3 = bytecode.currentPc();
        if (notDo)
            bytecode.write16bit(pc, pc3 - pc + 1);

        boolean alwaysBranch = compileBooleanExpr(true, expr);
        if (alwaysBranch) {
            bytecode.addOpcode(Opcode.GOTO);
            alwaysBranch = breakList.isEmpty();
        }

        bytecode.addIndex(pc2 - bytecode.currentPc() + 1);
        patchGoto(breakList, bytecode.currentPc());
        patchGoto(continueList, pc3);
        continueList = prevContList;
        breakList = prevBreakList;
        hasReturned = alwaysBranch;
    }

    protected void patchGoto(List<Integer> list, int targetPc) {
        for (int pc:list)
            bytecode.write16bit(pc, targetPc - pc + 1);
    }

    private void atForStmnt(Stmnt st) throws CompileError {
        List<Integer>  prevBreakList = breakList;
        List<Integer>  prevContList = continueList;
        breakList = new ArrayList<Integer>();
        continueList = new ArrayList<Integer>();

        Stmnt init = (Stmnt)st.head();
        ASTList p = st.tail();
        ASTree expr = p.head();
        p = p.tail();
        Stmnt update = (Stmnt)p.head();
        Stmnt body = (Stmnt)p.tail();

        if (init != null)
            init.accept(this);

        int pc = bytecode.currentPc();
        int pc2 = 0;
        if (expr != null) {
            if (compileBooleanExpr(false, expr)) {
                // in case of "for (...; false; ...)"
                continueList = prevContList;
                breakList = prevBreakList;
                hasReturned = false;
                return;
            }

            pc2 = bytecode.currentPc();
            bytecode.addIndex(0);
        }

        if (body != null)
            body.accept(this);

        int pc3 = bytecode.currentPc();
        if (update != null)
            update.accept(this);

        bytecode.addOpcode(Opcode.GOTO);
        bytecode.addIndex(pc - bytecode.currentPc() + 1);

        int pc4 = bytecode.currentPc();
        if (expr != null)
            bytecode.write16bit(pc2, pc4 - pc2 + 1);

        patchGoto(breakList, pc4);
        patchGoto(continueList, pc3);
        continueList = prevContList;
        breakList = prevBreakList;
        hasReturned = false;
    }

    private void atSwitchStmnt(Stmnt st) throws CompileError {
        boolean isString = false;
        if (typeChecker != null) {
            doTypeCheck(st.head());
            isString = typeChecker.exprType == TypeChecker.CLASS
                       && typeChecker.arrayDim == 0
                       && TypeChecker.jvmJavaLangString.equals(typeChecker.className);
        }

        compileExpr(st.head());
        int tmpVar = -1;
        if (isString) {
            tmpVar = getMaxLocals();
            incMaxLocals(1);
            bytecode.addAstore(tmpVar);
            bytecode.addAload(tmpVar);
            bytecode.addInvokevirtual(TypeChecker.jvmJavaLangString, "hashCode", "()I");
        }

        List<Integer>  prevBreakList = breakList;
        breakList = new ArrayList<Integer>();
        int opcodePc = bytecode.currentPc();
        bytecode.addOpcode(LOOKUPSWITCH);
        int npads = 3 - (opcodePc & 3);
        while (npads-- > 0)
            bytecode.add(0);

        Stmnt body = (Stmnt)st.tail();
        int npairs = 0;
        for (ASTList list = body; list != null; list = list.tail())
            if (((Stmnt)list.head()).getOperator() == CASE)
                ++npairs;

        // opcodePc2 is the position at which the default jump offset is.
        int opcodePc2 = bytecode.currentPc();
        bytecode.addGap(4);
        bytecode.add32bit(npairs);
        bytecode.addGap(npairs * 8);

        long[] pairs = new long[npairs];
        ArrayList<Integer> gotoDefaults = new ArrayList<Integer>();
        int ipairs = 0;
        int defaultPc = -1;
        for (ASTList list = body; list != null; list = list.tail()) {
            Stmnt label = (Stmnt)list.head();
            int op = label.getOperator();
            if (op == DEFAULT)
                defaultPc = bytecode.currentPc();
            else if (op != CASE)
                fatal();
            else {
                int curPos = bytecode.currentPc();
                long caseLabel;
                if (isString) {
                    // computeStringLabel() also adds bytecode as its side-effects.
                    caseLabel = (long)computeStringLabel(label.head(), tmpVar, gotoDefaults);
                }
                else
                    caseLabel = (long)computeLabel(label.head());

                pairs[ipairs++]
                    = (caseLabel << 32) + 
                      ((long)(curPos - opcodePc) & 0xffffffff);
            }

            hasReturned = false;
            ((Stmnt)label.tail()).accept(this);
        }

        Arrays.sort(pairs);
        int pc = opcodePc2 + 8;
        for (int i = 0; i < npairs; ++i) {
            bytecode.write32bit(pc, (int)(pairs[i] >>> 32));
            bytecode.write32bit(pc + 4, (int)pairs[i]);
            pc += 8;
        } 

        if (defaultPc < 0 || breakList.size() > 0)
            hasReturned = false;

        int endPc = bytecode.currentPc();
        if (defaultPc < 0)
            defaultPc = endPc;

        bytecode.write32bit(opcodePc2, defaultPc - opcodePc);
        for (int addr: gotoDefaults)
            bytecode.write16bit(addr, defaultPc - addr + 1);

        patchGoto(breakList, endPc);
        breakList = prevBreakList;
    }

    private int computeLabel(ASTree expr) throws CompileError {
        doTypeCheck(expr);
        expr = TypeChecker.stripPlusExpr(expr);
        if (expr instanceof IntConst)
            return (int)((IntConst)expr).get();
        throw new CompileError("bad case label");
    }

    private int computeStringLabel(ASTree expr, int tmpVar, List<Integer> gotoDefaults)
        throws CompileError
    {
        doTypeCheck(expr);
        expr = TypeChecker.stripPlusExpr(expr);
        if (expr instanceof StringL) {
            String label = ((StringL)expr).get();
            bytecode.addAload(tmpVar);
            bytecode.addLdc(label);
            bytecode.addInvokevirtual(TypeChecker.jvmJavaLangString, "equals",
                                      "(Ljava/lang/Object;)Z");
            bytecode.addOpcode(IFEQ);
            Integer pc = Integer.valueOf(bytecode.currentPc());
            bytecode.addIndex(0);
            gotoDefaults.add(pc);
            return (int)label.hashCode();
        }
        throw new CompileError("bad case label");
    }

    private void atBreakStmnt(Stmnt st, boolean notCont)
        throws CompileError
    {
        if (st.head() != null)
            throw new CompileError(
                        "sorry, not support labeled break or continue");

        bytecode.addOpcode(Opcode.GOTO);
        Integer pc = Integer.valueOf(bytecode.currentPc());
        bytecode.addIndex(0);
        if (notCont)
            breakList.add(pc);
        else
            continueList.add(pc);
    }

    protected void atReturnStmnt(Stmnt st) throws CompileError {
        atReturnStmnt2(st.getLeft());
    }

    protected final void atReturnStmnt2(ASTree result) throws CompileError {
        int op;
        if (result == null)
            op = Opcode.RETURN;
        else {
            compileExpr(result);
            if (arrayDim > 0)
                op = ARETURN;
            else {
                int type = exprType;
                if (type == DOUBLE)
                    op = DRETURN;
                else if (type == FLOAT)
                    op = FRETURN;
                else if (type == LONG)
                    op = LRETURN;
                else if (isRefType(type))
                    op = ARETURN;
                else
                    op = IRETURN;
            }
        }

        for (ReturnHook har = returnHooks; har != null; har = har.next)
            if (har.doit(bytecode, op)) {
                hasReturned = true;
                return;
            }

        bytecode.addOpcode(op);
        hasReturned = true;
    }

    private void atThrowStmnt(Stmnt st) throws CompileError {
        ASTree e = st.getLeft();
        compileExpr(e);
        if (exprType != CLASS || arrayDim > 0)
            throw new CompileError("bad throw statement");

        bytecode.addOpcode(ATHROW);
        hasReturned = true;
    }

    /* overridden in MemberCodeGen
     */
    protected void atTryStmnt(Stmnt st) throws CompileError {
        hasReturned = false;
    }

    private void atSyncStmnt(Stmnt st) throws CompileError {
        int nbreaks = getListSize(breakList);
        int ncontinues = getListSize(continueList);

        compileExpr(st.head());
        if (exprType != CLASS && arrayDim == 0)
            throw new CompileError("bad type expr for synchronized block");

        Bytecode bc = bytecode;
        final int var = bc.getMaxLocals();
        bc.incMaxLocals(1);
        bc.addOpcode(DUP);
        bc.addAstore(var);
        bc.addOpcode(MONITORENTER);

        ReturnHook rh = new ReturnHook(this) {
            @Override
            protected boolean doit(Bytecode b, int opcode) {
                b.addAload(var);
                b.addOpcode(MONITOREXIT);
                return false;
            }
        };

        int pc = bc.currentPc();
        Stmnt body = (Stmnt)st.tail();
        if (body != null)
            body.accept(this);

        int pc2 = bc.currentPc();
        int pc3 = 0;
        if (!hasReturned) {
            rh.doit(bc, 0);     // the 2nd arg is ignored.
            bc.addOpcode(Opcode.GOTO);
            pc3 = bc.currentPc();
            bc.addIndex(0);
        }

        if (pc < pc2) {         // if the body is not empty
            int pc4 = bc.currentPc();
            rh.doit(bc, 0);         // the 2nd arg is ignored.
            bc.addOpcode(ATHROW);
            bc.addExceptionHandler(pc, pc2, pc4, 0);
        }

        if (!hasReturned)
            bc.write16bit(pc3, bc.currentPc() - pc3 + 1);

        rh.remove(this);

        if (getListSize(breakList) != nbreaks
            || getListSize(continueList) != ncontinues)
            throw new CompileError(
                "sorry, cannot break/continue in synchronized block");
    }

    private static int getListSize(List<Integer>  list) {
        return list == null ? 0 : list.size();
    }

    private static boolean isPlusPlusExpr(ASTree expr) {
        if (expr instanceof Expr) {
            int op = ((Expr)expr).getOperator();
            return op == PLUSPLUS || op == MINUSMINUS;
        }

        return false;
    }

    @Override
    public void atDeclarator(Declarator d) throws CompileError {
        d.setLocalVar(getMaxLocals());
        d.setClassName(resolveClassName(d.getClassName()));

        int size;
        if (is2word(d.getType(), d.getArrayDim()))
            size = 2;
        else
            size = 1;

        incMaxLocals(size);

        /*  NOTE: Array initializers has not been supported.
         */
        ASTree init = d.getInitializer();
        if (init != null) {
            doTypeCheck(init);
            atVariableAssign(null, '=', null, d, init, false);
        }
    }

    @Override
    public abstract void atNewExpr(NewExpr n) throws CompileError;

    @Override
    public abstract void atArrayInit(ArrayInit init) throws CompileError;

    @Override
    public void atAssignExpr(AssignExpr expr) throws CompileError {
        atAssignExpr(expr, true);
    }

    protected void atAssignExpr(AssignExpr expr, boolean doDup)
        throws CompileError
    {
        // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>=
        int op = expr.getOperator();
        ASTree left = expr.oprand1();
        ASTree right = expr.oprand2();
        if (left instanceof Variable)
            atVariableAssign(expr, op, (Variable)left,
                             ((Variable)left).getDeclarator(),
                             right, doDup);
        else {
            if (left instanceof Expr) {
                Expr e = (Expr)left;
                if (e.getOperator() == ARRAY) {
                    atArrayAssign(expr, op, (Expr)left, right, doDup);
                    return;
                }
            }

            atFieldAssign(expr, op, left, right, doDup);
        }
    }

    protected static void badAssign(Expr expr) throws CompileError {
        String msg;
        if (expr == null)
            msg = "incompatible type for assignment";
        else
            msg = "incompatible type for " + expr.getName();

        throw new CompileError(msg);
    }

    /* op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=.
     *
     * expr and var can be null.
     */
    private void atVariableAssign(Expr expr, int op, Variable var,
                                  Declarator d, ASTree right,
                                  boolean doDup) throws CompileError
    {
        int varType = d.getType();
        int varArray = d.getArrayDim();
        String varClass = d.getClassName();
        int varNo = getLocalVar(d);

        if (op != '=')
            atVariable(var);

        // expr is null if the caller is atDeclarator().
        if (expr == null && right instanceof ArrayInit)
            atArrayVariableAssign((ArrayInit)right, varType, varArray, varClass);
        else
            atAssignCore(expr, op, right, varType, varArray, varClass);

        if (doDup)
            if (is2word(varType, varArray))
                bytecode.addOpcode(DUP2);
            else
                bytecode.addOpcode(DUP);

        if (varArray > 0)
            bytecode.addAstore(varNo);
        else if (varType == DOUBLE)
            bytecode.addDstore(varNo);
        else if (varType == FLOAT)
            bytecode.addFstore(varNo);
        else if (varType == LONG)
            bytecode.addLstore(varNo);
        else if (isRefType(varType))
            bytecode.addAstore(varNo);
        else
            bytecode.addIstore(varNo);

        exprType = varType;
        arrayDim = varArray;
        className = varClass;
    }

    protected abstract void atArrayVariableAssign(ArrayInit init,
            int varType, int varArray, String varClass) throws CompileError;

    private void atArrayAssign(Expr expr, int op, Expr array,
                        ASTree right, boolean doDup) throws CompileError
    {
        arrayAccess(array.oprand1(), array.oprand2());

        if (op != '=') {
            bytecode.addOpcode(DUP2);
            bytecode.addOpcode(getArrayReadOp(exprType, arrayDim));
        }

        int aType = exprType;
        int aDim = arrayDim;
        String cname = className;

        atAssignCore(expr, op, right, aType, aDim, cname);

        if (doDup)
            if (is2word(aType, aDim))
                bytecode.addOpcode(DUP2_X2);
            else
                bytecode.addOpcode(DUP_X2);

        bytecode.addOpcode(getArrayWriteOp(aType, aDim));
        exprType = aType;
        arrayDim = aDim;
        className = cname;
    }

    protected abstract void atFieldAssign(Expr expr, int op, ASTree left,
                        ASTree right, boolean doDup) throws CompileError;

    protected void atAssignCore(Expr expr, int op, ASTree right,
                                int type, int dim, String cname)
        throws CompileError
    {
        if (op == PLUS_E && dim == 0 && type == CLASS)
            atStringPlusEq(expr, type, dim, cname, right);
        else {
            right.accept(this);
            if (invalidDim(exprType, arrayDim, className, type, dim, cname,
                           false) || (op != '=' && dim > 0))
                badAssign(expr);

            if (op != '=') {
                int token = assignOps[op - MOD_E];
                int k = lookupBinOp(token);
                if (k < 0)
                    fatal();

                atArithBinExpr(expr, token, k, type);
            }
        }

        if (op != '=' || (dim == 0 && !isRefType(type)))
            atNumCastExpr(exprType, type);

        // type check should be done here.
    }

    private void atStringPlusEq(Expr expr, int type, int dim, String cname,
                                ASTree right)
        throws CompileError
    {
        if (!jvmJavaLangString.equals(cname))
            badAssign(expr);

        convToString(type, dim);    // the value might be null.
        right.accept(this);
        convToString(exprType, arrayDim);
        bytecode.addInvokevirtual(javaLangString, "concat",
                                "(Ljava/lang/String;)Ljava/lang/String;");
        exprType = CLASS;
        arrayDim = 0;
        className = jvmJavaLangString;
    }

    private boolean invalidDim(int srcType, int srcDim, String srcClass,
                               int destType, int destDim, String destClass,
                               boolean isCast)
    {
        if (srcDim != destDim)
            if (srcType == NULL)
                return false;
            else if (destDim == 0 && destType == CLASS
                     && jvmJavaLangObject.equals(destClass))
                return false;
            else if (isCast && srcDim == 0 && srcType == CLASS
                     && jvmJavaLangObject.equals(srcClass))
                return false;
            else
                return true;

        return false;
    }

    @Override
    public void atCondExpr(CondExpr expr) throws CompileError {
        if (booleanExpr(false, expr.condExpr()))
            expr.elseExpr().accept(this);
        else {
            int pc = bytecode.currentPc();
            bytecode.addIndex(0);   // correct later
            expr.thenExpr().accept(this);
            int dim1 = arrayDim;
            bytecode.addOpcode(Opcode.GOTO);
            int pc2 = bytecode.currentPc();
            bytecode.addIndex(0);
            bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
            expr.elseExpr().accept(this);
            if (dim1 != arrayDim)
                throw new CompileError("type mismatch in ?:");

            bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1);
        }
    }

    static final int[] binOp = {
        '+', DADD, FADD, LADD, IADD,
        '-', DSUB, FSUB, LSUB, ISUB,
        '*', DMUL, FMUL, LMUL, IMUL,
        '/', DDIV, FDIV, LDIV, IDIV,
        '%', DREM, FREM, LREM, IREM,
        '|', NOP,  NOP,  LOR,  IOR,
        '^', NOP,  NOP,  LXOR, IXOR,
        '&', NOP,  NOP,  LAND, IAND,
        LSHIFT, NOP, NOP, LSHL, ISHL,
        RSHIFT, NOP, NOP, LSHR, ISHR,
        ARSHIFT, NOP, NOP, LUSHR, IUSHR };

    static int lookupBinOp(int token) {
        int[] code = binOp;
        int s = code.length;
        for (int k = 0; k < s; k = k + 5)
            if (code[k] == token)
                return k;

        return -1;
    }

    @Override
    public void atBinExpr(BinExpr expr) throws CompileError {
        int token = expr.getOperator();

        /* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>>
         */
        int k = lookupBinOp(token);
        if (k >= 0) {
            expr.oprand1().accept(this);
            ASTree right = expr.oprand2();
            if (right == null)
                return;     // see TypeChecker.atBinExpr().

            int type1 = exprType;
            int dim1 = arrayDim;
            String cname1 = className;
            right.accept(this);
            if (dim1 != arrayDim)
                throw new CompileError("incompatible array types");

            if (token == '+' && dim1 == 0
                && (type1 == CLASS || exprType == CLASS))
                atStringConcatExpr(expr, type1, dim1, cname1);
            else
                atArithBinExpr(expr, token, k, type1);
        }
        else {
            /* equation: &&, ||, ==, !=, <=, >=, <, >
            */
            if (!booleanExpr(true, expr)) {
                bytecode.addIndex(7);
                bytecode.addIconst(0);  // false
                bytecode.addOpcode(Opcode.GOTO);
                bytecode.addIndex(4);
            }

            bytecode.addIconst(1);  // true
        }
    }

    /* arrayDim values of the two oprands must be equal.
     * If an oprand type is not a numeric type, this method
     * throws an exception.
     */
    private void atArithBinExpr(Expr expr, int token,
                                int index, int type1) throws CompileError
    {
        if (arrayDim != 0)
            badTypes(expr);

        int type2 = exprType;
        if (token == LSHIFT || token == RSHIFT || token == ARSHIFT)
            if (type2 == INT || type2 == SHORT
                || type2 == CHAR || type2 == BYTE)
                exprType = type1;
            else
                badTypes(expr);
        else
            convertOprandTypes(type1, type2, expr);

        int p = typePrecedence(exprType);
        if (p >= 0) {
            int op = binOp[index + p + 1];
            if (op != NOP) {
                if (p == P_INT && exprType != BOOLEAN)
                    exprType = INT;     // type1 may be BYTE, ...

                bytecode.addOpcode(op);
                return;
            }
        }

        badTypes(expr);
    }

    private void atStringConcatExpr(Expr expr, int type1, int dim1,
                                    String cname1) throws CompileError
    {
        int type2 = exprType;
        int dim2 = arrayDim;
        boolean type2Is2 = is2word(type2, dim2);
        boolean type2IsString
            = (type2 == CLASS && jvmJavaLangString.equals(className));

        if (type2Is2)
            convToString(type2, dim2);

        if (is2word(type1, dim1)) {
            bytecode.addOpcode(DUP_X2);
            bytecode.addOpcode(POP);
        }
        else
            bytecode.addOpcode(SWAP);

        // even if type1 is String, the left operand might be null.
        convToString(type1, dim1);
        bytecode.addOpcode(SWAP);

        if (!type2Is2 && !type2IsString)
            convToString(type2, dim2);

        bytecode.addInvokevirtual(javaLangString, "concat",
                                "(Ljava/lang/String;)Ljava/lang/String;");
        exprType = CLASS;
        arrayDim = 0;
        className = jvmJavaLangString;
    }

    private void convToString(int type, int dim) throws CompileError {
        final String method = "valueOf";

        if (isRefType(type) || dim > 0)
            bytecode.addInvokestatic(javaLangString, method,
                                "(Ljava/lang/Object;)Ljava/lang/String;");
        else if (type == DOUBLE)
            bytecode.addInvokestatic(javaLangString, method,
                                     "(D)Ljava/lang/String;");
        else if (type == FLOAT)
            bytecode.addInvokestatic(javaLangString, method,
                                     "(F)Ljava/lang/String;");
        else if (type == LONG)
            bytecode.addInvokestatic(javaLangString, method,
                                     "(J)Ljava/lang/String;");
        else if (type == BOOLEAN)
            bytecode.addInvokestatic(javaLangString, method,
                                     "(Z)Ljava/lang/String;");
        else if (type == CHAR)
            bytecode.addInvokestatic(javaLangString, method,
                                     "(C)Ljava/lang/String;");
        else if (type == VOID)
            throw new CompileError("void type expression");
        else /* INT, BYTE, SHORT */
            bytecode.addInvokestatic(javaLangString, method,
                                     "(I)Ljava/lang/String;");
    }

    /* Produces the opcode to branch if the condition is true.
     * The oprand (branch offset) is not produced.
     *
     * @return	true if the compiled code is GOTO (always branch).
     * 			GOTO is not produced.
     */
    private boolean booleanExpr(boolean branchIf, ASTree expr)
        throws CompileError
    {
        boolean isAndAnd;
        int op = getCompOperator(expr);
        if (op == EQ) {         // ==, !=, ...
            BinExpr bexpr = (BinExpr)expr;
            int type1 = compileOprands(bexpr);
            // here, arrayDim might represent the array dim. of the left oprand
            // if the right oprand is NULL.
            compareExpr(branchIf, bexpr.getOperator(), type1, bexpr);
        }
        else if (op == '!')
            return booleanExpr(!branchIf, ((Expr)expr).oprand1());
        else if ((isAndAnd = (op == ANDAND)) || op == OROR) {
            BinExpr bexpr = (BinExpr)expr;
            if (booleanExpr(!isAndAnd, bexpr.oprand1())) {
                exprType = BOOLEAN;
                arrayDim = 0;
                return true;
            }
            	int pc = bytecode.currentPc();
            	bytecode.addIndex(0);       // correct later
            	if (booleanExpr(isAndAnd, bexpr.oprand2()))
            		bytecode.addOpcode(Opcode.GOTO);

            	bytecode.write16bit(pc, bytecode.currentPc() - pc + 3);
            	if (branchIf != isAndAnd) {
            		bytecode.addIndex(6);   // skip GOTO instruction
            		bytecode.addOpcode(Opcode.GOTO);
            	}
        }
        else if (isAlwaysBranch(expr, branchIf)) {
        	// Opcode.GOTO is not added here.  The caller must add it.
            exprType = BOOLEAN;
            arrayDim = 0;
            return true;	// always branch
        }
        else {                          // others
            expr.accept(this);
            if (exprType != BOOLEAN || arrayDim != 0)
                throw new CompileError("boolean expr is required");

            bytecode.addOpcode(branchIf ? IFNE : IFEQ);
        }

        exprType = BOOLEAN;
        arrayDim = 0;
        return false;
    }

    private static boolean isAlwaysBranch(ASTree expr, boolean branchIf) {
        if (expr instanceof Keyword) {
            int t = ((Keyword)expr).get();
            return branchIf ? t == TRUE : t == FALSE;
        }

        return false;
    }

    static int getCompOperator(ASTree expr) throws CompileError {
        if (expr instanceof Expr) {
            Expr bexpr = (Expr)expr;
            int token = bexpr.getOperator();
            if (token == '!')
                return '!';
            else if ((bexpr instanceof BinExpr)
                     && token != OROR && token != ANDAND
                     && token != '&' && token != '|')
                return EQ;      // ==, !=, ...
            else
                return token;
        }

        return ' ';     // others
    }

    private int compileOprands(BinExpr expr) throws CompileError {
        expr.oprand1().accept(this);
        int type1 = exprType;
        int dim1 = arrayDim;
        expr.oprand2().accept(this);
        if (dim1 != arrayDim)
            if (type1 != NULL && exprType != NULL)
                throw new CompileError("incompatible array types");
            else if (exprType == NULL)
                arrayDim = dim1;

        if (type1 == NULL)
            return exprType;
        return type1;
    }

    private static final int ifOp[] = { EQ, IF_ICMPEQ, IF_ICMPNE,
                                        NEQ, IF_ICMPNE, IF_ICMPEQ,
                                        LE, IF_ICMPLE, IF_ICMPGT,
                                        GE, IF_ICMPGE, IF_ICMPLT,
                                        '<', IF_ICMPLT, IF_ICMPGE,
                                        '>', IF_ICMPGT, IF_ICMPLE };

    private static final int ifOp2[] = { EQ, IFEQ, IFNE,
                                         NEQ, IFNE, IFEQ,
                                         LE, IFLE, IFGT,
                                         GE, IFGE, IFLT,
                                         '<', IFLT, IFGE,
                                         '>', IFGT, IFLE };

    /* Produces the opcode to branch if the condition is true.
     * The oprands are not produced.
     *
     * Parameter expr - compare expression ==, !=, <=, >=, <, >
     */
    private void compareExpr(boolean branchIf,
                             int token, int type1, BinExpr expr)
        throws CompileError
    {
        if (arrayDim == 0)
            convertOprandTypes(type1, exprType, expr);

        int p = typePrecedence(exprType);
        if (p == P_OTHER || arrayDim > 0)
            if (token == EQ)
                bytecode.addOpcode(branchIf ? IF_ACMPEQ : IF_ACMPNE);
            else if (token == NEQ)
                bytecode.addOpcode(branchIf ? IF_ACMPNE : IF_ACMPEQ);
            else
                badTypes(expr);
        else
            if (p == P_INT) {
                int op[] = ifOp;
                for (int i = 0; i < op.length; i += 3)
                    if (op[i] == token) {
                        bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]);
                        return;
                    }

                badTypes(expr);
            }
            else {
                if (p == P_DOUBLE)
                    if (token == '<' || token == LE)
                        bytecode.addOpcode(DCMPG);
                    else
                        bytecode.addOpcode(DCMPL);
                else if (p == P_FLOAT)
                    if (token == '<' || token == LE)
                        bytecode.addOpcode(FCMPG);
                    else
                        bytecode.addOpcode(FCMPL);
                else if (p == P_LONG)
                    bytecode.addOpcode(LCMP); // 1: >, 0: =, -1: <
                else
                    fatal();

                int[] op = ifOp2;
                for (int i = 0; i < op.length; i += 3)
                    if (op[i] == token) {
                        bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]);
                        return;
                    }

                badTypes(expr);
            }
    }

    protected static void badTypes(Expr expr) throws CompileError {
        throw new CompileError("invalid types for " + expr.getName());
    }

    private static final int P_DOUBLE = 0;
    private static final int P_FLOAT = 1;
    private static final int P_LONG = 2;
    private static final int P_INT = 3;
    private static final int P_OTHER = -1;

    protected static boolean isRefType(int type) {
        return type == CLASS || type == NULL;
    }

    private static int typePrecedence(int type) {
        if (type == DOUBLE)
            return P_DOUBLE;
        else if (type == FLOAT)
            return P_FLOAT;
        else if (type == LONG)
            return P_LONG;
        else if (isRefType(type))
            return P_OTHER;
        else if (type == VOID)
            return P_OTHER;     // this is wrong, but ...
        else
            return P_INT;       // BOOLEAN, BYTE, CHAR, SHORT, INT
    }

    // used in TypeChecker.
    static boolean isP_INT(int type) {
        return typePrecedence(type) == P_INT;
    }

    // used in TypeChecker.
    static boolean rightIsStrong(int type1, int type2) {
        int type1_p = typePrecedence(type1);
        int type2_p = typePrecedence(type2);
        return type1_p >= 0 && type2_p >= 0 && type1_p > type2_p;
    }

    private static final int[] castOp = {
            /*            D    F    L    I */
            /* double */ NOP, D2F, D2L, D2I,
            /* float  */ F2D, NOP, F2L, F2I,
            /* long   */ L2D, L2F, NOP, L2I,
            /* other  */ I2D, I2F, I2L, NOP };

    /* do implicit type conversion.
     * arrayDim values of the two oprands must be zero.
     */
    private void convertOprandTypes(int type1, int type2, Expr expr)
        throws CompileError
    {
        boolean rightStrong;
        int type1_p = typePrecedence(type1);
        int type2_p = typePrecedence(type2);

        if (type2_p < 0 && type1_p < 0) // not primitive types
            return;

        if (type2_p < 0 || type1_p < 0) // either is not a primitive type
            badTypes(expr);

        int op, result_type;
        if (type1_p <= type2_p) {
            rightStrong = false;
            exprType = type1;
            op = castOp[type2_p * 4 + type1_p];
            result_type = type1_p;
        }
        else {
            rightStrong = true;
            op = castOp[type1_p * 4 + type2_p];
            result_type = type2_p;
        }

        if (rightStrong) {
            if (result_type == P_DOUBLE || result_type == P_LONG) {
                if (type1_p == P_DOUBLE || type1_p == P_LONG)
                    bytecode.addOpcode(DUP2_X2);
                else
                    bytecode.addOpcode(DUP2_X1);

                bytecode.addOpcode(POP2);
                bytecode.addOpcode(op);
                bytecode.addOpcode(DUP2_X2);
                bytecode.addOpcode(POP2);
            }
            else if (result_type == P_FLOAT) {
                if (type1_p == P_LONG) {
                    bytecode.addOpcode(DUP_X2);
                    bytecode.addOpcode(POP);
                }
                else
                    bytecode.addOpcode(SWAP);

                bytecode.addOpcode(op);
                bytecode.addOpcode(SWAP);
            }
            else
                fatal();
        }
        else if (op != NOP)
            bytecode.addOpcode(op);
    }

    @Override
    public void atCastExpr(CastExpr expr) throws CompileError {
        String cname = resolveClassName(expr.getClassName());
        String toClass = checkCastExpr(expr, cname);
        int srcType = exprType;
        exprType = expr.getType();
        arrayDim = expr.getArrayDim();
        className = cname;
        if (toClass == null)
            atNumCastExpr(srcType, exprType);   // built-in type
        else
            bytecode.addCheckcast(toClass);
    }

    @Override
    public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError {
        String cname = resolveClassName(expr.getClassName());
        String toClass = checkCastExpr(expr, cname);
        bytecode.addInstanceof(toClass);
        exprType = BOOLEAN;
        arrayDim = 0;
    }

    private String checkCastExpr(CastExpr expr, String name)
        throws CompileError
    {
        final String msg = "invalid cast";
        ASTree oprand = expr.getOprand();
        int dim = expr.getArrayDim();
        int type = expr.getType();
        oprand.accept(this);
        int srcType = exprType;
        int srcDim = arrayDim;
        if (invalidDim(srcType, arrayDim, className, type, dim, name, true)
            || srcType == VOID || type == VOID)
            throw new CompileError(msg);

        if (type == CLASS) {
            if (!isRefType(srcType) && srcDim == 0)
                throw new CompileError(msg);

            return toJvmArrayName(name, dim);
        }
        else
            if (dim > 0)
                return toJvmTypeName(type, dim);
            else
                return null;    // built-in type
    }

    void atNumCastExpr(int srcType, int destType)
        throws CompileError
    {
        if (srcType == destType)
            return;

        int op, op2;
        int stype = typePrecedence(srcType);
        int dtype = typePrecedence(destType);
        if (0 <= stype && stype < 3)
            op = castOp[stype * 4 + dtype];
        else
            op = NOP;

        if (destType == DOUBLE)
            op2 = I2D;
        else if (destType == FLOAT)
            op2 = I2F;
        else if (destType == LONG)
            op2 = I2L;
        else if (destType == SHORT)
            op2 = I2S;
        else if (destType == CHAR)
            op2 = I2C;
        else if (destType == BYTE)
            op2 = I2B;
        else
            op2 = NOP;

        if (op != NOP)
            bytecode.addOpcode(op);

        if (op == NOP || op == L2I || op == F2I || op == D2I)
            if (op2 != NOP)
                bytecode.addOpcode(op2);
    }

    @Override
    public void atExpr(Expr expr) throws CompileError {
        // array access, member access,
        // (unary) +, (unary) -, ++, --, !, ~

        int token = expr.getOperator();
        ASTree oprand = expr.oprand1();
        if (token == '.') {
            String member = ((Symbol)expr.oprand2()).get();
            if (member.equals("class"))
                atClassObject(expr);  // .class
            else
                atFieldRead(expr);
        }
        else if (token == MEMBER) {     // field read
            /* MEMBER ('#') is an extension by Javassist.
             * The compiler internally uses # for compiling .class
             * expressions such as "int.class".
             */
            atFieldRead(expr);
        }
        else if (token == ARRAY)
            atArrayRead(oprand, expr.oprand2());
        else if (token == PLUSPLUS || token == MINUSMINUS)
            atPlusPlus(token, oprand, expr, true);
        else if (token == '!') {
            if (!booleanExpr(false, expr)) {
                bytecode.addIndex(7);
                bytecode.addIconst(1);
                bytecode.addOpcode(Opcode.GOTO);
                bytecode.addIndex(4);
            }

            bytecode.addIconst(0);
        }
        else if (token == CALL)         // method call
            fatal();
        else {
            expr.oprand1().accept(this);
            int type = typePrecedence(exprType);
            if (arrayDim > 0)
                badType(expr);

            if (token == '-') {
                if (type == P_DOUBLE)
                    bytecode.addOpcode(DNEG);
                else if (type == P_FLOAT)
                    bytecode.addOpcode(FNEG);
                else if (type == P_LONG)
                    bytecode.addOpcode(LNEG);
                else if (type == P_INT) {
                    bytecode.addOpcode(INEG);
                    exprType = INT;     // type may be BYTE, ...
                }
                else
                    badType(expr);
            }
            else if (token == '~') {
                if (type == P_INT) {
                    bytecode.addIconst(-1);
                    bytecode.addOpcode(IXOR);
                    exprType = INT;     // type may be BYTE. ...
                }
                else if (type == P_LONG) {
                    bytecode.addLconst(-1);
                    bytecode.addOpcode(LXOR);
                }
                else
                    badType(expr);

            }
            else if (token == '+') {
                if (type == P_OTHER)
                    badType(expr);

                // do nothing. ignore.
            }
            else
                fatal();
        }
    }

    protected static void badType(Expr expr) throws CompileError {
        throw new CompileError("invalid type for " + expr.getName());
    }

    @Override
    public abstract void atCallExpr(CallExpr expr) throws CompileError;

    protected abstract void atFieldRead(ASTree expr) throws CompileError;

    public void atClassObject(Expr expr) throws CompileError {
        ASTree op1 = expr.oprand1();
        if (!(op1 instanceof Symbol))
            throw new CompileError("fatal error: badly parsed .class expr");

        String cname = ((Symbol)op1).get();
        if (cname.startsWith("[")) {
            int i = cname.indexOf("[L");
            if (i >= 0) {
                String name = cname.substring(i + 2, cname.length() - 1);
                String name2 = resolveClassName(name);
                if (!name.equals(name2)) {
                    /* For example, to obtain String[].class,
                     * "[Ljava.lang.String;" (not "[Ljava/lang/String"!)
                     * must be passed to Class.forName().
                     */
                    name2 = MemberResolver.jvmToJavaName(name2);
                    StringBuilder sbuf = new StringBuilder();
                    while (i-- >= 0)
                        sbuf.append('[');

                    sbuf.append('L').append(name2).append(';');
                    cname = sbuf.toString();
                }
            }
        }
        else {
            cname = resolveClassName(MemberResolver.javaToJvmName(cname));
            cname = MemberResolver.jvmToJavaName(cname);
        }

        atClassObject2(cname);
        exprType = CLASS;
        arrayDim = 0;
        className = "java/lang/Class";
    }

    /* MemberCodeGen overrides this method.
     */
    protected void atClassObject2(String cname) throws CompileError {
        int start = bytecode.currentPc();
        bytecode.addLdc(cname);
        bytecode.addInvokestatic("java.lang.Class", "forName",
                                 "(Ljava/lang/String;)Ljava/lang/Class;");
        int end = bytecode.currentPc();
        bytecode.addOpcode(Opcode.GOTO);
        int pc = bytecode.currentPc();
        bytecode.addIndex(0);   // correct later

        bytecode.addExceptionHandler(start, end, bytecode.currentPc(),
                                     "java.lang.ClassNotFoundException");

        /* -- the following code is for inlining a call to DotClass.fail().

        int var = getMaxLocals();
        incMaxLocals(1);
        bytecode.growStack(1);
        bytecode.addAstore(var);

        bytecode.addNew("java.lang.NoClassDefFoundError");
        bytecode.addOpcode(DUP);
        bytecode.addAload(var);
        bytecode.addInvokevirtual("java.lang.ClassNotFoundException",
                                  "getMessage", "()Ljava/lang/String;");
        bytecode.addInvokespecial("java.lang.NoClassDefFoundError", "<init>",
                                  "(Ljava/lang/String;)V");
        */

        bytecode.growStack(1);
        bytecode.addInvokestatic("javassist.runtime.DotClass", "fail",
                                 "(Ljava/lang/ClassNotFoundException;)"
                                 + "Ljava/lang/NoClassDefFoundError;");
        bytecode.addOpcode(ATHROW);
        bytecode.write16bit(pc, bytecode.currentPc() - pc + 1);
    }

    public void atArrayRead(ASTree array, ASTree index)
        throws CompileError
    {
        arrayAccess(array, index);
        bytecode.addOpcode(getArrayReadOp(exprType, arrayDim));
    }

    protected void arrayAccess(ASTree array, ASTree index)
        throws CompileError
    {
        array.accept(this);
        int type = exprType;
        int dim = arrayDim;
        if (dim == 0)
            throw new CompileError("bad array access");

        String cname = className;

        index.accept(this);
        if (typePrecedence(exprType) != P_INT || arrayDim > 0)
            throw new CompileError("bad array index");

        exprType = type;
        arrayDim = dim - 1;
        className = cname;
    }

    protected static int getArrayReadOp(int type, int dim) {
        if (dim > 0)
            return AALOAD;

        switch (type) {
        case DOUBLE :
            return DALOAD;
        case FLOAT :
            return FALOAD;
        case LONG :
            return LALOAD;
        case INT :
            return IALOAD;
        case SHORT :
            return SALOAD;
        case CHAR :
            return CALOAD;
        case BYTE :
        case BOOLEAN :
            return BALOAD;
        default :
            return AALOAD;
        }
    }

    protected static int getArrayWriteOp(int type, int dim) {
        if (dim > 0)
            return AASTORE;

        switch (type) {
        case DOUBLE :
            return DASTORE;
        case FLOAT :
            return FASTORE;
        case LONG :
            return LASTORE;
        case INT :
            return IASTORE;
        case SHORT :
            return SASTORE;
        case CHAR :
            return CASTORE;
        case BYTE :
        case BOOLEAN :
            return BASTORE;
        default :
            return AASTORE;
        }
    }

    private void atPlusPlus(int token, ASTree oprand, Expr expr,
                            boolean doDup) throws CompileError
    {
        boolean isPost = oprand == null;        // ++i or i++?
        if (isPost)
            oprand = expr.oprand2();

        if (oprand instanceof Variable) {
            Declarator d = ((Variable)oprand).getDeclarator();
            int t = exprType = d.getType();
            arrayDim = d.getArrayDim();
            int var = getLocalVar(d);
            if (arrayDim > 0)
                badType(expr);

            if (t == DOUBLE) {
                bytecode.addDload(var);
                if (doDup && isPost)
                    bytecode.addOpcode(DUP2);

                bytecode.addDconst(1.0);
                bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB);
                if (doDup && !isPost)
                    bytecode.addOpcode(DUP2);

                bytecode.addDstore(var);
            }
            else if (t == LONG) {
                bytecode.addLload(var);
                if (doDup && isPost)
                    bytecode.addOpcode(DUP2);

                bytecode.addLconst(1);
                bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB);
                if (doDup && !isPost)
                    bytecode.addOpcode(DUP2);

                bytecode.addLstore(var);
            }
            else if (t == FLOAT) {
                bytecode.addFload(var);
                if (doDup && isPost)
                    bytecode.addOpcode(DUP);

                bytecode.addFconst(1.0f);
                bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB);
                if (doDup && !isPost)
                    bytecode.addOpcode(DUP);

                bytecode.addFstore(var);
            }
            else if (t == BYTE || t == CHAR || t == SHORT || t == INT) {
                if (doDup && isPost)
                    bytecode.addIload(var);

                int delta = token == PLUSPLUS ? 1 : -1;
                if (var > 0xff) {
                    bytecode.addOpcode(WIDE);
                    bytecode.addOpcode(IINC);
                    bytecode.addIndex(var);
                    bytecode.addIndex(delta);
                }
                else {
                    bytecode.addOpcode(IINC);
                    bytecode.add(var);
                    bytecode.add(delta);
                }

                if (doDup && !isPost)
                    bytecode.addIload(var);
            }
            else
                badType(expr);
        }
        else {
            if (oprand instanceof Expr) {
                Expr e = (Expr)oprand;
                if (e.getOperator() == ARRAY) {
                    atArrayPlusPlus(token, isPost, e, doDup);
                    return;
                }
            }

            atFieldPlusPlus(token, isPost, oprand, expr, doDup);
        }
    }

    public void atArrayPlusPlus(int token, boolean isPost,
                        Expr expr, boolean doDup) throws CompileError
    {
        arrayAccess(expr.oprand1(), expr.oprand2());
        int t = exprType;
        int dim = arrayDim;
        if (dim > 0)
            badType(expr);

        bytecode.addOpcode(DUP2);
        bytecode.addOpcode(getArrayReadOp(t, arrayDim));
        int dup_code = is2word(t, dim) ? DUP2_X2 : DUP_X2;
        atPlusPlusCore(dup_code, doDup, token, isPost, expr);
        bytecode.addOpcode(getArrayWriteOp(t, dim));
    }

    protected void atPlusPlusCore(int dup_code, boolean doDup,
                                  int token, boolean isPost,
                                  Expr expr) throws CompileError
    {
        int t = exprType;

        if (doDup && isPost)
            bytecode.addOpcode(dup_code);

        if (t == INT || t == BYTE || t == CHAR || t == SHORT) {
            bytecode.addIconst(1);
            bytecode.addOpcode(token == PLUSPLUS ? IADD : ISUB);
            exprType = INT;
        }
        else if (t == LONG) {
            bytecode.addLconst(1);
            bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB);
        }
        else if (t == FLOAT) {
            bytecode.addFconst(1.0f);
            bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB);
        }
        else if (t == DOUBLE) {
            bytecode.addDconst(1.0);
            bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB);
        }
        else
            badType(expr);

        if (doDup && !isPost)
            bytecode.addOpcode(dup_code);
    }

    protected abstract void atFieldPlusPlus(int token, boolean isPost,
                ASTree oprand, Expr expr, boolean doDup) throws CompileError;

    @Override
    public abstract void atMember(Member n) throws CompileError;

    @Override
    public void atVariable(Variable v) throws CompileError {
        Declarator d = v.getDeclarator();
        exprType = d.getType();
        arrayDim = d.getArrayDim();
        className = d.getClassName();
        int var = getLocalVar(d);

        if (arrayDim > 0)
            bytecode.addAload(var);
        else
            switch (exprType) {
            case CLASS :
                bytecode.addAload(var);
                break;
            case LONG :
                bytecode.addLload(var);
                break;
            case FLOAT :
                bytecode.addFload(var);
                break;
            case DOUBLE :
                bytecode.addDload(var);
                break;
            default :   // BOOLEAN, BYTE, CHAR, SHORT, INT
                bytecode.addIload(var);
                break;
            }
    }

    @Override
    public void atKeyword(Keyword k) throws CompileError {
        arrayDim = 0;
        int token = k.get();
        switch (token) {
        case TRUE :
            bytecode.addIconst(1);
            exprType = BOOLEAN;
            break;
        case FALSE :
            bytecode.addIconst(0);
            exprType = BOOLEAN;
            break;
        case NULL :
            bytecode.addOpcode(ACONST_NULL);
            exprType = NULL;
            break;
        case THIS :
        case SUPER :
            if (inStaticMethod)
                throw new CompileError("not-available: "
                                       + (token == THIS ? "this" : "super"));

            bytecode.addAload(0);
            exprType = CLASS;
            if (token == THIS)
                className = getThisName();
            else
                className = getSuperName();
            break;
        default :
            fatal();
        }
    }

    @Override
    public void atStringL(StringL s) throws CompileError {
        exprType = CLASS;
        arrayDim = 0;
        className = jvmJavaLangString;
        bytecode.addLdc(s.get());
    }

    @Override
    public void atIntConst(IntConst i) throws CompileError {
        arrayDim = 0;
        long value = i.get();
        int type = i.getType();
        if (type == IntConstant || type == CharConstant) {
            exprType = (type == IntConstant ? INT : CHAR);
            bytecode.addIconst((int)value);
        }
        else {
            exprType = LONG;
            bytecode.addLconst(value);
        }
    }

    @Override
    public void atDoubleConst(DoubleConst d) throws CompileError {
        arrayDim = 0;
        if (d.getType() == DoubleConstant) {
            exprType = DOUBLE;
            bytecode.addDconst(d.get());
        }
        else {
            exprType = FLOAT;
            bytecode.addFconst((float)d.get());
        }
    }
}
