org.elasticsearch.painless.Writer.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.painless.Writer.java

Source

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.painless;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Constructor;
import org.elasticsearch.painless.Definition.Field;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Transform;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.PainlessParser.AfterthoughtContext;
import org.elasticsearch.painless.PainlessParser.ArgumentsContext;
import org.elasticsearch.painless.PainlessParser.AssignmentContext;
import org.elasticsearch.painless.PainlessParser.BinaryContext;
import org.elasticsearch.painless.PainlessParser.BlockContext;
import org.elasticsearch.painless.PainlessParser.BoolContext;
import org.elasticsearch.painless.PainlessParser.BreakContext;
import org.elasticsearch.painless.PainlessParser.CastContext;
import org.elasticsearch.painless.PainlessParser.CharContext;
import org.elasticsearch.painless.PainlessParser.CompContext;
import org.elasticsearch.painless.PainlessParser.ConditionalContext;
import org.elasticsearch.painless.PainlessParser.ContinueContext;
import org.elasticsearch.painless.PainlessParser.DeclContext;
import org.elasticsearch.painless.PainlessParser.DeclarationContext;
import org.elasticsearch.painless.PainlessParser.DecltypeContext;
import org.elasticsearch.painless.PainlessParser.DeclvarContext;
import org.elasticsearch.painless.PainlessParser.DoContext;
import org.elasticsearch.painless.PainlessParser.EmptyContext;
import org.elasticsearch.painless.PainlessParser.EmptyscopeContext;
import org.elasticsearch.painless.PainlessParser.ExprContext;
import org.elasticsearch.painless.PainlessParser.ExpressionContext;
import org.elasticsearch.painless.PainlessParser.ExtbraceContext;
import org.elasticsearch.painless.PainlessParser.ExtcallContext;
import org.elasticsearch.painless.PainlessParser.ExtcastContext;
import org.elasticsearch.painless.PainlessParser.ExtdotContext;
import org.elasticsearch.painless.PainlessParser.ExternalContext;
import org.elasticsearch.painless.PainlessParser.ExtfieldContext;
import org.elasticsearch.painless.PainlessParser.ExtnewContext;
import org.elasticsearch.painless.PainlessParser.ExtprecContext;
import org.elasticsearch.painless.PainlessParser.ExtstartContext;
import org.elasticsearch.painless.PainlessParser.ExtstringContext;
import org.elasticsearch.painless.PainlessParser.ExttypeContext;
import org.elasticsearch.painless.PainlessParser.ExtvarContext;
import org.elasticsearch.painless.PainlessParser.FalseContext;
import org.elasticsearch.painless.PainlessParser.ForContext;
import org.elasticsearch.painless.PainlessParser.IfContext;
import org.elasticsearch.painless.PainlessParser.IncrementContext;
import org.elasticsearch.painless.PainlessParser.InitializerContext;
import org.elasticsearch.painless.PainlessParser.MultipleContext;
import org.elasticsearch.painless.PainlessParser.NullContext;
import org.elasticsearch.painless.PainlessParser.NumericContext;
import org.elasticsearch.painless.PainlessParser.PostincContext;
import org.elasticsearch.painless.PainlessParser.PrecedenceContext;
import org.elasticsearch.painless.PainlessParser.PreincContext;
import org.elasticsearch.painless.PainlessParser.ReturnContext;
import org.elasticsearch.painless.PainlessParser.SingleContext;
import org.elasticsearch.painless.PainlessParser.SourceContext;
import org.elasticsearch.painless.PainlessParser.StatementContext;
import org.elasticsearch.painless.PainlessParser.ThrowContext;
import org.elasticsearch.painless.PainlessParser.TrapContext;
import org.elasticsearch.painless.PainlessParser.TrueContext;
import org.elasticsearch.painless.PainlessParser.TryContext;
import org.elasticsearch.painless.PainlessParser.UnaryContext;
import org.elasticsearch.painless.PainlessParser.WhileContext;
import org.elasticsearch.script.ScoreAccessor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;

import java.lang.invoke.MethodType;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.elasticsearch.painless.PainlessParser.ADD;
import static org.elasticsearch.painless.PainlessParser.BWAND;
import static org.elasticsearch.painless.PainlessParser.BWOR;
import static org.elasticsearch.painless.PainlessParser.BWXOR;
import static org.elasticsearch.painless.PainlessParser.DIV;
import static org.elasticsearch.painless.PainlessParser.LSH;
import static org.elasticsearch.painless.PainlessParser.MUL;
import static org.elasticsearch.painless.PainlessParser.REM;
import static org.elasticsearch.painless.PainlessParser.RSH;
import static org.elasticsearch.painless.PainlessParser.SUB;
import static org.elasticsearch.painless.PainlessParser.USH;

class Writer extends PainlessParserBaseVisitor<Void> {
    private static class Branch {
        final ParserRuleContext source;

        Label begin = null;
        Label end = null;
        Label tru = null;
        Label fals = null;

        private Branch(final ParserRuleContext source) {
            this.source = source;
        }
    }

    final static String BASE_CLASS_NAME = Executable.class.getName();
    final static String CLASS_NAME = BASE_CLASS_NAME + "$CompiledPainlessExecutable";
    private final static org.objectweb.asm.Type BASE_CLASS_TYPE = org.objectweb.asm.Type.getType(Executable.class);
    private final static org.objectweb.asm.Type CLASS_TYPE = org.objectweb.asm.Type
            .getType("L" + CLASS_NAME.replace(".", "/") + ";");

    private final static org.objectweb.asm.commons.Method CONSTRUCTOR = getAsmMethod(void.class, "<init>",
            Definition.class, String.class, String.class);
    private final static org.objectweb.asm.commons.Method EXECUTE = getAsmMethod(Object.class, "execute",
            Map.class);
    private final static String SIGNATURE = "(Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)Ljava/lang/Object;";

    private final static org.objectweb.asm.Type PAINLESS_ERROR_TYPE = org.objectweb.asm.Type
            .getType(PainlessError.class);

    private final static org.objectweb.asm.Type DEFINITION_TYPE = org.objectweb.asm.Type.getType(Definition.class);

    private final static org.objectweb.asm.Type MAP_TYPE = org.objectweb.asm.Type.getType(Map.class);
    private final static org.objectweb.asm.commons.Method MAP_GET = getAsmMethod(Object.class, "get", Object.class);

    private final static org.objectweb.asm.Type SCORE_ACCESSOR_TYPE = org.objectweb.asm.Type
            .getType(ScoreAccessor.class);
    private final static org.objectweb.asm.commons.Method SCORE_ACCESSOR_FLOAT = getAsmMethod(float.class,
            "floatValue");

    private final static org.objectweb.asm.commons.Method DEF_METHOD_CALL = getAsmMethod(Object.class, "methodCall",
            Object.class, String.class, Definition.class, Object[].class, boolean[].class);
    private final static org.objectweb.asm.commons.Method DEF_ARRAY_STORE = getAsmMethod(void.class, "arrayStore",
            Object.class, Object.class, Object.class, Definition.class, boolean.class, boolean.class);
    private final static org.objectweb.asm.commons.Method DEF_ARRAY_LOAD = getAsmMethod(Object.class, "arrayLoad",
            Object.class, Object.class, Definition.class, boolean.class);
    private final static org.objectweb.asm.commons.Method DEF_FIELD_STORE = getAsmMethod(void.class, "fieldStore",
            Object.class, Object.class, String.class, Definition.class, boolean.class);
    private final static org.objectweb.asm.commons.Method DEF_FIELD_LOAD = getAsmMethod(Object.class, "fieldLoad",
            Object.class, String.class, Definition.class);

