org.formulacompiler.compiler.internal.bytecode.ExpressionCompiler.java Source code

Java tutorial

Introduction

Here is the source code for org.formulacompiler.compiler.internal.bytecode.ExpressionCompiler.java

Source

/*
 * Copyright (c) 2006-2009 by Abacus Research AG, Switzerland.
 * All rights reserved.
 *
 * This file is part of the Abacus Formula Compiler (AFC).
 *
 * For commercial licensing, please contact sales(at)formulacompiler.com.
 *
 * AFC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * AFC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with AFC.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.formulacompiler.compiler.internal.bytecode;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;

import org.formulacompiler.compiler.CompilerException;
import org.formulacompiler.compiler.Function;
import org.formulacompiler.compiler.Operator;
import org.formulacompiler.compiler.internal.bytecode.MethodCompiler.LocalArrayRef;
import org.formulacompiler.compiler.internal.bytecode.MethodCompiler.LocalRef;
import org.formulacompiler.compiler.internal.bytecode.MethodCompiler.LocalValueRef;
import org.formulacompiler.compiler.internal.expressions.ArrayDescriptor;
import org.formulacompiler.compiler.internal.expressions.DataType;
import org.formulacompiler.compiler.internal.expressions.ExpressionBuilder;
import org.formulacompiler.compiler.internal.expressions.ExpressionNode;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForArrayReference;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForConstantValue;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForFoldDatabase;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForFoldList;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForFoldVectors;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForFunction;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForLet;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForLetVar;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForLogging;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForMaxValue;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForMinValue;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForOperator;
import org.formulacompiler.compiler.internal.expressions.ExpressionNodeForSwitch;
import org.formulacompiler.compiler.internal.expressions.InnerExpressionException;
import org.formulacompiler.compiler.internal.expressions.LetDictionary;
import org.formulacompiler.compiler.internal.expressions.LetDictionary.LetEntry;
import org.formulacompiler.compiler.internal.model.CellModel;
import org.formulacompiler.compiler.internal.model.ExpressionNodeForCellModel;
import org.formulacompiler.compiler.internal.model.ExpressionNodeForCount;
import org.formulacompiler.compiler.internal.model.ExpressionNodeForParentSectionModel;
import org.formulacompiler.compiler.internal.model.ExpressionNodeForSubSectionModel;
import org.formulacompiler.runtime.ComputationException;
import org.formulacompiler.runtime.FormulaException;
import org.formulacompiler.runtime.Milliseconds;
import org.formulacompiler.runtime.MillisecondsSinceUTC1970;
import org.formulacompiler.runtime.New;
import org.formulacompiler.runtime.NotAvailableException;
import org.formulacompiler.runtime.ScaledLong;
import org.formulacompiler.runtime.spreadsheet.CellAddress;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;

abstract class ExpressionCompiler {
    private static final Type COMPUTATION_ERROR_TYPE = Type.getType(ComputationException.class);
    private static final Type NOT_AVAILABLE_ERROR_TYPE = Type.getType(NotAvailableException.class);
    private static final Type ARITHMETIC_EXCEPTION_TYPE = Type.getType(ArithmeticException.class);
    protected static final Type FORMULA_ERROR_TYPE = Type.getType(FormulaException.class);

    private final MethodCompiler methodCompiler;

    ExpressionCompiler(MethodCompiler _methodCompiler) {
        super();
        this.methodCompiler = _methodCompiler;
    }

    protected final MethodCompiler method() {
        return this.methodCompiler;
    }

    protected final SectionCompiler section() {
        return method().section();
    }

    protected final SectionCompiler sectionInContext() {
        return method().sectionInContext();
    }

    protected abstract TypeCompiler typeCompiler();

    protected final DataType dataType() {
        return typeCompiler().dataType();
    }

    protected final Type type() {
        return typeCompiler().type();
    }

    protected final GeneratorAdapter mv() {
        return method().mv();
    }

    protected final LetDictionary<Compilable> letDict() {
        return method().letDict();
    }

    protected final Iterable<LetEntry<Compilable>> closureOf(ExpressionNode _node) {
        return method().closureOf(_node);
    }

    protected final int localsOffset() {
        return method().localsOffset();
    }

    protected final void incLocalsOffset(int _by) {
        method().incLocalsOffset(_by);
    }

    protected final void resetLocalsTo(int _to) {
        method().resetLocalsTo(_to);
    }

    protected final void compile(ExpressionNode _node) throws CompilerException {
        try {
            compileInner(_node);
        } catch (InnerExpressionException e) {
            throw e;
        } catch (CompilerException e) {
            throw new InnerExpressionException(_node, e);
        }
    }

    private final void compileInner(ExpressionNode _node) throws CompilerException {
        if (null == _node) {
            mv().visitInsn(Opcodes.ACONST_NULL);
            return;
        }

        final DataType nodeType = _node.getDataType();

        if (null == nodeType) {
            throw new CompilerException.UnsupportedDataType(
                    "Internal error: Data type not set on node " + _node.describe() + ".");
        }

        else if (DataType.NULL != nodeType && dataType() != nodeType) {
            method().expressionCompiler(nodeType).compile(_node);
            compileConversionFrom(nodeType);
        }

        else if (_node instanceof ExpressionNodeForConstantValue) {
            compileConst(((ExpressionNodeForConstantValue) _node).value());
        }

        else if (_node instanceof ExpressionNodeForMinValue) {
            typeCompiler().compileMinValue(mv());
        }

        else if (_node instanceof ExpressionNodeForMaxValue) {
            typeCompiler().compileMaxValue(mv());
        }

        else if (_node instanceof ExpressionNodeForCellModel) {
            compileRef(((ExpressionNodeForCellModel) _node).getCellModel());
        }

        else if (_node instanceof ExpressionNodeForParentSectionModel) {
            compileRef((ExpressionNodeForParentSectionModel) _node);
        }

        else if (_node instanceof ExpressionNodeForSubSectionModel) {
            compileRef((ExpressionNodeForSubSectionModel) _node);
        }

        else if (_node instanceof ExpressionNodeForArrayReference) {
            compileRef((ExpressionNodeForArrayReference) _node);
        }

        else if (_node instanceof ExpressionNodeForOperator) {
            final ExpressionNodeForOperator node = (ExpressionNodeForOperator) _node;
            if (needsIf(node.getOperator())) {
                compileIf(node, ExpressionBuilder.TRUE, ExpressionBuilder.FALSE);
            } else {
                compileOperator(node);
            }
        }

        else if (_node instanceof ExpressionNodeForFunction) {
            final ExpressionNodeForFunction node = (ExpressionNodeForFunction) _node;
            switch (node.getFunction()) {

            case IF:
                compileIf(node);
                break;

            case NOT:
                compileIf(node, ExpressionBuilder.TRUE, ExpressionBuilder.FALSE);
                break;

            case AND:
            case OR:
                compileIf(node, ExpressionBuilder.TRUE, ExpressionBuilder.FALSE);
                break;

            default:
                compileFunction(node);
            }
        }

        else if (_node instanceof ExpressionNodeForSwitch) {
            compileSwitch((ExpressionNodeForSwitch) _node);
        }

        else if (_node instanceof ExpressionNodeForCount) {
            compileCount((ExpressionNodeForCount) _node);
        }

        else if (_node instanceof ExpressionNodeForLet) {
            compileLet((ExpressionNodeForLet) _node);
        }

        else if (_node instanceof ExpressionNodeForLetVar) {
            compileLetVar(((ExpressionNodeForLetVar) _node).varName());
        }

        else if (_node instanceof ExpressionNodeForFoldList) {
            compileFoldList((ExpressionNodeForFoldList) _node);
        }

        else if (_node instanceof ExpressionNodeForFoldVectors) {
            compileFoldVectors((ExpressionNodeForFoldVectors) _node);
        }

        else if (_node instanceof ExpressionNodeForFoldDatabase) {
            compileFoldDatabase((ExpressionNodeForFoldDatabase) _node);
        }

        else if (_node instanceof ExpressionNodeForLogging) {
            compileLogging((ExpressionNodeForLogging) _node);
        }

        else {
            throw new CompilerException.UnsupportedExpression(
                    "Internal error: unsupported node type " + _node.describe() + ".");
        }
    }

    private final boolean needsIf(Operator _operator) {
        switch (_operator) {
        case EQUAL:
        case NOTEQUAL:
        case LESS:
        case LESSOREQUAL:
        case GREATER:
        case GREATEROREQUAL:
            return true;
        default:
            return false;
        }
    }

    protected void compileConst(Object _value) throws CompilerException {
        typeCompiler().compileConst(mv(), _value);
    }

    protected void compileOperator(ExpressionNodeForOperator _node) throws CompilerException {
        final List<ExpressionNode> args = _node.arguments();
        final Operator op = _node.getOperator();
        switch (args.size()) {

        case 0:
            throw new CompilerException.UnsupportedExpression(
                    "Internal error: must have at least one argument for operator node " + _node.describe() + ".");

        case 1:
            compile(args.get(0));
            compileOperatorWithFirstArgOnStack(op, null);
            break;

        default:
            compile(args.get(0));
            for (int i = 1; i < args.size(); i++) {
                compileOperatorWithFirstArgOnStack(op, args.get(i));
            }

        }
    }

    protected void compileOperatorWithFirstArgOnStack(Operator _operator, ExpressionNode _secondArg)
            throws CompilerException {
        throw new CompilerException.UnsupportedExpression(
                "Operator " + _operator + " is not supported for " + this + " engines.");
    }

    protected abstract int compileComparison(int _ifOpcode, int _comparisonOpcode) throws CompilerException;

    protected final void compileZero() throws CompilerException {
        typeCompiler().compileZero(mv());
    }

    protected final void compileConversionFrom(Class _class) throws CompilerException {
        compileConversionFromUnboxed(compileUnboxing(_class));
    }

    private void compileConversionFromUnboxed(Class _class) throws CompilerException {
        if (_class == Integer.TYPE || _class == Short.TYPE || _class == Byte.TYPE) {
            compileConversionFromInt();
        }

        else if (_class == Long.TYPE) {
            compileConversionFromLong();
        }

        else if (_class == Double.TYPE) {
            compileConversionFromDouble();
        }

        else if (_class == Float.TYPE) {
            compileConversionFromFloat();
        }

        else if (BigDecimal.class.isAssignableFrom(_class)) {
            compileConversionFromBigDecimal();
        }

        else if (BigInteger.class.isAssignableFrom(_class)) {
            compileConversionFromBigInteger();
        }

        else if (Number.class.isAssignableFrom(_class)) {
            compileConversionFromNumber();
        }

        else if (_class == Boolean.TYPE) {
            compile_util_fromBoolean();
        }

        else if (Date.class.isAssignableFrom(_class)) {
            compile_util_fromDate();
        }

        else if (String.class.isAssignableFrom(_class)) {
            compile_util_fromString();
        }

        else {
            throw new CompilerException.UnsupportedDataType(
                    "Cannot convert from a " + _class.getName() + " to a " + this + ".");
        }
    }

    protected void compileConversionFromLong() throws CompilerException {
        compile_util_fromLong();
    }

    protected void compileConversionFromInt() throws CompilerException {
        compile_util_fromInt();
    }

    protected void compileConversionFromNumber() throws CompilerException {
        compile_util_fromNumber();
    }

    protected void compileConversionFromBigInteger() throws CompilerException {
        compile_util_fromBigInteger();
    }

    protected void compileConversionFromBigDecimal() throws CompilerException {
        compile_util_fromBigDecimal();
    }

    protected void compileConversionFromFloat() throws CompilerException {
        compile_util_fromFloat();
    }

    protected void compileConversionFromDouble() throws CompilerException {
        compile_util_fromDouble();
    }

    protected abstract void compile_util_fromInt() throws CompilerException;

    protected abstract void compile_util_fromLong() throws CompilerException;

    protected abstract void compile_util_fromDouble() throws CompilerException;

    protected abstract void compile_util_fromFloat() throws CompilerException;

    protected abstract void compile_util_fromNumber() throws CompilerException;

    protected abstract void compile_util_fromBigDecimal() throws CompilerException;

    protected abstract void compile_util_fromBigInteger() throws CompilerException;

    protected abstract void compile_util_fromBoolean() throws CompilerException;

    protected abstract void compile_util_fromDate() throws CompilerException;

    protected abstract void compile_util_fromString() throws CompilerException;

    protected abstract void compile_util_fromMsSinceUTC1970() throws CompilerException;

    protected abstract void compile_util_fromMs() throws CompilerException;

    private Class compileUnboxing(Class _class) throws CompilerException {
        if (Byte.class.isAssignableFrom(_class)) {
            compile_util_unboxByte();
            return Byte.TYPE;
        } else if (Short.class.isAssignableFrom(_class)) {
            compile_util_unboxShort();
            return Short.TYPE;
        } else if (Integer.class.isAssignableFrom(_class)) {
            compile_util_unboxInteger();
            return Integer.TYPE;
        } else if (Long.class.isAssignableFrom(_class)) {
            compile_util_unboxLong();
            return Long.TYPE;
        } else if (Float.class.isAssignableFrom(_class)) {
            compile_util_unboxFloat();
            return Float.TYPE;
        } else if (Double.class.isAssignableFrom(_class)) {
            compile_util_unboxDouble();
            return Double.TYPE;
        } else if (Character.class.isAssignableFrom(_class)) {
            compile_util_unboxCharacter();
            return Character.TYPE;
        } else if (Boolean.class.isAssignableFrom(_class)) {
            compile_util_unboxBoolean();
            return Boolean.TYPE;
        }
        return _class;
    }

    protected abstract void compile_util_unboxByte() throws CompilerException;

    protected abstract void compile_util_unboxShort() throws CompilerException;

    protected abstract void compile_util_unboxInteger() throws CompilerException;

    protected abstract void compile_util_unboxLong() throws CompilerException;

    protected abstract void compile_util_unboxFloat() throws CompilerException;

    protected abstract void compile_util_unboxDouble() throws CompilerException;

    protected abstract void compile_util_unboxCharacter() throws CompilerException;

    protected abstract void compile_util_unboxBoolean() throws CompilerException;

    protected abstract void compileConversionFrom(DataType _type) throws CompilerException;

    protected abstract void compileConversionFrom(ScaledLong _scale) throws CompilerException;

    protected abstract void compileConversionTo(ScaledLong _scale) throws CompilerException;

    protected final void compileConversionFromResultOf(Method _method) throws CompilerException {
        try {
            final Class returnType = _method.getReturnType();
            if (returnType == Long.TYPE || returnType == Long.class) {
                if ((_method.getAnnotation(Milliseconds.class) != null)) {
                    if (returnType == Long.class) {
                        compile_util_unboxLong();
                    }
                    compile_util_fromMs();
                    return;
                }

                if ((_method.getAnnotation(MillisecondsSinceUTC1970.class) != null)) {
                    if (returnType == Long.class) {
                        compile_util_unboxLong();
                    }
                    compile_util_fromMsSinceUTC1970();
                    return;
                }

                final ScaledLong scale = scaleOf(_method);
                if (scale != null && scale.value() != 0) {
                    if (returnType == Long.class) {
                        compile_util_unboxLong();
                    }
                    compileConversionFrom(scale);
                    return;
                }
            }
            compileConversionFrom(returnType);
        } catch (CompilerException e) {
            e.addMessageContext("\nCaused by return type of input '" + _method + "'.");
            throw e;
        }
    }

    protected final ScaledLong scaleOf(Method _method) {
        final ScaledLong typeScale = _method.getDeclaringClass().getAnnotation(ScaledLong.class);
        final ScaledLong mtdScale = _method.getAnnotation(ScaledLong.class);
        final ScaledLong scale = (mtdScale != null) ? mtdScale : typeScale;
        return scale;
    }

    protected final void compileConversionTo(Class _class) throws CompilerException {
        if (_class == Number.class) {
            compileConversionToNumber();
        } else if (_class == Object.class) {
            compileConversionToObject();
        } else {
            final Class unboxed = unboxed(_class);
            if (null == unboxed) {
                compileConversionToUnboxed(_class);
            } else {
                compileConversionToUnboxed(unboxed);
                compileBoxing(_class);
            }
        }
    }

    private static Class unboxed(Class _class) {
        if (Byte.class == _class) {
            return Byte.TYPE;
        } else if (Short.class == _class) {
            return Short.TYPE;
        } else if (Integer.class == _class) {
            return Integer.TYPE;
        } else if (Long.class == _class) {
            return Long.TYPE;
        } else if (Float.class == _class) {
            return Float.TYPE;
        } else if (Double.class == _class) {
            return Double.TYPE;
        } else if (Character.class == _class) {
            return Character.TYPE;
        } else if (Boolean.class == _class) {
            return Boolean.TYPE;
        }
        return null;
    }

    private final void compileBoxing(Class _class) throws CompilerException {
        if (Byte.class == _class) {
            compile_util_boxByte();
        } else if (Short.class == _class) {
            compile_util_boxShort();
        } else if (Integer.class == _class) {
            compile_util_boxInteger();
        } else if (Long.class == _class) {
            compile_util_boxLong();
        } else if (Float.class == _class) {
            compile_util_boxFloat();
        } else if (Double.class == _class) {
            compile_util_boxDouble();
        } else if (Character.class == _class) {
            compile_util_boxCharacter();
        } else if (Boolean.class == _class) {
            compile_util_boxBoolean();
        }
    }

    private final void compileConversionToUnboxed(Class _class) throws CompilerException {
        if (_class == Long.TYPE) {
            compileConversionToLong();
        }

        else if (_class == Integer.TYPE) {
            compileConversionToInt();
        }

        else if (_class == Short.TYPE) {
            compileConversionToShort();
        }

        else if (_class == Byte.TYPE) {
            compileConversionToByte();
        }

        else if (_class == Boolean.TYPE) {
            compileConversionToBoolean();
        }

        else if (_class == Double.TYPE) {
            compileConversionToDouble();
        }

        else if (_class == Float.TYPE) {
            compileConversionToFloat();
        }

        else if (_class == BigInteger.class) {
            compileConversionToBigInteger();
        }

        else if (_class == BigDecimal.class) {
            compileConversionToBigDecimal();
        }

        else if (_class == Date.class) {
            compileConversionToDate();
        }

        else if (_class == String.class) {
            compileConversionToString();
        }

        else {
            throw new CompilerException.UnsupportedDataType(
                    "Cannot convert from a " + this + " to a " + _class.getName() + ".");
        }
    }

    protected abstract void compileConversionToLong() throws CompilerException;

    protected abstract void compileConversionToInt() throws CompilerException;

    protected abstract void compileConversionToShort() throws CompilerException;

    protected abstract void compileConversionToByte() throws CompilerException;

    protected abstract void compileConversionToBigDecimal() throws CompilerException;

    protected abstract void compileConversionToBigInteger() throws CompilerException;

    protected abstract void compileConversionToFloat() throws CompilerException;

    protected abstract void compileConversionToDouble() throws CompilerException;

    protected abstract void compileConversionToBoolean() throws CompilerException;

    protected abstract void compileConversionToNumber() throws CompilerException;

    protected abstract void compileConversionToString() throws CompilerException;

    protected abstract void compileConversionToDate() throws CompilerException;

    protected abstract void compileConversionToObject() throws CompilerException;

    protected final void compileConversionToResultOf(Method _method) throws CompilerException {
        try {
            final Class returnType = _method.getReturnType();
            if (returnType == Long.TYPE || returnType == Long.class) {

                if ((_method.getAnnotation(Milliseconds.class) != null)) {
                    compile_util_toMs();
                    if (returnType == Long.class) {
                        compile_util_boxLong();
                    }
                    return;
                }

                if ((_method.getAnnotation(MillisecondsSinceUTC1970.class) != null)) {
                    compile_util_toMsSinceUTC1970();
                    if (returnType == Long.class) {
                        compile_util_boxLong();
                    }
                    return;
                }

                final ScaledLong scale = scaleOf(_method);
                if (scale != null && scale.value() != 0) {
                    compileConversionTo(scale);
                    if (returnType == Long.class) {
                        compile_util_boxLong();
                    }
                    return;
                }

            }
            compileConversionTo(returnType);
        } catch (CompilerException e) {
            e.addMessageContext("\nCaused by return type of input '" + _method + "'.");
            throw e;
        }
    }

    protected abstract void compile_util_boxByte() throws CompilerException;

    protected abstract void compile_util_boxShort() throws CompilerException;

    protected abstract void compile_util_boxInteger() throws CompilerException;

    protected abstract void compile_util_boxLong() throws CompilerException;

    protected abstract void compile_util_boxFloat() throws CompilerException;

    protected abstract void compile_util_boxDouble() throws CompilerException;

    protected abstract void compile_util_boxCharacter() throws CompilerException;

    protected abstract void compile_util_boxBoolean() throws CompilerException;

    protected abstract void compile_util_toMs() throws CompilerException;

    protected abstract void compile_util_toMsSinceUTC1970() throws CompilerException;

    protected void compileFunction(ExpressionNodeForFunction _node) throws CompilerException {
        final Function fun = _node.getFunction();
        switch (fun) {

        case INDEX:
            compileIndex(_node);
            break;

        case ISERROR:
            compileIsException(_node, true, ERROR_TYPES, NO_TYPES);
            break;

        case ISERR:
            compileIsException(_node, true, ERR_TYPES, NA_TYPES);
            break;

        case ISNA:
            compileIsException(_node, false, NA_TYPES, ERR_TYPES);
            break;

        default:
            throw new CompilerException.UnsupportedExpression(
                    "Function " + fun + " is not supported for " + this + " engines.");
        }
    }

    private static final Type[] NO_TYPES = {};
    private static final Type[] ERROR_TYPES = { COMPUTATION_ERROR_TYPE, ARITHMETIC_EXCEPTION_TYPE };
    private static final Type[] ERR_TYPES = { FORMULA_ERROR_TYPE, ARITHMETIC_EXCEPTION_TYPE };
    private static final Type[] NA_TYPES = { NOT_AVAILABLE_ERROR_TYPE };

    private final void compileIndex(ExpressionNodeForFunction _node) throws CompilerException {
        final ExpressionNodeForArrayReference array = (ExpressionNodeForArrayReference) _node.argument(0);
        switch (_node.cardinality()) {
        case 2:
            if (array.arrayDescriptor().numberOfColumns() == 1) {
                compileIndex(array, _node.argument(1), null);
            } else if (array.arrayDescriptor().numberOfRows() == 1) {
                compileIndex(array, null, _node.argument(1));
            } else {
                throw new CompilerException.UnsupportedExpression(
                        "INDEX with single index expression must not have two-dimensional range argument.");
            }
            return;
        case 3:
            compileIndex(array, _node.argument(1), _node.argument(2));
            return;
        }
        throw new CompilerException.UnsupportedExpression("INDEX must have two or three arguments.");
    }

    private final void compileIndex(ExpressionNodeForArrayReference _array, ExpressionNode _row,
            ExpressionNode _col) throws CompilerException {
        final GeneratorAdapter mv = mv();
        final MethodCompiler mtd = method();
        final ExpressionCompilerForNumbers numCompiler = mtd.numericCompiler();

        // Push receiver for index switch method.
        mtd.compileObjectInContext();

        // Compute index value.
        final ArrayDescriptor desc = _array.arrayDescriptor();
        final int cols = desc.numberOfColumns();
        if (cols == 1 && isNullOrZeroOrOne(_col)) {
            // <row> - 1;
            numCompiler.compileInt(_row);
            mv.push(1);
            mv.visitInsn(Opcodes.ISUB);
        } else {
            final int rows = desc.numberOfRows();
            if (rows == 1 && isNullOrZeroOrOne(_row)) {
                // <col> - 1;
                numCompiler.compileInt(_col);
                mv.push(1);
                mv.visitInsn(Opcodes.ISUB);
            } else {
                // Push receiver for linearizer method.
                mtd.compileObjectInContext();
                numCompiler.compileInt(_row);
                numCompiler.compileInt(_col);
                section().getLinearizerFor(rows, cols).compileCall(mv);
            }
        }
        section().getIndexerFor(_array).compileCall(mv);
    }

    private final boolean isNullOrZeroOrOne(ExpressionNode _node) {
        if (_node == null)
            return true;
        if (_node instanceof ExpressionNodeForConstantValue) {
            final ExpressionNodeForConstantValue constNode = (ExpressionNodeForConstantValue) _node;
            final Number constValue = (Number) constNode.value();
            return (constValue == null || constValue.intValue() == 0 || constValue.intValue() == 1);
        }
        return false;
    }

    private void compileIsException(final ExpressionNodeForFunction _node, final boolean _testForErrors,
            final Type[] _handledTypesReturningTrue, final Type[] _handledTypesReturningFalse)
            throws CompilerException {
        /*
         * Move the handler into its own method because exception handlers clobber the stack.
         */
        final Iterable<LetEntry<Compilable>> closure = closureOf(_node);
        compileHelpedExpr(new HelperCompiler(sectionInContext(), _node, closure) {

            @Override
            protected void compileBody() throws CompilerException {
                final GeneratorAdapter mv = this.mv();
                final ExpressionCompiler ec = expressionCompiler();
                final Label handled = mv.newLabel();

                final Label beginHandling = mv.mark();
                ec.compile(_node.argument(0));
                ec.compileExceptionalValueTest(_testForErrors);
                mv.goTo(handled);
                final Label endHandling = mv.mark();

                for (final Type a_handledTypesReturningTrue : _handledTypesReturningTrue) {
                    mv.catchException(beginHandling, endHandling, a_handledTypesReturningTrue);
                    mv.visitVarInsn(Opcodes.ASTORE, method().newLocal(1));
                    ec.compileConst(Boolean.TRUE);
                    mv.goTo(handled);
                }

                for (final Type a_handledTypesReturningFalse : _handledTypesReturningFalse) {
                    mv.catchException(beginHandling, endHandling, a_handledTypesReturningFalse);
                    mv.visitVarInsn(Opcodes.ASTORE, method().newLocal(1));
                    ec.compileConst(Boolean.FALSE);
                    mv.goTo(handled);
                }

                mv.mark(handled);

                mv.returnValue();
            }

        }, closure);
    }

    protected void compileExceptionalValueTest(boolean _testForErrors) throws CompilerException {
        compilePop();
        compileConst(Boolean.FALSE);
    }

    private final void compileIf(ExpressionNodeForFunction _node) throws CompilerException {
        compileIf(_node.arguments().get(0), _node.arguments().get(1), _node.arguments().get(2));
    }

    private final void compileIf(ExpressionNode _test, ExpressionNode _ifTrue, ExpressionNode _ifFalse)
            throws CompilerException {
        final GeneratorAdapter mv = mv();
        final Label notMet = mv.newLabel();
        final Label done = mv.newLabel();

        method().numericCompiler().compileTest(_test, notMet);

        final Set<DelayedLet> outerSetsInTrueBranch = compileTrackingSetsOfOuterLets(_ifTrue);
        revertSetsOfOuterLets(outerSetsInTrueBranch, null);

        mv.visitJumpInsn(Opcodes.GOTO, done);
        mv.mark(notMet);

        final Set<DelayedLet> outerSetsInFalseBranch = compileTrackingSetsOfOuterLets(_ifFalse);
        revertSetsOfOuterLets(outerSetsInFalseBranch, outerSetsInTrueBranch);

        mv.mark(done);
    }

    private final Set<DelayedLet> compileTrackingSetsOfOuterLets(final ExpressionNode _node)
            throws CompilerException {
        final Object oldState = method().beginTrackingSetsOfOuterLets();
        try {

            compile(_node);

            return method().trackedSetsOfOuterLets();
        } finally {
            method().endTrackingSetsOfOuterLets(oldState);
        }
    }

    private final void revertSetsOfOuterLets(Set<DelayedLet> _ifInThisSetOnly, Set<DelayedLet> _notIfInThisSetToo) {
        for (final DelayedLet let : _ifInThisSetOnly) {
            if (_notIfInThisSetToo != null && _notIfInThisSetToo.contains(let)) {
                method().trackSetOfLet(let);
            } else {
                method().letDict().set(let.name, let);
            }
        }
    }

    private void compileSwitch(ExpressionNodeForSwitch _node) throws CompilerException {
        final Iterable<LetEntry<Compilable>> closure = closureOf(_node);
        compileHelpedExpr(new HelperCompilerForSwitch(sectionInContext(), _node, closure), closure);
    }

    final void compileCallTo(MethodCompiler _methodCompiler) {
        method().compileObjectInContext();
        _methodCompiler.compileCall(mv());
    }

    final void compileRef(CellModel _cell) {
        compileCallTo(sectionInContext().cellMethodCompiler(_cell));
    }

    private final void compileRef(ExpressionNodeForParentSectionModel _node) throws CompilerException {
        final SectionCompiler section = sectionInContext();
        final SectionCompiler parent = section.parentSectionCompiler();
        final int parentObject = method().newLocal(1); // Object
        final GeneratorAdapter mv = mv();
        method().compileObjectInContext();
        mv.getField(section.classType(), ByteCodeEngineCompiler.PARENT_MEMBER_NAME, parent.classType());
        mv().visitVarInsn(Opcodes.ASTORE, parentObject);
        compileInContextOfObject(parent, parentObject, _node);
    }

    protected final void compileInContextOfObject(final SectionCompiler _section, int _object, ExpressionNode _node)
            throws CompilerException {
        final int oldLocals = method().localsOffset();
        try {

            final SectionCompiler oldSection = method().sectionInContext();
            final int oldObject = method().objectInContext();
            try {
                method().setObjectInContext(_section, _object);
                compile(_node.argument(0));
            } finally {
                method().setObjectInContext(oldSection, oldObject);
            }
        }

        finally {
            method().resetLocalsTo(oldLocals);
        }
    }

    final void compileSubSectionTraversal(final ExpressionNodeForSubSectionModel _sub,
            final SubSectionTraversal _traversal) throws CompilerException {
        final SubSectionCompiler subSection = sectionInContext().subSectionCompiler(_sub.getSectionModel());
        final GeneratorAdapter mv = mv();
        method().compileObjectInContext();
        sectionInContext().compileCallToGetterFor(mv, subSection);
        compileScanArray(new ForEachElementCompilation() {

            public void compile(int _objectLocalOffset, int _indexLocalOffset) throws CompilerException {
                final SectionCompiler oldSection = sectionInContext();
                final int oldObject = method().objectInContext();
                try {
                    method().setObjectInContext(subSection, _objectLocalOffset);
                    _traversal.compile(_sub.arguments(), _indexLocalOffset);
                } finally {
                    method().setObjectInContext(oldSection, oldObject);
                }
            }

        });
    }

    protected static interface SubSectionTraversal {
        void compile(Collection<ExpressionNode> _elements, int _indexLocalOffset) throws CompilerException;
    }

    private final void compileRef(ExpressionNodeForSubSectionModel _node) throws CompilerException {
        throw new CompilerException.ReferenceToInnerCellNotAggregated();
    }

    private final void compileRef(ExpressionNodeForArrayReference _node) throws CompilerException {
        throw new CompilerException.ReferenceToArrayNotAggregated();
    }

    protected abstract void compileCount(ExpressionNodeForCount _node) throws CompilerException;

    private final void compileLet(ExpressionNodeForLet _node) throws CompilerException {
        final String varName = _node.varName();
        switch (_node.type()) {

        case BYVAL:
            /*
             * Note: It is important to allocate the local here rather than at the point of first
             * evaluation. Otherwise it could get reused when the first evaluation is within a local
             * scope.
             */
            final LocalRef local = compileNewLocal(isArray(_node.value()));
            letDict().let(varName, _node.value().getDataType(),
                    new DelayedLet(varName, local, _node.value(), method().letTrackingNestingLevel()));
            try {
                compile(_node.in());
            } finally {
                letDict().unlet(varName);
            }
            break;

        case BYNAME:
            /*
             * Note: Used internally to pass outer values as single-eval method arguments to helper
             * methods by wrapping the construct in a series of LETs. The closure then automatically
             * declares the parameters and passes the values to them.
             */
            letDict().let(varName, _node.value().getDataType(), new CompilableExpressionNode(_node.value()));
            try {
                compile(_node.in());
            } finally {
                letDict().unlet(varName);
            }
            break;

        default:
            throw new CompilerException.UnsupportedExpression("Cannot compile this type of LET.");

        }
    }

    static boolean isArray(ExpressionNode _node) {
        return _node instanceof ExpressionNodeForArrayReference;
    }

    private final void compileLetVar(String _varName) throws CompilerException {
        if (this.forbiddenLetVars.contains(_varName)) {
            throw new IllegalArgumentException("Cannot compile a letvar named " + _varName
                    + " when already compiling one of that name - rewriter bug?");
        }
        final Compilable val = letDict().lookup(_varName);
        compileLetValue(_varName, val);
    }

    final void compileLetValue(String _name, Compilable _value) throws CompilerException {
        if (_value != null) {
            _value.compile(this);
        } else {
            throw new CompilerException.NameNotFound("The variable " + _name + " is not bound in this context.");
        }
    }

    void compileDelayedLet(DelayedLet _letDef) throws CompilerException {
        this.forbiddenLetVars.add(_letDef.name);
        try {
            compile(_letDef.node);
        } finally {
            this.forbiddenLetVars.remove(_letDef.name);
        }
        compileDup(_letDef.isArray());
        _letDef.local.compileStoreLocal(this);
        letDict().set(_letDef.name, _letDef.local);
        method().trackSetOfLet(_letDef);
    }

    private final Set<String> forbiddenLetVars = New.set();

    protected final LocalRef compileNewLocal(boolean _isArray) {
        if (_isArray) {
            return new LocalArrayRef(method().newLocal(1));
        } else {
            return new LocalValueRef(method().newLocal(type().getSize()));
        }
    }

    private void compileDup(boolean _isArray) {
        if (_isArray) {
            mv().visitInsn(Opcodes.DUP);
        } else {
            compileDup();
        }
    }

    protected void compileDup() {
        mv().visitInsn(Opcodes.DUP);
    }

    protected void compilePop() {
        mv().visitInsn(Opcodes.POP);
    }

    final void compileHelpedExpr(HelperCompiler _compiler, Iterable<LetEntry<Compilable>> _closure)
            throws CompilerException {
        _compiler.compile();
        method().compileCalleeAndClosure(_closure);
        _compiler.compileCall(mv());
    }

    protected final void compileRuntimeMethod(String _methodName, String _methodSig) {
        typeCompiler().compileRuntimeMethod(mv(), _methodName, _methodSig);
    }

    protected void compile_environment() {
        section().compileEnvironmentAccess(mv());
    }

    protected void compile_sectionInfo() {
        section().compileSectionInfoAccess(mv());
    }

    protected void compile_computationMode() {
        method().section().compileComputationModeAccess(mv());
    }

    protected void compile_computationTime() {
        method().section().compileComputationTimeAccess(mv());
    }

    private static interface ForEachElementCompilation {
        void compile(int _objectLocalOffset, int _indexLocalOffset) throws CompilerException;
    }

    private void compileScanArray(ForEachElementCompilation _forElement) throws CompilerException {
        final GeneratorAdapter mv = mv();
        final int loc = localsOffset();
        incLocalsOffset(4);

        // store array
        mv.visitVarInsn(Opcodes.ASTORE, loc);

        // store array length
        mv.visitVarInsn(Opcodes.ALOAD, loc);
        mv.arrayLength();
        mv.visitVarInsn(Opcodes.ISTORE, 1 + loc);

        // loop index
        mv.push(0);
        mv.visitVarInsn(Opcodes.ISTORE, 2 + loc);

        // loop start
        final Label l0 = mv.mark();

        // check loop condition
        mv.visitVarInsn(Opcodes.ILOAD, 2 + loc);
        mv.visitVarInsn(Opcodes.ILOAD, 1 + loc);
        final Label l1 = new Label();
        mv.ifICmp(GeneratorAdapter.GE, l1);

        // loop body
        mv.visitVarInsn(Opcodes.ALOAD, loc);
        mv.visitVarInsn(Opcodes.ILOAD, 2 + loc);
        mv.visitInsn(Opcodes.AALOAD);
        mv.visitVarInsn(Opcodes.ASTORE, 3 + loc);
        _forElement.compile(3 + loc, 2 + loc);

        // inc loop index
        mv.visitIincInsn(2 + loc, 1);

        mv.goTo(l0);
        mv.visitLabel(l1);
    }

    protected final void compileArray(ExpressionNode _arrayNode) throws CompilerException {
        final GeneratorAdapter mv = mv();
        final ExpressionNodeForArrayReference arr = (ExpressionNodeForArrayReference) _arrayNode;
        mv.push(arr.arrayDescriptor().numberOfElements());
        mv.newArray(type());
        int i = 0;
        for (ExpressionNode arg : arr.arguments()) {
            mv.visitInsn(Opcodes.DUP);
            mv.push(i++);
            compile(arg);
            mv.arrayStore(type());
        }
    }

    private final void compileFoldList(ExpressionNodeForFoldList _node) throws CompilerException {
        if (ChainedFoldCompiler.isChainable(_node.fold())) {
            if (new ChainedFoldCompiler(this, _node).compile()) {
                return;
            }
            if (!_node.fold().isSpecialWhenEmpty()) {
                final Iterable<LetEntry<Compilable>> closure = closureOf(_node);
                compileHelpedExpr(new HelperCompilerForFoldChained(sectionInContext(), _node, closure), closure);
                return;
            }
        }
        final Iterable<LetEntry<Compilable>> closure = closureOf(_node);
        compileHelpedExpr(new HelperCompilerForFoldList(sectionInContext(), _node, closure), closure);
    }

    private final void compileFoldVectors(ExpressionNodeForFoldVectors _node) throws CompilerException {
        final Iterable<LetEntry<Compilable>> closure = closureOf(_node);
        compileHelpedExpr(new HelperCompilerForFoldVectors(sectionInContext(), _node, closure), closure);
    }

    private final void compileFoldDatabase(ExpressionNodeForFoldDatabase _node) throws CompilerException {
        final Iterable<LetEntry<Compilable>> closure = closureOf(_node);
        compileHelpedExpr(new HelperCompilerForFoldDatabase(sectionInContext(), _node, closure), closure);
    }

    private void compileLogging(final ExpressionNodeForLogging _expressionNodeForLogging) throws CompilerException {
        compile(_expressionNodeForLogging.argument(0));

        final Object source = _expressionNodeForLogging.getSource();
        if (source instanceof CellAddress) {
            final CellAddress cellAddress = (CellAddress) source;
            final String definedName = _expressionNodeForLogging.getDefinedName();
            final boolean input = _expressionNodeForLogging.isInput();
            final boolean output = _expressionNodeForLogging.isOutput();
            compileLogging(cellAddress, definedName, input, output);
        }
    }

    void compileLogging(final CellAddress _cellAddress, final String _name, boolean _input, boolean _output)
            throws CompilerException {
        final GeneratorAdapter mv = mv();
        final int valLocal = mv.newLocal(type());
        mv.storeLocal(valLocal);
        mv.loadLocal(valLocal);
        if (DataType.NUMERIC.equals(dataType())) {
            compileConversionTo(Number.class);
        }

        compile_util_log(_cellAddress.getSheetName(), _cellAddress.getColumnIndex(), _cellAddress.getRowIndex(),
                _name, _input, _output);

        mv.loadLocal(valLocal);
    }

    protected abstract void compile_util_log(String _sheetName, int _columnIndex, int _rowIndex,
            String _definedName, boolean _input, boolean _output) throws CompilerException;

    static final boolean isSubSectionIn(Iterable<ExpressionNode> _elts) {
        for (ExpressionNode elt : _elts) {
            if (elt instanceof ExpressionNodeForSubSectionModel) {
                return true;
            }
        }
        return false;
    }

}