    private final static org.objectweb.asm.commons.Method DEF_NOT_CALL = getAsmMethod(Object.class, "not",
            Object.class);
    private final static org.objectweb.asm.commons.Method DEF_NEG_CALL = getAsmMethod(Object.class, "neg",
            Object.class);
    private final static org.objectweb.asm.commons.Method DEF_MUL_CALL = getAsmMethod(Object.class, "mul",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_DIV_CALL = getAsmMethod(Object.class, "div",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_REM_CALL = getAsmMethod(Object.class, "rem",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_ADD_CALL = getAsmMethod(Object.class, "add",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_SUB_CALL = getAsmMethod(Object.class, "sub",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_LSH_CALL = getAsmMethod(Object.class, "lsh",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_RSH_CALL = getAsmMethod(Object.class, "rsh",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_USH_CALL = getAsmMethod(Object.class, "ush",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_AND_CALL = getAsmMethod(Object.class, "and",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_XOR_CALL = getAsmMethod(Object.class, "xor",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_OR_CALL = getAsmMethod(Object.class, "or",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_EQ_CALL = getAsmMethod(boolean.class, "eq",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_LT_CALL = getAsmMethod(boolean.class, "lt",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_LTE_CALL = getAsmMethod(boolean.class, "lte",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_GT_CALL = getAsmMethod(boolean.class, "gt",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method DEF_GTE_CALL = getAsmMethod(boolean.class, "gte",
            Object.class, Object.class);

    private final static org.objectweb.asm.Type STRINGBUILDER_TYPE = org.objectweb.asm.Type
            .getType(StringBuilder.class);

    private final static org.objectweb.asm.commons.Method STRINGBUILDER_CONSTRUCTOR = getAsmMethod(void.class,
            "<init>");
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_APPEND_BOOLEAN = getAsmMethod(
            StringBuilder.class, "append", boolean.class);
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_APPEND_CHAR = getAsmMethod(
            StringBuilder.class, "append", char.class);
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_APPEND_INT = getAsmMethod(
            StringBuilder.class, "append", int.class);
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_APPEND_LONG = getAsmMethod(
            StringBuilder.class, "append", long.class);
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_APPEND_FLOAT = getAsmMethod(
            StringBuilder.class, "append", float.class);
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_APPEND_DOUBLE = getAsmMethod(
            StringBuilder.class, "append", double.class);
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_APPEND_STRING = getAsmMethod(
            StringBuilder.class, "append", String.class);
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_APPEND_OBJECT = getAsmMethod(
            StringBuilder.class, "append", Object.class);
    private final static org.objectweb.asm.commons.Method STRINGBUILDER_TOSTRING = getAsmMethod(String.class,
            "toString");

    private final static org.objectweb.asm.commons.Method TOINTEXACT_LONG = getAsmMethod(int.class, "toIntExact",
            long.class);
    private final static org.objectweb.asm.commons.Method NEGATEEXACT_INT = getAsmMethod(int.class, "negateExact",
            int.class);
    private final static org.objectweb.asm.commons.Method NEGATEEXACT_LONG = getAsmMethod(long.class, "negateExact",
            long.class);
    private final static org.objectweb.asm.commons.Method MULEXACT_INT = getAsmMethod(int.class, "multiplyExact",
            int.class, int.class);
    private final static org.objectweb.asm.commons.Method MULEXACT_LONG = getAsmMethod(long.class, "multiplyExact",
            long.class, long.class);
    private final static org.objectweb.asm.commons.Method ADDEXACT_INT = getAsmMethod(int.class, "addExact",
            int.class, int.class);
    private final static org.objectweb.asm.commons.Method ADDEXACT_LONG = getAsmMethod(long.class, "addExact",
            long.class, long.class);
    private final static org.objectweb.asm.commons.Method SUBEXACT_INT = getAsmMethod(int.class, "subtractExact",
            int.class, int.class);
    private final static org.objectweb.asm.commons.Method SUBEXACT_LONG = getAsmMethod(long.class, "subtractExact",
            long.class, long.class);

    private final static org.objectweb.asm.commons.Method CHECKEQUALS = getAsmMethod(boolean.class, "checkEquals",
            Object.class, Object.class);
    private final static org.objectweb.asm.commons.Method TOBYTEEXACT_INT = getAsmMethod(byte.class, "toByteExact",
            int.class);
    private final static org.objectweb.asm.commons.Method TOBYTEEXACT_LONG = getAsmMethod(byte.class, "toByteExact",
            long.class);
    private final static org.objectweb.asm.commons.Method TOBYTEWOOVERFLOW_FLOAT = getAsmMethod(byte.class,
            "toByteWithoutOverflow", float.class);
    private final static org.objectweb.asm.commons.Method TOBYTEWOOVERFLOW_DOUBLE = getAsmMethod(byte.class,
            "toByteWithoutOverflow", double.class);
    private final static org.objectweb.asm.commons.Method TOSHORTEXACT_INT = getAsmMethod(short.class,
            "toShortExact", int.class);
    private final static org.objectweb.asm.commons.Method TOSHORTEXACT_LONG = getAsmMethod(short.class,
            "toShortExact", long.class);
    private final static org.objectweb.asm.commons.Method TOSHORTWOOVERFLOW_FLOAT = getAsmMethod(short.class,
            "toShortWithoutOverflow", float.class);
    private final static org.objectweb.asm.commons.Method TOSHORTWOOVERFLOW_DOUBLE = getAsmMethod(short.class,
            "toShortWihtoutOverflow", double.class);
    private final static org.objectweb.asm.commons.Method TOCHAREXACT_INT = getAsmMethod(char.class, "toCharExact",
            int.class);
    private final static org.objectweb.asm.commons.Method TOCHAREXACT_LONG = getAsmMethod(char.class, "toCharExact",
            long.class);
    private final static org.objectweb.asm.commons.Method TOCHARWOOVERFLOW_FLOAT = getAsmMethod(char.class,
            "toCharWithoutOverflow", float.class);
    private final static org.objectweb.asm.commons.Method TOCHARWOOVERFLOW_DOUBLE = getAsmMethod(char.class,
            "toCharWithoutOverflow", double.class);
    private final static org.objectweb.asm.commons.Method TOINTWOOVERFLOW_FLOAT = getAsmMethod(int.class,
            "toIntWithoutOverflow", float.class);
    private final static org.objectweb.asm.commons.Method TOINTWOOVERFLOW_DOUBLE = getAsmMethod(int.class,
            "toIntWithoutOverflow", double.class);
    private final static org.objectweb.asm.commons.Method TOLONGWOOVERFLOW_FLOAT = getAsmMethod(long.class,
            "toLongWithoutOverflow", float.class);
    private final static org.objectweb.asm.commons.Method TOLONGWOOVERFLOW_DOUBLE = getAsmMethod(long.class,
            "toLongWithoutOverflow", double.class);
    private final static org.objectweb.asm.commons.Method TOFLOATWOOVERFLOW_DOUBLE = getAsmMethod(float.class,
            "toFloatWihtoutOverflow", double.class);
    private final static org.objectweb.asm.commons.Method MULWOOVERLOW_FLOAT = getAsmMethod(float.class,
            "multiplyWithoutOverflow", float.class, float.class);
    private final static org.objectweb.asm.commons.Method MULWOOVERLOW_DOUBLE = getAsmMethod(double.class,
            "multiplyWithoutOverflow", double.class, double.class);
    private final static org.objectweb.asm.commons.Method DIVWOOVERLOW_INT = getAsmMethod(int.class,
            "divideWithoutOverflow", int.class, int.class);
    private final static org.objectweb.asm.commons.Method DIVWOOVERLOW_LONG = getAsmMethod(long.class,
            "divideWithoutOverflow", long.class, long.class);
    private final static org.objectweb.asm.commons.Method DIVWOOVERLOW_FLOAT = getAsmMethod(float.class,
            "divideWithoutOverflow", float.class, float.class);
    private final static org.objectweb.asm.commons.Method DIVWOOVERLOW_DOUBLE = getAsmMethod(double.class,
            "divideWithoutOverflow", double.class, double.class);
    private final static org.objectweb.asm.commons.Method REMWOOVERLOW_FLOAT = getAsmMethod(float.class,
            "remainderWithoutOverflow", float.class, float.class);
    private final static org.objectweb.asm.commons.Method REMWOOVERLOW_DOUBLE = getAsmMethod(double.class,
            "remainderWithoutOverflow", double.class, double.class);
    private final static org.objectweb.asm.commons.Method ADDWOOVERLOW_FLOAT = getAsmMethod(float.class,
            "addWithoutOverflow", float.class, float.class);
    private final static org.objectweb.asm.commons.Method ADDWOOVERLOW_DOUBLE = getAsmMethod(double.class,
            "addWithoutOverflow", double.class, double.class);
    private final static org.objectweb.asm.commons.Method SUBWOOVERLOW_FLOAT = getAsmMethod(float.class,
            "subtractWithoutOverflow", float.class, float.class);
    private final static org.objectweb.asm.commons.Method SUBWOOVERLOW_DOUBLE = getAsmMethod(double.class,
            "subtractWithoutOverflow", double.class, double.class);

    private static org.objectweb.asm.commons.Method getAsmMethod(final Class<?> rtype, final String name,
            final Class<?>... ptypes) {
        return new org.objectweb.asm.commons.Method(name,
                MethodType.methodType(rtype, ptypes).toMethodDescriptorString());
    }

    static byte[] write(Metadata metadata) {
        Writer writer = new Writer(metadata);

        return writer.getBytes();
    }

    private final Metadata metadata;
    private final Definition definition;
    private final ParseTree root;
    private final String source;
    private final CompilerSettings settings;

    private final Map<ParserRuleContext, Branch> branches = new HashMap<>();
    private final Deque<Branch> jumps = new ArrayDeque<>();
    private final Set<ParserRuleContext> strings = new HashSet<>();

    private ClassWriter writer;
    private GeneratorAdapter execute;

    private Writer(final Metadata metadata) {
        this.metadata = metadata;
        definition = metadata.definition;
        root = metadata.root;
        source = metadata.source;
        settings = metadata.settings;

        writeBegin();
        writeConstructor();
        writeExecute();
        writeEnd();
    }

    private Branch markBranch(final ParserRuleContext source, final ParserRuleContext... nodes) {
        final Branch branch = new Branch(source);

        for (final ParserRuleContext node : nodes) {
            branches.put(node, branch);
        }

        return branch;
    }

    private void copyBranch(final Branch branch, final ParserRuleContext... nodes) {
        for (final ParserRuleContext node : nodes) {
            branches.put(node, branch);
        }
    }

    private Branch getBranch(final ParserRuleContext source) {
        return branches.get(source);
    }

    private void writeBegin() {
        final int compute = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
        final int version = Opcodes.V1_7;
        final int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
        final String base = BASE_CLASS_TYPE.getInternalName();
        final String name = CLASS_TYPE.getInternalName();

        writer = new ClassWriter(compute);
        writer.visit(version, access, name, null, base, null);
        writer.visitSource(source, null);
    }

    private void writeConstructor() {
        final int access = Opcodes.ACC_PUBLIC;
        final GeneratorAdapter constructor = new GeneratorAdapter(access, CONSTRUCTOR, null, null, writer);
        constructor.loadThis();
        constructor.loadArgs();
        constructor.invokeConstructor(org.objectweb.asm.Type.getType(Executable.class), CONSTRUCTOR);
        constructor.returnValue();
        constructor.endMethod();
    }

    private void writeExecute() {
        final int access = Opcodes.ACC_PUBLIC;
        execute = new GeneratorAdapter(access, EXECUTE, SIGNATURE, null, writer);

        final Label fals = new Label();
        final Label end = new Label();
        execute.visitVarInsn(Opcodes.ALOAD, metadata.inputValueSlot);
        execute.push("#score");
        execute.invokeInterface(MAP_TYPE, MAP_GET);
        execute.dup();
        execute.ifNull(fals);
        execute.checkCast(SCORE_ACCESSOR_TYPE);
        execute.invokeVirtual(SCORE_ACCESSOR_TYPE, SCORE_ACCESSOR_FLOAT);
        execute.goTo(end);
        execute.mark(fals);
        execute.pop();
        execute.push(0F);
        execute.mark(end);
        execute.visitVarInsn(Opcodes.FSTORE, metadata.scoreValueSlot);

        execute.push(settings.getMaxLoopCounter());
        execute.visitVarInsn(Opcodes.ISTORE, metadata.loopCounterSlot);

        visit(root);
        execute.endMethod();
    }

    @Override
    public Void visitSource(final SourceContext ctx) {
        final Metadata.StatementMetadata sourcesmd = metadata.getStatementMetadata(ctx);

        for (final StatementContext sctx : ctx.statement()) {
            visit(sctx);
        }

        if (!sourcesmd.methodEscape) {
            execute.visitInsn(Opcodes.ACONST_NULL);
            execute.returnValue();
        }

        return null;
    }

    @Override
    public Void visitIf(final IfContext ctx) {
        final ExpressionContext exprctx = ctx.expression();
        final boolean els = ctx.ELSE() != null;
        final Branch branch = markBranch(ctx, exprctx);
        branch.end = new Label();
        branch.fals = els ? new Label() : branch.end;

        visit(exprctx);

        final BlockContext blockctx0 = ctx.block(0);
        final Metadata.StatementMetadata blockmd0 = metadata.getStatementMetadata(blockctx0);
        visit(blockctx0);

        if (els) {
            if (!blockmd0.allLast) {
                execute.goTo(branch.end);
            }

            execute.mark(branch.fals);
            visit(ctx.block(1));
        }

        execute.mark(branch.end);

        return null;
    }

    @Override
    public Void visitWhile(final WhileContext ctx) {
        final ExpressionContext exprctx = ctx.expression();
        final Branch branch = markBranch(ctx, exprctx);
        branch.begin = new Label();
        branch.end = new Label();
        branch.fals = branch.end;

        jumps.push(branch);
        execute.mark(branch.begin);
        visit(exprctx);

        final BlockContext blockctx = ctx.block();
        boolean allLast = false;

        if (blockctx != null) {
            final Metadata.StatementMetadata blocksmd = metadata.getStatementMetadata(blockctx);
            allLast = blocksmd.allLast;
            writeLoopCounter(blocksmd.count > 0 ? blocksmd.count : 1);
            visit(blockctx);
        } else if (ctx.empty() != null) {
            writeLoopCounter(1);
        } else {
            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
        }

        if (!allLast) {
            execute.goTo(branch.begin);
        }

        execute.mark(branch.end);
        jumps.pop();

        return null;
    }

    @Override
    public Void visitDo(final DoContext ctx) {
        final ExpressionContext exprctx = ctx.expression();
        final Branch branch = markBranch(ctx, exprctx);
        Label start = new Label();
        branch.begin = new Label();
        branch.end = new Label();
        branch.fals = branch.end;

        final BlockContext blockctx = ctx.block();
        final Metadata.StatementMetadata blocksmd = metadata.getStatementMetadata(blockctx);

        jumps.push(branch);
        execute.mark(start);
        visit(blockctx);
        execute.mark(branch.begin);
        visit(exprctx);
        writeLoopCounter(blocksmd.count > 0 ? blocksmd.count : 1);
        execute.goTo(start);
        execute.mark(branch.end);
        jumps.pop();

        return null;
    }

    @Override
    public Void visitFor(final ForContext ctx) {
        final ExpressionContext exprctx = ctx.expression();
        final AfterthoughtContext atctx = ctx.afterthought();
        final Branch branch = markBranch(ctx, exprctx);
        final Label start = new Label();
        branch.begin = atctx == null ? start : new Label();
        branch.end = new Label();
        branch.fals = branch.end;

        jumps.push(branch);

        if (ctx.initializer() != null) {
            visit(ctx.initializer());
        }

        execute.mark(start);

        if (exprctx != null) {
            visit(exprctx);
        }

        final BlockContext blockctx = ctx.block();
        boolean allLast = false;

        if (blockctx != null) {
            Metadata.StatementMetadata blocksmd = metadata.getStatementMetadata(blockctx);
            allLast = blocksmd.allLast;

            int count = blocksmd.count > 0 ? blocksmd.count : 1;

            if (atctx != null) {
                ++count;
            }

            writeLoopCounter(count);
            visit(blockctx);
        } else if (ctx.empty() != null) {
            writeLoopCounter(1);
        } else {
            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
        }

        if (atctx != null) {
            execute.mark(branch.begin);
            visit(atctx);
        }

        if (atctx != null || !allLast) {
            execute.goTo(start);
        }

        execute.mark(branch.end);
        jumps.pop();

        return null;
    }

    @Override
    public Void visitDecl(final DeclContext ctx) {
        visit(ctx.declaration());

        return null;
    }

    @Override
    public Void visitContinue(final ContinueContext ctx) {
        final Branch jump = jumps.peek();
        execute.goTo(jump.begin);

        return null;
    }

    @Override
    public Void visitBreak(final BreakContext ctx) {
        final Branch jump = jumps.peek();
        execute.goTo(jump.end);

        return null;
    }

    @Override
    public Void visitReturn(final ReturnContext ctx) {
        visit(ctx.expression());
        execute.returnValue();

        return null;
    }

    @Override
    public Void visitTry(final TryContext ctx) {
        final TrapContext[] trapctxs = new TrapContext[ctx.trap().size()];
        ctx.trap().toArray(trapctxs);
        final Branch branch = markBranch(ctx, trapctxs);

        Label end = new Label();
        branch.begin = new Label();
        branch.end = new Label();
        branch.tru = trapctxs.length > 1 ? end : null;

        execute.mark(branch.begin);

        final BlockContext blockctx = ctx.block();
        final Metadata.StatementMetadata blocksmd = metadata.getStatementMetadata(blockctx);
        visit(blockctx);

        if (!blocksmd.allLast) {
            execute.goTo(end);
        }

        execute.mark(branch.end);

        for (final TrapContext trapctx : trapctxs) {
            visit(trapctx);
        }

        if (!blocksmd.allLast || trapctxs.length > 1) {
            execute.mark(end);
        }

        return null;
    }

    @Override
    public Void visitThrow(final ThrowContext ctx) {
        visit(ctx.expression());
        execute.throwException();

        return null;
    }

    @Override
    public Void visitExpr(final ExprContext ctx) {
        final Metadata.StatementMetadata exprsmd = metadata.getStatementMetadata(ctx);
        final ExpressionContext exprctx = ctx.expression();
        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(exprctx);
        visit(exprctx);

        if (exprsmd.methodEscape) {
            execute.returnValue();
        } else {
            writePop(expremd.to.type.getSize());
        }

        return null;
    }

    @Override
    public Void visitMultiple(final MultipleContext ctx) {
        for (final StatementContext sctx : ctx.statement()) {
            visit(sctx);
        }

        return null;
    }

    @Override
    public Void visitSingle(final SingleContext ctx) {
        visit(ctx.statement());

        return null;
    }

    @Override
    public Void visitEmpty(final EmptyContext ctx) {
        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected writer state.");
    }

    @Override
    public Void visitInitializer(InitializerContext ctx) {
        final DeclarationContext declctx = ctx.declaration();
        final ExpressionContext exprctx = ctx.expression();

        if (declctx != null) {
            visit(declctx);
        } else if (exprctx != null) {
            final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(exprctx);
            visit(exprctx);
            writePop(expremd.to.type.getSize());
        } else {
            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
        }

        return null;
    }

    @Override
    public Void visitAfterthought(AfterthoughtContext ctx) {
        final ExpressionContext exprctx = ctx.expression();
        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(exprctx);
        visit(ctx.expression());
        writePop(expremd.to.type.getSize());

        return null;
    }

    @Override
    public Void visitDeclaration(DeclarationContext ctx) {
        for (final DeclvarContext declctx : ctx.declvar()) {
            visit(declctx);
        }

        return null;
    }

    @Override
    public Void visitDecltype(final DecltypeContext ctx) {
        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected writer state.");
    }

    @Override
    public Void visitDeclvar(final DeclvarContext ctx) {
        final Metadata.ExpressionMetadata declvaremd = metadata.getExpressionMetadata(ctx);
        final org.objectweb.asm.Type type = declvaremd.to.type;
        final Sort sort = declvaremd.to.sort;
        final int slot = (int) declvaremd.postConst;

        final ExpressionContext exprctx = ctx.expression();
        final boolean initialize = exprctx == null;

        if (!initialize) {
            visit(exprctx);
        }

        switch (sort) {
        case VOID:
            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
        case BOOL:
        case BYTE:
        case SHORT:
        case CHAR:
        case INT:
            if (initialize)
                execute.push(0);
            break;
        case LONG:
            if (initialize)
                execute.push(0L);
            break;
        case FLOAT:
            if (initialize)
                execute.push(0.0F);
            break;
        case DOUBLE:
            if (initialize)
                execute.push(0.0);
            break;
        default:
            if (initialize)
                execute.visitInsn(Opcodes.ACONST_NULL);
        }

        execute.visitVarInsn(type.getOpcode(Opcodes.ISTORE), slot);

        return null;
    }

    @Override
    public Void visitTrap(final TrapContext ctx) {
        final Metadata.StatementMetadata trapsmd = metadata.getStatementMetadata(ctx);

        final Branch branch = getBranch(ctx);
        final Label jump = new Label();

        final BlockContext blockctx = ctx.block();
        final EmptyscopeContext emptyctx = ctx.emptyscope();

        execute.mark(jump);
        writeLoadStoreVariable(ctx, true, trapsmd.exception, trapsmd.slot);

        if (blockctx != null) {
            visit(ctx.block());
        } else if (emptyctx == null) {
            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
        }

        execute.visitTryCatchBlock(branch.begin, branch.end, jump, trapsmd.exception.type.getInternalName());

        if (branch.tru != null && !trapsmd.allLast) {
            execute.goTo(branch.tru);
        }

        return null;
    }

    @Override
    public Void visitPrecedence(final PrecedenceContext ctx) {
        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected writer state.");
    }

    @Override
    public Void visitNumeric(final NumericContext ctx) {
        final Metadata.ExpressionMetadata numericemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = numericemd.postConst;

        if (postConst == null) {
            writeNumeric(ctx, numericemd.preConst);
            checkWriteCast(numericemd);
        } else {
            writeConstant(ctx, postConst);
        }

        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitChar(final CharContext ctx) {
        final Metadata.ExpressionMetadata charemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = charemd.postConst;

        if (postConst == null) {
            writeNumeric(ctx, (int) (char) charemd.preConst);
            checkWriteCast(charemd);
        } else {
            writeConstant(ctx, postConst);
        }

        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitTrue(final TrueContext ctx) {
        final Metadata.ExpressionMetadata trueemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = trueemd.postConst;
        final Branch branch = getBranch(ctx);

        if (branch == null) {
            if (postConst == null) {
                writeBoolean(ctx, true);
                checkWriteCast(trueemd);
            } else {
                writeConstant(ctx, postConst);
            }
        } else if (branch.tru != null) {
            execute.goTo(branch.tru);
        }

        return null;
    }

    @Override
    public Void visitFalse(final FalseContext ctx) {
        final Metadata.ExpressionMetadata falseemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = falseemd.postConst;
        final Branch branch = getBranch(ctx);

        if (branch == null) {
            if (postConst == null) {
                writeBoolean(ctx, false);
                checkWriteCast(falseemd);
            } else {
                writeConstant(ctx, postConst);
            }
        } else if (branch.fals != null) {
            execute.goTo(branch.fals);
        }

        return null;
    }

    @Override
    public Void visitNull(final NullContext ctx) {
        final Metadata.ExpressionMetadata nullemd = metadata.getExpressionMetadata(ctx);

        execute.visitInsn(Opcodes.ACONST_NULL);
        checkWriteCast(nullemd);
        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitExternal(final ExternalContext ctx) {
        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
        visit(ctx.extstart());
        checkWriteCast(expremd);
        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitPostinc(final PostincContext ctx) {
        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
        visit(ctx.extstart());
        checkWriteCast(expremd);
        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitPreinc(final PreincContext ctx) {
        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
        visit(ctx.extstart());
        checkWriteCast(expremd);
        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitUnary(final UnaryContext ctx) {
        final Metadata.ExpressionMetadata unaryemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = unaryemd.postConst;
        final Object preConst = unaryemd.preConst;
        final Branch branch = getBranch(ctx);

        if (postConst != null) {
            if (ctx.BOOLNOT() != null) {
                if (branch == null) {
                    writeConstant(ctx, postConst);
                } else {
                    if ((boolean) postConst && branch.tru != null) {
                        execute.goTo(branch.tru);
                    } else if (!(boolean) postConst && branch.fals != null) {
                        execute.goTo(branch.fals);
                    }
                }
            } else {
                writeConstant(ctx, postConst);
                checkWriteBranch(ctx);
            }
        } else if (preConst != null) {
            if (branch == null) {
                writeConstant(ctx, preConst);
                checkWriteCast(unaryemd);
            } else {
                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
            }
        } else {
            final ExpressionContext exprctx = ctx.expression();

            if (ctx.BOOLNOT() != null) {
                final Branch local = markBranch(ctx, exprctx);

                if (branch == null) {
                    local.fals = new Label();
                    final Label aend = new Label();

                    visit(exprctx);

                    execute.push(false);
                    execute.goTo(aend);
                    execute.mark(local.fals);
                    execute.push(true);
                    execute.mark(aend);

                    checkWriteCast(unaryemd);
                } else {
                    local.tru = branch.fals;
                    local.fals = branch.tru;

                    visit(exprctx);
                }
            } else {
                final org.objectweb.asm.Type type = unaryemd.from.type;
                final Sort sort = unaryemd.from.sort;

                visit(exprctx);

                if (ctx.BWNOT() != null) {
                    if (sort == Sort.DEF) {
                        execute.invokeStatic(definition.defobjType.type, DEF_NOT_CALL);
                    } else {
                        if (sort == Sort.INT) {
                            writeConstant(ctx, -1);
                        } else if (sort == Sort.LONG) {
                            writeConstant(ctx, -1L);
                        } else {
                            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                        }

                        execute.math(GeneratorAdapter.XOR, type);
                    }
                } else if (ctx.SUB() != null) {
                    if (sort == Sort.DEF) {
                        execute.invokeStatic(definition.defobjType.type, DEF_NEG_CALL);
                    } else {
                        if (settings.getNumericOverflow()) {
                            execute.math(GeneratorAdapter.NEG, type);
                        } else {
                            if (sort == Sort.INT) {
                                execute.invokeStatic(definition.mathType.type, NEGATEEXACT_INT);
                            } else if (sort == Sort.LONG) {
                                execute.invokeStatic(definition.mathType.type, NEGATEEXACT_LONG);
                            } else {
                                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                            }
                        }
                    }
                } else if (ctx.ADD() == null) {
                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                }

                checkWriteCast(unaryemd);
                checkWriteBranch(ctx);
            }
        }

        return null;
    }

    @Override
    public Void visitCast(final CastContext ctx) {
        final Metadata.ExpressionMetadata castemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = castemd.postConst;

        if (postConst == null) {
            visit(ctx.expression());
            checkWriteCast(castemd);
        } else {
            writeConstant(ctx, postConst);
        }

        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitBinary(final BinaryContext ctx) {
        final Metadata.ExpressionMetadata binaryemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = binaryemd.postConst;
        final Object preConst = binaryemd.preConst;
        final Branch branch = getBranch(ctx);

        if (postConst != null) {
            writeConstant(ctx, postConst);
        } else if (preConst != null) {
            if (branch == null) {
                writeConstant(ctx, preConst);
                checkWriteCast(binaryemd);
            } else {
                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
            }
        } else if (binaryemd.from.sort == Sort.STRING) {
            final boolean marked = strings.contains(ctx);

            if (!marked) {
                writeNewStrings();
            }

            final ExpressionContext exprctx0 = ctx.expression(0);
            final Metadata.ExpressionMetadata expremd0 = metadata.getExpressionMetadata(exprctx0);
            strings.add(exprctx0);
            visit(exprctx0);

            if (strings.contains(exprctx0)) {
                writeAppendStrings(expremd0.from.sort);
                strings.remove(exprctx0);
            }

            final ExpressionContext exprctx1 = ctx.expression(1);
            final Metadata.ExpressionMetadata expremd1 = metadata.getExpressionMetadata(exprctx1);
            strings.add(exprctx1);
            visit(exprctx1);

            if (strings.contains(exprctx1)) {
                writeAppendStrings(expremd1.from.sort);
                strings.remove(exprctx1);
            }

            if (marked) {
                strings.remove(ctx);
            } else {
                writeToStrings();
            }

            checkWriteCast(binaryemd);
        } else {
            final ExpressionContext exprctx0 = ctx.expression(0);
            final ExpressionContext exprctx1 = ctx.expression(1);

            visit(exprctx0);
            visit(exprctx1);

            final Type type = binaryemd.from;

            if (ctx.MUL() != null)
                writeBinaryInstruction(ctx, type, MUL);
            else if (ctx.DIV() != null)
                writeBinaryInstruction(ctx, type, DIV);
            else if (ctx.REM() != null)
                writeBinaryInstruction(ctx, type, REM);
            else if (ctx.ADD() != null)
                writeBinaryInstruction(ctx, type, ADD);
            else if (ctx.SUB() != null)
                writeBinaryInstruction(ctx, type, SUB);
            else if (ctx.LSH() != null)
                writeBinaryInstruction(ctx, type, LSH);
            else if (ctx.USH() != null)
                writeBinaryInstruction(ctx, type, USH);
            else if (ctx.RSH() != null)
                writeBinaryInstruction(ctx, type, RSH);
            else if (ctx.BWAND() != null)
                writeBinaryInstruction(ctx, type, BWAND);
            else if (ctx.BWXOR() != null)
                writeBinaryInstruction(ctx, type, BWXOR);
            else if (ctx.BWOR() != null)
                writeBinaryInstruction(ctx, type, BWOR);
            else {
                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
            }

            checkWriteCast(binaryemd);
        }

        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitComp(final CompContext ctx) {
        final Metadata.ExpressionMetadata compemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = compemd.postConst;
        final Object preConst = compemd.preConst;
        final Branch branch = getBranch(ctx);

        if (postConst != null) {
            if (branch == null) {
                writeConstant(ctx, postConst);
            } else {
                if ((boolean) postConst && branch.tru != null) {
                    execute.mark(branch.tru);
                } else if (!(boolean) postConst && branch.fals != null) {
                    execute.mark(branch.fals);
                }
            }
        } else if (preConst != null) {
            if (branch == null) {
                writeConstant(ctx, preConst);
                checkWriteCast(compemd);
            } else {
                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
            }
        } else {
            final ExpressionContext exprctx0 = ctx.expression(0);
            final Metadata.ExpressionMetadata expremd0 = metadata.getExpressionMetadata(exprctx0);

            final ExpressionContext exprctx1 = ctx.expression(1);
            final Metadata.ExpressionMetadata expremd1 = metadata.getExpressionMetadata(exprctx1);
            final org.objectweb.asm.Type type = expremd1.to.type;
            final Sort sort1 = expremd1.to.sort;

            visit(exprctx0);

            if (!expremd1.isNull) {
                visit(exprctx1);
            }

            final boolean tru = branch != null && branch.tru != null;
            final boolean fals = branch != null && branch.fals != null;
            final Label jump = tru ? branch.tru : fals ? branch.fals : new Label();
            final Label end = new Label();

            final boolean eq = (ctx.EQ() != null || ctx.EQR() != null) && (tru || !fals)
                    || (ctx.NE() != null || ctx.NER() != null) && fals;
            final boolean ne = (ctx.NE() != null || ctx.NER() != null) && (tru || !fals)
                    || (ctx.EQ() != null || ctx.EQR() != null) && fals;
            final boolean lt = ctx.LT() != null && (tru || !fals) || ctx.GTE() != null && fals;
            final boolean lte = ctx.LTE() != null && (tru || !fals) || ctx.GT() != null && fals;
            final boolean gt = ctx.GT() != null && (tru || !fals) || ctx.LTE() != null && fals;
            final boolean gte = ctx.GTE() != null && (tru || !fals) || ctx.LT() != null && fals;

            boolean writejump = true;

            switch (sort1) {
            case VOID:
            case BYTE:
            case SHORT:
            case CHAR:
                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
            case BOOL:
                if (eq)
                    execute.ifZCmp(GeneratorAdapter.EQ, jump);
                else if (ne)
                    execute.ifZCmp(GeneratorAdapter.NE, jump);
                else {
                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                }

                break;
            case INT:
            case LONG:
            case FLOAT:
            case DOUBLE:
                if (eq)
                    execute.ifCmp(type, GeneratorAdapter.EQ, jump);
                else if (ne)
                    execute.ifCmp(type, GeneratorAdapter.NE, jump);
                else if (lt)
                    execute.ifCmp(type, GeneratorAdapter.LT, jump);
                else if (lte)
                    execute.ifCmp(type, GeneratorAdapter.LE, jump);
                else if (gt)
                    execute.ifCmp(type, GeneratorAdapter.GT, jump);
                else if (gte)
                    execute.ifCmp(type, GeneratorAdapter.GE, jump);
                else {
                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                }

                break;
            case DEF:
                if (eq) {
                    if (expremd1.isNull) {
                        execute.ifNull(jump);
                    } else if (!expremd0.isNull && ctx.EQ() != null) {
                        execute.invokeStatic(definition.defobjType.type, DEF_EQ_CALL);
                    } else {
                        execute.ifCmp(type, GeneratorAdapter.EQ, jump);
                    }
                } else if (ne) {
                    if (expremd1.isNull) {
                        execute.ifNonNull(jump);
                    } else if (!expremd0.isNull && ctx.NE() != null) {
                        execute.invokeStatic(definition.defobjType.type, DEF_EQ_CALL);
                        execute.ifZCmp(GeneratorAdapter.EQ, jump);
                    } else {
                        execute.ifCmp(type, GeneratorAdapter.NE, jump);
                    }
                } else if (lt) {
                    execute.invokeStatic(definition.defobjType.type, DEF_LT_CALL);
                } else if (lte) {
                    execute.invokeStatic(definition.defobjType.type, DEF_LTE_CALL);
                } else if (gt) {
                    execute.invokeStatic(definition.defobjType.type, DEF_GT_CALL);
                } else if (gte) {
                    execute.invokeStatic(definition.defobjType.type, DEF_GTE_CALL);
                } else {
                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                }

                writejump = expremd1.isNull || ne || ctx.EQR() != null;

                if (branch != null && !writejump) {
                    execute.ifZCmp(GeneratorAdapter.NE, jump);
                }

                break;
            default:
                if (eq) {
                    if (expremd1.isNull) {
                        execute.ifNull(jump);
                    } else if (ctx.EQ() != null) {
                        execute.invokeStatic(definition.utilityType.type, CHECKEQUALS);

                        if (branch != null) {
                            execute.ifZCmp(GeneratorAdapter.NE, jump);
                        }

                        writejump = false;
                    } else {
                        execute.ifCmp(type, GeneratorAdapter.EQ, jump);
                    }
                } else if (ne) {
                    if (expremd1.isNull) {
                        execute.ifNonNull(jump);
                    } else if (ctx.NE() != null) {
                        execute.invokeStatic(definition.utilityType.type, CHECKEQUALS);
                        execute.ifZCmp(GeneratorAdapter.EQ, jump);
                    } else {
                        execute.ifCmp(type, GeneratorAdapter.NE, jump);
                    }
                } else {
                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                }
            }

            if (branch == null) {
                if (writejump) {
                    execute.push(false);
                    execute.goTo(end);
                    execute.mark(jump);
                    execute.push(true);
                    execute.mark(end);
                }

                checkWriteCast(compemd);
            }
        }

        return null;
    }

    @Override
    public Void visitBool(final BoolContext ctx) {
        final Metadata.ExpressionMetadata boolemd = metadata.getExpressionMetadata(ctx);
        final Object postConst = boolemd.postConst;
        final Object preConst = boolemd.preConst;
        final Branch branch = getBranch(ctx);

        if (postConst != null) {
            if (branch == null) {
                writeConstant(ctx, postConst);
            } else {
                if ((boolean) postConst && branch.tru != null) {
                    execute.mark(branch.tru);
                } else if (!(boolean) postConst && branch.fals != null) {
                    execute.mark(branch.fals);
                }
            }
        } else if (preConst != null) {
            if (branch == null) {
                writeConstant(ctx, preConst);
                checkWriteCast(boolemd);
            } else {
                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
            }
        } else {
            final ExpressionContext exprctx0 = ctx.expression(0);
            final ExpressionContext exprctx1 = ctx.expression(1);

            if (branch == null) {
                if (ctx.BOOLAND() != null) {
                    final Branch local = markBranch(ctx, exprctx0, exprctx1);
                    local.fals = new Label();
                    final Label end = new Label();

                    visit(exprctx0);
                    visit(exprctx1);

                    execute.push(true);
                    execute.goTo(end);
                    execute.mark(local.fals);
                    execute.push(false);
                    execute.mark(end);
                } else if (ctx.BOOLOR() != null) {
                    final Branch branch0 = markBranch(ctx, exprctx0);
                    branch0.tru = new Label();
                    final Branch branch1 = markBranch(ctx, exprctx1);
                    branch1.fals = new Label();
                    final Label aend = new Label();

                    visit(exprctx0);
                    visit(exprctx1);

                    execute.mark(branch0.tru);
                    execute.push(true);
                    execute.goTo(aend);
                    execute.mark(branch1.fals);
                    execute.push(false);
                    execute.mark(aend);
                } else {
                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                }

                checkWriteCast(boolemd);
            } else {
                if (ctx.BOOLAND() != null) {
                    final Branch branch0 = markBranch(ctx, exprctx0);
                    branch0.fals = branch.fals == null ? new Label() : branch.fals;
                    final Branch branch1 = markBranch(ctx, exprctx1);
                    branch1.tru = branch.tru;
                    branch1.fals = branch.fals;

                    visit(exprctx0);
                    visit(exprctx1);

                    if (branch.fals == null) {
                        execute.mark(branch0.fals);
                    }
                } else if (ctx.BOOLOR() != null) {
                    final Branch branch0 = markBranch(ctx, exprctx0);
                    branch0.tru = branch.tru == null ? new Label() : branch.tru;
                    final Branch branch1 = markBranch(ctx, exprctx1);
                    branch1.tru = branch.tru;
                    branch1.fals = branch.fals;

                    visit(exprctx0);
                    visit(exprctx1);

                    if (branch.tru == null) {
                        execute.mark(branch0.tru);
                    }
                } else {
                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
                }
            }
        }

        return null;
    }

    @Override
    public Void visitConditional(final ConditionalContext ctx) {
        final Metadata.ExpressionMetadata condemd = metadata.getExpressionMetadata(ctx);
        final Branch branch = getBranch(ctx);

        final ExpressionContext expr0 = ctx.expression(0);
        final ExpressionContext expr1 = ctx.expression(1);
        final ExpressionContext expr2 = ctx.expression(2);

        final Branch local = markBranch(ctx, expr0);
        local.fals = new Label();
        local.end = new Label();

        if (branch != null) {
            copyBranch(branch, expr1, expr2);
        }

        visit(expr0);
        visit(expr1);
        execute.goTo(local.end);
        execute.mark(local.fals);
        visit(expr2);
        execute.mark(local.end);

        if (branch == null) {
            checkWriteCast(condemd);
        }

        return null;
    }

    @Override
    public Void visitAssignment(final AssignmentContext ctx) {
        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
        visit(ctx.extstart());
        checkWriteCast(expremd);
        checkWriteBranch(ctx);

        return null;
    }

    @Override
    public Void visitExtstart(ExtstartContext ctx) {
        final Metadata.ExternalMetadata startemd = metadata.getExternalMetadata(ctx);

        if (startemd.token == ADD) {
            final Metadata.ExpressionMetadata storeemd = metadata.getExpressionMetadata(startemd.storeExpr);

            if (startemd.current.sort == Sort.STRING || storeemd.from.sort == Sort.STRING) {
                writeNewStrings();
                strings.add(startemd.storeExpr);
            }
        }

        final ExtprecContext precctx = ctx.extprec();
        final ExtcastContext castctx = ctx.extcast();
        final ExttypeContext typectx = ctx.exttype();
        final ExtvarContext varctx = ctx.extvar();
        final ExtnewContext newctx = ctx.extnew();
        final ExtstringContext stringctx = ctx.extstring();

        if (precctx != null) {
            visit(precctx);
        } else if (castctx != null) {
            visit(castctx);
        } else if (typectx != null) {
            visit(typectx);
        } else if (varctx != null) {
            visit(varctx);
        } else if (newctx != null) {
            visit(newctx);
        } else if (stringctx != null) {
            visit(stringctx);
        } else {
            throw new IllegalStateException();
        }

        return null;
    }

    @Override
    public Void visitExtprec(final ExtprecContext ctx) {
        final ExtprecContext precctx = ctx.extprec();
        final ExtcastContext castctx = ctx.extcast();
        final ExttypeContext typectx = ctx.exttype();
        final ExtvarContext varctx = ctx.extvar();
        final ExtnewContext newctx = ctx.extnew();
        final ExtstringContext stringctx = ctx.extstring();

        if (precctx != null) {
            visit(precctx);
        } else if (castctx != null) {
            visit(castctx);
        } else if (typectx != null) {
            visit(typectx);
        } else if (varctx != null) {
            visit(varctx);
        } else if (newctx != null) {
            visit(newctx);
        } else if (stringctx != null) {
            visit(stringctx);
        } else {
            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
        }

        final ExtdotContext dotctx = ctx.extdot();
        final ExtbraceContext bracectx = ctx.extbrace();

        if (dotctx != null) {
            visit(dotctx);
        } else if (bracectx != null) {
            visit(bracectx);
        }

        return null;
    }

    @Override
    public Void visitExtcast(final ExtcastContext ctx) {
        Metadata.ExtNodeMetadata castenmd = metadata.getExtNodeMetadata(ctx);

        final ExtprecContext precctx = ctx.extprec();
        final ExtcastContext castctx = ctx.extcast();
        final ExttypeContext typectx = ctx.exttype();
        final ExtvarContext varctx = ctx.extvar();
        final ExtnewContext newctx = ctx.extnew();
        final ExtstringContext stringctx = ctx.extstring();

        if (precctx != null) {
            visit(precctx);
        } else if (castctx != null) {
            visit(castctx);
        } else if (typectx != null) {
            visit(typectx);
        } else if (varctx != null) {
            visit(varctx);
        } else if (newctx != null) {
            visit(newctx);
        } else if (stringctx != null) {
            visit(stringctx);
        } else {
            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected writer state.");
        }

        checkWriteCast(ctx, castenmd.castTo);

        return null;
    }

    @Override
    public Void visitExtbrace(final ExtbraceContext ctx) {
        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());

        visit(exprctx);
        writeLoadStoreExternal(ctx);

        final ExtdotContext dotctx = ctx.extdot();
        final ExtbraceContext bracectx = ctx.extbrace();

        if (dotctx != null) {
            visit(dotctx);
        } else if (bracectx != null) {
            visit(bracectx);
        }

        return null;
    }

    @Override
    public Void visitExtdot(final ExtdotContext ctx) {
        final ExtcallContext callctx = ctx.extcall();
        final ExtfieldContext fieldctx = ctx.extfield();

        if (callctx != null) {
            visit(callctx);
        } else if (fieldctx != null) {
            visit(fieldctx);
        }

        return null;
    }

    @Override
    public Void visitExttype(final ExttypeContext ctx) {
        visit(ctx.extdot());

        return null;
    }

    @Override
    public Void visitExtcall(final ExtcallContext ctx) {
        writeCallExternal(ctx);

        final ExtdotContext dotctx = ctx.extdot();
        final ExtbraceContext bracectx = ctx.extbrace();

        if (dotctx != null) {
            visit(dotctx);
        } else if (bracectx != null) {
            visit(bracectx);
        }

        return null;
    }

    @Override
    public Void visitExtvar(final ExtvarContext ctx) {
        writeLoadStoreExternal(ctx);

        final ExtdotContext dotctx = ctx.extdot();
        final ExtbraceContext bracectx = ctx.extbrace();

        if (dotctx != null) {
            visit(dotctx);
        } else if (bracectx != null) {
            visit(bracectx);
        }

        return null;
    }

    @Override
    public Void visitExtfield(final ExtfieldContext ctx) {
        writeLoadStoreExternal(ctx);

        final ExtdotContext dotctx = ctx.extdot();
        final ExtbraceContext bracectx = ctx.extbrace();

        if (dotctx != null) {
            visit(dotctx);
        } else if (bracectx != null) {
            visit(bracectx);
        }

        return null;
    }

    @Override
    public Void visitExtnew(ExtnewContext ctx) {
        writeNewExternal(ctx);

        final ExtdotContext dotctx = ctx.extdot();
        final ExtbraceContext bracectx = ctx.extbrace();

        if (dotctx != null) {
            visit(dotctx);
        } else if (bracectx != null) {
            visit(bracectx);
        }

        return null;
    }

    @Override
    public Void visitExtstring(ExtstringContext ctx) {
        final Metadata.ExtNodeMetadata stringenmd = metadata.getExtNodeMetadata(ctx);

        writeConstant(ctx, stringenmd.target);

        final ExtdotContext dotctx = ctx.extdot();
        final ExtbraceContext bracectx = ctx.extbrace();

        if (dotctx != null) {
            visit(dotctx);
        } else if (bracectx != null) {
            visit(bracectx);
        }

        return null;
    }

    @Override
    public Void visitArguments(final ArgumentsContext ctx) {
        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected writer state.");
    }

    @Override
    public Void visitIncrement(IncrementContext ctx) {
        final Metadata.ExpressionMetadata incremd = metadata.getExpressionMetadata(ctx);
        final Object postConst = incremd.postConst;

        if (postConst == null) {
            writeNumeric(ctx, incremd.preConst);
            checkWriteCast(incremd);
        } else {
            writeConstant(ctx, postConst);
        }

        checkWriteBranch(ctx);

        return null;
    }

    private void writeLoopCounter(final int count) {
        final Label end = new Label();

        execute.iinc(metadata.loopCounterSlot, -count);
        execute.visitVarInsn(Opcodes.ILOAD, metadata.loopCounterSlot);
        execute.push(0);
        execute.ifICmp(GeneratorAdapter.GT, end);
        execute.throwException(PAINLESS_ERROR_TYPE,
                "The maximum number of statements that can be executed in a loop has been reached.");
        execute.mark(end);
    }

    private void writeConstant(final ParserRuleContext source, final Object constant) {
        if (constant instanceof Number) {
            writeNumeric(source, constant);
        } else if (constant instanceof Character) {
            writeNumeric(source, (int) (char) constant);
        } else if (constant instanceof String) {
            writeString(source, constant);
        } else if (constant instanceof Boolean) {
            writeBoolean(source, constant);
        } else if (constant != null) {
            throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
        }
    }

    private void writeNumeric(final ParserRuleContext source, final Object numeric) {
        if (numeric instanceof Double) {
            execute.push((double) numeric);
        } else if (numeric instanceof Float) {
            execute.push((float) numeric);
        } else if (numeric instanceof Long) {
            execute.push((long) numeric);
        } else if (numeric instanceof Number) {
            execute.push(((Number) numeric).intValue());
        } else {
            throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
        }
    }

    private void writeString(final ParserRuleContext source, final Object string) {
        if (string instanceof String) {
            execute.push((String) string);
        } else {
            throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
        }
    }

    private void writeBoolean(final ParserRuleContext source, final Object bool) {
        if (bool instanceof Boolean) {
            execute.push((boolean) bool);
        } else {
            throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
        }
    }

    private void writeNewStrings() {
        execute.newInstance(STRINGBUILDER_TYPE);
        execute.dup();
        execute.invokeConstructor(STRINGBUILDER_TYPE, STRINGBUILDER_CONSTRUCTOR);
    }

    private void writeAppendStrings(final Sort sort) {
        switch (sort) {
        case BOOL:
            execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_BOOLEAN);
            break;
        case CHAR:
            execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_CHAR);
            break;
        case BYTE:
        case SHORT:
        case INT:
            execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_INT);
            break;
        case LONG:
            execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_LONG);
            break;
        case FLOAT:
            execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_FLOAT);
            break;
        case DOUBLE:
            execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_DOUBLE);
            break;
        case STRING:
            execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_STRING);
            break;
        default:
            execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_OBJECT);
        }
    }

    private void writeToStrings() {
        execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_TOSTRING);
    }

    private void writeBinaryInstruction(final ParserRuleContext source, final Type type, final int token) {
        final Sort sort = type.sort;
        final boolean exact = !settings.getNumericOverflow() && ((sort == Sort.INT || sort == Sort.LONG)
                && (token == MUL || token == DIV || token == ADD || token == SUB)
                || (sort == Sort.FLOAT || sort == Sort.DOUBLE)
                        && (token == MUL || token == DIV || token == REM || token == ADD || token == SUB));

        // if its a 64-bit shift, fixup the lastSource argument to truncate to 32-bits
        // note unlike java, this means we still do binary promotion of shifts,
        // but it keeps things simple -- this check works because we promote shifts.
        if (sort == Sort.LONG && (token == LSH || token == USH || token == RSH)) {
            execute.cast(org.objectweb.asm.Type.LONG_TYPE, org.objectweb.asm.Type.INT_TYPE);
        }

        if (exact) {
            switch (sort) {
            case INT:
                switch (token) {
                case MUL:
                    execute.invokeStatic(definition.mathType.type, MULEXACT_INT);
                    break;
                case DIV:
                    execute.invokeStatic(definition.utilityType.type, DIVWOOVERLOW_INT);
                    break;
                case ADD:
                    execute.invokeStatic(definition.mathType.type, ADDEXACT_INT);
                    break;
                case SUB:
                    execute.invokeStatic(definition.mathType.type, SUBEXACT_INT);
                    break;
                default:
                    throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
                }

                break;
            case LONG:
                switch (token) {
                case MUL:
                    execute.invokeStatic(definition.mathType.type, MULEXACT_LONG);
                    break;
                case DIV:
                    execute.invokeStatic(definition.utilityType.type, DIVWOOVERLOW_LONG);
                    break;
                case ADD:
                    execute.invokeStatic(definition.mathType.type, ADDEXACT_LONG);
                    break;
                case SUB:
                    execute.invokeStatic(definition.mathType.type, SUBEXACT_LONG);
                    break;
                default:
                    throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
                }

                break;
            case FLOAT:
                switch (token) {
                case MUL:
                    execute.invokeStatic(definition.utilityType.type, MULWOOVERLOW_FLOAT);
                    break;
                case DIV:
                    execute.invokeStatic(definition.utilityType.type, DIVWOOVERLOW_FLOAT);
                    break;
                case REM:
                    execute.invokeStatic(definition.utilityType.type, REMWOOVERLOW_FLOAT);
                    break;
                case ADD:
                    execute.invokeStatic(definition.utilityType.type, ADDWOOVERLOW_FLOAT);
                    break;
                case SUB:
                    execute.invokeStatic(definition.utilityType.type, SUBWOOVERLOW_FLOAT);
                    break;
                default:
                    throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
                }

                break;
            case DOUBLE:
                switch (token) {
                case MUL:
                    execute.invokeStatic(definition.utilityType.type, MULWOOVERLOW_DOUBLE);
                    break;
                case DIV:
                    execute.invokeStatic(definition.utilityType.type, DIVWOOVERLOW_DOUBLE);
                    break;
                case REM:
                    execute.invokeStatic(definition.utilityType.type, REMWOOVERLOW_DOUBLE);
                    break;
                case ADD:
                    execute.invokeStatic(definition.utilityType.type, ADDWOOVERLOW_DOUBLE);
                    break;
                case SUB:
                    execute.invokeStatic(definition.utilityType.type, SUBWOOVERLOW_DOUBLE);
                    break;
                default:
                    throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
                }

                break;
            default:
                throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
            }
        } else {
            if ((sort == Sort.FLOAT || sort == Sort.DOUBLE) && (token == LSH || token == USH || token == RSH
                    || token == BWAND || token == BWXOR || token == BWOR)) {
                throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
            }

            if (sort == Sort.DEF) {
                switch (token) {
                case MUL:
                    execute.invokeStatic(definition.defobjType.type, DEF_MUL_CALL);
                    break;
                case DIV:
                    execute.invokeStatic(definition.defobjType.type, DEF_DIV_CALL);
                    break;
                case REM:
                    execute.invokeStatic(definition.defobjType.type, DEF_REM_CALL);
                    break;
                case ADD:
                    execute.invokeStatic(definition.defobjType.type, DEF_ADD_CALL);
                    break;
                case SUB:
                    execute.invokeStatic(definition.defobjType.type, DEF_SUB_CALL);
                    break;
                case LSH:
                    execute.invokeStatic(definition.defobjType.type, DEF_LSH_CALL);
                    break;
                case USH:
                    execute.invokeStatic(definition.defobjType.type, DEF_RSH_CALL);
                    break;
                case RSH:
                    execute.invokeStatic(definition.defobjType.type, DEF_USH_CALL);
                    break;
                case BWAND:
                    execute.invokeStatic(definition.defobjType.type, DEF_AND_CALL);
                    break;
                case BWXOR:
                    execute.invokeStatic(definition.defobjType.type, DEF_XOR_CALL);
                    break;
                case BWOR:
                    execute.invokeStatic(definition.defobjType.type, DEF_OR_CALL);
                    break;
                default:
                    throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
                }
            } else {
                switch (token) {
                case MUL:
                    execute.math(GeneratorAdapter.MUL, type.type);
                    break;
                case DIV:
                    execute.math(GeneratorAdapter.DIV, type.type);
                    break;
                case REM:
                    execute.math(GeneratorAdapter.REM, type.type);
                    break;
                case ADD:
                    execute.math(GeneratorAdapter.ADD, type.type);
                    break;
                case SUB:
                    execute.math(GeneratorAdapter.SUB, type.type);
                    break;
                case LSH:
                    execute.math(GeneratorAdapter.SHL, type.type);
                    break;
                case USH:
                    execute.math(GeneratorAdapter.USHR, type.type);
                    break;
                case RSH:
                    execute.math(GeneratorAdapter.SHR, type.type);
                    break;
                case BWAND:
                    execute.math(GeneratorAdapter.AND, type.type);
                    break;
                case BWXOR:
                    execute.math(GeneratorAdapter.XOR, type.type);
                    break;
                case BWOR:
                    execute.math(GeneratorAdapter.OR, type.type);
                    break;
                default:
                    throw new IllegalStateException(Metadata.error(source) + "Unexpected writer state.");
                }
            }
        }
    }

    /**
     * Called for any compound assignment (including increment/decrement instructions).
     * We have to be stricter than writeBinary, and do overflow checks against the original type's size
     * instead of the promoted type's size, since the result will be implicitly cast back.
     *
     * @return true if an instruction is written, false otherwise
     */
    private boolean writeExactInstruction(final Sort osort, final Sort psort) {
        if (psort == Sort.DOUBLE) {
            if (osort == Sort.FLOAT) {
                execute.invokeStatic(definition.utilityType.type, TOFLOATWOOVERFLOW_DOUBLE);
            } else if (osort == Sort.FLOAT_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOFLOATWOOVERFLOW_DOUBLE);
                execute.checkCast(definition.floatobjType.type);
            } else if (osort == Sort.LONG) {
                execute.invokeStatic(definition.utilityType.type, TOLONGWOOVERFLOW_DOUBLE);
            } else if (osort == Sort.LONG_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOLONGWOOVERFLOW_DOUBLE);
                execute.checkCast(definition.longobjType.type);
            } else if (osort == Sort.INT) {
                execute.invokeStatic(definition.utilityType.type, TOINTWOOVERFLOW_DOUBLE);
            } else if (osort == Sort.INT_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOINTWOOVERFLOW_DOUBLE);
                execute.checkCast(definition.intobjType.type);
            } else if (osort == Sort.CHAR) {
                execute.invokeStatic(definition.utilityType.type, TOCHARWOOVERFLOW_DOUBLE);
            } else if (osort == Sort.CHAR_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOCHARWOOVERFLOW_DOUBLE);
                execute.checkCast(definition.charobjType.type);
            } else if (osort == Sort.SHORT) {
                execute.invokeStatic(definition.utilityType.type, TOSHORTWOOVERFLOW_DOUBLE);
            } else if (osort == Sort.SHORT_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOSHORTWOOVERFLOW_DOUBLE);
                execute.checkCast(definition.shortobjType.type);
            } else if (osort == Sort.BYTE) {
                execute.invokeStatic(definition.utilityType.type, TOBYTEWOOVERFLOW_DOUBLE);
            } else if (osort == Sort.BYTE_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOBYTEWOOVERFLOW_DOUBLE);
                execute.checkCast(definition.byteobjType.type);
            } else {
                return false;
            }
        } else if (psort == Sort.FLOAT) {
            if (osort == Sort.LONG) {
                execute.invokeStatic(definition.utilityType.type, TOLONGWOOVERFLOW_FLOAT);
            } else if (osort == Sort.LONG_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOLONGWOOVERFLOW_FLOAT);
                execute.checkCast(definition.longobjType.type);
            } else if (osort == Sort.INT) {
                execute.invokeStatic(definition.utilityType.type, TOINTWOOVERFLOW_FLOAT);
            } else if (osort == Sort.INT_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOINTWOOVERFLOW_FLOAT);
                execute.checkCast(definition.intobjType.type);
            } else if (osort == Sort.CHAR) {
                execute.invokeStatic(definition.utilityType.type, TOCHARWOOVERFLOW_FLOAT);
            } else if (osort == Sort.CHAR_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOCHARWOOVERFLOW_FLOAT);
                execute.checkCast(definition.charobjType.type);
            } else if (osort == Sort.SHORT) {
                execute.invokeStatic(definition.utilityType.type, TOSHORTWOOVERFLOW_FLOAT);
            } else if (osort == Sort.SHORT_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOSHORTWOOVERFLOW_FLOAT);
                execute.checkCast(definition.shortobjType.type);
            } else if (osort == Sort.BYTE) {
                execute.invokeStatic(definition.utilityType.type, TOBYTEWOOVERFLOW_FLOAT);
            } else if (osort == Sort.BYTE_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOBYTEWOOVERFLOW_FLOAT);
                execute.checkCast(definition.byteobjType.type);
            } else {
                return false;
            }
        } else if (psort == Sort.LONG) {
            if (osort == Sort.INT) {
                execute.invokeStatic(definition.mathType.type, TOINTEXACT_LONG);
            } else if (osort == Sort.INT_OBJ) {
                execute.invokeStatic(definition.mathType.type, TOINTEXACT_LONG);
                execute.checkCast(definition.intobjType.type);
            } else if (osort == Sort.CHAR) {
                execute.invokeStatic(definition.utilityType.type, TOCHAREXACT_LONG);
            } else if (osort == Sort.CHAR_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOCHAREXACT_LONG);
                execute.checkCast(definition.charobjType.type);
            } else if (osort == Sort.SHORT) {
                execute.invokeStatic(definition.utilityType.type, TOSHORTEXACT_LONG);
            } else if (osort == Sort.SHORT_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOSHORTEXACT_LONG);
                execute.checkCast(definition.shortobjType.type);
            } else if (osort == Sort.BYTE) {
                execute.invokeStatic(definition.utilityType.type, TOBYTEEXACT_LONG);
            } else if (osort == Sort.BYTE_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOBYTEEXACT_LONG);
                execute.checkCast(definition.byteobjType.type);
            } else {
                return false;
            }
        } else if (psort == Sort.INT) {
            if (osort == Sort.CHAR) {
                execute.invokeStatic(definition.utilityType.type, TOCHAREXACT_INT);
            } else if (osort == Sort.CHAR_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOCHAREXACT_INT);
                execute.checkCast(definition.charobjType.type);
            } else if (osort == Sort.SHORT) {
                execute.invokeStatic(definition.utilityType.type, TOSHORTEXACT_INT);
            } else if (osort == Sort.SHORT_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOSHORTEXACT_INT);
                execute.checkCast(definition.shortobjType.type);
            } else if (osort == Sort.BYTE) {
                execute.invokeStatic(definition.utilityType.type, TOBYTEEXACT_INT);
            } else if (osort == Sort.BYTE_OBJ) {
                execute.invokeStatic(definition.utilityType.type, TOBYTEEXACT_INT);
                execute.checkCast(definition.byteobjType.type);
            } else {
                return false;
            }
        } else {
            return false;
        }

        return true;
    }

    private void writeLoadStoreExternal(final ParserRuleContext source) {
        final Metadata.ExtNodeMetadata sourceenmd = metadata.getExtNodeMetadata(source);
        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(sourceenmd.parent);

        final boolean length = "#length".equals(sourceenmd.target);
        final boolean array = "#brace".equals(sourceenmd.target);
        final boolean name = sourceenmd.target instanceof String && !length && !array;
        final boolean variable = sourceenmd.target instanceof Integer;
        final boolean field = sourceenmd.target instanceof Field;
        final boolean shortcut = sourceenmd.target instanceof Object[];

        if (!length && !variable && !field && !array && !name && !shortcut) {
            throw new IllegalStateException(Metadata.error(source) + "Target not found for load/store.");
        }

        final boolean maplist = shortcut && (boolean) ((Object[]) sourceenmd.target)[2];
        final Object constant = shortcut ? ((Object[]) sourceenmd.target)[3] : null;

        final boolean x1 = field || name || (shortcut && !maplist);
        final boolean x2 = array || (shortcut && maplist);

        if (length) {
            execute.arrayLength();
        } else if (sourceenmd.last && parentemd.storeExpr != null) {
            final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(parentemd.storeExpr);
            final boolean cat = strings.contains(parentemd.storeExpr);

            if (cat) {
                if (field || name || shortcut) {
                    execute.dupX1();
                } else if (array) {
                    execute.dup2X1();
                }

                if (maplist) {
                    if (constant != null) {
                        writeConstant(source, constant);
                    }

                    execute.dupX2();
                }

                writeLoadStoreInstruction(source, false, variable, field, name, array, shortcut);
                writeAppendStrings(sourceenmd.type.sort);
                visit(parentemd.storeExpr);

                if (strings.contains(parentemd.storeExpr)) {
                    writeAppendStrings(expremd.to.sort);
                    strings.remove(parentemd.storeExpr);
                }

                writeToStrings();
                checkWriteCast(source, sourceenmd.castTo);

                if (parentemd.read) {
                    writeDup(sourceenmd.type.sort.size, x1, x2);
                }

                writeLoadStoreInstruction(source, true, variable, field, name, array, shortcut);
            } else if (parentemd.token > 0) {
                final int token = parentemd.token;

                if (field || name || shortcut) {
                    execute.dup();
                } else if (array) {
                    execute.dup2();
                }

                if (maplist) {
                    if (constant != null) {
                        writeConstant(source, constant);
                    }

                    execute.dupX1();
                }

                writeLoadStoreInstruction(source, false, variable, field, name, array, shortcut);

                if (parentemd.read && parentemd.post) {
                    writeDup(sourceenmd.type.sort.size, x1, x2);
                }

                checkWriteCast(source, sourceenmd.castFrom);
                visit(parentemd.storeExpr);

                writeBinaryInstruction(source, sourceenmd.promote, token);

                boolean exact = false;

                if (!settings.getNumericOverflow() && expremd.typesafe && sourceenmd.type.sort != Sort.DEF
                        && (token == MUL || token == DIV || token == REM || token == ADD || token == SUB)) {
                    exact = writeExactInstruction(sourceenmd.type.sort, sourceenmd.promote.sort);
                }

                if (!exact) {
                    checkWriteCast(source, sourceenmd.castTo);
                }

                if (parentemd.read && !parentemd.post) {
                    writeDup(sourceenmd.type.sort.size, x1, x2);
                }

                writeLoadStoreInstruction(source, true, variable, field, name, array, shortcut);
            } else {
                if (constant != null) {
                    writeConstant(source, constant);
                }

                visit(parentemd.storeExpr);

                if (parentemd.read) {
                    writeDup(sourceenmd.type.sort.size, x1, x2);
                }

                writeLoadStoreInstruction(source, true, variable, field, name, array, shortcut);
            }
        } else {
            if (constant != null) {
                writeConstant(source, constant);
            }

            writeLoadStoreInstruction(source, false, variable, field, name, array, shortcut);
        }
    }

    private void writeLoadStoreInstruction(final ParserRuleContext source, final boolean store,
            final boolean variable, final boolean field, final boolean name, final boolean array,
            final boolean shortcut) {
        final Metadata.ExtNodeMetadata sourceemd = metadata.getExtNodeMetadata(source);

        if (variable) {
            writeLoadStoreVariable(source, store, sourceemd.type, (int) sourceemd.target);
        } else if (field) {
            writeLoadStoreField(store, (Field) sourceemd.target);
        } else if (name) {
            writeLoadStoreField(source, store, (String) sourceemd.target);
        } else if (array) {
            writeLoadStoreArray(source, store, sourceemd.type);
        } else if (shortcut) {
            Object[] targets = (Object[]) sourceemd.target;
            writeLoadStoreShortcut(store, (Method) targets[0], (Method) targets[1]);
        } else {
            throw new IllegalStateException(
                    Metadata.error(source) + "Load/Store requires a variable, field, or array.");
        }
    }

    private void writeLoadStoreVariable(final ParserRuleContext source, final boolean store, final Type type,
            final int slot) {
        if (type.sort == Sort.VOID) {
            throw new IllegalStateException(Metadata.error(source) + "Cannot load/store void type.");
        }

        if (store) {
            execute.visitVarInsn(type.type.getOpcode(Opcodes.ISTORE), slot);
        } else {
            execute.visitVarInsn(type.type.getOpcode(Opcodes.ILOAD), slot);
        }
    }

    private void writeLoadStoreField(final boolean store, final Field field) {
        if (java.lang.reflect.Modifier.isStatic(field.reflect.getModifiers())) {
            if (store) {
                execute.putStatic(field.owner.type, field.reflect.getName(), field.type.type);
            } else {
                execute.getStatic(field.owner.type, field.reflect.getName(), field.type.type);

                if (!field.generic.clazz.equals(field.type.clazz)) {
                    execute.checkCast(field.generic.type);
                }
            }
        } else {
            if (store) {
                execute.putField(field.owner.type, field.reflect.getName(), field.type.type);
            } else {
                execute.getField(field.owner.type, field.reflect.getName(), field.type.type);

                if (!field.generic.clazz.equals(field.type.clazz)) {
                    execute.checkCast(field.generic.type);
                }
            }
        }
    }

    private void writeLoadStoreField(final ParserRuleContext source, final boolean store, final String name) {
        if (store) {
            final Metadata.ExtNodeMetadata sourceemd = metadata.getExtNodeMetadata(source);
            final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(sourceemd.parent);
            final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(parentemd.storeExpr);

            execute.push(name);
            execute.loadThis();
            execute.getField(CLASS_TYPE, "definition", DEFINITION_TYPE);
            execute.push(parentemd.token == 0 && expremd.typesafe);
            execute.invokeStatic(definition.defobjType.type, DEF_FIELD_STORE);
        } else {
            execute.push(name);
            execute.loadThis();
            execute.getField(CLASS_TYPE, "definition", DEFINITION_TYPE);
            execute.invokeStatic(definition.defobjType.type, DEF_FIELD_LOAD);
        }
    }

    private void writeLoadStoreArray(final ParserRuleContext source, final boolean store, final Type type) {
        if (type.sort == Sort.VOID) {
            throw new IllegalStateException(Metadata.error(source) + "Cannot load/store void type.");
        }

        if (type.sort == Sort.DEF) {
            final ExtbraceContext bracectx = (ExtbraceContext) source;
            final Metadata.ExpressionMetadata expremd0 = metadata.getExpressionMetadata(bracectx.expression());

            if (store) {
                final Metadata.ExtNodeMetadata braceenmd = metadata.getExtNodeMetadata(bracectx);
                final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(braceenmd.parent);
                final Metadata.ExpressionMetadata expremd1 = metadata.getExpressionMetadata(parentemd.storeExpr);

                execute.loadThis();
                execute.getField(CLASS_TYPE, "definition", DEFINITION_TYPE);
                execute.push(expremd0.typesafe);
                execute.push(parentemd.token == 0 && expremd1.typesafe);
                execute.invokeStatic(definition.defobjType.type, DEF_ARRAY_STORE);
            } else {
                execute.loadThis();
                execute.getField(CLASS_TYPE, "definition", DEFINITION_TYPE);
                execute.push(expremd0.typesafe);
                execute.invokeStatic(definition.defobjType.type, DEF_ARRAY_LOAD);
            }
        } else {
            if (store) {
                execute.arrayStore(type.type);
            } else {
                execute.arrayLoad(type.type);
            }
        }
    }

    private void writeLoadStoreShortcut(final boolean store, final Method getter, final Method setter) {
        final Method method = store ? setter : getter;

        if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
            execute.invokeInterface(method.owner.type, method.method);
        } else {
            execute.invokeVirtual(method.owner.type, method.method);
        }

        if (store) {
            writePop(method.rtn.type.getSize());
        } else if (!method.rtn.clazz.equals(method.handle.type().returnType())) {
            execute.checkCast(method.rtn.type);
        }
    }

    private void writeDup(final int size, final boolean x1, final boolean x2) {
        if (size == 1) {
            if (x2) {
                execute.dupX2();
            } else if (x1) {
                execute.dupX1();
            } else {
                execute.dup();
            }
        } else if (size == 2) {
            if (x2) {
                execute.dup2X2();
            } else if (x1) {
                execute.dup2X1();
            } else {
                execute.dup2();
            }
        }
    }

    private void writeNewExternal(final ExtnewContext source) {
        final Metadata.ExtNodeMetadata sourceenmd = metadata.getExtNodeMetadata(source);
        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(sourceenmd.parent);

        final boolean makearray = "#makearray".equals(sourceenmd.target);
        final boolean constructor = sourceenmd.target instanceof Constructor;

        if (!makearray && !constructor) {
            throw new IllegalStateException(Metadata.error(source) + "Target not found for new call.");
        }

        if (makearray) {
            for (final ExpressionContext exprctx : source.expression()) {
                visit(exprctx);
            }

            if (sourceenmd.type.sort == Sort.ARRAY) {
                execute.visitMultiANewArrayInsn(sourceenmd.type.type.getDescriptor(),
                        sourceenmd.type.type.getDimensions());
            } else {
                execute.newArray(sourceenmd.type.type);
            }
        } else {
            execute.newInstance(sourceenmd.type.type);

            if (parentemd.read) {
                execute.dup();
            }

            for (final ExpressionContext exprctx : source.arguments().expression()) {
                visit(exprctx);
            }

            final Constructor target = (Constructor) sourceenmd.target;
            execute.invokeConstructor(target.owner.type, target.method);
        }
    }

    private void writeCallExternal(final ExtcallContext source) {
        final Metadata.ExtNodeMetadata sourceenmd = metadata.getExtNodeMetadata(source);

        final boolean method = sourceenmd.target instanceof Method;
        final boolean def = sourceenmd.target instanceof String;

        if (!method && !def) {
            throw new IllegalStateException(Metadata.error(source) + "Target not found for call.");
        }

        final List<ExpressionContext> arguments = source.arguments().expression();

        if (method) {
            for (final ExpressionContext exprctx : arguments) {
                visit(exprctx);
            }

            final Method target = (Method) sourceenmd.target;

            if (java.lang.reflect.Modifier.isStatic(target.reflect.getModifiers())) {
                execute.invokeStatic(target.owner.type, target.method);
            } else if (java.lang.reflect.Modifier.isInterface(target.owner.clazz.getModifiers())) {
                execute.invokeInterface(target.owner.type, target.method);
            } else {
                execute.invokeVirtual(target.owner.type, target.method);
            }

            if (!target.rtn.clazz.equals(target.handle.type().returnType())) {
                execute.checkCast(target.rtn.type);
            }
        } else {
            execute.push((String) sourceenmd.target);
            execute.loadThis();
            execute.getField(CLASS_TYPE, "definition", DEFINITION_TYPE);

            execute.push(arguments.size());
            execute.newArray(definition.defType.type);

            for (int argument = 0; argument < arguments.size(); ++argument) {
                execute.dup();
                execute.push(argument);
                visit(arguments.get(argument));
                execute.arrayStore(definition.defType.type);
            }

            execute.push(arguments.size());
            execute.newArray(definition.booleanType.type);

            for (int argument = 0; argument < arguments.size(); ++argument) {
                execute.dup();
                execute.push(argument);
                execute.push(metadata.getExpressionMetadata(arguments.get(argument)).typesafe);
                execute.arrayStore(definition.booleanType.type);
            }

            execute.invokeStatic(definition.defobjType.type, DEF_METHOD_CALL);
        }
    }

    private void writePop(final int size) {
        if (size == 1) {
            execute.pop();
        } else if (size == 2) {
            execute.pop2();
        }
    }

    private void checkWriteCast(final Metadata.ExpressionMetadata sort) {
        checkWriteCast(sort.source, sort.cast);
    }

    private void checkWriteCast(final ParserRuleContext source, final Cast cast) {
        if (cast instanceof Transform) {
            writeTransform((Transform) cast);
        } else if (cast != null) {
            writeCast(cast);
        } else {
            throw new IllegalStateException(Metadata.error(source) + "Unexpected cast object.");
        }
    }

    private void writeCast(final Cast cast) {
        final Type from = cast.from;
        final Type to = cast.to;

        if (from.equals(to)) {
            return;
        }

        if (from.sort.numeric && from.sort.primitive && to.sort.numeric && to.sort.primitive) {
            execute.cast(from.type, to.type);
        } else {
            try {
                from.clazz.asSubclass(to.clazz);
            } catch (ClassCastException exception) {
                execute.checkCast(to.type);
            }
        }
    }

    private void writeTransform(final Transform transform) {
        if (transform.upcast != null) {
            execute.checkCast(transform.upcast.type);
        }

        if (java.lang.reflect.Modifier.isStatic(transform.method.reflect.getModifiers())) {
            execute.invokeStatic(transform.method.owner.type, transform.method.method);
        } else if (java.lang.reflect.Modifier.isInterface(transform.method.owner.clazz.getModifiers())) {
            execute.invokeInterface(transform.method.owner.type, transform.method.method);
        } else {
            execute.invokeVirtual(transform.method.owner.type, transform.method.method);
        }

        if (transform.downcast != null) {
            execute.checkCast(transform.downcast.type);
        }
    }

    void checkWriteBranch(final ParserRuleContext source) {
        final Branch branch = getBranch(source);

        if (branch != null) {
            if (branch.tru != null) {
                execute.visitJumpInsn(Opcodes.IFNE, branch.tru);
            } else if (branch.fals != null) {
                execute.visitJumpInsn(Opcodes.IFEQ, branch.fals);
            }
        }
    }

    private void writeEnd() {
        writer.visitEnd();
    }

    private byte[] getBytes() {
        return writer.toByteArray();
    }
}