Bläddra i källkod

Split up Analyzer and Writer into multiple pieces.

Closes #17158
Jack Conradson 9 år sedan
förälder
incheckning
800c844ebd
16 ändrade filer med 5886 tillägg och 4781 borttagningar
  1. 78 2783
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Analyzer.java
  2. 563 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java
  3. 868 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerExpression.java
  4. 816 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerExternal.java
  5. 281 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerPromoter.java
  6. 581 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerStatement.java
  7. 144 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerUtility.java
  8. 1 1
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java
  9. 4 53
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Metadata.java
  10. 95 1944
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Writer.java
  11. 86 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterCaster.java
  12. 138 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java
  13. 684 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterExpression.java
  14. 769 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterExternal.java
  15. 391 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterStatement.java
  16. 387 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterUtility.java

+ 78 - 2783
modules/lang-painless/src/main/java/org/elasticsearch/painless/Analyzer.java

@@ -19,21 +19,10 @@
 
 package org.elasticsearch.painless;
 
-import org.antlr.v4.runtime.ParserRuleContext;
-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.Pair;
-import org.elasticsearch.painless.Definition.Sort;
-import org.elasticsearch.painless.Definition.Struct;
-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;
@@ -47,8 +36,8 @@ 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;
@@ -75,7 +64,6 @@ 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;
@@ -83,3077 +71,384 @@ import org.elasticsearch.painless.PainlessParser.TryContext;
 import org.elasticsearch.painless.PainlessParser.UnaryContext;
 import org.elasticsearch.painless.PainlessParser.WhileContext;
 
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-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 Analyzer extends PainlessParserBaseVisitor<Void> {
-    private static class Variable {
-        final String name;
-        final Type type;
-        final int slot;
-
-        private Variable(final String name, final Type type, final int slot) {
-            this.name = name;
-            this.type = type;
-            this.slot = slot;
-        }
-    }
-
     static void analyze(final Metadata metadata) {
         new Analyzer(metadata);
     }
 
-    private final Metadata metadata;
-    private final Definition definition;
-    private final CompilerSettings settings;
-
-    private final Deque<Integer> scopes = new ArrayDeque<>();
-    private final Deque<Variable> variables = new ArrayDeque<>();
+    private final AnalyzerStatement statement;
+    private final AnalyzerExpression expression;
+    private final AnalyzerExternal external;
 
     private Analyzer(final Metadata metadata) {
-        this.metadata = metadata;
-        definition = metadata.definition;
-        settings = metadata.settings;
+        final Definition definition = metadata.definition;
 
-        incrementScope();
-        addVariable(null, "#this", definition.execType);
-        metadata.inputValueSlot = addVariable(null, "input", definition.smapType).slot;
-        metadata.scoreValueSlot = addVariable(null, "_score", definition.floatType).slot;
-        metadata.loopCounterSlot = addVariable(null, "#loop", definition.intType).slot;
-
-        metadata.createStatementMetadata(metadata.root);
-        visit(metadata.root);
+        final AnalyzerUtility utility = new AnalyzerUtility();
+        final AnalyzerCaster caster = new AnalyzerCaster(definition);
+        final AnalyzerPromoter promoter = new AnalyzerPromoter(definition);
 
-        decrementScope();
-    }
-
-    void incrementScope() {
-        scopes.push(0);
-    }
-
-    void decrementScope() {
-        int remove = scopes.pop();
-
-        while (remove > 0) {
-            variables.pop();
-            --remove;
-        }
-    }
+        statement = new AnalyzerStatement(metadata, this, utility, caster);
+        expression = new AnalyzerExpression(metadata, this, caster, promoter);
+        external = new AnalyzerExternal(metadata, this, utility, caster, promoter);
 
-    Variable getVariable(final String name) {
-        final Iterator<Variable> itr = variables.iterator();
+        utility.incrementScope();
+        utility.addVariable(null, "#this", definition.execType);
+        metadata.inputValueSlot = utility.addVariable(null, "input", definition.smapType).slot;
+        metadata.scoreValueSlot = utility.addVariable(null, "_score", definition.floatType).slot;
+        metadata.loopCounterSlot = utility.addVariable(null, "#loop", definition.intType).slot;
 
-        while (itr.hasNext()) {
-            final Variable variable = itr.next();
-
-            if (variable.name.equals(name)) {
-                return variable;
-            }
-        }
-
-        return null;
-    }
-
-    Variable addVariable(final ParserRuleContext source, final String name, final Type type) {
-        if (getVariable(name) != null) {
-            if (source == null) {
-                throw new IllegalArgumentException("Argument name [" + name + "] already defined within the scope.");
-            } else {
-                throw new IllegalArgumentException(
-                    Metadata.error(source) + "Variable name [" + name + "] already defined within the scope.");
-            }
-        }
-
-        final Variable previous = variables.peekFirst();
-        int slot = 0;
-
-        if (previous != null) {
-            slot += previous.slot + previous.type.type.getSize();
-        }
-
-        final Variable variable = new Variable(name, type, slot);
-        variables.push(variable);
-
-        final int update = scopes.pop() + 1;
-        scopes.push(update);
+        metadata.createStatementMetadata(metadata.root);
+        visit(metadata.root);
 
-        return variable;
+        utility.decrementScope();
     }
 
     @Override
     public Void visitSource(final SourceContext ctx) {
-        final Metadata.StatementMetadata sourcesmd = metadata.getStatementMetadata(ctx);
-        final List<StatementContext> statectxs = ctx.statement();
-        final StatementContext lastctx = statectxs.get(statectxs.size() - 1);
-
-        incrementScope();
-
-        for (final StatementContext statectx : statectxs) {
-            if (sourcesmd.allLast) {
-                throw new IllegalArgumentException(Metadata.error(statectx) +
-                    "Statement will never be executed because all prior paths escape.");
-            }
-
-            final Metadata.StatementMetadata statesmd = metadata.createStatementMetadata(statectx);
-            statesmd.lastSource = statectx == lastctx;
-            visit(statectx);
-
-            sourcesmd.methodEscape = statesmd.methodEscape;
-            sourcesmd.allLast = statesmd.allLast;
-        }
-
-        decrementScope();
+        statement.processSource(ctx);
 
         return null;
     }
 
     @Override
     public Void visitIf(final IfContext ctx) {
-        final Metadata.StatementMetadata ifsmd = metadata.getStatementMetadata(ctx);
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-        expremd.to = definition.booleanType;
-        visit(exprctx);
-        markCast(expremd);
-
-        if (expremd.postConst != null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "If statement is not necessary.");
-        }
-
-        final BlockContext blockctx0 = ctx.block(0);
-        final Metadata.StatementMetadata blocksmd0 = metadata.createStatementMetadata(blockctx0);
-        blocksmd0.lastSource = ifsmd.lastSource;
-        blocksmd0.inLoop = ifsmd.inLoop;
-        blocksmd0.lastLoop = ifsmd.lastLoop;
-        incrementScope();
-        visit(blockctx0);
-        decrementScope();
-
-        ifsmd.anyContinue = blocksmd0.anyContinue;
-        ifsmd.anyBreak = blocksmd0.anyBreak;
-
-        ifsmd.count = blocksmd0.count;
-
-        if (ctx.ELSE() != null) {
-            final BlockContext blockctx1 = ctx.block(1);
-            final Metadata.StatementMetadata blocksmd1 = metadata.createStatementMetadata(blockctx1);
-            blocksmd1.lastSource = ifsmd.lastSource;
-            incrementScope();
-            visit(blockctx1);
-            decrementScope();
-
-            ifsmd.methodEscape = blocksmd0.methodEscape && blocksmd1.methodEscape;
-            ifsmd.loopEscape = blocksmd0.loopEscape && blocksmd1.loopEscape;
-            ifsmd.allLast = blocksmd0.allLast && blocksmd1.allLast;
-            ifsmd.anyContinue |= blocksmd1.anyContinue;
-            ifsmd.anyBreak |= blocksmd1.anyBreak;
-
-            ifsmd.count = Math.max(ifsmd.count, blocksmd1.count);
-        }
+        statement.processIf(ctx);
 
         return null;
     }
 
     @Override
     public Void visitWhile(final WhileContext ctx) {
-        final Metadata.StatementMetadata whilesmd = metadata.getStatementMetadata(ctx);
-
-        incrementScope();
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-        expremd.to = definition.booleanType;
-        visit(exprctx);
-        markCast(expremd);
-
-        boolean continuous = false;
-
-        if (expremd.postConst != null) {
-            continuous = (boolean)expremd.postConst;
-
-            if (!continuous) {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "The loop will never be executed.");
-            }
-
-            if (ctx.empty() != null) {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "The loop will never exit.");
-            }
-        }
-
-        final BlockContext blockctx = ctx.block();
-
-        if (blockctx != null) {
-            final Metadata.StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
-            blocksmd.beginLoop = true;
-            blocksmd.inLoop = true;
-            visit(blockctx);
-
-            if (blocksmd.loopEscape && !blocksmd.anyContinue) {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "All paths escape so the loop is not necessary.");
-            }
-
-            if (continuous && !blocksmd.anyBreak) {
-                whilesmd.methodEscape = true;
-                whilesmd.allLast = true;
-            }
-        }
-
-        whilesmd.count = 1;
-
-        decrementScope();
+        statement.processWhile(ctx);
 
         return null;
     }
 
     @Override
     public Void visitDo(final DoContext ctx) {
-        final Metadata.StatementMetadata dosmd = metadata.getStatementMetadata(ctx);
-
-        incrementScope();
-
-        final BlockContext blockctx = ctx.block();
-        final Metadata.StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
-        blocksmd.beginLoop = true;
-        blocksmd.inLoop = true;
-        visit(blockctx);
-
-        if (blocksmd.loopEscape && !blocksmd.anyContinue) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "All paths escape so the loop is not necessary.");
-        }
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-        expremd.to = definition.booleanType;
-        visit(exprctx);
-        markCast(expremd);
-
-        if (expremd.postConst != null) {
-            final boolean continuous = (boolean)expremd.postConst;
-
-            if (!continuous) {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "All paths escape so the loop is not necessary.");
-            }
-
-            if (!blocksmd.anyBreak) {
-                dosmd.methodEscape = true;
-                dosmd.allLast = true;
-            }
-        }
-
-        dosmd.count = 1;
-
-        decrementScope();
+        statement.processDo(ctx);
 
         return null;
     }
 
     @Override
     public Void visitFor(final ForContext ctx) {
-        final Metadata.StatementMetadata forsmd = metadata.getStatementMetadata(ctx);
-        boolean continuous = false;
-
-        incrementScope();
-
-        final InitializerContext initctx = ctx.initializer();
-
-        if (initctx != null) {
-            metadata.createStatementMetadata(initctx);
-            visit(initctx);
-        }
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-
-        if (exprctx != null) {
-            final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-            expremd.to = definition.booleanType;
-            visit(exprctx);
-            markCast(expremd);
-
-            if (expremd.postConst != null) {
-                continuous = (boolean)expremd.postConst;
-
-                if (!continuous) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "The loop will never be executed.");
-                }
-
-                if (ctx.empty() != null) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "The loop is continuous.");
-                }
-            }
-        } else {
-            continuous = true;
-        }
-
-        final AfterthoughtContext atctx = ctx.afterthought();
-
-        if (atctx != null) {
-            metadata.createStatementMetadata(atctx);
-            visit(atctx);
-        }
-
-        final BlockContext blockctx = ctx.block();
-
-        if (blockctx != null) {
-            final Metadata.StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
-            blocksmd.beginLoop = true;
-            blocksmd.inLoop = true;
-            visit(blockctx);
-
-            if (blocksmd.loopEscape && !blocksmd.anyContinue) {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "All paths escape so the loop is not necessary.");
-            }
-
-            if (continuous && !blocksmd.anyBreak) {
-                forsmd.methodEscape = true;
-                forsmd.allLast = true;
-            }
-        }
-
-        forsmd.count = 1;
-
-        decrementScope();
+        statement.processFor(ctx);
 
         return null;
     }
 
     @Override
     public Void visitDecl(final DeclContext ctx) {
-        final Metadata.StatementMetadata declsmd = metadata.getStatementMetadata(ctx);
-
-        final DeclarationContext declctx = ctx.declaration();
-        metadata.createStatementMetadata(declctx);
-        visit(declctx);
-
-        declsmd.count = 1;
+        statement.processDecl(ctx);
 
         return null;
     }
 
     @Override
     public Void visitContinue(final ContinueContext ctx) {
-        final Metadata.StatementMetadata continuesmd = metadata.getStatementMetadata(ctx);
-
-        if (!continuesmd.inLoop) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Cannot have a continue statement outside of a loop.");
-        }
-
-        if (continuesmd.lastLoop) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unnessary continue statement at the end of a loop.");
-        }
-
-        continuesmd.allLast = true;
-        continuesmd.anyContinue = true;
-
-        continuesmd.count = 1;
+        statement.processContinue(ctx);
 
         return null;
     }
 
     @Override
     public Void visitBreak(final BreakContext ctx) {
-        final Metadata.StatementMetadata breaksmd = metadata.getStatementMetadata(ctx);
-
-        if (!breaksmd.inLoop) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Cannot have a break statement outside of a loop.");
-        }
-
-        breaksmd.loopEscape = true;
-        breaksmd.allLast = true;
-        breaksmd.anyBreak = true;
-
-        breaksmd.count = 1;
+        statement.processBreak(ctx);
 
         return null;
     }
 
     @Override
     public Void visitReturn(final ReturnContext ctx) {
-        final Metadata.StatementMetadata returnsmd = metadata.getStatementMetadata(ctx);
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-        expremd.to = definition.objectType;
-        visit(exprctx);
-        markCast(expremd);
-
-        returnsmd.methodEscape = true;
-        returnsmd.loopEscape = true;
-        returnsmd.allLast = true;
-
-        returnsmd.count = 1;
+        statement.processReturn(ctx);
 
         return null;
     }
 
     @Override
     public Void visitTry(final TryContext ctx) {
-        final Metadata.StatementMetadata trysmd = metadata.getStatementMetadata(ctx);
-
-        final BlockContext blockctx = ctx.block();
-        final Metadata.StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
-        blocksmd.lastSource = trysmd.lastSource;
-        blocksmd.inLoop = trysmd.inLoop;
-        blocksmd.lastLoop = trysmd.lastLoop;
-        incrementScope();
-        visit(blockctx);
-        decrementScope();
-
-        trysmd.methodEscape = blocksmd.methodEscape;
-        trysmd.loopEscape = blocksmd.loopEscape;
-        trysmd.allLast = blocksmd.allLast;
-        trysmd.anyContinue = blocksmd.anyContinue;
-        trysmd.anyBreak = blocksmd.anyBreak;
-
-        int trapcount = 0;
-
-        for (final TrapContext trapctx : ctx.trap()) {
-            final Metadata.StatementMetadata trapsmd = metadata.createStatementMetadata(trapctx);
-            trapsmd.lastSource = trysmd.lastSource;
-            trapsmd.inLoop = trysmd.inLoop;
-            trapsmd.lastLoop = trysmd.lastLoop;
-            incrementScope();
-            visit(trapctx);
-            decrementScope();
-
-            trysmd.methodEscape &= trapsmd.methodEscape;
-            trysmd.loopEscape &= trapsmd.loopEscape;
-            trysmd.allLast &= trapsmd.allLast;
-            trysmd.anyContinue |= trapsmd.anyContinue;
-            trysmd.anyBreak |= trapsmd.anyBreak;
-
-            trapcount = Math.max(trapcount, trapsmd.count);
-        }
-
-        trysmd.count = blocksmd.count + trapcount;
+        statement.processTry(ctx);
 
         return null;
     }
 
     @Override
     public Void visitThrow(final ThrowContext ctx) {
-        final Metadata.StatementMetadata throwsmd = metadata.getStatementMetadata(ctx);
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-        expremd.to = definition.exceptionType;
-        visit(exprctx);
-        markCast(expremd);
-
-        throwsmd.methodEscape = true;
-        throwsmd.loopEscape = true;
-        throwsmd.allLast = true;
-
-        throwsmd.count = 1;
+        statement.processThrow(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExpr(final ExprContext ctx) {
-        final Metadata.StatementMetadata exprsmd = metadata.getStatementMetadata(ctx);
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-        expremd.read = exprsmd.lastSource;
-        visit(exprctx);
-
-        if (!expremd.statement && !exprsmd.lastSource) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Not a statement.");
-        }
-
-        final boolean rtn = exprsmd.lastSource && expremd.from.sort != Sort.VOID;
-        exprsmd.methodEscape = rtn;
-        exprsmd.loopEscape = rtn;
-        exprsmd.allLast = rtn;
-        expremd.to = rtn ? definition.objectType : expremd.from;
-        markCast(expremd);
-
-        exprsmd.count = 1;
+        statement.processExpr(ctx);
 
         return null;
     }
 
     @Override
     public Void visitMultiple(final MultipleContext ctx) {
-        final Metadata.StatementMetadata multiplesmd = metadata.getStatementMetadata(ctx);
-        final List<StatementContext> statectxs = ctx.statement();
-        final StatementContext lastctx = statectxs.get(statectxs.size() - 1);
-
-        for (StatementContext statectx : statectxs) {
-            if (multiplesmd.allLast) {
-                throw new IllegalArgumentException(Metadata.error(statectx) +
-                    "Statement will never be executed because all prior paths escape.");
-            }
-
-            final Metadata.StatementMetadata statesmd = metadata.createStatementMetadata(statectx);
-            statesmd.lastSource = multiplesmd.lastSource && statectx == lastctx;
-            statesmd.inLoop = multiplesmd.inLoop;
-            statesmd.lastLoop = (multiplesmd.beginLoop || multiplesmd.lastLoop) && statectx == lastctx;
-            visit(statectx);
-
-            multiplesmd.methodEscape = statesmd.methodEscape;
-            multiplesmd.loopEscape = statesmd.loopEscape;
-            multiplesmd.allLast = statesmd.allLast;
-            multiplesmd.anyContinue |= statesmd.anyContinue;
-            multiplesmd.anyBreak |= statesmd.anyBreak;
-
-            multiplesmd.count += statesmd.count;
-        }
+        statement.processMultiple(ctx);
 
         return null;
     }
 
     @Override
     public Void visitSingle(final SingleContext ctx) {
-        final Metadata.StatementMetadata singlesmd = metadata.getStatementMetadata(ctx);
-
-        final StatementContext statectx = ctx.statement();
-        final Metadata.StatementMetadata statesmd = metadata.createStatementMetadata(statectx);
-        statesmd.lastSource = singlesmd.lastSource;
-        statesmd.inLoop = singlesmd.inLoop;
-        statesmd.lastLoop = singlesmd.beginLoop || singlesmd.lastLoop;
-        visit(statectx);
-
-        singlesmd.methodEscape = statesmd.methodEscape;
-        singlesmd.loopEscape = statesmd.loopEscape;
-        singlesmd.allLast = statesmd.allLast;
-        singlesmd.anyContinue = statesmd.anyContinue;
-        singlesmd.anyBreak = statesmd.anyBreak;
-
-        singlesmd.count = statesmd.count;
+        statement.processSingle(ctx);
 
         return null;
     }
 
     @Override
     public Void visitEmpty(final EmptyContext ctx) {
-        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected parser state.");
+        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
     }
 
     @Override
-    public Void visitInitializer(InitializerContext ctx) {
-        final DeclarationContext declctx = ctx.declaration();
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-
-        if (declctx != null) {
-            metadata.createStatementMetadata(declctx);
-            visit(declctx);
-        } else if (exprctx != null) {
-            final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-            expremd.read = false;
-            visit(exprctx);
-
-            expremd.to = expremd.from;
-            markCast(expremd);
-
-            if (!expremd.statement) {
-                throw new IllegalArgumentException(Metadata.error(exprctx) +
-                    "The intializer of a for loop must be a statement.");
-            }
-        } else {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
-
-        return null;
+    public Void visitEmptyscope(final EmptyscopeContext ctx) {
+        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
     }
 
     @Override
-    public Void visitAfterthought(AfterthoughtContext ctx) {
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
+    public Void visitInitializer(final InitializerContext ctx) {
+        statement.processInitializer(ctx);
 
-        if (exprctx != null) {
-            final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-            expremd.read = false;
-            visit(exprctx);
-
-            expremd.to = expremd.from;
-            markCast(expremd);
+        return null;
+    }
 
-            if (!expremd.statement) {
-                throw new IllegalArgumentException(Metadata.error(exprctx) +
-                    "The afterthought of a for loop must be a statement.");
-            }
-        }
+    @Override
+    public Void visitAfterthought(final AfterthoughtContext ctx) {
+        statement.processAfterthought(ctx);
 
         return null;
     }
 
     @Override
     public Void visitDeclaration(final DeclarationContext ctx) {
-        final DecltypeContext decltypectx = ctx.decltype();
-        final Metadata.ExpressionMetadata decltypeemd = metadata.createExpressionMetadata(decltypectx);
-        visit(decltypectx);
-
-        for (final DeclvarContext declvarctx : ctx.declvar()) {
-            final Metadata.ExpressionMetadata declvaremd = metadata.createExpressionMetadata(declvarctx);
-            declvaremd.to = decltypeemd.from;
-            visit(declvarctx);
-        }
+        statement.processDeclaration(ctx);
 
         return null;
     }
 
     @Override
     public Void visitDecltype(final DecltypeContext ctx) {
-        final Metadata.ExpressionMetadata decltypeemd = metadata.getExpressionMetadata(ctx);
-
-        final String name = ctx.getText();
-        decltypeemd.from = definition.getType(name);
+        statement.processDecltype(ctx);
 
         return null;
     }
 
     @Override
     public Void visitDeclvar(final DeclvarContext ctx) {
-        final Metadata.ExpressionMetadata declvaremd = metadata.getExpressionMetadata(ctx);
-
-        final String name = ctx.ID().getText();
-        declvaremd.postConst = addVariable(ctx, name, declvaremd.to).slot;
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-
-        if (exprctx != null) {
-            final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-            expremd.to = declvaremd.to;
-            visit(exprctx);
-            markCast(expremd);
-        }
+        statement.processDeclvar(ctx);
 
         return null;
     }
 
     @Override
     public Void visitTrap(final TrapContext ctx) {
-        final Metadata.StatementMetadata trapsmd = metadata.getStatementMetadata(ctx);
-
-        final String type = ctx.TYPE().getText();
-        trapsmd.exception = definition.getType(type);
-
-        try {
-            trapsmd.exception.clazz.asSubclass(Exception.class);
-        } catch (final ClassCastException exception) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Invalid exception type [" + trapsmd.exception.name + "].");
-        }
-
-        final String id = ctx.ID().getText();
-        trapsmd.slot = addVariable(ctx, id, trapsmd.exception).slot;
-
-        final BlockContext blockctx = ctx.block();
-
-        if (blockctx != null) {
-            final Metadata.StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
-            blocksmd.lastSource = trapsmd.lastSource;
-            blocksmd.inLoop = trapsmd.inLoop;
-            blocksmd.lastLoop = trapsmd.lastLoop;
-            visit(blockctx);
-
-            trapsmd.methodEscape = blocksmd.methodEscape;
-            trapsmd.loopEscape = blocksmd.loopEscape;
-            trapsmd.allLast = blocksmd.allLast;
-            trapsmd.anyContinue = blocksmd.anyContinue;
-            trapsmd.anyBreak = blocksmd.anyBreak;
-        } else if (ctx.emptyscope() == null) {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
+        statement.processTrap(ctx);
 
         return null;
     }
 
     @Override
     public Void visitPrecedence(final PrecedenceContext ctx) {
-        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected parser state.");
+        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
     }
 
     @Override
     public Void visitNumeric(final NumericContext ctx) {
-        final Metadata.ExpressionMetadata numericemd = metadata.getExpressionMetadata(ctx);
-        final boolean negate = ctx.parent instanceof UnaryContext && ((UnaryContext)ctx.parent).SUB() != null;
-
-        if (ctx.DECIMAL() != null) {
-            final String svalue = (negate ? "-" : "") + ctx.DECIMAL().getText();
-
-            if (svalue.endsWith("f") || svalue.endsWith("F")) {
-                try {
-                    numericemd.from = definition.floatType;
-                    numericemd.preConst = Float.parseFloat(svalue.substring(0, svalue.length() - 1));
-                } catch (NumberFormatException exception) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Invalid float constant [" + svalue + "].");
-                }
-            } else {
-                try {
-                    numericemd.from = definition.doubleType;
-                    numericemd.preConst = Double.parseDouble(svalue);
-                } catch (NumberFormatException exception) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Invalid double constant [" + svalue + "].");
-                }
-            }
-        } else {
-            String svalue = negate ? "-" : "";
-            int radix;
-
-            if (ctx.OCTAL() != null) {
-                svalue += ctx.OCTAL().getText();
-                radix = 8;
-            } else if (ctx.INTEGER() != null) {
-                svalue += ctx.INTEGER().getText();
-                radix = 10;
-            } else if (ctx.HEX() != null) {
-                svalue += ctx.HEX().getText();
-                radix = 16;
-            } else {
-                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-            }
-
-            if (svalue.endsWith("d") || svalue.endsWith("D")) {
-                try {
-                    numericemd.from = definition.doubleType;
-                    numericemd.preConst = Double.parseDouble(svalue.substring(0, svalue.length() - 1));
-                } catch (NumberFormatException exception) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Invalid float constant [" + svalue + "].");
-                }
-            } else if (svalue.endsWith("f") || svalue.endsWith("F")) {
-                try {
-                    numericemd.from = definition.floatType;
-                    numericemd.preConst = Float.parseFloat(svalue.substring(0, svalue.length() - 1));
-                } catch (NumberFormatException exception) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Invalid float constant [" + svalue + "].");
-                }
-            } else if (svalue.endsWith("l") || svalue.endsWith("L")) {
-                try {
-                    numericemd.from = definition.longType;
-                    numericemd.preConst = Long.parseLong(svalue.substring(0, svalue.length() - 1), radix);
-                } catch (NumberFormatException exception) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Invalid long constant [" + svalue + "].");
-                }
-            } else {
-                try {
-                    final Type type = numericemd.to;
-                    final Sort sort = type == null ? Sort.INT : type.sort;
-                    final int value = Integer.parseInt(svalue, radix);
-
-                    if (sort == Sort.BYTE && value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
-                        numericemd.from = definition.byteType;
-                        numericemd.preConst = (byte)value;
-                    } else if (sort == Sort.CHAR && value >= Character.MIN_VALUE && value <= Character.MAX_VALUE) {
-                        numericemd.from = definition.charType;
-                        numericemd.preConst = (char)value;
-                    } else if (sort == Sort.SHORT && value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
-                        numericemd.from = definition.shortType;
-                        numericemd.preConst = (short)value;
-                    } else {
-                        numericemd.from = definition.intType;
-                        numericemd.preConst = value;
-                    }
-                } catch (NumberFormatException exception) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Invalid int constant [" + svalue + "].");
-                }
-            }
-        }
+        expression.processNumeric(ctx);
 
         return null;
     }
 
     @Override
     public Void visitChar(final CharContext ctx) {
-        final Metadata.ExpressionMetadata charemd = metadata.getExpressionMetadata(ctx);
-
-        if (ctx.CHAR() == null) {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
-
-        charemd.preConst = ctx.CHAR().getText().charAt(0);
-        charemd.from = definition.charType;
+        expression.processChar(ctx);
 
         return null;
     }
 
     @Override
     public Void visitTrue(final TrueContext ctx) {
-        final Metadata.ExpressionMetadata trueemd = metadata.getExpressionMetadata(ctx);
-
-        if (ctx.TRUE() == null) {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
-
-        trueemd.preConst = true;
-        trueemd.from = definition.booleanType;
+        expression.processTrue(ctx);
 
         return null;
     }
 
     @Override
     public Void visitFalse(final FalseContext ctx) {
-        final Metadata.ExpressionMetadata falseemd = metadata.getExpressionMetadata(ctx);
-
-        if (ctx.FALSE() == null) {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
-
-        falseemd.preConst = false;
-        falseemd.from = definition.booleanType;
+        expression.processFalse(ctx);
 
         return null;
     }
 
     @Override
     public Void visitNull(final NullContext ctx) {
-        final Metadata.ExpressionMetadata nullemd = metadata.getExpressionMetadata(ctx);
-
-        if (ctx.NULL() == null) {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
-
-        nullemd.isNull = true;
-
-        if (nullemd.to != null) {
-            if (nullemd.to.sort.primitive) {
-                throw new IllegalArgumentException("Cannot cast null to a primitive type [" + nullemd.to.name + "].");
-            }
-
-            nullemd.from = nullemd.to;
-        } else {
-            nullemd.from = definition.objectType;
-        }
+        expression.processNull(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExternal(final ExternalContext ctx) {
-        final Metadata.ExpressionMetadata extemd = metadata.getExpressionMetadata(ctx);
-
-        final ExtstartContext extstartctx = ctx.extstart();
-        final Metadata.ExternalMetadata extstartemd = metadata.createExternalMetadata(extstartctx);
-        extstartemd.read = extemd.read;
-        visit(extstartctx);
-
-        extemd.statement = extstartemd.statement;
-        extemd.preConst = extstartemd.constant;
-        extemd.from = extstartemd.current;
-        extemd.typesafe = extstartemd.current.sort != Sort.DEF;
+        expression.processExternal(ctx);
 
         return null;
     }
 
     @Override
     public Void visitPostinc(final PostincContext ctx) {
-        final Metadata.ExpressionMetadata postincemd = metadata.getExpressionMetadata(ctx);
-
-        final ExtstartContext extstartctx = ctx.extstart();
-        final Metadata.ExternalMetadata extstartemd = metadata.createExternalMetadata(extstartctx);
-        extstartemd.read = postincemd.read;
-        extstartemd.storeExpr = ctx.increment();
-        extstartemd.token = ADD;
-        extstartemd.post = true;
-        visit(extstartctx);
-
-        postincemd.statement = true;
-        postincemd.from = extstartemd.read ? extstartemd.current : definition.voidType;
-        postincemd.typesafe = extstartemd.current.sort != Sort.DEF;
+        expression.processPostinc(ctx);
 
         return null;
     }
 
     @Override
     public Void visitPreinc(final PreincContext ctx) {
-        final Metadata.ExpressionMetadata preincemd = metadata.getExpressionMetadata(ctx);
-
-        final ExtstartContext extstartctx = ctx.extstart();
-        final Metadata.ExternalMetadata extstartemd = metadata.createExternalMetadata(extstartctx);
-        extstartemd.read = preincemd.read;
-        extstartemd.storeExpr = ctx.increment();
-        extstartemd.token = ADD;
-        extstartemd.pre = true;
-        visit(extstartctx);
-
-        preincemd.statement = true;
-        preincemd.from = extstartemd.read ? extstartemd.current : definition.voidType;
-        preincemd.typesafe = extstartemd.current.sort != Sort.DEF;
+        expression.processPreinc(ctx);
 
         return null;
     }
 
     @Override
     public Void visitUnary(final UnaryContext ctx) {
-        final Metadata.ExpressionMetadata unaryemd = metadata.getExpressionMetadata(ctx);
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-
-        if (ctx.BOOLNOT() != null) {
-            expremd.to = definition.booleanType;
-            visit(exprctx);
-            markCast(expremd);
-
-            if (expremd.postConst != null) {
-                unaryemd.preConst = !(boolean)expremd.postConst;
-            }
-
-            unaryemd.from = definition.booleanType;
-        } else if (ctx.BWNOT() != null || ctx.ADD() != null || ctx.SUB() != null) {
-            visit(exprctx);
-
-            final Type promote = promoteNumeric(expremd.from, ctx.BWNOT() == null, true);
-
-            if (promote == null) {
-                throw new ClassCastException("Cannot apply [" + ctx.getChild(0).getText() + "] " +
-                    "operation to type [" + expremd.from.name + "].");
-            }
-
-            expremd.to = promote;
-            markCast(expremd);
-
-            if (expremd.postConst != null) {
-                final Sort sort = promote.sort;
-
-                if (ctx.BWNOT() != null) {
-                    if (sort == Sort.INT) {
-                        unaryemd.preConst = ~(int)expremd.postConst;
-                    } else if (sort == Sort.LONG) {
-                        unaryemd.preConst = ~(long)expremd.postConst;
-                    } else {
-                        throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                    }
-                } else if (ctx.SUB() != null) {
-                    if (exprctx instanceof NumericContext) {
-                        unaryemd.preConst = expremd.postConst;
-                    } else {
-                        if (sort == Sort.INT) {
-                            if (settings.getNumericOverflow()) {
-                                unaryemd.preConst = -(int)expremd.postConst;
-                            } else {
-                                unaryemd.preConst = Math.negateExact((int)expremd.postConst);
-                            }
-                        } else if (sort == Sort.LONG) {
-                            if (settings.getNumericOverflow()) {
-                                unaryemd.preConst = -(long)expremd.postConst;
-                            } else {
-                                unaryemd.preConst = Math.negateExact((long)expremd.postConst);
-                            }
-                        } else if (sort == Sort.FLOAT) {
-                            unaryemd.preConst = -(float)expremd.postConst;
-                        } else if (sort == Sort.DOUBLE) {
-                            unaryemd.preConst = -(double)expremd.postConst;
-                        } else {
-                            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                        }
-                    }
-                } else if (ctx.ADD() != null) {
-                    if (sort == Sort.INT) {
-                        unaryemd.preConst = +(int)expremd.postConst;
-                    } else if (sort == Sort.LONG) {
-                        unaryemd.preConst = +(long)expremd.postConst;
-                    } else if (sort == Sort.FLOAT) {
-                        unaryemd.preConst = +(float)expremd.postConst;
-                    } else if (sort == Sort.DOUBLE) {
-                        unaryemd.preConst = +(double)expremd.postConst;
-                    } else {
-                        throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                    }
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            }
-
-            unaryemd.from = promote;
-            unaryemd.typesafe = expremd.typesafe;
-        } else {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
+        expression.processUnary(ctx);
 
         return null;
     }
 
     @Override
     public Void visitCast(final CastContext ctx) {
-        final Metadata.ExpressionMetadata castemd = metadata.getExpressionMetadata(ctx);
-
-        final DecltypeContext decltypectx = ctx.decltype();
-        final Metadata.ExpressionMetadata decltypemd = metadata.createExpressionMetadata(decltypectx);
-        visit(decltypectx);
-
-        final Type type = decltypemd.from;
-        castemd.from = type;
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-        expremd.to = type;
-        expremd.explicit = true;
-        visit(exprctx);
-        markCast(expremd);
-
-        if (expremd.postConst != null) {
-            castemd.preConst = expremd.postConst;
-        }
-
-        castemd.typesafe = expremd.typesafe && castemd.from.sort != Sort.DEF;
+        expression.processCast(ctx);
 
         return null;
     }
 
     @Override
     public Void visitBinary(final BinaryContext ctx) {
-        final Metadata.ExpressionMetadata binaryemd = metadata.getExpressionMetadata(ctx);
-
-        final ExpressionContext exprctx0 = metadata.updateExpressionTree(ctx.expression(0));
-        final Metadata.ExpressionMetadata expremd0 = metadata.createExpressionMetadata(exprctx0);
-        visit(exprctx0);
-
-        final ExpressionContext exprctx1 = metadata.updateExpressionTree(ctx.expression(1));
-        final Metadata.ExpressionMetadata expremd1 = metadata.createExpressionMetadata(exprctx1);
-        visit(exprctx1);
-
-        final boolean decimal = ctx.MUL() != null || ctx.DIV() != null || ctx.REM() != null || ctx.SUB() != null;
-        final boolean add = ctx.ADD() != null;
-        final boolean xor = ctx.BWXOR() != null;
-        final Type promote = add ? promoteAdd(expremd0.from, expremd1.from) :
-            xor ? promoteXor(expremd0.from, expremd1.from) :
-                promoteNumeric(expremd0.from, expremd1.from, decimal, true);
-
-        if (promote == null) {
-            throw new ClassCastException("Cannot apply [" + ctx.getChild(1).getText() + "] " +
-                "operation to types [" + expremd0.from.name + "] and [" + expremd1.from.name + "].");
-        }
-
-        final Sort sort = promote.sort;
-        expremd0.to = add && sort == Sort.STRING ? expremd0.from : promote;
-        expremd1.to = add && sort == Sort.STRING ? expremd1.from : promote;
-        markCast(expremd0);
-        markCast(expremd1);
-
-        if (expremd0.postConst != null && expremd1.postConst != null) {
-            if (ctx.MUL() != null) {
-                if (sort == Sort.INT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (int)expremd0.postConst * (int)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Math.multiplyExact((int)expremd0.postConst, (int)expremd1.postConst);
-                    }
-                } else if (sort == Sort.LONG) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (long)expremd0.postConst * (long)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Math.multiplyExact((long)expremd0.postConst, (long)expremd1.postConst);
-                    }
-                } else if (sort == Sort.FLOAT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (float)expremd0.postConst * (float)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.multiplyWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
-                    }
-                } else if (sort == Sort.DOUBLE) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (double)expremd0.postConst * (double)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.multiplyWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
-                    }
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.DIV() != null) {
-                if (sort == Sort.INT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (int)expremd0.postConst / (int)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.divideWithoutOverflow((int)expremd0.postConst, (int)expremd1.postConst);
-                    }
-                } else if (sort == Sort.LONG) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (long)expremd0.postConst / (long)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.divideWithoutOverflow((long)expremd0.postConst, (long)expremd1.postConst);
-                    }
-                } else if (sort == Sort.FLOAT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (float)expremd0.postConst / (float)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.divideWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
-                    }
-                } else if (sort == Sort.DOUBLE) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (double)expremd0.postConst / (double)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.divideWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
-                    }
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.REM() != null) {
-                if (sort == Sort.INT) {
-                    binaryemd.preConst = (int)expremd0.postConst % (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    binaryemd.preConst = (long)expremd0.postConst % (long)expremd1.postConst;
-                } else if (sort == Sort.FLOAT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (float)expremd0.postConst % (float)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.remainderWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
-                    }
-                } else if (sort == Sort.DOUBLE) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (double)expremd0.postConst % (double)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.remainderWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
-                    }
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.ADD() != null) {
-                if (sort == Sort.INT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (int)expremd0.postConst + (int)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Math.addExact((int)expremd0.postConst, (int)expremd1.postConst);
-                    }
-                } else if (sort == Sort.LONG) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (long)expremd0.postConst + (long)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Math.addExact((long)expremd0.postConst, (long)expremd1.postConst);
-                    }
-                } else if (sort == Sort.FLOAT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (float)expremd0.postConst + (float)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.addWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
-                    }
-                } else if (sort == Sort.DOUBLE) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (double)expremd0.postConst + (double)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.addWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
-                    }
-                } else if (sort == Sort.STRING) {
-                    binaryemd.preConst = "" + expremd0.postConst + expremd1.postConst;
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.SUB() != null) {
-                if (sort == Sort.INT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (int)expremd0.postConst - (int)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Math.subtractExact((int)expremd0.postConst, (int)expremd1.postConst);
-                    }
-                } else if (sort == Sort.LONG) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (long)expremd0.postConst - (long)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Math.subtractExact((long)expremd0.postConst, (long)expremd1.postConst);
-                    }
-                } else if (sort == Sort.FLOAT) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (float)expremd0.postConst - (float)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.subtractWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
-                    }
-                } else if (sort == Sort.DOUBLE) {
-                    if (settings.getNumericOverflow()) {
-                        binaryemd.preConst = (double)expremd0.postConst - (double)expremd1.postConst;
-                    } else {
-                        binaryemd.preConst = Utility.subtractWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
-                    }
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.LSH() != null) {
-                if (sort == Sort.INT) {
-                    binaryemd.preConst = (int)expremd0.postConst << (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    binaryemd.preConst = (long)expremd0.postConst << (long)expremd1.postConst;
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.RSH() != null) {
-                if (sort == Sort.INT) {
-                    binaryemd.preConst = (int)expremd0.postConst >> (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    binaryemd.preConst = (long)expremd0.postConst >> (long)expremd1.postConst;
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.USH() != null) {
-                if (sort == Sort.INT) {
-                    binaryemd.preConst = (int)expremd0.postConst >>> (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    binaryemd.preConst = (long)expremd0.postConst >>> (long)expremd1.postConst;
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.BWAND() != null) {
-                if (sort == Sort.INT) {
-                    binaryemd.preConst = (int)expremd0.postConst & (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    binaryemd.preConst = (long)expremd0.postConst & (long)expremd1.postConst;
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.BWXOR() != null) {
-                if (sort == Sort.BOOL) {
-                    binaryemd.preConst = (boolean)expremd0.postConst ^ (boolean)expremd1.postConst;
-                } else if (sort == Sort.INT) {
-                    binaryemd.preConst = (int)expremd0.postConst ^ (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    binaryemd.preConst = (long)expremd0.postConst ^ (long)expremd1.postConst;
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else if (ctx.BWOR() != null) {
-                if (sort == Sort.INT) {
-                    binaryemd.preConst = (int)expremd0.postConst | (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    binaryemd.preConst = (long)expremd0.postConst | (long)expremd1.postConst;
-                } else {
-                    throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                }
-            } else {
-                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-            }
-        }
-
-        binaryemd.from = promote;
-        binaryemd.typesafe = expremd0.typesafe && expremd1.typesafe;
+        expression.processBinary(ctx);
 
         return null;
     }
 
     @Override
     public Void visitComp(final CompContext ctx) {
-        final Metadata.ExpressionMetadata compemd = metadata.getExpressionMetadata(ctx);
-        final boolean equality = ctx.EQ() != null || ctx.NE() != null;
-        final boolean reference = ctx.EQR() != null || ctx.NER() != null;
-
-        final ExpressionContext exprctx0 = metadata.updateExpressionTree(ctx.expression(0));
-        final Metadata.ExpressionMetadata expremd0 = metadata.createExpressionMetadata(exprctx0);
-        visit(exprctx0);
-
-        final ExpressionContext exprctx1 = metadata.updateExpressionTree(ctx.expression(1));
-        final Metadata.ExpressionMetadata expremd1 = metadata.createExpressionMetadata(exprctx1);
-        visit(exprctx1);
-
-        if (expremd0.isNull && expremd1.isNull) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unnecessary comparison of null constants.");
-        }
-
-        final Type promote = equality ? promoteEquality(expremd0.from, expremd1.from) :
-            reference ? promoteReference(expremd0.from, expremd1.from) :
-                promoteNumeric(expremd0.from, expremd1.from, true, true);
-
-        if (promote == null) {
-            throw new ClassCastException("Cannot apply [" + ctx.getChild(1).getText() + "] " +
-                "operation to types [" + expremd0.from.name + "] and [" + expremd1.from.name + "].");
-        }
-
-        expremd0.to = promote;
-        expremd1.to = promote;
-        markCast(expremd0);
-        markCast(expremd1);
-
-        if (expremd0.postConst != null && expremd1.postConst != null) {
-            final Sort sort = promote.sort;
-
-            if (ctx.EQ() != null || ctx.EQR() != null) {
-                if (sort == Sort.BOOL) {
-                    compemd.preConst = (boolean)expremd0.postConst == (boolean)expremd1.postConst;
-                } else if (sort == Sort.INT) {
-                    compemd.preConst = (int)expremd0.postConst == (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    compemd.preConst = (long)expremd0.postConst == (long)expremd1.postConst;
-                } else if (sort == Sort.FLOAT) {
-                    compemd.preConst = (float)expremd0.postConst == (float)expremd1.postConst;
-                } else if (sort == Sort.DOUBLE) {
-                    compemd.preConst = (double)expremd0.postConst == (double)expremd1.postConst;
-                } else {
-                    if (ctx.EQ() != null && !expremd0.isNull && !expremd1.isNull) {
-                        compemd.preConst = expremd0.postConst.equals(expremd1.postConst);
-                    } else if (ctx.EQR() != null) {
-                        compemd.preConst = expremd0.postConst == expremd1.postConst;
-                    }
-                }
-            } else if (ctx.NE() != null || ctx.NER() != null) {
-                if (sort == Sort.BOOL) {
-                    compemd.preConst = (boolean)expremd0.postConst != (boolean)expremd1.postConst;
-                } else if (sort == Sort.INT) {
-                    compemd.preConst = (int)expremd0.postConst != (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    compemd.preConst = (long)expremd0.postConst != (long)expremd1.postConst;
-                } else if (sort == Sort.FLOAT) {
-                    compemd.preConst = (float)expremd0.postConst != (float)expremd1.postConst;
-                } else if (sort == Sort.DOUBLE) {
-                    compemd.preConst = (double)expremd0.postConst != (double)expremd1.postConst;
-                } else {
-                    if (ctx.NE() != null && !expremd0.isNull && !expremd1.isNull) {
-                        compemd.preConst = expremd0.postConst.equals(expremd1.postConst);
-                    } else if (ctx.NER() != null) {
-                        compemd.preConst = expremd0.postConst == expremd1.postConst;
-                    }
-                }
-            } else if (ctx.GTE() != null) {
-                if (sort == Sort.INT) {
-                    compemd.preConst = (int)expremd0.postConst >= (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    compemd.preConst = (long)expremd0.postConst >= (long)expremd1.postConst;
-                } else if (sort == Sort.FLOAT) {
-                    compemd.preConst = (float)expremd0.postConst >= (float)expremd1.postConst;
-                } else if (sort == Sort.DOUBLE) {
-                    compemd.preConst = (double)expremd0.postConst >= (double)expremd1.postConst;
-                }
-            } else if (ctx.GT() != null) {
-                if (sort == Sort.INT) {
-                    compemd.preConst = (int)expremd0.postConst > (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    compemd.preConst = (long)expremd0.postConst > (long)expremd1.postConst;
-                } else if (sort == Sort.FLOAT) {
-                    compemd.preConst = (float)expremd0.postConst > (float)expremd1.postConst;
-                } else if (sort == Sort.DOUBLE) {
-                    compemd.preConst = (double)expremd0.postConst > (double)expremd1.postConst;
-                }
-            } else if (ctx.LTE() != null) {
-                if (sort == Sort.INT) {
-                    compemd.preConst = (int)expremd0.postConst <= (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    compemd.preConst = (long)expremd0.postConst <= (long)expremd1.postConst;
-                } else if (sort == Sort.FLOAT) {
-                    compemd.preConst = (float)expremd0.postConst <= (float)expremd1.postConst;
-                } else if (sort == Sort.DOUBLE) {
-                    compemd.preConst = (double)expremd0.postConst <= (double)expremd1.postConst;
-                }
-            } else if (ctx.LT() != null) {
-                if (sort == Sort.INT) {
-                    compemd.preConst = (int)expremd0.postConst < (int)expremd1.postConst;
-                } else if (sort == Sort.LONG) {
-                    compemd.preConst = (long)expremd0.postConst < (long)expremd1.postConst;
-                } else if (sort == Sort.FLOAT) {
-                    compemd.preConst = (float)expremd0.postConst < (float)expremd1.postConst;
-                } else if (sort == Sort.DOUBLE) {
-                    compemd.preConst = (double)expremd0.postConst < (double)expremd1.postConst;
-                }
-            } else {
-                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-            }
-        }
-
-        compemd.from = definition.booleanType;
-        compemd.typesafe = expremd0.typesafe && expremd1.typesafe;
+        expression.processComp(ctx);
 
         return null;
     }
 
     @Override
     public Void visitBool(final BoolContext ctx) {
-        final Metadata.ExpressionMetadata boolemd = metadata.getExpressionMetadata(ctx);
-
-        final ExpressionContext exprctx0 = metadata.updateExpressionTree(ctx.expression(0));
-        final Metadata.ExpressionMetadata expremd0 = metadata.createExpressionMetadata(exprctx0);
-        expremd0.to = definition.booleanType;
-        visit(exprctx0);
-        markCast(expremd0);
-
-        final ExpressionContext exprctx1 = metadata.updateExpressionTree(ctx.expression(1));
-        final Metadata.ExpressionMetadata expremd1 = metadata.createExpressionMetadata(exprctx1);
-        expremd1.to = definition.booleanType;
-        visit(exprctx1);
-        markCast(expremd1);
-
-        if (expremd0.postConst != null && expremd1.postConst != null) {
-            if (ctx.BOOLAND() != null) {
-                boolemd.preConst = (boolean)expremd0.postConst && (boolean)expremd1.postConst;
-            } else if (ctx.BOOLOR() != null) {
-                boolemd.preConst = (boolean)expremd0.postConst || (boolean)expremd1.postConst;
-            } else {
-                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-            }
-        }
-
-        boolemd.from = definition.booleanType;
-        boolemd.typesafe = expremd0.typesafe && expremd1.typesafe;
+        expression.processBool(ctx);
 
         return null;
     }
 
     @Override
     public Void visitConditional(final ConditionalContext ctx) {
-        final Metadata.ExpressionMetadata condemd = metadata.getExpressionMetadata(ctx);
-
-        final ExpressionContext exprctx0 = metadata.updateExpressionTree(ctx.expression(0));
-        final Metadata.ExpressionMetadata expremd0 = metadata.createExpressionMetadata(exprctx0);
-        expremd0.to = definition.booleanType;
-        visit(exprctx0);
-        markCast(expremd0);
-
-        if (expremd0.postConst != null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unnecessary conditional statement.");
-        }
-
-        final ExpressionContext exprctx1 = metadata.updateExpressionTree(ctx.expression(1));
-        final Metadata.ExpressionMetadata expremd1 = metadata.createExpressionMetadata(exprctx1);
-        expremd1.to = condemd.to;
-        expremd1.explicit = condemd.explicit;
-        visit(exprctx1);
-
-        final ExpressionContext exprctx2 = metadata.updateExpressionTree(ctx.expression(2));
-        final Metadata.ExpressionMetadata expremd2 = metadata.createExpressionMetadata(exprctx2);
-        expremd2.to = condemd.to;
-        expremd2.explicit = condemd.explicit;
-        visit(exprctx2);
-
-        if (condemd.to == null) {
-            final Type promote = promoteConditional(expremd1.from, expremd2.from, expremd1.preConst, expremd2.preConst);
-
-            expremd1.to = promote;
-            expremd2.to = promote;
-            condemd.from = promote;
-        } else {
-            condemd.from = condemd.to;
-        }
-
-        markCast(expremd1);
-        markCast(expremd2);
-
-        condemd.typesafe = expremd0.typesafe && expremd1.typesafe;
+        expression.processConditional(ctx);
 
         return null;
     }
 
     @Override
     public Void visitAssignment(final AssignmentContext ctx) {
-        final Metadata.ExpressionMetadata assignemd = metadata.getExpressionMetadata(ctx);
-
-        final ExtstartContext extstartctx = ctx.extstart();
-        final Metadata.ExternalMetadata extstartemd = metadata.createExternalMetadata(extstartctx);
-
-        extstartemd.read = assignemd.read;
-        extstartemd.storeExpr = metadata.updateExpressionTree(ctx.expression());
-
-        if (ctx.AMUL() != null) {
-            extstartemd.token = MUL;
-        } else if (ctx.ADIV() != null) {
-            extstartemd.token = DIV;
-        } else if (ctx.AREM() != null) {
-            extstartemd.token = REM;
-        } else if (ctx.AADD() != null) {
-            extstartemd.token = ADD;
-        } else if (ctx.ASUB() != null) {
-            extstartemd.token = SUB;
-        } else if (ctx.ALSH() != null) {
-            extstartemd.token = LSH;
-        } else if (ctx.AUSH() != null) {
-            extstartemd.token = USH;
-        } else if (ctx.ARSH() != null) {
-            extstartemd.token = RSH;
-        } else if (ctx.AAND() != null) {
-            extstartemd.token = BWAND;
-        } else if (ctx.AXOR() != null) {
-            extstartemd.token = BWXOR;
-        } else if (ctx.AOR() != null) {
-            extstartemd.token = BWOR;
-        }
-
-        visit(extstartctx);
-
-        assignemd.statement = true;
-        assignemd.from = extstartemd.read ? extstartemd.current : definition.voidType;
-        assignemd.typesafe = extstartemd.current.sort != Sort.DEF;
+        expression.processAssignment(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtstart(final ExtstartContext 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) {
-            metadata.createExtNodeMetadata(ctx, precctx);
-            visit(precctx);
-        } else if (castctx != null) {
-            metadata.createExtNodeMetadata(ctx, castctx);
-            visit(castctx);
-        } else if (typectx != null) {
-            metadata.createExtNodeMetadata(ctx, typectx);
-            visit(typectx);
-        } else if (varctx != null) {
-            metadata.createExtNodeMetadata(ctx, varctx);
-            visit(varctx);
-        } else if (newctx != null) {
-            metadata.createExtNodeMetadata(ctx, newctx);
-            visit(newctx);
-        } else if (stringctx != null) {
-            metadata.createExtNodeMetadata(ctx, stringctx);
-            visit(stringctx);
-        } else {
-            throw new IllegalStateException();
-        }
+        external.processExtstart(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtprec(final ExtprecContext ctx) {
-        final Metadata.ExtNodeMetadata precenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = precenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        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();
-
-        final ExtdotContext dotctx = ctx.extdot();
-        final ExtbraceContext bracectx = ctx.extbrace();
-
-        if (dotctx != null || bracectx != null) {
-            ++parentemd.scope;
-        }
-
-        if (precctx != null) {
-            metadata.createExtNodeMetadata(parent, precctx);
-            visit(precctx);
-        } else if (castctx != null) {
-            metadata.createExtNodeMetadata(parent, castctx);
-            visit(castctx);
-        } else if (typectx != null) {
-            metadata.createExtNodeMetadata(parent, typectx);
-            visit(typectx);
-        } else if (varctx != null) {
-            metadata.createExtNodeMetadata(parent, varctx);
-            visit(varctx);
-        } else if (newctx != null) {
-            metadata.createExtNodeMetadata(parent, newctx);
-            visit(newctx);
-        } else if (stringctx != null) {
-            metadata.createExtNodeMetadata(ctx, stringctx);
-            visit(stringctx);
-        } else {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
-
-        parentemd.statement = false;
-
-        if (dotctx != null) {
-            --parentemd.scope;
-
-            metadata.createExtNodeMetadata(parent, dotctx);
-            visit(dotctx);
-        } else if (bracectx != null) {
-            --parentemd.scope;
-
-            metadata.createExtNodeMetadata(parent, bracectx);
-            visit(bracectx);
-        }
+        external.processExtprec(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtcast(final ExtcastContext ctx) {
-        final Metadata.ExtNodeMetadata castenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = castenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        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) {
-            metadata.createExtNodeMetadata(parent, precctx);
-            visit(precctx);
-        } else if (castctx != null) {
-            metadata.createExtNodeMetadata(parent, castctx);
-            visit(castctx);
-        } else if (typectx != null) {
-            metadata.createExtNodeMetadata(parent, typectx);
-            visit(typectx);
-        } else if (varctx != null) {
-            metadata.createExtNodeMetadata(parent, varctx);
-            visit(varctx);
-        } else if (newctx != null) {
-            metadata.createExtNodeMetadata(parent, newctx);
-            visit(newctx);
-        } else if (stringctx != null) {
-            metadata.createExtNodeMetadata(ctx, stringctx);
-            visit(stringctx);
-        } else {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
-
-        final DecltypeContext declctx = ctx.decltype();
-        final Metadata.ExpressionMetadata declemd = metadata.createExpressionMetadata(declctx);
-        visit(declctx);
-
-        castenmd.castTo = getLegalCast(ctx, parentemd.current, declemd.from, true);
-        castenmd.type = declemd.from;
-        parentemd.current = declemd.from;
-        parentemd.statement = false;
+        external.processExtcast(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtbrace(final ExtbraceContext ctx) {
-        final Metadata.ExtNodeMetadata braceenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = braceenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        final boolean array = parentemd.current.sort == Sort.ARRAY;
-        final boolean def = parentemd.current.sort == Sort.DEF;
-        boolean map = false;
-        boolean list = false;
-
-        try {
-            parentemd.current.clazz.asSubclass(Map.class);
-            map = true;
-        } catch (ClassCastException exception) {
-            // Do nothing.
-        }
-
-        try {
-            parentemd.current.clazz.asSubclass(List.class);
-            list = true;
-        } catch (ClassCastException exception) {
-            // Do nothing.
-        }
-
-        final ExtdotContext dotctx = ctx.extdot();
-        final ExtbraceContext bracectx = ctx.extbrace();
-
-        braceenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
-
-        final ExpressionContext exprctx = metadata.updateExpressionTree(ctx.expression());
-        final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-
-        if (array || def) {
-            expremd.to = array ? definition.intType : definition.objectType;
-            visit(exprctx);
-            markCast(expremd);
-
-            braceenmd.target = "#brace";
-            braceenmd.type = def ? definition.defType :
-                definition.getType(parentemd.current.struct, parentemd.current.type.getDimensions() - 1);
-            analyzeLoadStoreExternal(ctx);
-            parentemd.current = braceenmd.type;
-
-            if (dotctx != null) {
-                metadata.createExtNodeMetadata(parent, dotctx);
-                visit(dotctx);
-            } else if (bracectx != null) {
-                metadata.createExtNodeMetadata(parent, bracectx);
-                visit(bracectx);
-            }
-        } else {
-            final boolean store = braceenmd.last && parentemd.storeExpr != null;
-            final boolean get = parentemd.read || parentemd.token > 0 || !braceenmd.last;
-            final boolean set = braceenmd.last && store;
-
-            Method getter;
-            Method setter;
-            Type valuetype;
-            Type settype;
-
-            if (map) {
-                getter = parentemd.current.struct.methods.get("get");
-                setter = parentemd.current.struct.methods.get("put");
-
-                if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1)) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) +
-                        "Illegal map get shortcut for type [" + parentemd.current.name + "].");
-                }
-
-                if (setter != null && setter.arguments.size() != 2) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) +
-                        "Illegal map set shortcut for type [" + parentemd.current.name + "].");
-                }
-
-                if (getter != null && setter != null && (!getter.arguments.get(0).equals(setter.arguments.get(0))
-                    || !getter.rtn.equals(setter.arguments.get(1)))) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Shortcut argument types must match.");
-                }
-
-                valuetype = setter != null ? setter.arguments.get(0) : getter != null ? getter.arguments.get(0) : null;
-                settype = setter == null ? null : setter.arguments.get(1);
-            } else if (list) {
-                getter = parentemd.current.struct.methods.get("get");
-                setter = parentemd.current.struct.methods.get("set");
-
-                if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
-                    getter.arguments.get(0).sort != Sort.INT)) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) +
-                        "Illegal list get shortcut for type [" + parentemd.current.name + "].");
-                }
-
-                if (setter != null && (setter.arguments.size() != 2 || setter.arguments.get(0).sort != Sort.INT)) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) +
-                        "Illegal list set shortcut for type [" + parentemd.current.name + "].");
-                }
-
-                if (getter != null && setter != null && (!getter.arguments.get(0).equals(setter.arguments.get(0))
-                    || !getter.rtn.equals(setter.arguments.get(1)))) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Shortcut argument types must match.");
-                }
-
-                valuetype = definition.intType;
-                settype = setter == null ? null : setter.arguments.get(1);
-            } else {
-                throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-            }
-
-            if ((get || set) && (!get || getter != null) && (!set || setter != null)) {
-                expremd.to = valuetype;
-                visit(exprctx);
-                markCast(expremd);
-
-                braceenmd.target = new Object[] {getter, setter, true, null};
-                braceenmd.type = get ? getter.rtn : settype;
-                analyzeLoadStoreExternal(ctx);
-                parentemd.current = get ? getter.rtn : setter.rtn;
-            }
-        }
-
-        if (braceenmd.target == null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) +
-                "Attempting to address a non-array type [" + parentemd.current.name + "] as an array.");
-        }
+        external.processExtbrace(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtdot(final ExtdotContext ctx) {
-        final Metadata.ExtNodeMetadata dotemnd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = dotemnd.parent;
-
-        final ExtcallContext callctx = ctx.extcall();
-        final ExtfieldContext fieldctx = ctx.extfield();
-
-        if (callctx != null) {
-            metadata.createExtNodeMetadata(parent, callctx);
-            visit(callctx);
-        } else if (fieldctx != null) {
-            metadata.createExtNodeMetadata(parent, fieldctx);
-            visit(fieldctx);
-        }
+        external.processExtdot(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExttype(final ExttypeContext ctx) {
-        final Metadata.ExtNodeMetadata typeenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = typeenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        if (parentemd.current != null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unexpected static type.");
-        }
-
-        final String typestr = ctx.TYPE().getText();
-        typeenmd.type = definition.getType(typestr);
-        parentemd.current = typeenmd.type;
-        parentemd.statik = true;
-
-        final ExtdotContext dotctx = ctx.extdot();
-        metadata.createExtNodeMetadata(parent, dotctx);
-        visit(dotctx);
+        external.processExttype(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtcall(final ExtcallContext ctx) {
-        final Metadata.ExtNodeMetadata callenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = callenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        final ExtdotContext dotctx = ctx.extdot();
-        final ExtbraceContext bracectx = ctx.extbrace();
-
-        callenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
-
-        final String name = ctx.EXTID().getText();
-
-        if (parentemd.current.sort == Sort.ARRAY) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unexpected call [" + name + "] on an array.");
-        } else if (callenmd.last && parentemd.storeExpr != null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Cannot assign a value to a call [" + name + "].");
-        }
-
-        final Struct struct = parentemd.current.struct;
-        final List<ExpressionContext> arguments = ctx.arguments().expression();
-        final int size = arguments.size();
-        Type[] types;
-
-        final Method method = parentemd.statik ? struct.functions.get(name) : struct.methods.get(name);
-        final boolean def = parentemd.current.sort == Sort.DEF;
-
-        if (method == null && !def) {
-            throw new IllegalArgumentException(
-                Metadata.error(ctx) + "Unknown call [" + name + "] on type [" + struct.name + "].");
-        } else if (method != null) {
-            types = new Type[method.arguments.size()];
-            method.arguments.toArray(types);
-
-            callenmd.target = method;
-            callenmd.type = method.rtn;
-            parentemd.statement = !parentemd.read && callenmd.last;
-            parentemd.current = method.rtn;
-
-            if (size != types.length) {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "When calling [" + name + "] on type " +
-                    "[" + struct.name + "] expected [" + types.length + "] arguments," +
-                    " but found [" + arguments.size() + "].");
-            }
-        } else {
-            types = new Type[arguments.size()];
-            Arrays.fill(types, definition.defType);
-
-            callenmd.target = name;
-            callenmd.type = definition.defType;
-            parentemd.statement = !parentemd.read && callenmd.last;
-            parentemd.current = callenmd.type;
-        }
-
-        for (int argument = 0; argument < size; ++argument) {
-            final ExpressionContext exprctx = metadata.updateExpressionTree(arguments.get(argument));
-            final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-            expremd.to = types[argument];
-            visit(exprctx);
-            markCast(expremd);
-        }
-
-        parentemd.statik = false;
-
-        if (dotctx != null) {
-            metadata.createExtNodeMetadata(parent, dotctx);
-            visit(dotctx);
-        } else if (bracectx != null) {
-            metadata.createExtNodeMetadata(parent, bracectx);
-            visit(bracectx);
-        }
+        external.processExtcall(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtvar(final ExtvarContext ctx) {
-        final Metadata.ExtNodeMetadata varenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = varenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        final String name = ctx.ID().getText();
-
-        final ExtdotContext dotctx = ctx.extdot();
-        final ExtbraceContext bracectx = ctx.extbrace();
-
-        if (parentemd.current != null) {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected variable [" + name + "] load.");
-        }
-
-        varenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
-
-        final Variable variable = getVariable(name);
-
-        if (variable == null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unknown variable [" + name + "].");
-        }
-
-        varenmd.target = variable.slot;
-        varenmd.type = variable.type;
-        analyzeLoadStoreExternal(ctx);
-        parentemd.current = varenmd.type;
-
-        if (dotctx != null) {
-            metadata.createExtNodeMetadata(parent, dotctx);
-            visit(dotctx);
-        } else if (bracectx != null) {
-            metadata.createExtNodeMetadata(parent, bracectx);
-            visit(bracectx);
-        }
+        external.processExtvar(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtfield(final ExtfieldContext ctx) {
-        final Metadata.ExtNodeMetadata memberenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = memberenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        if (ctx.EXTID() == null && ctx.EXTINTEGER() == null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unexpected parser state.");
-        }
-
-        final String value = ctx.EXTID() == null ? ctx.EXTINTEGER().getText() : ctx.EXTID().getText();
-
-        final ExtdotContext dotctx = ctx.extdot();
-        final ExtbraceContext bracectx = ctx.extbrace();
-
-        memberenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
-        final boolean store = memberenmd.last && parentemd.storeExpr != null;
-
-        if (parentemd.current == null) {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected field [" + value + "] load.");
-        }
-
-        if (parentemd.current.sort == Sort.ARRAY) {
-            if ("length".equals(value)) {
-                if (!parentemd.read) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Must read array field [length].");
-                } else if (store) {
-                    throw new IllegalArgumentException(
-                        Metadata.error(ctx) + "Cannot write to read-only array field [length].");
-                }
-
-                memberenmd.target = "#length";
-                memberenmd.type = definition.intType;
-                parentemd.current = definition.intType;
-            } else {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "Unexpected array field [" + value + "].");
-            }
-        } else if (parentemd.current.sort == Sort.DEF) {
-            memberenmd.target = value;
-            memberenmd.type = definition.defType;
-            analyzeLoadStoreExternal(ctx);
-            parentemd.current = memberenmd.type;
-        } else {
-            final Struct struct = parentemd.current.struct;
-            final Field field = parentemd.statik ? struct.statics.get(value) : struct.members.get(value);
-
-            if (field != null) {
-                if (store && java.lang.reflect.Modifier.isFinal(field.reflect.getModifiers())) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) + "Cannot write to read-only" +
-                        " field [" + value + "] for type [" + struct.name + "].");
-                }
-
-                memberenmd.target = field;
-                memberenmd.type = field.type;
-                analyzeLoadStoreExternal(ctx);
-                parentemd.current = memberenmd.type;
-            } else {
-                final boolean get = parentemd.read || parentemd.token > 0 || !memberenmd.last;
-                final boolean set = memberenmd.last && store;
-
-                Method getter = struct.methods.get("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1));
-                Method setter = struct.methods.get("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1));
-                Object constant = null;
-
-                if (getter != null && (getter.rtn.sort == Sort.VOID || !getter.arguments.isEmpty())) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) +
-                        "Illegal get shortcut on field [" + value + "] for type [" + struct.name + "].");
-                }
-
-                if (setter != null && (setter.rtn.sort != Sort.VOID || setter.arguments.size() != 1)) {
-                    throw new IllegalArgumentException(Metadata.error(ctx) +
-                        "Illegal set shortcut on field [" + value + "] for type [" + struct.name + "].");
-                }
-
-                Type settype = setter == null ? null : setter.arguments.get(0);
-
-                if (getter == null && setter == null) {
-                    if (ctx.EXTID() != null) {
-                        try {
-                            parentemd.current.clazz.asSubclass(Map.class);
-
-                            getter = parentemd.current.struct.methods.get("get");
-                            setter = parentemd.current.struct.methods.get("put");
-
-                            if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
-                                getter.arguments.get(0).sort != Sort.STRING)) {
-                                throw new IllegalArgumentException(Metadata.error(ctx) +
-                                    "Illegal map get shortcut [" + value + "] for type [" + struct.name + "].");
-                            }
-
-                            if (setter != null && (setter.arguments.size() != 2 ||
-                                setter.arguments.get(0).sort != Sort.STRING)) {
-                                throw new IllegalArgumentException(Metadata.error(ctx) +
-                                    "Illegal map set shortcut [" + value + "] for type [" + struct.name + "].");
-                            }
-
-                            if (getter != null && setter != null && !getter.rtn.equals(setter.arguments.get(1))) {
-                                throw new IllegalArgumentException(Metadata.error(ctx) + "Shortcut argument types must match.");
-                            }
-
-                            settype = setter == null ? null : setter.arguments.get(1);
-                            constant = value;
-                        } catch (ClassCastException exception) {
-                            //Do nothing.
-                        }
-                    } else if (ctx.EXTINTEGER() != null) {
-                        try {
-                            parentemd.current.clazz.asSubclass(List.class);
-
-                            getter = parentemd.current.struct.methods.get("get");
-                            setter = parentemd.current.struct.methods.get("set");
-
-                            if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
-                                getter.arguments.get(0).sort != Sort.INT)) {
-                                throw new IllegalArgumentException(Metadata.error(ctx) +
-                                    "Illegal list get shortcut [" + value + "] for type [" + struct.name + "].");
-                            }
-
-                            if (setter != null && (setter.rtn.sort != Sort.VOID || setter.arguments.size() != 2 ||
-                                setter.arguments.get(0).sort != Sort.INT)) {
-                                throw new IllegalArgumentException(Metadata.error(ctx) +
-                                    "Illegal list set shortcut [" + value + "] for type [" + struct.name + "].");
-                            }
-
-                            if (getter != null && setter != null && !getter.rtn.equals(setter.arguments.get(1))) {
-                                throw new IllegalArgumentException(Metadata.error(ctx) + "Shortcut argument types must match.");
-                            }
-
-                            settype = setter == null ? null : setter.arguments.get(1);
-
-                            try {
-                                constant = Integer.parseInt(value);
-                            } catch (NumberFormatException exception) {
-                                throw new IllegalArgumentException(Metadata.error(ctx) +
-                                    "Illegal list shortcut value [" + value + "].");
-                            }
-                        } catch (ClassCastException exception) {
-                            //Do nothing.
-                        }
-                    } else {
-                        throw new IllegalStateException(Metadata.error(ctx) + "Unexpected parser state.");
-                    }
-                }
-
-                if ((get || set) && (!get || getter != null) && (!set || setter != null)) {
-                    memberenmd.target = new Object[] {getter, setter, constant != null, constant};
-                    memberenmd.type = get ? getter.rtn : settype;
-                    analyzeLoadStoreExternal(ctx);
-                    parentemd.current = get ? getter.rtn : setter.rtn;
-                }
-            }
-
-            if (memberenmd.target == null) {
-                throw new IllegalArgumentException(
-                    Metadata.error(ctx) + "Unknown field [" + value + "] for type [" + struct.name + "].");
-            }
-        }
-
-        parentemd.statik = false;
-
-        if (dotctx != null) {
-            metadata.createExtNodeMetadata(parent, dotctx);
-            visit(dotctx);
-        } else if (bracectx != null) {
-            metadata.createExtNodeMetadata(parent, bracectx);
-            visit(bracectx);
-        }
+        external.processExtfield(ctx);
 
         return null;
     }
 
     @Override
-    public Void visitExtnew(ExtnewContext ctx) {
-        final Metadata.ExtNodeMetadata newenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = newenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        final ExtdotContext dotctx = ctx.extdot();
-        final ExtbraceContext bracectx = ctx.extbrace();
-
-        newenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
-
-        final String name = ctx.TYPE().getText();
-        final Struct struct = definition.structs.get(name);
-
-        if (parentemd.current != null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unexpected new call.");
-        } else if (struct == null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Specified type [" + name + "] not found.");
-        } else if (newenmd.last && parentemd.storeExpr != null) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Cannot assign a value to a new call.");
-        }
-
-        final boolean newclass = ctx.arguments() != null;
-        final boolean newarray = !ctx.expression().isEmpty();
-
-        final List<ExpressionContext> arguments = newclass ? ctx.arguments().expression() : ctx.expression();
-        final int size = arguments.size();
-
-        Type[] types;
-
-        if (newarray) {
-            if (!parentemd.read) {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "A newly created array must be assigned.");
-            }
-
-            types = new Type[size];
-            Arrays.fill(types, definition.intType);
-
-            newenmd.target = "#makearray";
-
-            if (size > 1) {
-                newenmd.type = definition.getType(struct, size);
-                parentemd.current = newenmd.type;
-            } else if (size == 1) {
-                newenmd.type = definition.getType(struct, 0);
-                parentemd.current = definition.getType(struct, 1);
-            } else {
-                throw new IllegalArgumentException(Metadata.error(ctx) + "A newly created array cannot have zero dimensions.");
-            }
-        } else if (newclass) {
-            final Constructor constructor = struct.constructors.get("new");
-
-            if (constructor != null) {
-                types = new Type[constructor.arguments.size()];
-                constructor.arguments.toArray(types);
-
-                newenmd.target = constructor;
-                newenmd.type = definition.getType(struct, 0);
-                parentemd.statement = !parentemd.read && newenmd.last;
-                parentemd.current = newenmd.type;
-            } else {
-                throw new IllegalArgumentException(
-                    Metadata.error(ctx) + "Unknown new call on type [" + struct.name + "].");
-            }
-        } else {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Unknown parser state.");
-        }
-
-        if (size != types.length) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "When calling [" + name + "] on type " +
-                "[" + struct.name + "] expected [" + types.length + "] arguments," +
-                " but found [" + arguments.size() + "].");
-        }
-
-        for (int argument = 0; argument < size; ++argument) {
-            final ExpressionContext exprctx = metadata.updateExpressionTree(arguments.get(argument));
-            final Metadata.ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
-            expremd.to = types[argument];
-            visit(exprctx);
-            markCast(expremd);
-        }
-
-        if (dotctx != null) {
-            metadata.createExtNodeMetadata(parent, dotctx);
-            visit(dotctx);
-        } else if (bracectx != null) {
-            metadata.createExtNodeMetadata(parent, bracectx);
-            visit(bracectx);
-        }
+    public Void visitExtnew(final ExtnewContext ctx) {
+        external.processExtnew(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExtstring(final ExtstringContext ctx) {
-        final Metadata.ExtNodeMetadata memberenmd = metadata.getExtNodeMetadata(ctx);
-        final ParserRuleContext parent = memberenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        final String string = ctx.STRING().getText();
-
-        final ExtdotContext dotctx = ctx.extdot();
-        final ExtbraceContext bracectx = ctx.extbrace();
-
-        memberenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
-        final boolean store = memberenmd.last && parentemd.storeExpr != null;
-
-        if (parentemd.current != null) {
-            throw new IllegalStateException(Metadata.error(ctx) + "Unexpected String constant [" + string + "].");
-        }
-
-        if (!parentemd.read) {
-            throw new IllegalArgumentException(Metadata.error(ctx) + "Must read String constant [" + string + "].");
-        } else if (store) {
-            throw new IllegalArgumentException(
-                Metadata.error(ctx) + "Cannot write to read-only String constant [" + string + "].");
-        }
-
-        memberenmd.target = string;
-        memberenmd.type = definition.stringType;
-        parentemd.current = definition.stringType;
-
-        if (memberenmd.last) {
-            parentemd.constant = string;
-        }
-
-        if (dotctx != null) {
-            metadata.createExtNodeMetadata(parent, dotctx);
-            visit(dotctx);
-        } else if (bracectx != null) {
-            metadata.createExtNodeMetadata(parent, bracectx);
-            visit(bracectx);
-        }
+        external.processExtstring(ctx);
 
         return null;
     }
 
     @Override
     public Void visitArguments(final ArgumentsContext ctx) {
-        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected parser state.");
+        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
     }
 
     @Override
-    public Void visitIncrement(IncrementContext ctx) {
-        final Metadata.ExpressionMetadata incremd = metadata.getExpressionMetadata(ctx);
-        final Sort sort = incremd.to == null ? null : incremd.to.sort;
-        final boolean positive = ctx.INCR() != null;
-
-        if (incremd.to == null) {
-            incremd.preConst = positive ? 1 : -1;
-            incremd.from = definition.intType;
-        } else {
-            switch (sort) {
-                case LONG:
-                    incremd.preConst = positive ? 1L : -1L;
-                    incremd.from = definition.longType;
-                    break;
-                case FLOAT:
-                    incremd.preConst = positive ? 1.0F : -1.0F;
-                    incremd.from = definition.floatType;
-                    break;
-                case DOUBLE:
-                    incremd.preConst = positive ? 1.0 : -1.0;
-                    incremd.from = definition.doubleType;
-                    break;
-                default:
-                    incremd.preConst = positive ? 1 : -1;
-                    incremd.from = definition.intType;
-            }
-        }
-
-        return null;
-    }
-
-    private void analyzeLoadStoreExternal(final ParserRuleContext source) {
-        final Metadata.ExtNodeMetadata extenmd = metadata.getExtNodeMetadata(source);
-        final ParserRuleContext parent = extenmd.parent;
-        final Metadata.ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
-
-        if (extenmd.last && parentemd.storeExpr != null) {
-            final ParserRuleContext store = parentemd.storeExpr;
-            final Metadata.ExpressionMetadata storeemd = metadata.createExpressionMetadata(parentemd.storeExpr);
-            final int token = parentemd.token;
-
-            if (token > 0) {
-                visit(store);
-
-                final boolean add = token == ADD;
-                final boolean xor = token == BWAND || token == BWXOR || token == BWOR;
-                final boolean decimal = token == MUL || token == DIV || token == REM || token == SUB;
-
-                extenmd.promote = add ? promoteAdd(extenmd.type, storeemd.from) :
-                    xor ? promoteXor(extenmd.type, storeemd.from) :
-                        promoteNumeric(extenmd.type, storeemd.from, decimal, true);
-
-                if (extenmd.promote == null) {
-                    throw new IllegalArgumentException("Cannot apply compound assignment to " +
-                        "types [" + extenmd.type.name + "] and [" + storeemd.from.name + "].");
-                }
-
-                extenmd.castFrom = getLegalCast(source, extenmd.type, extenmd.promote, false);
-                extenmd.castTo = getLegalCast(source, extenmd.promote, extenmd.type, true);
-
-                storeemd.to = add && extenmd.promote.sort == Sort.STRING ? storeemd.from : extenmd.promote;
-                markCast(storeemd);
-            } else {
-                storeemd.to = extenmd.type;
-                visit(store);
-                markCast(storeemd);
-            }
-        }
-    }
-
-    private void markCast(final Metadata.ExpressionMetadata emd) {
-        if (emd.from == null) {
-            throw new IllegalStateException(Metadata.error(emd.source) + "From cast type should never be null.");
-        }
-
-        if (emd.to != null) {
-            emd.cast = getLegalCast(emd.source, emd.from, emd.to, emd.explicit || !emd.typesafe);
-
-            if (emd.preConst != null && emd.to.sort.constant) {
-                emd.postConst = constCast(emd.source, emd.preConst, emd.cast);
-            }
-        } else {
-            throw new IllegalStateException(Metadata.error(emd.source) + "To cast type should never be null.");
-        }
-    }
-
-    private Cast getLegalCast(final ParserRuleContext source, final Type from, final Type to, final boolean explicit) {
-        final Cast cast = new Cast(from, to);
-
-        if (from.equals(to)) {
-            return cast;
-        }
-
-        if (from.sort == Sort.DEF && to.sort != Sort.VOID || from.sort != Sort.VOID && to.sort == Sort.DEF) {
-            final Transform transform = definition.transforms.get(cast);
-
-            if (transform != null) {
-                return transform;
-            }
-
-            return cast;
-        }
-
-        switch (from.sort) {
-            case BOOL:
-                switch (to.sort) {
-                    case OBJECT:
-                    case BOOL_OBJ:
-                        return checkTransform(source, cast);
-                }
-
-                break;
-            case BYTE:
-                switch (to.sort) {
-                    case SHORT:
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                        return cast;
-                    case CHAR:
-                        if (explicit)
-                            return cast;
-
-                        break;
-                    case OBJECT:
-                    case NUMBER:
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case CHAR_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case SHORT:
-                switch (to.sort) {
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                        return cast;
-                    case BYTE:
-                    case CHAR:
-                        if (explicit)
-                            return cast;
-
-                        break;
-                    case OBJECT:
-                    case NUMBER:
-                    case SHORT_OBJ:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE_OBJ:
-                    case CHAR_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case CHAR:
-                switch (to.sort) {
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                        return cast;
-                    case BYTE:
-                    case SHORT:
-                        if (explicit)
-                            return cast;
-
-                        break;
-                    case OBJECT:
-                    case NUMBER:
-                    case CHAR_OBJ:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case INT:
-                switch (to.sort) {
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                        return cast;
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                        if (explicit)
-                            return cast;
-
-                        break;
-                    case OBJECT:
-                    case NUMBER:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case CHAR_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case LONG:
-                switch (to.sort) {
-                    case FLOAT:
-                    case DOUBLE:
-                        return cast;
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                    case INT:
-                        if (explicit)
-                            return cast;
-
-                        break;
-                    case OBJECT:
-                    case NUMBER:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case CHAR_OBJ:
-                    case INT_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case FLOAT:
-                switch (to.sort) {
-                    case DOUBLE:
-                        return cast;
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                    case INT:
-                    case LONG:
-                        if (explicit)
-                            return cast;
-
-                        break;
-                    case OBJECT:
-                    case NUMBER:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case CHAR_OBJ:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case DOUBLE:
-                switch (to.sort) {
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                        if (explicit)
-                            return cast;
-
-                        break;
-                    case OBJECT:
-                    case NUMBER:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case CHAR_OBJ:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case OBJECT:
-            case NUMBER:
-                switch (to.sort) {
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case BOOL_OBJ:
-                switch (to.sort) {
-                    case BOOL:
-                        return checkTransform(source, cast);
-                }
-
-                break;
-            case BYTE_OBJ:
-                switch (to.sort) {
-                    case BYTE:
-                    case SHORT:
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                    case SHORT_OBJ:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case CHAR:
-                    case CHAR_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case SHORT_OBJ:
-                switch (to.sort) {
-                    case SHORT:
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE:
-                    case CHAR:
-                    case BYTE_OBJ:
-                    case CHAR_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case CHAR_OBJ:
-                switch (to.sort) {
-                    case CHAR:
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE:
-                    case SHORT:
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case INT_OBJ:
-                switch (to.sort) {
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case CHAR_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case LONG_OBJ:
-                switch (to.sort) {
-                    case LONG:
-                    case FLOAT:
-                    case DOUBLE:
-                    case FLOAT_OBJ:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                    case INT:
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case CHAR_OBJ:
-                    case INT_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case FLOAT_OBJ:
-                switch (to.sort) {
-                    case FLOAT:
-                    case DOUBLE:
-                    case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                    case INT:
-                    case LONG:
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case CHAR_OBJ:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-            case DOUBLE_OBJ:
-                switch (to.sort) {
-                    case DOUBLE:
-                        return checkTransform(source, cast);
-                    case BYTE:
-                    case SHORT:
-                    case CHAR:
-                    case INT:
-                    case LONG:
-                    case FLOAT:
-                    case BYTE_OBJ:
-                    case SHORT_OBJ:
-                    case CHAR_OBJ:
-                    case INT_OBJ:
-                    case LONG_OBJ:
-                    case FLOAT_OBJ:
-                        if (explicit)
-                            return checkTransform(source, cast);
-
-                        break;
-                }
-
-                break;
-        }
-
-        try {
-            from.clazz.asSubclass(to.clazz);
-
-            return cast;
-        } catch (final ClassCastException cce0) {
-            try {
-                if (explicit) {
-                    to.clazz.asSubclass(from.clazz);
-
-                    return cast;
-                } else {
-                    throw new ClassCastException(
-                        Metadata.error(source) + "Cannot cast from [" + from.name + "] to [" + to.name + "].");
-                }
-            } catch (final ClassCastException cce1) {
-                throw new ClassCastException(
-                    Metadata.error(source) + "Cannot cast from [" + from.name + "] to [" + to.name + "].");
-            }
-        }
-    }
-
-    private Transform checkTransform(final ParserRuleContext source, final Cast cast) {
-        final Transform transform = definition.transforms.get(cast);
-
-        if (transform == null) {
-            throw new ClassCastException(
-                Metadata.error(source) + "Cannot cast from [" + cast.from.name + "] to [" + cast.to.name + "].");
-        }
-
-        return transform;
-    }
-
-    private Object constCast(final ParserRuleContext source, final Object constant, final Cast cast) {
-        if (cast instanceof Transform) {
-            final Transform transform = (Transform)cast;
-            return invokeTransform(source, transform, constant);
-        } else {
-            final Sort fsort = cast.from.sort;
-            final Sort tsort = cast.to.sort;
-
-            if (fsort == tsort) {
-                return constant;
-            } else if (fsort.numeric && tsort.numeric) {
-                Number number;
-
-                if (fsort == Sort.CHAR) {
-                    number = (int)(char)constant;
-                } else {
-                    number = (Number)constant;
-                }
-
-                switch (tsort) {
-                    case BYTE:   return number.byteValue();
-                    case SHORT:  return number.shortValue();
-                    case CHAR:   return (char)number.intValue();
-                    case INT:    return number.intValue();
-                    case LONG:   return number.longValue();
-                    case FLOAT:  return number.floatValue();
-                    case DOUBLE: return number.doubleValue();
-                    default:
-                        throw new IllegalStateException(Metadata.error(source) + "Expected numeric type for cast.");
-                }
-            } else {
-                throw new IllegalStateException(Metadata.error(source) + "No valid constant cast from " +
-                    "[" + cast.from.clazz.getCanonicalName() + "] to " +
-                    "[" + cast.to.clazz.getCanonicalName() + "].");
-            }
-        }
-    }
-
-    private Object invokeTransform(final ParserRuleContext source, final Transform transform, final Object object) {
-        final Method method = transform.method;
-        final java.lang.reflect.Method jmethod = method.reflect;
-        final int modifiers = jmethod.getModifiers();
-
-        try {
-            if (java.lang.reflect.Modifier.isStatic(modifiers)) {
-                return jmethod.invoke(null, object);
-            } else {
-                return jmethod.invoke(object);
-            }
-        } catch (IllegalAccessException | IllegalArgumentException |
-            java.lang.reflect.InvocationTargetException | NullPointerException |
-            ExceptionInInitializerError exception) {
-            throw new IllegalStateException(Metadata.error(source) + "Unable to invoke transform to cast constant from " +
-                "[" + transform.from.name + "] to [" + transform.to.name + "].");
-        }
-    }
-
-    private Type promoteNumeric(final Type from, boolean decimal, boolean primitive) {
-        final Sort sort = from.sort;
-
-        if (sort == Sort.DEF) {
-            return definition.defType;
-        } else if ((sort == Sort.DOUBLE || sort == Sort.DOUBLE_OBJ || sort == Sort.NUMBER) && decimal) {
-            return primitive ? definition.doubleType : definition.doubleobjType;
-        } else if ((sort == Sort.FLOAT || sort == Sort.FLOAT_OBJ) && decimal) {
-            return primitive ? definition.floatType : definition.floatobjType;
-        } else if (sort == Sort.LONG || sort == Sort.LONG_OBJ || sort == Sort.NUMBER) {
-            return primitive ? definition.longType : definition.longobjType;
-        } else if (sort.numeric) {
-            return primitive ? definition.intType : definition.intobjType;
-        }
-
-        return null;
-    }
-
-    private Type promoteNumeric(final Type from0, final Type from1, boolean decimal, boolean primitive) {
-        final Sort sort0 = from0.sort;
-        final Sort sort1 = from1.sort;
-
-        if (sort0 == Sort.DEF || sort1 == Sort.DEF) {
-            return definition.defType;
-        }
-
-        if (decimal) {
-            if (sort0 == Sort.DOUBLE || sort0 == Sort.DOUBLE_OBJ || sort0 == Sort.NUMBER ||
-                sort1 == Sort.DOUBLE || sort1 == Sort.DOUBLE_OBJ || sort1 == Sort.NUMBER) {
-                return primitive ? definition.doubleType : definition.doubleobjType;
-            } else if (sort0 == Sort.FLOAT || sort0 == Sort.FLOAT_OBJ || sort1 == Sort.FLOAT || sort1 == Sort.FLOAT_OBJ) {
-                return primitive ? definition.floatType : definition.floatobjType;
-            }
-        }
-
-        if (sort0 == Sort.LONG || sort0 == Sort.LONG_OBJ || sort0 == Sort.NUMBER ||
-            sort1 == Sort.LONG || sort1 == Sort.LONG_OBJ || sort1 == Sort.NUMBER) {
-            return primitive ? definition.longType : definition.longobjType;
-        } else if (sort0.numeric && sort1.numeric) {
-            return primitive ? definition.intType : definition.intobjType;
-        }
+    public Void visitIncrement(final IncrementContext ctx) {
+        expression.processIncrement(ctx);
 
         return null;
     }
-
-    private Type promoteAdd(final Type from0, final Type from1) {
-        final Sort sort0 = from0.sort;
-        final Sort sort1 = from1.sort;
-
-        if (sort0 == Sort.STRING || sort1 == Sort.STRING) {
-            return definition.stringType;
-        }
-
-        return promoteNumeric(from0, from1, true, true);
-    }
-
-    private Type promoteXor(final Type from0, final Type from1) {
-        final Sort sort0 = from0.sort;
-        final Sort sort1 = from1.sort;
-
-        if (sort0.bool || sort1.bool) {
-            return definition.booleanType;
-        }
-
-        return promoteNumeric(from0, from1, false, true);
-    }
-
-    private Type promoteEquality(final Type from0, final Type from1) {
-        final Sort sort0 = from0.sort;
-        final Sort sort1 = from1.sort;
-
-        if (sort0 == Sort.DEF || sort1 == Sort.DEF) {
-            return definition.defType;
-        }
-
-        final boolean primitive = sort0.primitive && sort1.primitive;
-
-        if (sort0.bool && sort1.bool) {
-            return primitive ? definition.booleanType : definition.booleanobjType;
-        }
-
-        if (sort0.numeric && sort1.numeric) {
-            return promoteNumeric(from0, from1, true, primitive);
-        }
-
-        return definition.objectType;
-    }
-
-    private Type promoteReference(final Type from0, final Type from1) {
-        final Sort sort0 = from0.sort;
-        final Sort sort1 = from1.sort;
-
-        if (sort0 == Sort.DEF || sort1 == Sort.DEF) {
-            return definition.defType;
-        }
-
-        if (sort0.primitive && sort1.primitive) {
-            if (sort0.bool && sort1.bool) {
-                return definition.booleanType;
-            }
-
-            if (sort0.numeric && sort1.numeric) {
-                return promoteNumeric(from0, from1, true, true);
-            }
-        }
-
-        return definition.objectType;
-    }
-
-    private Type promoteConditional(final Type from0, final Type from1, final Object const0, final Object const1) {
-        if (from0.equals(from1)) {
-            return from0;
-        }
-
-        final Sort sort0 = from0.sort;
-        final Sort sort1 = from1.sort;
-
-        if (sort0 == Sort.DEF || sort1 == Sort.DEF) {
-            return definition.defType;
-        }
-
-        final boolean primitive = sort0.primitive && sort1.primitive;
-
-        if (sort0.bool && sort1.bool) {
-            return primitive ? definition.booleanType : definition.booleanobjType;
-        }
-
-        if (sort0.numeric && sort1.numeric) {
-            if (sort0 == Sort.DOUBLE || sort0 == Sort.DOUBLE_OBJ || sort1 == Sort.DOUBLE || sort1 == Sort.DOUBLE_OBJ) {
-                return primitive ? definition.doubleType : definition.doubleobjType;
-            } else if (sort0 == Sort.FLOAT || sort0 == Sort.FLOAT_OBJ || sort1 == Sort.FLOAT || sort1 == Sort.FLOAT_OBJ) {
-                return primitive ? definition.floatType : definition.floatobjType;
-            } else if (sort0 == Sort.LONG || sort0 == Sort.LONG_OBJ || sort1 == Sort.LONG || sort1 == Sort.LONG_OBJ) {
-                return sort0.primitive && sort1.primitive ? definition.longType : definition.longobjType;
-            } else {
-                if (sort0 == Sort.BYTE || sort0 == Sort.BYTE_OBJ) {
-                    if (sort1 == Sort.BYTE || sort1 == Sort.BYTE_OBJ) {
-                        return primitive ? definition.byteType : definition.byteobjType;
-                    } else if (sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ) {
-                        if (const1 != null) {
-                            final short constant = (short)const1;
-
-                            if (constant <= Byte.MAX_VALUE && constant >= Byte.MIN_VALUE) {
-                                return primitive ? definition.byteType : definition.byteobjType;
-                            }
-                        }
-
-                        return primitive ? definition.shortType : definition.shortobjType;
-                    } else if (sort1 == Sort.CHAR || sort1 == Sort.CHAR_OBJ) {
-                        return primitive ? definition.intType : definition.intobjType;
-                    } else if (sort1 == Sort.INT || sort1 == Sort.INT_OBJ) {
-                        if (const1 != null) {
-                            final int constant = (int)const1;
-
-                            if (constant <= Byte.MAX_VALUE && constant >= Byte.MIN_VALUE) {
-                                return primitive ? definition.byteType : definition.byteobjType;
-                            }
-                        }
-
-                        return primitive ? definition.intType : definition.intobjType;
-                    }
-                } else if (sort0 == Sort.SHORT || sort0 == Sort.SHORT_OBJ) {
-                    if (sort1 == Sort.BYTE || sort1 == Sort.BYTE_OBJ) {
-                        if (const0 != null) {
-                            final short constant = (short)const0;
-
-                            if (constant <= Byte.MAX_VALUE && constant >= Byte.MIN_VALUE) {
-                                return primitive ? definition.byteType : definition.byteobjType;
-                            }
-                        }
-
-                        return primitive ? definition.shortType : definition.shortobjType;
-                    } else if (sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ) {
-                        return primitive ? definition.shortType : definition.shortobjType;
-                    } else if (sort1 == Sort.CHAR || sort1 == Sort.CHAR_OBJ) {
-                        return primitive ? definition.intType : definition.intobjType;
-                    } else if (sort1 == Sort.INT || sort1 == Sort.INT_OBJ) {
-                        if (const1 != null) {
-                            final int constant = (int)const1;
-
-                            if (constant <= Short.MAX_VALUE && constant >= Short.MIN_VALUE) {
-                                return primitive ? definition.shortType : definition.shortobjType;
-                            }
-                        }
-
-                        return primitive ? definition.intType : definition.intobjType;
-                    }
-                } else if (sort0 == Sort.CHAR || sort0 == Sort.CHAR_OBJ) {
-                    if (sort1 == Sort.BYTE || sort1 == Sort.BYTE_OBJ) {
-                        return primitive ? definition.intType : definition.intobjType;
-                    } else if (sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ) {
-                        return primitive ? definition.intType : definition.intobjType;
-                    } else if (sort1 == Sort.CHAR || sort1 == Sort.CHAR_OBJ) {
-                        return primitive ? definition.charType : definition.charobjType;
-                    } else if (sort1 == Sort.INT || sort1 == Sort.INT_OBJ) {
-                        if (const1 != null) {
-                            final int constant = (int)const1;
-
-                            if (constant <= Character.MAX_VALUE && constant >= Character.MIN_VALUE) {
-                                return primitive ? definition.byteType : definition.byteobjType;
-                            }
-                        }
-
-                        return primitive ? definition.intType : definition.intobjType;
-                    }
-                } else if (sort0 == Sort.INT || sort0 == Sort.INT_OBJ) {
-                    if (sort1 == Sort.BYTE || sort1 == Sort.BYTE_OBJ) {
-                        if (const0 != null) {
-                            final int constant = (int)const0;
-
-                            if (constant <= Byte.MAX_VALUE && constant >= Byte.MIN_VALUE) {
-                                return primitive ? definition.byteType : definition.byteobjType;
-                            }
-                        }
-
-                        return primitive ? definition.intType : definition.intobjType;
-                    } else if (sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ) {
-                        if (const0 != null) {
-                            final int constant = (int)const0;
-
-                            if (constant <= Short.MAX_VALUE && constant >= Short.MIN_VALUE) {
-                                return primitive ? definition.byteType : definition.byteobjType;
-                            }
-                        }
-
-                        return primitive ? definition.intType : definition.intobjType;
-                    } else if (sort1 == Sort.CHAR || sort1 == Sort.CHAR_OBJ) {
-                        if (const0 != null) {
-                            final int constant = (int)const0;
-
-                            if (constant <= Character.MAX_VALUE && constant >= Character.MIN_VALUE) {
-                                return primitive ? definition.byteType : definition.byteobjType;
-                            }
-                        }
-
-                        return primitive ? definition.intType : definition.intobjType;
-                    } else if (sort1 == Sort.INT || sort1 == Sort.INT_OBJ) {
-                        return primitive ? definition.intType : definition.intobjType;
-                    }
-                }
-            }
-        }
-
-        final Pair pair = new Pair(from0, from1);
-        final Type bound = definition.bounds.get(pair);
-
-        return bound == null ? definition.objectType : bound;
-    }
 }

+ 563 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java

@@ -0,0 +1,563 @@
+/*
+ * 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.elasticsearch.painless.Definition.Cast;
+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.Metadata.ExpressionMetadata;
+
+class AnalyzerCaster {
+    private final Definition definition;
+
+    AnalyzerCaster(final Definition definition) {
+        this.definition = definition;
+    }
+
+    void markCast(final ExpressionMetadata emd) {
+        if (emd.from == null) {
+            throw new IllegalStateException(AnalyzerUtility.error(emd.source) + "From cast type should never be null.");
+        }
+
+        if (emd.to != null) {
+            emd.cast = getLegalCast(emd.source, emd.from, emd.to, emd.explicit || !emd.typesafe);
+
+            if (emd.preConst != null && emd.to.sort.constant) {
+                emd.postConst = constCast(emd.source, emd.preConst, emd.cast);
+            }
+        } else {
+            throw new IllegalStateException(AnalyzerUtility.error(emd.source) + "To cast type should never be null.");
+        }
+    }
+
+    Cast getLegalCast(final ParserRuleContext source, final Type from, final Type to, final boolean explicit) {
+        final Cast cast = new Cast(from, to);
+
+        if (from.equals(to)) {
+            return cast;
+        }
+
+        if (from.sort == Sort.DEF && to.sort != Sort.VOID || from.sort != Sort.VOID && to.sort == Sort.DEF) {
+            final Transform transform = definition.transforms.get(cast);
+
+            if (transform != null) {
+                return transform;
+            }
+
+            return cast;
+        }
+
+        switch (from.sort) {
+            case BOOL:
+                switch (to.sort) {
+                    case OBJECT:
+                    case BOOL_OBJ:
+                        return checkTransform(source, cast);
+                }
+
+                break;
+            case BYTE:
+                switch (to.sort) {
+                    case SHORT:
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                        return cast;
+                    case CHAR:
+                        if (explicit)
+                            return cast;
+
+                        break;
+                    case OBJECT:
+                    case NUMBER:
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case CHAR_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case SHORT:
+                switch (to.sort) {
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                        return cast;
+                    case BYTE:
+                    case CHAR:
+                        if (explicit)
+                            return cast;
+
+                        break;
+                    case OBJECT:
+                    case NUMBER:
+                    case SHORT_OBJ:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE_OBJ:
+                    case CHAR_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case CHAR:
+                switch (to.sort) {
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                        return cast;
+                    case BYTE:
+                    case SHORT:
+                        if (explicit)
+                            return cast;
+
+                        break;
+                    case OBJECT:
+                    case NUMBER:
+                    case CHAR_OBJ:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case INT:
+                switch (to.sort) {
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                        return cast;
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                        if (explicit)
+                            return cast;
+
+                        break;
+                    case OBJECT:
+                    case NUMBER:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case CHAR_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case LONG:
+                switch (to.sort) {
+                    case FLOAT:
+                    case DOUBLE:
+                        return cast;
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                    case INT:
+                        if (explicit)
+                            return cast;
+
+                        break;
+                    case OBJECT:
+                    case NUMBER:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case CHAR_OBJ:
+                    case INT_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case FLOAT:
+                switch (to.sort) {
+                    case DOUBLE:
+                        return cast;
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                    case INT:
+                    case LONG:
+                        if (explicit)
+                            return cast;
+
+                        break;
+                    case OBJECT:
+                    case NUMBER:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case CHAR_OBJ:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case DOUBLE:
+                switch (to.sort) {
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                        if (explicit)
+                            return cast;
+
+                        break;
+                    case OBJECT:
+                    case NUMBER:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case CHAR_OBJ:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case OBJECT:
+            case NUMBER:
+                switch (to.sort) {
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case BOOL_OBJ:
+                switch (to.sort) {
+                    case BOOL:
+                        return checkTransform(source, cast);
+                }
+
+                break;
+            case BYTE_OBJ:
+                switch (to.sort) {
+                    case BYTE:
+                    case SHORT:
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                    case SHORT_OBJ:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case CHAR:
+                    case CHAR_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case SHORT_OBJ:
+                switch (to.sort) {
+                    case SHORT:
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE:
+                    case CHAR:
+                    case BYTE_OBJ:
+                    case CHAR_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case CHAR_OBJ:
+                switch (to.sort) {
+                    case CHAR:
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE:
+                    case SHORT:
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case INT_OBJ:
+                switch (to.sort) {
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case CHAR_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case LONG_OBJ:
+                switch (to.sort) {
+                    case LONG:
+                    case FLOAT:
+                    case DOUBLE:
+                    case FLOAT_OBJ:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                    case INT:
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case CHAR_OBJ:
+                    case INT_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case FLOAT_OBJ:
+                switch (to.sort) {
+                    case FLOAT:
+                    case DOUBLE:
+                    case DOUBLE_OBJ:
+                        return checkTransform(source, cast);
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                    case INT:
+                    case LONG:
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case CHAR_OBJ:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+            case DOUBLE_OBJ:
+                switch (to.sort) {
+                    case DOUBLE:
+                        return checkTransform(source, cast);
+                    case BYTE:
+                    case SHORT:
+                    case CHAR:
+                    case INT:
+                    case LONG:
+                    case FLOAT:
+                    case BYTE_OBJ:
+                    case SHORT_OBJ:
+                    case CHAR_OBJ:
+                    case INT_OBJ:
+                    case LONG_OBJ:
+                    case FLOAT_OBJ:
+                        if (explicit)
+                            return checkTransform(source, cast);
+
+                        break;
+                }
+
+                break;
+        }
+
+        try {
+            from.clazz.asSubclass(to.clazz);
+
+            return cast;
+        } catch (final ClassCastException cce0) {
+            try {
+                if (explicit) {
+                    to.clazz.asSubclass(from.clazz);
+
+                    return cast;
+                } else {
+                    throw new ClassCastException(
+                        AnalyzerUtility.error(source) + "Cannot cast from [" + from.name + "] to [" + to.name + "].");
+                }
+            } catch (final ClassCastException cce1) {
+                throw new ClassCastException(
+                    AnalyzerUtility.error(source) + "Cannot cast from [" + from.name + "] to [" + to.name + "].");
+            }
+        }
+    }
+
+    private Transform checkTransform(final ParserRuleContext source, final Cast cast) {
+        final Transform transform = definition.transforms.get(cast);
+
+        if (transform == null) {
+            throw new ClassCastException(
+                AnalyzerUtility.error(source) + "Cannot cast from [" + cast.from.name + "] to [" + cast.to.name + "].");
+        }
+
+        return transform;
+    }
+
+    private Object constCast(final ParserRuleContext source, final Object constant, final Cast cast) {
+        if (cast instanceof Transform) {
+            final Transform transform = (Transform)cast;
+            return invokeTransform(source, transform, constant);
+        } else {
+            final Sort fsort = cast.from.sort;
+            final Sort tsort = cast.to.sort;
+
+            if (fsort == tsort) {
+                return constant;
+            } else if (fsort.numeric && tsort.numeric) {
+                Number number;
+
+                if (fsort == Sort.CHAR) {
+                    number = (int)(char)constant;
+                } else {
+                    number = (Number)constant;
+                }
+
+                switch (tsort) {
+                    case BYTE:   return number.byteValue();
+                    case SHORT:  return number.shortValue();
+                    case CHAR:   return (char)number.intValue();
+                    case INT:    return number.intValue();
+                    case LONG:   return number.longValue();
+                    case FLOAT:  return number.floatValue();
+                    case DOUBLE: return number.doubleValue();
+                    default:
+                        throw new IllegalStateException(AnalyzerUtility.error(source) + "Expected numeric type for cast.");
+                }
+            } else {
+                throw new IllegalStateException(AnalyzerUtility.error(source) + "No valid constant cast from " +
+                    "[" + cast.from.clazz.getCanonicalName() + "] to " +
+                    "[" + cast.to.clazz.getCanonicalName() + "].");
+            }
+        }
+    }
+
+    private Object invokeTransform(final ParserRuleContext source, final Transform transform, final Object object) {
+        final Method method = transform.method;
+        final java.lang.reflect.Method jmethod = method.reflect;
+        final int modifiers = jmethod.getModifiers();
+
+        try {
+            if (java.lang.reflect.Modifier.isStatic(modifiers)) {
+                return jmethod.invoke(null, object);
+            } else {
+                return jmethod.invoke(object);
+            }
+        } catch (IllegalAccessException | IllegalArgumentException |
+            java.lang.reflect.InvocationTargetException | NullPointerException |
+            ExceptionInInitializerError exception) {
+            throw new IllegalStateException(AnalyzerUtility.error(source) + "Unable to invoke transform to cast constant from " +
+                "[" + transform.from.name + "] to [" + transform.to.name + "].");
+        }
+    }
+}

+ 868 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerExpression.java

@@ -0,0 +1,868 @@
+/*
+ * 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.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Metadata.ExpressionMetadata;
+import org.elasticsearch.painless.Metadata.ExternalMetadata;
+import org.elasticsearch.painless.PainlessParser.AssignmentContext;
+import org.elasticsearch.painless.PainlessParser.BinaryContext;
+import org.elasticsearch.painless.PainlessParser.BoolContext;
+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.DecltypeContext;
+import org.elasticsearch.painless.PainlessParser.ExpressionContext;
+import org.elasticsearch.painless.PainlessParser.ExternalContext;
+import org.elasticsearch.painless.PainlessParser.ExtstartContext;
+import org.elasticsearch.painless.PainlessParser.FalseContext;
+import org.elasticsearch.painless.PainlessParser.IncrementContext;
+import org.elasticsearch.painless.PainlessParser.NullContext;
+import org.elasticsearch.painless.PainlessParser.NumericContext;
+import org.elasticsearch.painless.PainlessParser.PostincContext;
+import org.elasticsearch.painless.PainlessParser.PreincContext;
+import org.elasticsearch.painless.PainlessParser.TrueContext;
+import org.elasticsearch.painless.PainlessParser.UnaryContext;
+
+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 AnalyzerExpression {
+    private final Metadata metadata;
+    private final Definition definition;
+    private final CompilerSettings settings;
+
+    private final Analyzer analyzer;
+    private final AnalyzerCaster caster;
+    private final AnalyzerPromoter promoter;
+
+    AnalyzerExpression(final Metadata metadata, final Analyzer analyzer,
+                       final AnalyzerCaster caster, final AnalyzerPromoter promoter) {
+        this.metadata = metadata;
+        this.definition = metadata.definition;
+        this.settings = metadata.settings;
+
+        this.analyzer = analyzer;
+        this.caster = caster;
+        this.promoter = promoter;
+    }
+
+    void processNumeric(final NumericContext ctx) {
+        final ExpressionMetadata numericemd = metadata.getExpressionMetadata(ctx);
+        final boolean negate = ctx.parent instanceof UnaryContext && ((UnaryContext)ctx.parent).SUB() != null;
+
+        if (ctx.DECIMAL() != null) {
+            final String svalue = (negate ? "-" : "") + ctx.DECIMAL().getText();
+
+            if (svalue.endsWith("f") || svalue.endsWith("F")) {
+                try {
+                    numericemd.from = definition.floatType;
+                    numericemd.preConst = Float.parseFloat(svalue.substring(0, svalue.length() - 1));
+                } catch (NumberFormatException exception) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Invalid float constant [" + svalue + "].");
+                }
+            } else {
+                try {
+                    numericemd.from = definition.doubleType;
+                    numericemd.preConst = Double.parseDouble(svalue);
+                } catch (NumberFormatException exception) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Invalid double constant [" + svalue + "].");
+                }
+            }
+        } else {
+            String svalue = negate ? "-" : "";
+            int radix;
+
+            if (ctx.OCTAL() != null) {
+                svalue += ctx.OCTAL().getText();
+                radix = 8;
+            } else if (ctx.INTEGER() != null) {
+                svalue += ctx.INTEGER().getText();
+                radix = 10;
+            } else if (ctx.HEX() != null) {
+                svalue += ctx.HEX().getText();
+                radix = 16;
+            } else {
+                throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+            }
+
+            if (svalue.endsWith("d") || svalue.endsWith("D")) {
+                try {
+                    numericemd.from = definition.doubleType;
+                    numericemd.preConst = Double.parseDouble(svalue.substring(0, svalue.length() - 1));
+                } catch (NumberFormatException exception) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Invalid float constant [" + svalue + "].");
+                }
+            } else if (svalue.endsWith("f") || svalue.endsWith("F")) {
+                try {
+                    numericemd.from = definition.floatType;
+                    numericemd.preConst = Float.parseFloat(svalue.substring(0, svalue.length() - 1));
+                } catch (NumberFormatException exception) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Invalid float constant [" + svalue + "].");
+                }
+            } else if (svalue.endsWith("l") || svalue.endsWith("L")) {
+                try {
+                    numericemd.from = definition.longType;
+                    numericemd.preConst = Long.parseLong(svalue.substring(0, svalue.length() - 1), radix);
+                } catch (NumberFormatException exception) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Invalid long constant [" + svalue + "].");
+                }
+            } else {
+                try {
+                    final Type type = numericemd.to;
+                    final Sort sort = type == null ? Sort.INT : type.sort;
+                    final int value = Integer.parseInt(svalue, radix);
+
+                    if (sort == Sort.BYTE && value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
+                        numericemd.from = definition.byteType;
+                        numericemd.preConst = (byte)value;
+                    } else if (sort == Sort.CHAR && value >= Character.MIN_VALUE && value <= Character.MAX_VALUE) {
+                        numericemd.from = definition.charType;
+                        numericemd.preConst = (char)value;
+                    } else if (sort == Sort.SHORT && value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
+                        numericemd.from = definition.shortType;
+                        numericemd.preConst = (short)value;
+                    } else {
+                        numericemd.from = definition.intType;
+                        numericemd.preConst = value;
+                    }
+                } catch (NumberFormatException exception) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Invalid int constant [" + svalue + "].");
+                }
+            }
+        }
+    }
+
+    void processChar(final CharContext ctx) {
+        final ExpressionMetadata charemd = metadata.getExpressionMetadata(ctx);
+
+        if (ctx.CHAR() == null) {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+
+        charemd.preConst = ctx.CHAR().getText().charAt(0);
+        charemd.from = definition.charType;
+    }
+
+    void processTrue(final TrueContext ctx) {
+        final ExpressionMetadata trueemd = metadata.getExpressionMetadata(ctx);
+
+        if (ctx.TRUE() == null) {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+
+        trueemd.preConst = true;
+        trueemd.from = definition.booleanType;
+    }
+
+    void processFalse(final FalseContext ctx) {
+        final ExpressionMetadata falseemd = metadata.getExpressionMetadata(ctx);
+
+        if (ctx.FALSE() == null) {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+
+        falseemd.preConst = false;
+        falseemd.from = definition.booleanType;
+    }
+
+    void processNull(final NullContext ctx) {
+        final ExpressionMetadata nullemd = metadata.getExpressionMetadata(ctx);
+
+        if (ctx.NULL() == null) {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+
+        nullemd.isNull = true;
+
+        if (nullemd.to != null) {
+            if (nullemd.to.sort.primitive) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                    "Cannot cast null to a primitive type [" + nullemd.to.name + "].");
+            }
+
+            nullemd.from = nullemd.to;
+        } else {
+            nullemd.from = definition.objectType;
+        }
+    }
+
+    void processExternal(final ExternalContext ctx) {
+        final ExpressionMetadata extemd = metadata.getExpressionMetadata(ctx);
+
+        final ExtstartContext extstartctx = ctx.extstart();
+        final ExternalMetadata extstartemd = metadata.createExternalMetadata(extstartctx);
+        extstartemd.read = extemd.read;
+        analyzer.visit(extstartctx);
+
+        extemd.statement = extstartemd.statement;
+        extemd.preConst = extstartemd.constant;
+        extemd.from = extstartemd.current;
+        extemd.typesafe = extstartemd.current.sort != Sort.DEF;
+    }
+
+    void processPostinc(final PostincContext ctx) {
+        final ExpressionMetadata postincemd = metadata.getExpressionMetadata(ctx);
+
+        final ExtstartContext extstartctx = ctx.extstart();
+        final ExternalMetadata extstartemd = metadata.createExternalMetadata(extstartctx);
+        extstartemd.read = postincemd.read;
+        extstartemd.storeExpr = ctx.increment();
+        extstartemd.token = ADD;
+        extstartemd.post = true;
+        analyzer.visit(extstartctx);
+
+        postincemd.statement = true;
+        postincemd.from = extstartemd.read ? extstartemd.current : definition.voidType;
+        postincemd.typesafe = extstartemd.current.sort != Sort.DEF;
+    }
+
+    void processPreinc(final PreincContext ctx) {
+        final ExpressionMetadata preincemd = metadata.getExpressionMetadata(ctx);
+
+        final ExtstartContext extstartctx = ctx.extstart();
+        final ExternalMetadata extstartemd = metadata.createExternalMetadata(extstartctx);
+        extstartemd.read = preincemd.read;
+        extstartemd.storeExpr = ctx.increment();
+        extstartemd.token = ADD;
+        extstartemd.pre = true;
+        analyzer.visit(extstartctx);
+
+        preincemd.statement = true;
+        preincemd.from = extstartemd.read ? extstartemd.current : definition.voidType;
+        preincemd.typesafe = extstartemd.current.sort != Sort.DEF;
+    }
+
+    void processUnary(final UnaryContext ctx) {
+        final ExpressionMetadata unaryemd = metadata.getExpressionMetadata(ctx);
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+
+        if (ctx.BOOLNOT() != null) {
+            expremd.to = definition.booleanType;
+            analyzer.visit(exprctx);
+            caster.markCast(expremd);
+
+            if (expremd.postConst != null) {
+                unaryemd.preConst = !(boolean)expremd.postConst;
+            }
+
+            unaryemd.from = definition.booleanType;
+        } else if (ctx.BWNOT() != null || ctx.ADD() != null || ctx.SUB() != null) {
+            analyzer.visit(exprctx);
+
+            final Type promote = promoter.promoteNumeric(expremd.from, ctx.BWNOT() == null, true);
+
+            if (promote == null) {
+                throw new ClassCastException(AnalyzerUtility.error(ctx) + "Cannot apply [" + ctx.getChild(0).getText() + "] " +
+                    "operation to type [" + expremd.from.name + "].");
+            }
+
+            expremd.to = promote;
+            caster.markCast(expremd);
+
+            if (expremd.postConst != null) {
+                final Sort sort = promote.sort;
+
+                if (ctx.BWNOT() != null) {
+                    if (sort == Sort.INT) {
+                        unaryemd.preConst = ~(int)expremd.postConst;
+                    } else if (sort == Sort.LONG) {
+                        unaryemd.preConst = ~(long)expremd.postConst;
+                    } else {
+                        throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                    }
+                } else if (ctx.SUB() != null) {
+                    if (exprctx instanceof NumericContext) {
+                        unaryemd.preConst = expremd.postConst;
+                    } else {
+                        if (sort == Sort.INT) {
+                            if (settings.getNumericOverflow()) {
+                                unaryemd.preConst = -(int)expremd.postConst;
+                            } else {
+                                unaryemd.preConst = Math.negateExact((int)expremd.postConst);
+                            }
+                        } else if (sort == Sort.LONG) {
+                            if (settings.getNumericOverflow()) {
+                                unaryemd.preConst = -(long)expremd.postConst;
+                            } else {
+                                unaryemd.preConst = Math.negateExact((long)expremd.postConst);
+                            }
+                        } else if (sort == Sort.FLOAT) {
+                            unaryemd.preConst = -(float)expremd.postConst;
+                        } else if (sort == Sort.DOUBLE) {
+                            unaryemd.preConst = -(double)expremd.postConst;
+                        } else {
+                            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                        }
+                    }
+                } else if (ctx.ADD() != null) {
+                    if (sort == Sort.INT) {
+                        unaryemd.preConst = +(int)expremd.postConst;
+                    } else if (sort == Sort.LONG) {
+                        unaryemd.preConst = +(long)expremd.postConst;
+                    } else if (sort == Sort.FLOAT) {
+                        unaryemd.preConst = +(float)expremd.postConst;
+                    } else if (sort == Sort.DOUBLE) {
+                        unaryemd.preConst = +(double)expremd.postConst;
+                    } else {
+                        throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                    }
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            }
+
+            unaryemd.from = promote;
+            unaryemd.typesafe = expremd.typesafe;
+        } else {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+    }
+
+    void processCast(final CastContext ctx) {
+        final ExpressionMetadata castemd = metadata.getExpressionMetadata(ctx);
+
+        final DecltypeContext decltypectx = ctx.decltype();
+        final ExpressionMetadata decltypemd = metadata.createExpressionMetadata(decltypectx);
+        analyzer.visit(decltypectx);
+
+        final Type type = decltypemd.from;
+        castemd.from = type;
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+        expremd.to = type;
+        expremd.explicit = true;
+        analyzer.visit(exprctx);
+        caster.markCast(expremd);
+
+        if (expremd.postConst != null) {
+            castemd.preConst = expremd.postConst;
+        }
+
+        castemd.typesafe = expremd.typesafe && castemd.from.sort != Sort.DEF;
+    }
+
+    void processBinary(final BinaryContext ctx) {
+        final ExpressionMetadata binaryemd = metadata.getExpressionMetadata(ctx);
+
+        final ExpressionContext exprctx0 = AnalyzerUtility.updateExpressionTree(ctx.expression(0));
+        final ExpressionMetadata expremd0 = metadata.createExpressionMetadata(exprctx0);
+        analyzer.visit(exprctx0);
+
+        final ExpressionContext exprctx1 = AnalyzerUtility.updateExpressionTree(ctx.expression(1));
+        final ExpressionMetadata expremd1 = metadata.createExpressionMetadata(exprctx1);
+        analyzer.visit(exprctx1);
+
+        final boolean decimal = ctx.MUL() != null || ctx.DIV() != null || ctx.REM() != null || ctx.SUB() != null;
+        final boolean add = ctx.ADD() != null;
+        final boolean xor = ctx.BWXOR() != null;
+        final Type promote = add ? promoter.promoteAdd(expremd0.from, expremd1.from) :
+            xor ? promoter.promoteXor(expremd0.from, expremd1.from) :
+                promoter.promoteNumeric(expremd0.from, expremd1.from, decimal, true);
+
+        if (promote == null) {
+            throw new ClassCastException(AnalyzerUtility.error(ctx) + "Cannot apply [" + ctx.getChild(1).getText() + "] " +
+                "operation to types [" + expremd0.from.name + "] and [" + expremd1.from.name + "].");
+        }
+
+        final Sort sort = promote.sort;
+        expremd0.to = add && sort == Sort.STRING ? expremd0.from : promote;
+        expremd1.to = add && sort == Sort.STRING ? expremd1.from : promote;
+        caster.markCast(expremd0);
+        caster.markCast(expremd1);
+
+        if (expremd0.postConst != null && expremd1.postConst != null) {
+            if (ctx.MUL() != null) {
+                if (sort == Sort.INT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (int)expremd0.postConst * (int)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Math.multiplyExact((int)expremd0.postConst, (int)expremd1.postConst);
+                    }
+                } else if (sort == Sort.LONG) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (long)expremd0.postConst * (long)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Math.multiplyExact((long)expremd0.postConst, (long)expremd1.postConst);
+                    }
+                } else if (sort == Sort.FLOAT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (float)expremd0.postConst * (float)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.multiplyWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
+                    }
+                } else if (sort == Sort.DOUBLE) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (double)expremd0.postConst * (double)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.multiplyWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
+                    }
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.DIV() != null) {
+                if (sort == Sort.INT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (int)expremd0.postConst / (int)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.divideWithoutOverflow((int)expremd0.postConst, (int)expremd1.postConst);
+                    }
+                } else if (sort == Sort.LONG) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (long)expremd0.postConst / (long)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.divideWithoutOverflow((long)expremd0.postConst, (long)expremd1.postConst);
+                    }
+                } else if (sort == Sort.FLOAT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (float)expremd0.postConst / (float)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.divideWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
+                    }
+                } else if (sort == Sort.DOUBLE) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (double)expremd0.postConst / (double)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.divideWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
+                    }
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.REM() != null) {
+                if (sort == Sort.INT) {
+                    binaryemd.preConst = (int)expremd0.postConst % (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    binaryemd.preConst = (long)expremd0.postConst % (long)expremd1.postConst;
+                } else if (sort == Sort.FLOAT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (float)expremd0.postConst % (float)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.remainderWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
+                    }
+                } else if (sort == Sort.DOUBLE) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (double)expremd0.postConst % (double)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.remainderWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
+                    }
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.ADD() != null) {
+                if (sort == Sort.INT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (int)expremd0.postConst + (int)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Math.addExact((int)expremd0.postConst, (int)expremd1.postConst);
+                    }
+                } else if (sort == Sort.LONG) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (long)expremd0.postConst + (long)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Math.addExact((long)expremd0.postConst, (long)expremd1.postConst);
+                    }
+                } else if (sort == Sort.FLOAT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (float)expremd0.postConst + (float)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.addWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
+                    }
+                } else if (sort == Sort.DOUBLE) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (double)expremd0.postConst + (double)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.addWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
+                    }
+                } else if (sort == Sort.STRING) {
+                    binaryemd.preConst = "" + expremd0.postConst + expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.SUB() != null) {
+                if (sort == Sort.INT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (int)expremd0.postConst - (int)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Math.subtractExact((int)expremd0.postConst, (int)expremd1.postConst);
+                    }
+                } else if (sort == Sort.LONG) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (long)expremd0.postConst - (long)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Math.subtractExact((long)expremd0.postConst, (long)expremd1.postConst);
+                    }
+                } else if (sort == Sort.FLOAT) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (float)expremd0.postConst - (float)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.subtractWithoutOverflow((float)expremd0.postConst, (float)expremd1.postConst);
+                    }
+                } else if (sort == Sort.DOUBLE) {
+                    if (settings.getNumericOverflow()) {
+                        binaryemd.preConst = (double)expremd0.postConst - (double)expremd1.postConst;
+                    } else {
+                        binaryemd.preConst = Utility.subtractWithoutOverflow((double)expremd0.postConst, (double)expremd1.postConst);
+                    }
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.LSH() != null) {
+                if (sort == Sort.INT) {
+                    binaryemd.preConst = (int)expremd0.postConst << (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    binaryemd.preConst = (long)expremd0.postConst << (long)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.RSH() != null) {
+                if (sort == Sort.INT) {
+                    binaryemd.preConst = (int)expremd0.postConst >> (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    binaryemd.preConst = (long)expremd0.postConst >> (long)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.USH() != null) {
+                if (sort == Sort.INT) {
+                    binaryemd.preConst = (int)expremd0.postConst >>> (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    binaryemd.preConst = (long)expremd0.postConst >>> (long)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.BWAND() != null) {
+                if (sort == Sort.INT) {
+                    binaryemd.preConst = (int)expremd0.postConst & (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    binaryemd.preConst = (long)expremd0.postConst & (long)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.BWXOR() != null) {
+                if (sort == Sort.BOOL) {
+                    binaryemd.preConst = (boolean)expremd0.postConst ^ (boolean)expremd1.postConst;
+                } else if (sort == Sort.INT) {
+                    binaryemd.preConst = (int)expremd0.postConst ^ (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    binaryemd.preConst = (long)expremd0.postConst ^ (long)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.BWOR() != null) {
+                if (sort == Sort.INT) {
+                    binaryemd.preConst = (int)expremd0.postConst | (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    binaryemd.preConst = (long)expremd0.postConst | (long)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else {
+                throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+            }
+        }
+
+        binaryemd.from = promote;
+        binaryemd.typesafe = expremd0.typesafe && expremd1.typesafe;
+    }
+
+    void processComp(final CompContext ctx) {
+        final ExpressionMetadata compemd = metadata.getExpressionMetadata(ctx);
+        final boolean equality = ctx.EQ() != null || ctx.NE() != null;
+        final boolean reference = ctx.EQR() != null || ctx.NER() != null;
+
+        final ExpressionContext exprctx0 = AnalyzerUtility.updateExpressionTree(ctx.expression(0));
+        final ExpressionMetadata expremd0 = metadata.createExpressionMetadata(exprctx0);
+        analyzer.visit(exprctx0);
+
+        final ExpressionContext exprctx1 = AnalyzerUtility.updateExpressionTree(ctx.expression(1));
+        final ExpressionMetadata expremd1 = metadata.createExpressionMetadata(exprctx1);
+        analyzer.visit(exprctx1);
+
+        if (expremd0.isNull && expremd1.isNull) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unnecessary comparison of null constants.");
+        }
+
+        final Type promote = equality ? promoter.promoteEquality(expremd0.from, expremd1.from) :
+            reference ? promoter.promoteReference(expremd0.from, expremd1.from) :
+                promoter.promoteNumeric(expremd0.from, expremd1.from, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(AnalyzerUtility.error(ctx) + "Cannot apply [" + ctx.getChild(1).getText() + "] " +
+                "operation to types [" + expremd0.from.name + "] and [" + expremd1.from.name + "].");
+        }
+
+        expremd0.to = promote;
+        expremd1.to = promote;
+        caster.markCast(expremd0);
+        caster.markCast(expremd1);
+
+        if (expremd0.postConst != null && expremd1.postConst != null) {
+            final Sort sort = promote.sort;
+
+            if (ctx.EQ() != null || ctx.EQR() != null) {
+                if (sort == Sort.BOOL) {
+                    compemd.preConst = (boolean)expremd0.postConst == (boolean)expremd1.postConst;
+                } else if (sort == Sort.INT) {
+                    compemd.preConst = (int)expremd0.postConst == (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    compemd.preConst = (long)expremd0.postConst == (long)expremd1.postConst;
+                } else if (sort == Sort.FLOAT) {
+                    compemd.preConst = (float)expremd0.postConst == (float)expremd1.postConst;
+                } else if (sort == Sort.DOUBLE) {
+                    compemd.preConst = (double)expremd0.postConst == (double)expremd1.postConst;
+                } else {
+                    if (ctx.EQ() != null && !expremd0.isNull && !expremd1.isNull) {
+                        compemd.preConst = expremd0.postConst.equals(expremd1.postConst);
+                    } else if (ctx.EQR() != null) {
+                        compemd.preConst = expremd0.postConst == expremd1.postConst;
+                    } else {
+                        throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                    }
+                }
+            } else if (ctx.NE() != null || ctx.NER() != null) {
+                if (sort == Sort.BOOL) {
+                    compemd.preConst = (boolean)expremd0.postConst != (boolean)expremd1.postConst;
+                } else if (sort == Sort.INT) {
+                    compemd.preConst = (int)expremd0.postConst != (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    compemd.preConst = (long)expremd0.postConst != (long)expremd1.postConst;
+                } else if (sort == Sort.FLOAT) {
+                    compemd.preConst = (float)expremd0.postConst != (float)expremd1.postConst;
+                } else if (sort == Sort.DOUBLE) {
+                    compemd.preConst = (double)expremd0.postConst != (double)expremd1.postConst;
+                } else {
+                    if (ctx.NE() != null && !expremd0.isNull && !expremd1.isNull) {
+                        compemd.preConst = expremd0.postConst.equals(expremd1.postConst);
+                    } else if (ctx.NER() != null) {
+                        compemd.preConst = expremd0.postConst == expremd1.postConst;
+                    } else {
+                        throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                    }
+                }
+            } else if (ctx.GTE() != null) {
+                if (sort == Sort.INT) {
+                    compemd.preConst = (int)expremd0.postConst >= (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    compemd.preConst = (long)expremd0.postConst >= (long)expremd1.postConst;
+                } else if (sort == Sort.FLOAT) {
+                    compemd.preConst = (float)expremd0.postConst >= (float)expremd1.postConst;
+                } else if (sort == Sort.DOUBLE) {
+                    compemd.preConst = (double)expremd0.postConst >= (double)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.GT() != null) {
+                if (sort == Sort.INT) {
+                    compemd.preConst = (int)expremd0.postConst > (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    compemd.preConst = (long)expremd0.postConst > (long)expremd1.postConst;
+                } else if (sort == Sort.FLOAT) {
+                    compemd.preConst = (float)expremd0.postConst > (float)expremd1.postConst;
+                } else if (sort == Sort.DOUBLE) {
+                    compemd.preConst = (double)expremd0.postConst > (double)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.LTE() != null) {
+                if (sort == Sort.INT) {
+                    compemd.preConst = (int)expremd0.postConst <= (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    compemd.preConst = (long)expremd0.postConst <= (long)expremd1.postConst;
+                } else if (sort == Sort.FLOAT) {
+                    compemd.preConst = (float)expremd0.postConst <= (float)expremd1.postConst;
+                } else if (sort == Sort.DOUBLE) {
+                    compemd.preConst = (double)expremd0.postConst <= (double)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else if (ctx.LT() != null) {
+                if (sort == Sort.INT) {
+                    compemd.preConst = (int)expremd0.postConst < (int)expremd1.postConst;
+                } else if (sort == Sort.LONG) {
+                    compemd.preConst = (long)expremd0.postConst < (long)expremd1.postConst;
+                } else if (sort == Sort.FLOAT) {
+                    compemd.preConst = (float)expremd0.postConst < (float)expremd1.postConst;
+                } else if (sort == Sort.DOUBLE) {
+                    compemd.preConst = (double)expremd0.postConst < (double)expremd1.postConst;
+                } else {
+                    throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                }
+            } else {
+                throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+            }
+        }
+
+        compemd.from = definition.booleanType;
+        compemd.typesafe = expremd0.typesafe && expremd1.typesafe;
+    }
+
+    void processBool(final BoolContext ctx) {
+        final ExpressionMetadata boolemd = metadata.getExpressionMetadata(ctx);
+
+        final ExpressionContext exprctx0 = AnalyzerUtility.updateExpressionTree(ctx.expression(0));
+        final ExpressionMetadata expremd0 = metadata.createExpressionMetadata(exprctx0);
+        expremd0.to = definition.booleanType;
+        analyzer.visit(exprctx0);
+        caster.markCast(expremd0);
+
+        final ExpressionContext exprctx1 = AnalyzerUtility.updateExpressionTree(ctx.expression(1));
+        final ExpressionMetadata expremd1 = metadata.createExpressionMetadata(exprctx1);
+        expremd1.to = definition.booleanType;
+        analyzer.visit(exprctx1);
+        caster.markCast(expremd1);
+
+        if (expremd0.postConst != null && expremd1.postConst != null) {
+            if (ctx.BOOLAND() != null) {
+                boolemd.preConst = (boolean)expremd0.postConst && (boolean)expremd1.postConst;
+            } else if (ctx.BOOLOR() != null) {
+                boolemd.preConst = (boolean)expremd0.postConst || (boolean)expremd1.postConst;
+            } else {
+                throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+            }
+        }
+
+        boolemd.from = definition.booleanType;
+        boolemd.typesafe = expremd0.typesafe && expremd1.typesafe;
+    }
+
+    void processConditional(final ConditionalContext ctx) {
+        final ExpressionMetadata condemd = metadata.getExpressionMetadata(ctx);
+
+        final ExpressionContext exprctx0 = AnalyzerUtility.updateExpressionTree(ctx.expression(0));
+        final ExpressionMetadata expremd0 = metadata.createExpressionMetadata(exprctx0);
+        expremd0.to = definition.booleanType;
+        analyzer.visit(exprctx0);
+        caster.markCast(expremd0);
+
+        if (expremd0.postConst != null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unnecessary conditional statement.");
+        }
+
+        final ExpressionContext exprctx1 = AnalyzerUtility.updateExpressionTree(ctx.expression(1));
+        final ExpressionMetadata expremd1 = metadata.createExpressionMetadata(exprctx1);
+        expremd1.to = condemd.to;
+        expremd1.explicit = condemd.explicit;
+        analyzer.visit(exprctx1);
+
+        final ExpressionContext exprctx2 = AnalyzerUtility.updateExpressionTree(ctx.expression(2));
+        final ExpressionMetadata expremd2 = metadata.createExpressionMetadata(exprctx2);
+        expremd2.to = condemd.to;
+        expremd2.explicit = condemd.explicit;
+        analyzer.visit(exprctx2);
+
+        if (condemd.to == null) {
+            final Type promote = promoter.promoteConditional(expremd1.from, expremd2.from, expremd1.preConst, expremd2.preConst);
+
+            expremd1.to = promote;
+            expremd2.to = promote;
+            condemd.from = promote;
+        } else {
+            condemd.from = condemd.to;
+        }
+
+        caster.markCast(expremd1);
+        caster.markCast(expremd2);
+
+        condemd.typesafe = expremd0.typesafe && expremd1.typesafe;
+    }
+
+    void processAssignment(final AssignmentContext ctx) {
+        final ExpressionMetadata assignemd = metadata.getExpressionMetadata(ctx);
+
+        final ExtstartContext extstartctx = ctx.extstart();
+        final ExternalMetadata extstartemd = metadata.createExternalMetadata(extstartctx);
+
+        extstartemd.read = assignemd.read;
+        extstartemd.storeExpr = AnalyzerUtility.updateExpressionTree(ctx.expression());
+
+        if (ctx.AMUL() != null) {
+            extstartemd.token = MUL;
+        } else if (ctx.ADIV() != null) {
+            extstartemd.token = DIV;
+        } else if (ctx.AREM() != null) {
+            extstartemd.token = REM;
+        } else if (ctx.AADD() != null) {
+            extstartemd.token = ADD;
+        } else if (ctx.ASUB() != null) {
+            extstartemd.token = SUB;
+        } else if (ctx.ALSH() != null) {
+            extstartemd.token = LSH;
+        } else if (ctx.AUSH() != null) {
+            extstartemd.token = USH;
+        } else if (ctx.ARSH() != null) {
+            extstartemd.token = RSH;
+        } else if (ctx.AAND() != null) {
+            extstartemd.token = BWAND;
+        } else if (ctx.AXOR() != null) {
+            extstartemd.token = BWXOR;
+        } else if (ctx.AOR() != null) {
+            extstartemd.token = BWOR;
+        }
+
+        analyzer.visit(extstartctx);
+
+        assignemd.statement = true;
+        assignemd.from = extstartemd.read ? extstartemd.current : definition.voidType;
+        assignemd.typesafe = extstartemd.current.sort != Sort.DEF;
+    }
+
+    void processIncrement(final IncrementContext ctx) {
+        final ExpressionMetadata incremd = metadata.getExpressionMetadata(ctx);
+        final Sort sort = incremd.to == null ? null : incremd.to.sort;
+        final boolean positive = ctx.INCR() != null;
+
+        if (incremd.to == null) {
+            incremd.preConst = positive ? 1 : -1;
+            incremd.from = definition.intType;
+        } else {
+            switch (sort) {
+                case LONG:
+                    incremd.preConst = positive ? 1L : -1L;
+                    incremd.from = definition.longType;
+                    break;
+                case FLOAT:
+                    incremd.preConst = positive ? 1.0F : -1.0F;
+                    incremd.from = definition.floatType;
+                    break;
+                case DOUBLE:
+                    incremd.preConst = positive ? 1.0 : -1.0;
+                    incremd.from = definition.doubleType;
+                    break;
+                default:
+                    incremd.preConst = positive ? 1 : -1;
+                    incremd.from = definition.intType;
+            }
+        }
+    }
+}

+ 816 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerExternal.java

@@ -0,0 +1,816 @@
+/*
+ * 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.elasticsearch.painless.AnalyzerUtility.Variable;
+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.Struct;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Metadata.ExpressionMetadata;
+import org.elasticsearch.painless.Metadata.ExtNodeMetadata;
+import org.elasticsearch.painless.Metadata.ExternalMetadata;
+import org.elasticsearch.painless.PainlessParser.DecltypeContext;
+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.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 java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+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.MUL;
+import static org.elasticsearch.painless.PainlessParser.REM;
+import static org.elasticsearch.painless.PainlessParser.SUB;
+
+class AnalyzerExternal {
+    private final Metadata metadata;
+    private final Definition definition;
+
+    private final Analyzer analyzer;
+    private final AnalyzerUtility utility;
+    private final AnalyzerCaster caster;
+    private final AnalyzerPromoter promoter;
+
+    AnalyzerExternal(final Metadata metadata, final Analyzer analyzer, final AnalyzerUtility utility,
+                     final AnalyzerCaster caster, final AnalyzerPromoter promoter) {
+        this.metadata = metadata;
+        this.definition = metadata.definition;
+
+        this.analyzer = analyzer;
+        this.utility = utility;
+        this.caster = caster;
+        this.promoter = promoter;
+    }
+
+    void processExtstart(final ExtstartContext 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) {
+            metadata.createExtNodeMetadata(ctx, precctx);
+            analyzer.visit(precctx);
+        } else if (castctx != null) {
+            metadata.createExtNodeMetadata(ctx, castctx);
+            analyzer.visit(castctx);
+        } else if (typectx != null) {
+            metadata.createExtNodeMetadata(ctx, typectx);
+            analyzer.visit(typectx);
+        } else if (varctx != null) {
+            metadata.createExtNodeMetadata(ctx, varctx);
+            analyzer.visit(varctx);
+        } else if (newctx != null) {
+            metadata.createExtNodeMetadata(ctx, newctx);
+            analyzer.visit(newctx);
+        } else if (stringctx != null) {
+            metadata.createExtNodeMetadata(ctx, stringctx);
+            analyzer.visit(stringctx);
+        } else {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+    }
+
+    void processExtprec(final ExtprecContext ctx) {
+        final ExtNodeMetadata precenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = precenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        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();
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (dotctx != null || bracectx != null) {
+            ++parentemd.scope;
+        }
+
+        if (precctx != null) {
+            metadata.createExtNodeMetadata(parent, precctx);
+            analyzer.visit(precctx);
+        } else if (castctx != null) {
+            metadata.createExtNodeMetadata(parent, castctx);
+            analyzer.visit(castctx);
+        } else if (typectx != null) {
+            metadata.createExtNodeMetadata(parent, typectx);
+            analyzer.visit(typectx);
+        } else if (varctx != null) {
+            metadata.createExtNodeMetadata(parent, varctx);
+            analyzer.visit(varctx);
+        } else if (newctx != null) {
+            metadata.createExtNodeMetadata(parent, newctx);
+            analyzer.visit(newctx);
+        } else if (stringctx != null) {
+            metadata.createExtNodeMetadata(ctx, stringctx);
+            analyzer.visit(stringctx);
+        } else {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+
+        parentemd.statement = false;
+
+        if (dotctx != null) {
+            --parentemd.scope;
+
+            metadata.createExtNodeMetadata(parent, dotctx);
+            analyzer.visit(dotctx);
+        } else if (bracectx != null) {
+            --parentemd.scope;
+
+            metadata.createExtNodeMetadata(parent, bracectx);
+            analyzer.visit(bracectx);
+        }
+    }
+
+    void processExtcast(final ExtcastContext ctx) {
+        final ExtNodeMetadata castenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = castenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        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) {
+            metadata.createExtNodeMetadata(parent, precctx);
+            analyzer.visit(precctx);
+        } else if (castctx != null) {
+            metadata.createExtNodeMetadata(parent, castctx);
+            analyzer.visit(castctx);
+        } else if (typectx != null) {
+            metadata.createExtNodeMetadata(parent, typectx);
+            analyzer.visit(typectx);
+        } else if (varctx != null) {
+            metadata.createExtNodeMetadata(parent, varctx);
+            analyzer.visit(varctx);
+        } else if (newctx != null) {
+            metadata.createExtNodeMetadata(parent, newctx);
+            analyzer.visit(newctx);
+        } else if (stringctx != null) {
+            metadata.createExtNodeMetadata(ctx, stringctx);
+            analyzer.visit(stringctx);
+        } else {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+
+        final DecltypeContext declctx = ctx.decltype();
+        final ExpressionMetadata declemd = metadata.createExpressionMetadata(declctx);
+        analyzer.visit(declctx);
+
+        castenmd.castTo = caster.getLegalCast(ctx, parentemd.current, declemd.from, true);
+        castenmd.type = declemd.from;
+        parentemd.current = declemd.from;
+        parentemd.statement = false;
+    }
+
+    void processExtbrace(final ExtbraceContext ctx) {
+        final ExtNodeMetadata braceenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = braceenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        final boolean array = parentemd.current.sort == Sort.ARRAY;
+        final boolean def = parentemd.current.sort == Sort.DEF;
+        boolean map = false;
+        boolean list = false;
+
+        try {
+            parentemd.current.clazz.asSubclass(Map.class);
+            map = true;
+        } catch (final ClassCastException exception) {
+            // Do nothing.
+        }
+
+        try {
+            parentemd.current.clazz.asSubclass(List.class);
+            list = true;
+        } catch (final ClassCastException exception) {
+            // Do nothing.
+        }
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        braceenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+
+        if (array || def) {
+            expremd.to = array ? definition.intType : definition.objectType;
+            analyzer.visit(exprctx);
+            caster.markCast(expremd);
+
+            braceenmd.target = "#brace";
+            braceenmd.type = def ? definition.defType :
+                definition.getType(parentemd.current.struct, parentemd.current.type.getDimensions() - 1);
+            analyzeLoadStoreExternal(ctx);
+            parentemd.current = braceenmd.type;
+
+            if (dotctx != null) {
+                metadata.createExtNodeMetadata(parent, dotctx);
+                analyzer.visit(dotctx);
+            } else if (bracectx != null) {
+                metadata.createExtNodeMetadata(parent, bracectx);
+                analyzer.visit(bracectx);
+            }
+        } else {
+            final boolean store = braceenmd.last && parentemd.storeExpr != null;
+            final boolean get = parentemd.read || parentemd.token > 0 || !braceenmd.last;
+            final boolean set = braceenmd.last && store;
+
+            Method getter;
+            Method setter;
+            Type valuetype;
+            Type settype;
+
+            if (map) {
+                getter = parentemd.current.struct.methods.get("get");
+                setter = parentemd.current.struct.methods.get("put");
+
+                if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1)) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                        "Illegal map get shortcut for type [" + parentemd.current.name + "].");
+                }
+
+                if (setter != null && setter.arguments.size() != 2) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                        "Illegal map set shortcut for type [" + parentemd.current.name + "].");
+                }
+
+                if (getter != null && setter != null && (!getter.arguments.get(0).equals(setter.arguments.get(0))
+                    || !getter.rtn.equals(setter.arguments.get(1)))) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Shortcut argument types must match.");
+                }
+
+                valuetype = setter != null ? setter.arguments.get(0) : getter != null ? getter.arguments.get(0) : null;
+                settype = setter == null ? null : setter.arguments.get(1);
+            } else if (list) {
+                getter = parentemd.current.struct.methods.get("get");
+                setter = parentemd.current.struct.methods.get("set");
+
+                if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
+                    getter.arguments.get(0).sort != Sort.INT)) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                        "Illegal list get shortcut for type [" + parentemd.current.name + "].");
+                }
+
+                if (setter != null && (setter.arguments.size() != 2 || setter.arguments.get(0).sort != Sort.INT)) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                        "Illegal list set shortcut for type [" + parentemd.current.name + "].");
+                }
+
+                if (getter != null && setter != null && (!getter.arguments.get(0).equals(setter.arguments.get(0))
+                    || !getter.rtn.equals(setter.arguments.get(1)))) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Shortcut argument types must match.");
+                }
+
+                valuetype = definition.intType;
+                settype = setter == null ? null : setter.arguments.get(1);
+            } else {
+                throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+            }
+
+            if ((get || set) && (!get || getter != null) && (!set || setter != null)) {
+                expremd.to = valuetype;
+                analyzer.visit(exprctx);
+                caster.markCast(expremd);
+
+                braceenmd.target = new Object[] {getter, setter, true, null};
+                braceenmd.type = get ? getter.rtn : settype;
+                analyzeLoadStoreExternal(ctx);
+                parentemd.current = get ? getter.rtn : setter.rtn;
+            }
+        }
+
+        if (braceenmd.target == null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                "Attempting to address a non-array type [" + parentemd.current.name + "] as an array.");
+        }
+    }
+
+    void processExtdot(final ExtdotContext ctx) {
+        final ExtNodeMetadata dotemnd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = dotemnd.parent;
+
+        final ExtcallContext callctx = ctx.extcall();
+        final ExtfieldContext fieldctx = ctx.extfield();
+
+        if (callctx != null) {
+            metadata.createExtNodeMetadata(parent, callctx);
+            analyzer.visit(callctx);
+        } else if (fieldctx != null) {
+            metadata.createExtNodeMetadata(parent, fieldctx);
+            analyzer.visit(fieldctx);
+        }
+    }
+
+    void processExttype(final ExttypeContext ctx) {
+        final ExtNodeMetadata typeenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = typeenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        if (parentemd.current != null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unexpected static type.");
+        }
+
+        final String typestr = ctx.TYPE().getText();
+        typeenmd.type = definition.getType(typestr);
+        parentemd.current = typeenmd.type;
+        parentemd.statik = true;
+
+        final ExtdotContext dotctx = ctx.extdot();
+        metadata.createExtNodeMetadata(parent, dotctx);
+        analyzer.visit(dotctx);
+    }
+
+    void processExtcall(final ExtcallContext ctx) {
+        final ExtNodeMetadata callenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = callenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        callenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
+
+        final String name = ctx.EXTID().getText();
+
+        if (parentemd.current.sort == Sort.ARRAY) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unexpected call [" + name + "] on an array.");
+        } else if (callenmd.last && parentemd.storeExpr != null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Cannot assign a value to a call [" + name + "].");
+        }
+
+        final Struct struct = parentemd.current.struct;
+        final List<ExpressionContext> arguments = ctx.arguments().expression();
+        final int size = arguments.size();
+        Type[] types;
+
+        final Method method = parentemd.statik ? struct.functions.get(name) : struct.methods.get(name);
+        final boolean def = parentemd.current.sort == Sort.DEF;
+
+        if (method == null && !def) {
+            throw new IllegalArgumentException(
+                AnalyzerUtility.error(ctx) + "Unknown call [" + name + "] on type [" + struct.name + "].");
+        } else if (method != null) {
+            types = new Type[method.arguments.size()];
+            method.arguments.toArray(types);
+
+            callenmd.target = method;
+            callenmd.type = method.rtn;
+            parentemd.statement = !parentemd.read && callenmd.last;
+            parentemd.current = method.rtn;
+
+            if (size != types.length) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "When calling [" + name + "] on type " +
+                    "[" + struct.name + "] expected [" + types.length + "] arguments," +
+                    " but found [" + arguments.size() + "].");
+            }
+        } else {
+            types = new Type[arguments.size()];
+            Arrays.fill(types, definition.defType);
+
+            callenmd.target = name;
+            callenmd.type = definition.defType;
+            parentemd.statement = !parentemd.read && callenmd.last;
+            parentemd.current = callenmd.type;
+        }
+
+        for (int argument = 0; argument < size; ++argument) {
+            final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(arguments.get(argument));
+            final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+            expremd.to = types[argument];
+            analyzer.visit(exprctx);
+            caster.markCast(expremd);
+        }
+
+        parentemd.statik = false;
+
+        if (dotctx != null) {
+            metadata.createExtNodeMetadata(parent, dotctx);
+            analyzer.visit(dotctx);
+        } else if (bracectx != null) {
+            metadata.createExtNodeMetadata(parent, bracectx);
+            analyzer.visit(bracectx);
+        }
+    }
+
+    void processExtvar(final ExtvarContext ctx) {
+        final ExtNodeMetadata varenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = varenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        final String name = ctx.ID().getText();
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (parentemd.current != null) {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected variable [" + name + "] load.");
+        }
+
+        varenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
+
+        final Variable variable = utility.getVariable(name);
+
+        if (variable == null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unknown variable [" + name + "].");
+        }
+
+        varenmd.target = variable.slot;
+        varenmd.type = variable.type;
+        analyzeLoadStoreExternal(ctx);
+        parentemd.current = varenmd.type;
+
+        if (dotctx != null) {
+            metadata.createExtNodeMetadata(parent, dotctx);
+            analyzer.visit(dotctx);
+        } else if (bracectx != null) {
+            metadata.createExtNodeMetadata(parent, bracectx);
+            analyzer.visit(bracectx);
+        }
+    }
+
+    void processExtfield(final ExtfieldContext ctx) {
+        final ExtNodeMetadata memberenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = memberenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        if (ctx.EXTID() == null && ctx.EXTINTEGER() == null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+
+        final String value = ctx.EXTID() == null ? ctx.EXTINTEGER().getText() : ctx.EXTID().getText();
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        memberenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
+        final boolean store = memberenmd.last && parentemd.storeExpr != null;
+
+        if (parentemd.current == null) {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected field [" + value + "] load.");
+        }
+
+        if (parentemd.current.sort == Sort.ARRAY) {
+            if ("length".equals(value)) {
+                if (!parentemd.read) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Must read array field [length].");
+                } else if (store) {
+                    throw new IllegalArgumentException(
+                        AnalyzerUtility.error(ctx) + "Cannot write to read-only array field [length].");
+                }
+
+                memberenmd.target = "#length";
+                memberenmd.type = definition.intType;
+                parentemd.current = definition.intType;
+            } else {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unexpected array field [" + value + "].");
+            }
+        } else if (parentemd.current.sort == Sort.DEF) {
+            memberenmd.target = value;
+            memberenmd.type = definition.defType;
+            analyzeLoadStoreExternal(ctx);
+            parentemd.current = memberenmd.type;
+        } else {
+            final Struct struct = parentemd.current.struct;
+            final Field field = parentemd.statik ? struct.statics.get(value) : struct.members.get(value);
+
+            if (field != null) {
+                if (store && java.lang.reflect.Modifier.isFinal(field.reflect.getModifiers())) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Cannot write to read-only" +
+                        " field [" + value + "] for type [" + struct.name + "].");
+                }
+
+                memberenmd.target = field;
+                memberenmd.type = field.type;
+                analyzeLoadStoreExternal(ctx);
+                parentemd.current = memberenmd.type;
+            } else {
+                final boolean get = parentemd.read || parentemd.token > 0 || !memberenmd.last;
+                final boolean set = memberenmd.last && store;
+
+                Method getter = struct.methods.get("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1));
+                Method setter = struct.methods.get("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1));
+                Object constant = null;
+
+                if (getter != null && (getter.rtn.sort == Sort.VOID || !getter.arguments.isEmpty())) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                        "Illegal get shortcut on field [" + value + "] for type [" + struct.name + "].");
+                }
+
+                if (setter != null && (setter.rtn.sort != Sort.VOID || setter.arguments.size() != 1)) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                        "Illegal set shortcut on field [" + value + "] for type [" + struct.name + "].");
+                }
+
+                Type settype = setter == null ? null : setter.arguments.get(0);
+
+                if (getter == null && setter == null) {
+                    if (ctx.EXTID() != null) {
+                        try {
+                            parentemd.current.clazz.asSubclass(Map.class);
+
+                            getter = parentemd.current.struct.methods.get("get");
+                            setter = parentemd.current.struct.methods.get("put");
+
+                            if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
+                                getter.arguments.get(0).sort != Sort.STRING)) {
+                                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                                    "Illegal map get shortcut [" + value + "] for type [" + struct.name + "].");
+                            }
+
+                            if (setter != null && (setter.arguments.size() != 2 ||
+                                setter.arguments.get(0).sort != Sort.STRING)) {
+                                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                                    "Illegal map set shortcut [" + value + "] for type [" + struct.name + "].");
+                            }
+
+                            if (getter != null && setter != null && !getter.rtn.equals(setter.arguments.get(1))) {
+                                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Shortcut argument types must match.");
+                            }
+
+                            settype = setter == null ? null : setter.arguments.get(1);
+                            constant = value;
+                        } catch (ClassCastException exception) {
+                            //Do nothing.
+                        }
+                    } else if (ctx.EXTINTEGER() != null) {
+                        try {
+                            parentemd.current.clazz.asSubclass(List.class);
+
+                            getter = parentemd.current.struct.methods.get("get");
+                            setter = parentemd.current.struct.methods.get("set");
+
+                            if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
+                                getter.arguments.get(0).sort != Sort.INT)) {
+                                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                                    "Illegal list get shortcut [" + value + "] for type [" + struct.name + "].");
+                            }
+
+                            if (setter != null && (setter.rtn.sort != Sort.VOID || setter.arguments.size() != 2 ||
+                                setter.arguments.get(0).sort != Sort.INT)) {
+                                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                                    "Illegal list set shortcut [" + value + "] for type [" + struct.name + "].");
+                            }
+
+                            if (getter != null && setter != null && !getter.rtn.equals(setter.arguments.get(1))) {
+                                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Shortcut argument types must match.");
+                            }
+
+                            settype = setter == null ? null : setter.arguments.get(1);
+
+                            try {
+                                constant = Integer.parseInt(value);
+                            } catch (NumberFormatException exception) {
+                                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) +
+                                    "Illegal list shortcut value [" + value + "].");
+                            }
+                        } catch (ClassCastException exception) {
+                            //Do nothing.
+                        }
+                    } else {
+                        throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+                    }
+                }
+
+                if ((get || set) && (!get || getter != null) && (!set || setter != null)) {
+                    memberenmd.target = new Object[] {getter, setter, constant != null, constant};
+                    memberenmd.type = get ? getter.rtn : settype;
+                    analyzeLoadStoreExternal(ctx);
+                    parentemd.current = get ? getter.rtn : setter.rtn;
+                }
+            }
+
+            if (memberenmd.target == null) {
+                throw new IllegalArgumentException(
+                    AnalyzerUtility.error(ctx) + "Unknown field [" + value + "] for type [" + struct.name + "].");
+            }
+        }
+
+        parentemd.statik = false;
+
+        if (dotctx != null) {
+            metadata.createExtNodeMetadata(parent, dotctx);
+            analyzer.visit(dotctx);
+        } else if (bracectx != null) {
+            metadata.createExtNodeMetadata(parent, bracectx);
+            analyzer.visit(bracectx);
+        }
+    }
+
+    void processExtnew(final ExtnewContext ctx) {
+        final ExtNodeMetadata newenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = newenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        newenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
+
+        final String name = ctx.TYPE().getText();
+        final Struct struct = definition.structs.get(name);
+
+        if (parentemd.current != null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unexpected new call.");
+        } else if (struct == null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Specified type [" + name + "] not found.");
+        } else if (newenmd.last && parentemd.storeExpr != null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Cannot assign a value to a new call.");
+        }
+
+        final boolean newclass = ctx.arguments() != null;
+        final boolean newarray = !ctx.expression().isEmpty();
+
+        final List<ExpressionContext> arguments = newclass ? ctx.arguments().expression() : ctx.expression();
+        final int size = arguments.size();
+
+        Type[] types;
+
+        if (newarray) {
+            if (!parentemd.read) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "A newly created array must be assigned.");
+            }
+
+            types = new Type[size];
+            Arrays.fill(types, definition.intType);
+
+            newenmd.target = "#makearray";
+
+            if (size > 1) {
+                newenmd.type = definition.getType(struct, size);
+                parentemd.current = newenmd.type;
+            } else if (size == 1) {
+                newenmd.type = definition.getType(struct, 0);
+                parentemd.current = definition.getType(struct, 1);
+            } else {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "A newly created array cannot have zero dimensions.");
+            }
+        } else if (newclass) {
+            final Constructor constructor = struct.constructors.get("new");
+
+            if (constructor != null) {
+                types = new Type[constructor.arguments.size()];
+                constructor.arguments.toArray(types);
+
+                newenmd.target = constructor;
+                newenmd.type = definition.getType(struct, 0);
+                parentemd.statement = !parentemd.read && newenmd.last;
+                parentemd.current = newenmd.type;
+            } else {
+                throw new IllegalArgumentException(
+                    AnalyzerUtility.error(ctx) + "Unknown new call on type [" + struct.name + "].");
+            }
+        } else {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unknown state.");
+        }
+
+        if (size != types.length) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "When calling [" + name + "] on type " +
+                "[" + struct.name + "] expected [" + types.length + "] arguments," +
+                " but found [" + arguments.size() + "].");
+        }
+
+        for (int argument = 0; argument < size; ++argument) {
+            final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(arguments.get(argument));
+            final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+            expremd.to = types[argument];
+            analyzer.visit(exprctx);
+            caster.markCast(expremd);
+        }
+
+        if (dotctx != null) {
+            metadata.createExtNodeMetadata(parent, dotctx);
+            analyzer.visit(dotctx);
+        } else if (bracectx != null) {
+            metadata.createExtNodeMetadata(parent, bracectx);
+            analyzer.visit(bracectx);
+        }
+    }
+
+    void processExtstring(final ExtstringContext ctx) {
+        final ExtNodeMetadata memberenmd = metadata.getExtNodeMetadata(ctx);
+        final ParserRuleContext parent = memberenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        final String string = ctx.STRING().getText();
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        memberenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
+        final boolean store = memberenmd.last && parentemd.storeExpr != null;
+
+        if (parentemd.current != null) {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected String constant [" + string + "].");
+        }
+
+        if (!parentemd.read) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Must read String constant [" + string + "].");
+        } else if (store) {
+            throw new IllegalArgumentException(
+                AnalyzerUtility.error(ctx) + "Cannot write to read-only String constant [" + string + "].");
+        }
+
+        memberenmd.target = string;
+        memberenmd.type = definition.stringType;
+        parentemd.current = definition.stringType;
+
+        if (memberenmd.last) {
+            parentemd.constant = string;
+        }
+
+        if (dotctx != null) {
+            metadata.createExtNodeMetadata(parent, dotctx);
+            analyzer.visit(dotctx);
+        } else if (bracectx != null) {
+            metadata.createExtNodeMetadata(parent, bracectx);
+            analyzer.visit(bracectx);
+        }
+    }
+
+    private void analyzeLoadStoreExternal(final ParserRuleContext source) {
+        final ExtNodeMetadata extenmd = metadata.getExtNodeMetadata(source);
+        final ParserRuleContext parent = extenmd.parent;
+        final ExternalMetadata parentemd = metadata.getExternalMetadata(parent);
+
+        if (extenmd.last && parentemd.storeExpr != null) {
+            final ParserRuleContext store = parentemd.storeExpr;
+            final ExpressionMetadata storeemd = metadata.createExpressionMetadata(parentemd.storeExpr);
+            final int token = parentemd.token;
+
+            if (token > 0) {
+                analyzer.visit(store);
+
+                final boolean add = token == ADD;
+                final boolean xor = token == BWAND || token == BWXOR || token == BWOR;
+                final boolean decimal = token == MUL || token == DIV || token == REM || token == SUB;
+
+                extenmd.promote = add ? promoter.promoteAdd(extenmd.type, storeemd.from) :
+                    xor ? promoter.promoteXor(extenmd.type, storeemd.from) :
+                        promoter.promoteNumeric(extenmd.type, storeemd.from, decimal, true);
+
+                if (extenmd.promote == null) {
+                    throw new IllegalArgumentException("Cannot apply compound assignment to " +
+                        "types [" + extenmd.type.name + "] and [" + storeemd.from.name + "].");
+                }
+
+                extenmd.castFrom = caster.getLegalCast(source, extenmd.type, extenmd.promote, false);
+                extenmd.castTo = caster.getLegalCast(source, extenmd.promote, extenmd.type, true);
+
+                storeemd.to = add && extenmd.promote.sort == Sort.STRING ? storeemd.from : extenmd.promote;
+                caster.markCast(storeemd);
+            } else {
+                storeemd.to = extenmd.type;
+                analyzer.visit(store);
+                caster.markCast(storeemd);
+            }
+        }
+    }
+}

+ 281 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerPromoter.java

@@ -0,0 +1,281 @@
+/*
+ * 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.elasticsearch.painless.Definition.Pair;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Type;
+
+class AnalyzerPromoter {
+    private final Definition definition;
+
+    AnalyzerPromoter(final Definition definition) {
+        this.definition = definition;
+    }
+
+    Type promoteNumeric(final Type from, final boolean decimal, final boolean primitive) {
+        final Sort sort = from.sort;
+
+        if (sort == Sort.DEF) {
+            return definition.defType;
+        } else if ((sort == Sort.DOUBLE || sort == Sort.DOUBLE_OBJ || sort == Sort.NUMBER) && decimal) {
+            return primitive ? definition.doubleType : definition.doubleobjType;
+        } else if ((sort == Sort.FLOAT || sort == Sort.FLOAT_OBJ) && decimal) {
+            return primitive ? definition.floatType : definition.floatobjType;
+        } else if (sort == Sort.LONG || sort == Sort.LONG_OBJ || sort == Sort.NUMBER) {
+            return primitive ? definition.longType : definition.longobjType;
+        } else if (sort.numeric) {
+            return primitive ? definition.intType : definition.intobjType;
+        }
+
+        return null;
+    }
+
+    Type promoteNumeric(final Type from0, final Type from1, final boolean decimal, final boolean primitive) {
+        final Sort sort0 = from0.sort;
+        final Sort sort1 = from1.sort;
+
+        if (sort0 == Sort.DEF || sort1 == Sort.DEF) {
+            return definition.defType;
+        }
+
+        if (decimal) {
+            if (sort0 == Sort.DOUBLE || sort0 == Sort.DOUBLE_OBJ || sort0 == Sort.NUMBER ||
+                sort1 == Sort.DOUBLE || sort1 == Sort.DOUBLE_OBJ || sort1 == Sort.NUMBER) {
+                return primitive ? definition.doubleType : definition.doubleobjType;
+            } else if (sort0 == Sort.FLOAT || sort0 == Sort.FLOAT_OBJ || sort1 == Sort.FLOAT || sort1 == Sort.FLOAT_OBJ) {
+                return primitive ? definition.floatType : definition.floatobjType;
+            }
+        }
+
+        if (sort0 == Sort.LONG || sort0 == Sort.LONG_OBJ || sort0 == Sort.NUMBER ||
+            sort1 == Sort.LONG || sort1 == Sort.LONG_OBJ || sort1 == Sort.NUMBER) {
+            return primitive ? definition.longType : definition.longobjType;
+        } else if (sort0.numeric && sort1.numeric) {
+            return primitive ? definition.intType : definition.intobjType;
+        }
+
+        return null;
+    }
+
+    Type promoteAdd(final Type from0, final Type from1) {
+        final Sort sort0 = from0.sort;
+        final Sort sort1 = from1.sort;
+
+        if (sort0 == Sort.STRING || sort1 == Sort.STRING) {
+            return definition.stringType;
+        }
+
+        return promoteNumeric(from0, from1, true, true);
+    }
+
+    Type promoteXor(final Type from0, final Type from1) {
+        final Sort sort0 = from0.sort;
+        final Sort sort1 = from1.sort;
+
+        if (sort0.bool || sort1.bool) {
+            return definition.booleanType;
+        }
+
+        return promoteNumeric(from0, from1, false, true);
+    }
+
+    Type promoteEquality(final Type from0, final Type from1) {
+        final Sort sort0 = from0.sort;
+        final Sort sort1 = from1.sort;
+
+        if (sort0 == Sort.DEF || sort1 == Sort.DEF) {
+            return definition.defType;
+        }
+
+        final boolean primitive = sort0.primitive && sort1.primitive;
+
+        if (sort0.bool && sort1.bool) {
+            return primitive ? definition.booleanType : definition.booleanobjType;
+        }
+
+        if (sort0.numeric && sort1.numeric) {
+            return promoteNumeric(from0, from1, true, primitive);
+        }
+
+        return definition.objectType;
+    }
+
+    Type promoteReference(final Type from0, final Type from1) {
+        final Sort sort0 = from0.sort;
+        final Sort sort1 = from1.sort;
+
+        if (sort0 == Sort.DEF || sort1 == Sort.DEF) {
+            return definition.defType;
+        }
+
+        if (sort0.primitive && sort1.primitive) {
+            if (sort0.bool && sort1.bool) {
+                return definition.booleanType;
+            }
+
+            if (sort0.numeric && sort1.numeric) {
+                return promoteNumeric(from0, from1, true, true);
+            }
+        }
+
+        return definition.objectType;
+    }
+
+    Type promoteConditional(final Type from0, final Type from1, final Object const0, final Object const1) {
+        if (from0.equals(from1)) {
+            return from0;
+        }
+
+        final Sort sort0 = from0.sort;
+        final Sort sort1 = from1.sort;
+
+        if (sort0 == Sort.DEF || sort1 == Sort.DEF) {
+            return definition.defType;
+        }
+
+        final boolean primitive = sort0.primitive && sort1.primitive;
+
+        if (sort0.bool && sort1.bool) {
+            return primitive ? definition.booleanType : definition.booleanobjType;
+        }
+
+        if (sort0.numeric && sort1.numeric) {
+            if (sort0 == Sort.DOUBLE || sort0 == Sort.DOUBLE_OBJ || sort1 == Sort.DOUBLE || sort1 == Sort.DOUBLE_OBJ) {
+                return primitive ? definition.doubleType : definition.doubleobjType;
+            } else if (sort0 == Sort.FLOAT || sort0 == Sort.FLOAT_OBJ || sort1 == Sort.FLOAT || sort1 == Sort.FLOAT_OBJ) {
+                return primitive ? definition.floatType : definition.floatobjType;
+            } else if (sort0 == Sort.LONG || sort0 == Sort.LONG_OBJ || sort1 == Sort.LONG || sort1 == Sort.LONG_OBJ) {
+                return sort0.primitive && sort1.primitive ? definition.longType : definition.longobjType;
+            } else {
+                if (sort0 == Sort.BYTE || sort0 == Sort.BYTE_OBJ) {
+                    if (sort1 == Sort.BYTE || sort1 == Sort.BYTE_OBJ) {
+                        return primitive ? definition.byteType : definition.byteobjType;
+                    } else if (sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ) {
+                        if (const1 != null) {
+                            final short constant = (short)const1;
+
+                            if (constant <= Byte.MAX_VALUE && constant >= Byte.MIN_VALUE) {
+                                return primitive ? definition.byteType : definition.byteobjType;
+                            }
+                        }
+
+                        return primitive ? definition.shortType : definition.shortobjType;
+                    } else if (sort1 == Sort.CHAR || sort1 == Sort.CHAR_OBJ) {
+                        return primitive ? definition.intType : definition.intobjType;
+                    } else if (sort1 == Sort.INT || sort1 == Sort.INT_OBJ) {
+                        if (const1 != null) {
+                            final int constant = (int)const1;
+
+                            if (constant <= Byte.MAX_VALUE && constant >= Byte.MIN_VALUE) {
+                                return primitive ? definition.byteType : definition.byteobjType;
+                            }
+                        }
+
+                        return primitive ? definition.intType : definition.intobjType;
+                    }
+                } else if (sort0 == Sort.SHORT || sort0 == Sort.SHORT_OBJ) {
+                    if (sort1 == Sort.BYTE || sort1 == Sort.BYTE_OBJ) {
+                        if (const0 != null) {
+                            final short constant = (short)const0;
+
+                            if (constant <= Byte.MAX_VALUE && constant >= Byte.MIN_VALUE) {
+                                return primitive ? definition.byteType : definition.byteobjType;
+                            }
+                        }
+
+                        return primitive ? definition.shortType : definition.shortobjType;
+                    } else if (sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ) {
+                        return primitive ? definition.shortType : definition.shortobjType;
+                    } else if (sort1 == Sort.CHAR || sort1 == Sort.CHAR_OBJ) {
+                        return primitive ? definition.intType : definition.intobjType;
+                    } else if (sort1 == Sort.INT || sort1 == Sort.INT_OBJ) {
+                        if (const1 != null) {
+                            final int constant = (int)const1;
+
+                            if (constant <= Short.MAX_VALUE && constant >= Short.MIN_VALUE) {
+                                return primitive ? definition.shortType : definition.shortobjType;
+                            }
+                        }
+
+                        return primitive ? definition.intType : definition.intobjType;
+                    }
+                } else if (sort0 == Sort.CHAR || sort0 == Sort.CHAR_OBJ) {
+                    if (sort1 == Sort.BYTE || sort1 == Sort.BYTE_OBJ) {
+                        return primitive ? definition.intType : definition.intobjType;
+                    } else if (sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ) {
+                        return primitive ? definition.intType : definition.intobjType;
+                    } else if (sort1 == Sort.CHAR || sort1 == Sort.CHAR_OBJ) {
+                        return primitive ? definition.charType : definition.charobjType;
+                    } else if (sort1 == Sort.INT || sort1 == Sort.INT_OBJ) {
+                        if (const1 != null) {
+                            final int constant = (int)const1;
+
+                            if (constant <= Character.MAX_VALUE && constant >= Character.MIN_VALUE) {
+                                return primitive ? definition.byteType : definition.byteobjType;
+                            }
+                        }
+
+                        return primitive ? definition.intType : definition.intobjType;
+                    }
+                } else if (sort0 == Sort.INT || sort0 == Sort.INT_OBJ) {
+                    if (sort1 == Sort.BYTE || sort1 == Sort.BYTE_OBJ) {
+                        if (const0 != null) {
+                            final int constant = (int)const0;
+
+                            if (constant <= Byte.MAX_VALUE && constant >= Byte.MIN_VALUE) {
+                                return primitive ? definition.byteType : definition.byteobjType;
+                            }
+                        }
+
+                        return primitive ? definition.intType : definition.intobjType;
+                    } else if (sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ) {
+                        if (const0 != null) {
+                            final int constant = (int)const0;
+
+                            if (constant <= Short.MAX_VALUE && constant >= Short.MIN_VALUE) {
+                                return primitive ? definition.byteType : definition.byteobjType;
+                            }
+                        }
+
+                        return primitive ? definition.intType : definition.intobjType;
+                    } else if (sort1 == Sort.CHAR || sort1 == Sort.CHAR_OBJ) {
+                        if (const0 != null) {
+                            final int constant = (int)const0;
+
+                            if (constant <= Character.MAX_VALUE && constant >= Character.MIN_VALUE) {
+                                return primitive ? definition.byteType : definition.byteobjType;
+                            }
+                        }
+
+                        return primitive ? definition.intType : definition.intobjType;
+                    } else if (sort1 == Sort.INT || sort1 == Sort.INT_OBJ) {
+                        return primitive ? definition.intType : definition.intobjType;
+                    }
+                }
+            }
+        }
+
+        final Pair pair = new Pair(from0, from1);
+        final Type bound = definition.bounds.get(pair);
+
+        return bound == null ? definition.objectType : bound;
+    }
+}

+ 581 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerStatement.java

@@ -0,0 +1,581 @@
+/*
+ * 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.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Metadata.ExpressionMetadata;
+import org.elasticsearch.painless.Metadata.StatementMetadata;
+import org.elasticsearch.painless.PainlessParser.AfterthoughtContext;
+import org.elasticsearch.painless.PainlessParser.BlockContext;
+import org.elasticsearch.painless.PainlessParser.BreakContext;
+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.ExprContext;
+import org.elasticsearch.painless.PainlessParser.ExpressionContext;
+import org.elasticsearch.painless.PainlessParser.ForContext;
+import org.elasticsearch.painless.PainlessParser.IfContext;
+import org.elasticsearch.painless.PainlessParser.InitializerContext;
+import org.elasticsearch.painless.PainlessParser.MultipleContext;
+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.TryContext;
+import org.elasticsearch.painless.PainlessParser.WhileContext;
+
+import java.util.List;
+
+class AnalyzerStatement {
+    private final Metadata metadata;
+    private final Definition definition;
+
+    private final Analyzer analyzer;
+    private final AnalyzerUtility utility;
+    private final AnalyzerCaster caster;
+
+    AnalyzerStatement(final Metadata metadata, final Analyzer analyzer,
+                      final AnalyzerUtility utility, final AnalyzerCaster caster) {
+        this.metadata = metadata;
+        this.definition = metadata.definition;
+
+        this.analyzer = analyzer;
+        this.utility = utility;
+        this.caster = caster;
+    }
+
+    void processSource(final SourceContext ctx) {
+        final StatementMetadata sourcesmd = metadata.getStatementMetadata(ctx);
+        final List<StatementContext> statectxs = ctx.statement();
+        final StatementContext lastctx = statectxs.get(statectxs.size() - 1);
+
+        utility.incrementScope();
+
+        for (final StatementContext statectx : statectxs) {
+            if (sourcesmd.allLast) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(statectx) +
+                    "Statement will never be executed because all prior paths escape.");
+            }
+
+            final StatementMetadata statesmd = metadata.createStatementMetadata(statectx);
+            statesmd.lastSource = statectx == lastctx;
+            analyzer.visit(statectx);
+
+            sourcesmd.methodEscape = statesmd.methodEscape;
+            sourcesmd.allLast = statesmd.allLast;
+        }
+
+        utility.decrementScope();
+    }
+
+    void processIf(final IfContext ctx) {
+        final StatementMetadata ifsmd = metadata.getStatementMetadata(ctx);
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+        expremd.to = definition.booleanType;
+        analyzer.visit(exprctx);
+        caster.markCast(expremd);
+
+        if (expremd.postConst != null) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "If statement is not necessary.");
+        }
+
+        final BlockContext blockctx0 = ctx.block(0);
+        final StatementMetadata blocksmd0 = metadata.createStatementMetadata(blockctx0);
+        blocksmd0.lastSource = ifsmd.lastSource;
+        blocksmd0.inLoop = ifsmd.inLoop;
+        blocksmd0.lastLoop = ifsmd.lastLoop;
+        utility.incrementScope();
+        analyzer.visit(blockctx0);
+        utility.decrementScope();
+
+        ifsmd.anyContinue = blocksmd0.anyContinue;
+        ifsmd.anyBreak = blocksmd0.anyBreak;
+
+        ifsmd.count = blocksmd0.count;
+
+        if (ctx.ELSE() != null) {
+            final BlockContext blockctx1 = ctx.block(1);
+            final StatementMetadata blocksmd1 = metadata.createStatementMetadata(blockctx1);
+            blocksmd1.lastSource = ifsmd.lastSource;
+            utility.incrementScope();
+            analyzer.visit(blockctx1);
+            utility.decrementScope();
+
+            ifsmd.methodEscape = blocksmd0.methodEscape && blocksmd1.methodEscape;
+            ifsmd.loopEscape = blocksmd0.loopEscape && blocksmd1.loopEscape;
+            ifsmd.allLast = blocksmd0.allLast && blocksmd1.allLast;
+            ifsmd.anyContinue |= blocksmd1.anyContinue;
+            ifsmd.anyBreak |= blocksmd1.anyBreak;
+
+            ifsmd.count = Math.max(ifsmd.count, blocksmd1.count);
+        }
+    }
+
+    void processWhile(final WhileContext ctx) {
+        final StatementMetadata whilesmd = metadata.getStatementMetadata(ctx);
+
+        utility.incrementScope();
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+        expremd.to = definition.booleanType;
+        analyzer.visit(exprctx);
+        caster.markCast(expremd);
+
+        boolean continuous = false;
+
+        if (expremd.postConst != null) {
+            continuous = (boolean)expremd.postConst;
+
+            if (!continuous) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "The loop will never be executed.");
+            }
+
+            if (ctx.empty() != null) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "The loop will never exit.");
+            }
+        }
+
+        final BlockContext blockctx = ctx.block();
+
+        if (blockctx != null) {
+            final StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
+            blocksmd.beginLoop = true;
+            blocksmd.inLoop = true;
+            analyzer.visit(blockctx);
+
+            if (blocksmd.loopEscape && !blocksmd.anyContinue) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "All paths escape so the loop is not necessary.");
+            }
+
+            if (continuous && !blocksmd.anyBreak) {
+                whilesmd.methodEscape = true;
+                whilesmd.allLast = true;
+            }
+        }
+
+        whilesmd.count = 1;
+
+        utility.decrementScope();
+    }
+
+    void processDo(final DoContext ctx) {
+        final StatementMetadata dosmd = metadata.getStatementMetadata(ctx);
+
+        utility.incrementScope();
+
+        final BlockContext blockctx = ctx.block();
+        final StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
+        blocksmd.beginLoop = true;
+        blocksmd.inLoop = true;
+        analyzer.visit(blockctx);
+
+        if (blocksmd.loopEscape && !blocksmd.anyContinue) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "All paths escape so the loop is not necessary.");
+        }
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+        expremd.to = definition.booleanType;
+        analyzer.visit(exprctx);
+        caster.markCast(expremd);
+
+        if (expremd.postConst != null) {
+            final boolean continuous = (boolean)expremd.postConst;
+
+            if (!continuous) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "All paths escape so the loop is not necessary.");
+            }
+
+            if (!blocksmd.anyBreak) {
+                dosmd.methodEscape = true;
+                dosmd.allLast = true;
+            }
+        }
+
+        dosmd.count = 1;
+
+        utility.decrementScope();
+    }
+
+    void processFor(final ForContext ctx) {
+        final StatementMetadata forsmd = metadata.getStatementMetadata(ctx);
+        boolean continuous = false;
+
+        utility.incrementScope();
+
+        final InitializerContext initctx = ctx.initializer();
+
+        if (initctx != null) {
+            metadata.createStatementMetadata(initctx);
+            analyzer.visit(initctx);
+        }
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+
+        if (exprctx != null) {
+            final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+            expremd.to = definition.booleanType;
+            analyzer.visit(exprctx);
+            caster.markCast(expremd);
+
+            if (expremd.postConst != null) {
+                continuous = (boolean)expremd.postConst;
+
+                if (!continuous) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "The loop will never be executed.");
+                }
+
+                if (ctx.empty() != null) {
+                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "The loop is continuous.");
+                }
+            }
+        } else {
+            continuous = true;
+        }
+
+        final AfterthoughtContext atctx = ctx.afterthought();
+
+        if (atctx != null) {
+            metadata.createStatementMetadata(atctx);
+            analyzer.visit(atctx);
+        }
+
+        final BlockContext blockctx = ctx.block();
+
+        if (blockctx != null) {
+            final StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
+            blocksmd.beginLoop = true;
+            blocksmd.inLoop = true;
+            analyzer.visit(blockctx);
+
+            if (blocksmd.loopEscape && !blocksmd.anyContinue) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "All paths escape so the loop is not necessary.");
+            }
+
+            if (continuous && !blocksmd.anyBreak) {
+                forsmd.methodEscape = true;
+                forsmd.allLast = true;
+            }
+        }
+
+        forsmd.count = 1;
+
+        utility.decrementScope();
+    }
+
+    void processDecl(final DeclContext ctx) {
+        final StatementMetadata declsmd = metadata.getStatementMetadata(ctx);
+
+        final DeclarationContext declctx = ctx.declaration();
+        metadata.createStatementMetadata(declctx);
+        analyzer.visit(declctx);
+
+        declsmd.count = 1;
+    }
+
+    void processContinue(final ContinueContext ctx) {
+        final StatementMetadata continuesmd = metadata.getStatementMetadata(ctx);
+
+        if (!continuesmd.inLoop) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Cannot have a continue statement outside of a loop.");
+        }
+
+        if (continuesmd.lastLoop) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unnecessary continue statement at the end of a loop.");
+        }
+
+        continuesmd.allLast = true;
+        continuesmd.anyContinue = true;
+
+        continuesmd.count = 1;
+    }
+
+    void processBreak(final BreakContext ctx) {
+        final StatementMetadata breaksmd = metadata.getStatementMetadata(ctx);
+
+        if (!breaksmd.inLoop) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Cannot have a break statement outside of a loop.");
+        }
+
+        breaksmd.loopEscape = true;
+        breaksmd.allLast = true;
+        breaksmd.anyBreak = true;
+
+        breaksmd.count = 1;
+    }
+
+    void processReturn(final ReturnContext ctx) {
+        final StatementMetadata returnsmd = metadata.getStatementMetadata(ctx);
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+        expremd.to = definition.objectType;
+        analyzer.visit(exprctx);
+        caster.markCast(expremd);
+
+        returnsmd.methodEscape = true;
+        returnsmd.loopEscape = true;
+        returnsmd.allLast = true;
+
+        returnsmd.count = 1;
+    }
+
+    void processTry(final TryContext ctx) {
+        final StatementMetadata trysmd = metadata.getStatementMetadata(ctx);
+
+        final BlockContext blockctx = ctx.block();
+        final StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
+        blocksmd.lastSource = trysmd.lastSource;
+        blocksmd.inLoop = trysmd.inLoop;
+        blocksmd.lastLoop = trysmd.lastLoop;
+        utility.incrementScope();
+        analyzer.visit(blockctx);
+        utility.decrementScope();
+
+        trysmd.methodEscape = blocksmd.methodEscape;
+        trysmd.loopEscape = blocksmd.loopEscape;
+        trysmd.allLast = blocksmd.allLast;
+        trysmd.anyContinue = blocksmd.anyContinue;
+        trysmd.anyBreak = blocksmd.anyBreak;
+
+        int trapcount = 0;
+
+        for (final TrapContext trapctx : ctx.trap()) {
+            final StatementMetadata trapsmd = metadata.createStatementMetadata(trapctx);
+            trapsmd.lastSource = trysmd.lastSource;
+            trapsmd.inLoop = trysmd.inLoop;
+            trapsmd.lastLoop = trysmd.lastLoop;
+            utility.incrementScope();
+            analyzer.visit(trapctx);
+            utility.decrementScope();
+
+            trysmd.methodEscape &= trapsmd.methodEscape;
+            trysmd.loopEscape &= trapsmd.loopEscape;
+            trysmd.allLast &= trapsmd.allLast;
+            trysmd.anyContinue |= trapsmd.anyContinue;
+            trysmd.anyBreak |= trapsmd.anyBreak;
+
+            trapcount = Math.max(trapcount, trapsmd.count);
+        }
+
+        trysmd.count = blocksmd.count + trapcount;
+    }
+
+    void processThrow(final ThrowContext ctx) {
+        final StatementMetadata throwsmd = metadata.getStatementMetadata(ctx);
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+        expremd.to = definition.exceptionType;
+        analyzer.visit(exprctx);
+        caster.markCast(expremd);
+
+        throwsmd.methodEscape = true;
+        throwsmd.loopEscape = true;
+        throwsmd.allLast = true;
+
+        throwsmd.count = 1;
+    }
+
+    void processExpr(final ExprContext ctx) {
+        final StatementMetadata exprsmd = metadata.getStatementMetadata(ctx);
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+        final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+        expremd.read = exprsmd.lastSource;
+        analyzer.visit(exprctx);
+
+        if (!expremd.statement && !exprsmd.lastSource) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Not a statement.");
+        }
+
+        final boolean rtn = exprsmd.lastSource && expremd.from.sort != Sort.VOID;
+        exprsmd.methodEscape = rtn;
+        exprsmd.loopEscape = rtn;
+        exprsmd.allLast = rtn;
+        expremd.to = rtn ? definition.objectType : expremd.from;
+        caster.markCast(expremd);
+
+        exprsmd.count = 1;
+    }
+
+    void processMultiple(final MultipleContext ctx) {
+        final StatementMetadata multiplesmd = metadata.getStatementMetadata(ctx);
+        final List<StatementContext> statectxs = ctx.statement();
+        final StatementContext lastctx = statectxs.get(statectxs.size() - 1);
+
+        for (StatementContext statectx : statectxs) {
+            if (multiplesmd.allLast) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(statectx) +
+                    "Statement will never be executed because all prior paths escape.");
+            }
+
+            final StatementMetadata statesmd = metadata.createStatementMetadata(statectx);
+            statesmd.lastSource = multiplesmd.lastSource && statectx == lastctx;
+            statesmd.inLoop = multiplesmd.inLoop;
+            statesmd.lastLoop = (multiplesmd.beginLoop || multiplesmd.lastLoop) && statectx == lastctx;
+            analyzer.visit(statectx);
+
+            multiplesmd.methodEscape = statesmd.methodEscape;
+            multiplesmd.loopEscape = statesmd.loopEscape;
+            multiplesmd.allLast = statesmd.allLast;
+            multiplesmd.anyContinue |= statesmd.anyContinue;
+            multiplesmd.anyBreak |= statesmd.anyBreak;
+
+            multiplesmd.count += statesmd.count;
+        }
+    }
+
+    void processSingle(final SingleContext ctx) {
+        final StatementMetadata singlesmd = metadata.getStatementMetadata(ctx);
+
+        final StatementContext statectx = ctx.statement();
+        final StatementMetadata statesmd = metadata.createStatementMetadata(statectx);
+        statesmd.lastSource = singlesmd.lastSource;
+        statesmd.inLoop = singlesmd.inLoop;
+        statesmd.lastLoop = singlesmd.beginLoop || singlesmd.lastLoop;
+        analyzer.visit(statectx);
+
+        singlesmd.methodEscape = statesmd.methodEscape;
+        singlesmd.loopEscape = statesmd.loopEscape;
+        singlesmd.allLast = statesmd.allLast;
+        singlesmd.anyContinue = statesmd.anyContinue;
+        singlesmd.anyBreak = statesmd.anyBreak;
+
+        singlesmd.count = statesmd.count;
+    }
+
+    void processInitializer(InitializerContext ctx) {
+        final DeclarationContext declctx = ctx.declaration();
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+
+        if (declctx != null) {
+            metadata.createStatementMetadata(declctx);
+            analyzer.visit(declctx);
+        } else if (exprctx != null) {
+            final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+            expremd.read = false;
+            analyzer.visit(exprctx);
+
+            expremd.to = expremd.from;
+            caster.markCast(expremd);
+
+            if (!expremd.statement) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(exprctx) +
+                    "The initializer of a for loop must be a statement.");
+            }
+        } else {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+    }
+
+    void processAfterthought(AfterthoughtContext ctx) {
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+
+        if (exprctx != null) {
+            final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+            expremd.read = false;
+            analyzer.visit(exprctx);
+
+            expremd.to = expremd.from;
+            caster.markCast(expremd);
+
+            if (!expremd.statement) {
+                throw new IllegalArgumentException(AnalyzerUtility.error(exprctx) +
+                    "The afterthought of a for loop must be a statement.");
+            }
+        }
+    }
+
+    void processDeclaration(final DeclarationContext ctx) {
+        final DecltypeContext decltypectx = ctx.decltype();
+        final ExpressionMetadata decltypeemd = metadata.createExpressionMetadata(decltypectx);
+        analyzer.visit(decltypectx);
+
+        for (final DeclvarContext declvarctx : ctx.declvar()) {
+            final ExpressionMetadata declvaremd = metadata.createExpressionMetadata(declvarctx);
+            declvaremd.to = decltypeemd.from;
+            analyzer.visit(declvarctx);
+        }
+    }
+
+    void processDecltype(final DecltypeContext ctx) {
+        final ExpressionMetadata decltypeemd = metadata.getExpressionMetadata(ctx);
+        final String name = ctx.getText();
+        decltypeemd.from = definition.getType(name);
+    }
+
+    void processDeclvar(final DeclvarContext ctx) {
+        final ExpressionMetadata declvaremd = metadata.getExpressionMetadata(ctx);
+
+        final String name = ctx.ID().getText();
+        declvaremd.postConst = utility.addVariable(ctx, name, declvaremd.to).slot;
+
+        final ExpressionContext exprctx = AnalyzerUtility.updateExpressionTree(ctx.expression());
+
+        if (exprctx != null) {
+            final ExpressionMetadata expremd = metadata.createExpressionMetadata(exprctx);
+            expremd.to = declvaremd.to;
+            analyzer.visit(exprctx);
+            caster.markCast(expremd);
+        }
+    }
+
+    void processTrap(final TrapContext ctx) {
+        final StatementMetadata trapsmd = metadata.getStatementMetadata(ctx);
+
+        final String type = ctx.TYPE().getText();
+        trapsmd.exception = definition.getType(type);
+
+        try {
+            trapsmd.exception.clazz.asSubclass(Exception.class);
+        } catch (final ClassCastException exception) {
+            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Invalid exception type [" + trapsmd.exception.name + "].");
+        }
+
+        final String id = ctx.ID().getText();
+        trapsmd.slot = utility.addVariable(ctx, id, trapsmd.exception).slot;
+
+        final BlockContext blockctx = ctx.block();
+
+        if (blockctx != null) {
+            final StatementMetadata blocksmd = metadata.createStatementMetadata(blockctx);
+            blocksmd.lastSource = trapsmd.lastSource;
+            blocksmd.inLoop = trapsmd.inLoop;
+            blocksmd.lastLoop = trapsmd.lastLoop;
+            analyzer.visit(blockctx);
+
+            trapsmd.methodEscape = blocksmd.methodEscape;
+            trapsmd.loopEscape = blocksmd.loopEscape;
+            trapsmd.allLast = blocksmd.allLast;
+            trapsmd.anyContinue = blocksmd.anyContinue;
+            trapsmd.anyBreak = blocksmd.anyBreak;
+        } else if (ctx.emptyscope() == null) {
+            throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        }
+    }
+}

+ 144 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerUtility.java

@@ -0,0 +1,144 @@
+/*
+ * 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.Type;
+import org.elasticsearch.painless.PainlessParser.ExpressionContext;
+import org.elasticsearch.painless.PainlessParser.PrecedenceContext;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+class AnalyzerUtility {
+    static class Variable {
+        final String name;
+        final Type type;
+        final int slot;
+
+        private Variable(final String name, final Type type, final int slot) {
+            this.name = name;
+            this.type = type;
+            this.slot = slot;
+        }
+    }
+
+    /**
+     * A utility method to output consistent error messages.
+     * @param ctx The ANTLR node the error occurred in.
+     * @return The error message with tacked on line number and character position.
+     */
+    static String error(final ParserRuleContext ctx) {
+        return "Analyzer Error [" + ctx.getStart().getLine() + ":" + ctx.getStart().getCharPositionInLine() + "]: ";
+    }
+
+    /**
+     * The ANTLR parse tree is modified in one single case; a parent node needs to check a child node to see if it's
+     * a precedence node, and if so, it must be removed from the tree permanently.  Once the ANTLR tree is built,
+     * precedence nodes are no longer necessary to maintain the correct ordering of the tree, so they only
+     * add a level of indirection where complicated decisions about metadata passing would have to be made.  This
+     * method removes the need for those decisions.
+     * @param source The child ANTLR node to check for precedence.
+     * @return The updated child ANTLR node.
+     */
+    static ExpressionContext updateExpressionTree(ExpressionContext source) {
+        // Check to see if the ANTLR node is a precedence node.
+        if (source instanceof PainlessParser.PrecedenceContext) {
+            final ParserRuleContext parent = source.getParent();
+            int index = 0;
+
+            // Mark the index of the source node within the list of child nodes from the parent.
+            for (final ParseTree child : parent.children) {
+                if (child == source) {
+                    break;
+                }
+
+                ++index;
+            }
+
+            // If there are multiple precedence nodes in a row, remove them all.
+            while (source instanceof PrecedenceContext) {
+                source = ((PrecedenceContext)source).expression();
+            }
+
+            // Update the parent node with the child of the precedence node.
+            parent.children.set(index, source);
+        }
+
+        return source;
+    }
+
+    private final Deque<Integer> scopes = new ArrayDeque<>();
+    private final Deque<Variable> variables = new ArrayDeque<>();
+
+    void incrementScope() {
+        scopes.push(0);
+    }
+
+    void decrementScope() {
+        int remove = scopes.pop();
+
+        while (remove > 0) {
+            variables.pop();
+            --remove;
+        }
+    }
+
+    Variable getVariable(final String name) {
+        final Iterator<Variable> itr = variables.iterator();
+
+        while (itr.hasNext()) {
+            final Variable variable = itr.next();
+
+            if (variable.name.equals(name)) {
+                return variable;
+            }
+        }
+
+        return null;
+    }
+
+    Variable addVariable(final ParserRuleContext source, final String name, final Type type) {
+        if (getVariable(name) != null) {
+            if (source == null) {
+                throw new IllegalArgumentException("Argument name [" + name + "] already defined within the scope.");
+            } else {
+                throw new IllegalArgumentException(error(source) + "Variable name [" + name + "] already defined within the scope.");
+            }
+        }
+
+        final Variable previous = variables.peekFirst();
+        int slot = 0;
+
+        if (previous != null) {
+            slot += previous.slot + previous.type.type.getSize();
+        }
+
+        final Variable variable = new Variable(name, type, slot);
+        variables.push(variable);
+
+        final int update = scopes.pop() + 1;
+        scopes.push(update);
+
+        return variable;
+    }
+}

+ 1 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java

@@ -160,7 +160,7 @@ final class Compiler {
             //    throw new RuntimeException(e);
             // }
 
-            final Class<? extends Executable> clazz = loader.define(Writer.CLASS_NAME, bytes);
+            final Class<? extends Executable> clazz = loader.define(WriterConstants.CLASS_NAME, bytes);
             final java.lang.reflect.Constructor<? extends Executable> constructor =
                     clazz.getConstructor(Definition.class, String.class, String.class);
 

+ 4 - 53
modules/lang-painless/src/main/java/org/elasticsearch/painless/Metadata.java

@@ -20,11 +20,8 @@
 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.Type;
-import org.elasticsearch.painless.PainlessParser.ExpressionContext;
-import org.elasticsearch.painless.PainlessParser.PrecedenceContext;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -37,7 +34,6 @@ import java.util.Map;
  * the root of the ANTLR parse tree, and the {@link CompilerSettings}.
  */
 class Metadata {
-
     /**
      * StatementMetadata is used to store metadata mostly about
      * control flow for ANTLR nodes related to if/else, do, while, for, etc.
@@ -386,15 +382,6 @@ class Metadata {
         }
     }
 
-    /**
-     * A utility method to output consistent error messages.
-     * @param ctx The ANTLR node the error occurred in.
-     * @return The error message with tacked on line number and character position.
-     */
-    static String error(final ParserRuleContext ctx) {
-        return "Error [" + ctx.getStart().getLine() + ":" + ctx.getStart().getCharPositionInLine() + "]: ";
-    }
-
     /**
      * Acts as both the Painless API and white-list for what types and methods are allowed.
      */
@@ -490,49 +477,13 @@ class Metadata {
         final StatementMetadata sourcesmd = statementMetadata.get(source);
 
         if (sourcesmd == null) {
-            throw new IllegalStateException(error(source) + "Statement metadata does not exist at" +
+            throw new IllegalStateException("Statement metadata does not exist at" +
                 " the parse node with text [" + source.getText() + "].");
         }
 
         return sourcesmd;
     }
 
-    /**
-     * The ANTLR parse tree is modified in one single case; a parent node needs to check a child node to see if it's
-     * a precedence node, and if so, it must be removed from the tree permanently. Once the ANTLR tree is built,
-     * precedence nodes are no longer necessary to maintain the correct ordering of the tree, so they only
-     * add a level of indirection where complicated decisions about metadata passing would have to be made.  This
-     * method removes the need for those decisions.
-     * @param source The child ANTLR node to check for precedence.
-     * @return The updated child ANTLR node.
-     */
-    ExpressionContext updateExpressionTree(ExpressionContext source) {
-        // Check to see if the ANTLR node is a precedence node.
-        if (source instanceof PrecedenceContext) {
-            final ParserRuleContext parent = source.getParent();
-            int index = 0;
-
-            // Mark the index of the source node within the list of child nodes from the parent.
-            for (final ParseTree child : parent.children) {
-                if (child == source) {
-                    break;
-                }
-
-                ++index;
-            }
-
-            // If there are multiple precedence nodes in a row, remove them all.
-            while (source instanceof PrecedenceContext) {
-                source = ((PrecedenceContext)source).expression();
-            }
-
-            // Update the parent node with the child of the precedence node.
-            parent.children.set(index, source);
-        }
-
-        return source;
-    }
-
     /**
      * Creates a new ExpressionMetadata and stores it in the expressionMetadata map.
      * @param source The ANTLR node for this metadata.
@@ -554,7 +505,7 @@ class Metadata {
         final ExpressionMetadata sourceemd = expressionMetadata.get(source);
 
         if (sourceemd == null) {
-            throw new IllegalStateException(error(source) + "Expression metadata does not exist at" +
+            throw new IllegalStateException("Expression metadata does not exist at" +
                 " the parse node with text [" + source.getText() + "].");
         }
 
@@ -582,7 +533,7 @@ class Metadata {
         final ExternalMetadata sourceemd = externalMetadata.get(source);
 
         if (sourceemd == null) {
-            throw new IllegalStateException(error(source) + "External metadata does not exist at" +
+            throw new IllegalStateException("External metadata does not exist at" +
                 " the parse node with text [" + source.getText() + "].");
         }
 
@@ -610,7 +561,7 @@ class Metadata {
         final ExtNodeMetadata sourceemd = extNodeMetadata.get(source);
 
         if (sourceemd == null) {
-            throw new IllegalStateException(error(source) + "External metadata does not exist at" +
+            throw new IllegalStateException("External metadata does not exist at" +
                 " the parse node with text [" + source.getText() + "].");
         }
 

+ 95 - 1944
modules/lang-painless/src/main/java/org/elasticsearch/painless/Writer.java

@@ -19,20 +19,11 @@
 
 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;
@@ -48,7 +39,6 @@ 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;
@@ -75,261 +65,82 @@ 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;
+import static org.elasticsearch.painless.WriterConstants.BASE_CLASS_TYPE;
+import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
+import static org.elasticsearch.painless.WriterConstants.CONSTRUCTOR;
+import static org.elasticsearch.painless.WriterConstants.EXECUTE;
+import static org.elasticsearch.painless.WriterConstants.MAP_GET;
+import static org.elasticsearch.painless.WriterConstants.MAP_TYPE;
+import static org.elasticsearch.painless.WriterConstants.SCORE_ACCESSOR_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.SCORE_ACCESSOR_TYPE;
+import static org.elasticsearch.painless.WriterConstants.SIGNATURE;
 
 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);
+        final 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 final ClassWriter writer;
+    private final GeneratorAdapter execute;
 
-    private ClassWriter writer;
-    private GeneratorAdapter execute;
+    private final WriterStatement statement;
+    private final WriterExpression expression;
+    private final WriterExternal external;
 
     private Writer(final Metadata metadata) {
         this.metadata = metadata;
-        definition = metadata.definition;
         root = metadata.root;
         source = metadata.source;
         settings = metadata.settings;
 
+        writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
+
         writeBegin();
         writeConstructor();
-        writeExecute();
-        writeEnd();
-    }
 
-    private Branch markBranch(final ParserRuleContext source, final ParserRuleContext... nodes) {
-        final Branch branch = new Branch(source);
+        execute = new GeneratorAdapter(Opcodes.ACC_PUBLIC, EXECUTE, SIGNATURE, null, writer);
 
-        for (final ParserRuleContext node : nodes) {
-            branches.put(node, branch);
-        }
+        final WriterUtility utility = new WriterUtility(metadata, execute);
+        final WriterCaster caster = new WriterCaster(execute);
 
-        return branch;
-    }
+        statement = new WriterStatement(metadata, execute, this, utility);
+        expression = new WriterExpression(metadata, execute, this, utility, caster);
+        external = new WriterExternal(metadata, execute, this, utility, caster);
 
-    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);
+        writeExecute();
+        writeEnd();
     }
 
     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);
+        final GeneratorAdapter constructor = new GeneratorAdapter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, null, null, writer);
         constructor.loadThis();
         constructor.loadArgs();
         constructor.invokeConstructor(org.objectweb.asm.Type.getType(Executable.class), CONSTRUCTOR);
@@ -338,9 +149,6 @@ class Writer extends PainlessParserBaseVisitor<Void> {
     }
 
     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);
@@ -364,477 +172,205 @@ class Writer extends PainlessParserBaseVisitor<Void> {
         execute.endMethod();
     }
 
-    @Override
-    public Void visitSource(final SourceContext ctx) {
-        final Metadata.StatementMetadata sourcesmd = metadata.getStatementMetadata(ctx);
+    private void writeEnd() {
+        writer.visitEnd();
+    }
 
-        for (final StatementContext sctx : ctx.statement()) {
-            visit(sctx);
-        }
+    private byte[] getBytes() {
+        return writer.toByteArray();
+    }
 
-        if (!sourcesmd.methodEscape) {
-            execute.visitInsn(Opcodes.ACONST_NULL);
-            execute.returnValue();
-        }
+    @Override
+    public Void visitSource(final SourceContext ctx) {
+        statement.processSource(ctx);
 
         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);
+        statement.processIf(ctx);
 
         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();
+        statement.processWhile(ctx);
 
         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();
+        statement.processDo(ctx);
 
         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();
+        statement.processFor(ctx);
 
         return null;
     }
 
     @Override
     public Void visitDecl(final DeclContext ctx) {
-        visit(ctx.declaration());
+        statement.processDecl(ctx);
 
         return null;
     }
 
     @Override
     public Void visitContinue(final ContinueContext ctx) {
-        final Branch jump = jumps.peek();
-        execute.goTo(jump.begin);
+        statement.processContinue();
 
         return null;
     }
 
     @Override
     public Void visitBreak(final BreakContext ctx) {
-        final Branch jump = jumps.peek();
-        execute.goTo(jump.end);
+        statement.processBreak();
 
         return null;
     }
 
     @Override
     public Void visitReturn(final ReturnContext ctx) {
-        visit(ctx.expression());
-        execute.returnValue();
+        statement.processReturn(ctx);
 
         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);
-        }
+        statement.processTry(ctx);
 
         return null;
     }
 
     @Override
     public Void visitThrow(final ThrowContext ctx) {
-        visit(ctx.expression());
-        execute.throwException();
+        statement.processThrow(ctx);
 
         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());
-        }
+        statement.processExpr(ctx);
 
         return null;
     }
 
     @Override
     public Void visitMultiple(final MultipleContext ctx) {
-        for (final StatementContext sctx : ctx.statement()) {
-            visit(sctx);
-        }
+        statement.processMultiple(ctx);
 
         return null;
     }
 
     @Override
     public Void visitSingle(final SingleContext ctx) {
-        visit(ctx.statement());
+        statement.processSingle(ctx);
 
         return null;
     }
 
     @Override
     public Void visitEmpty(final EmptyContext ctx) {
-        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected writer state.");
+        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected 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.");
-        }
+    public Void visitEmptyscope(final EmptyscopeContext ctx) {
+        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected state.");
+    }
+
+    @Override
+    public Void visitInitializer(final InitializerContext ctx) {
+        statement.processInitializer(ctx);
 
         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());
+    public Void visitAfterthought(final AfterthoughtContext ctx) {
+        statement.processAfterthought(ctx);
 
         return null;
     }
 
     @Override
     public Void visitDeclaration(DeclarationContext ctx) {
-        for (final DeclvarContext declctx : ctx.declvar()) {
-            visit(declctx);
-        }
+        statement.processDeclaration(ctx);
 
         return null;
     }
 
     @Override
     public Void visitDecltype(final DecltypeContext ctx) {
-        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected writer state.");
+        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected 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);
+        statement.processDeclvar(ctx);
 
         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);
-        }
+        statement.processTrap(ctx);
 
         return null;
     }
 
     @Override
     public Void visitPrecedence(final PrecedenceContext ctx) {
-        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected writer state.");
+        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected 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);
+        expression.processNumeric(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);
+        expression.processChar(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);
-        }
+        expression.processTrue(ctx);
 
         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);
-        }
+        expression.processFalse(ctx);
 
         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);
+        expression.processNull(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExternal(final ExternalContext ctx) {
-        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
-        visit(ctx.extstart());
-        checkWriteCast(expremd);
-        checkWriteBranch(ctx);
+        expression.processExternal(ctx);
 
         return null;
     }
@@ -842,1538 +378,153 @@ class Writer extends PainlessParserBaseVisitor<Void> {
 
     @Override
     public Void visitPostinc(final PostincContext ctx) {
-        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
-        visit(ctx.extstart());
-        checkWriteCast(expremd);
-        checkWriteBranch(ctx);
+        expression.processPostinc(ctx);
 
         return null;
     }
 
     @Override
     public Void visitPreinc(final PreincContext ctx) {
-        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
-        visit(ctx.extstart());
-        checkWriteCast(expremd);
-        checkWriteBranch(ctx);
+        expression.processPreinc(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);
-            }
-        }
+        expression.processUnary(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);
+        expression.processCast(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);
+        expression.processBinary(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);
-            }
-        }
+        expression.processComp(ctx);
 
         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.");
-                }
-            }
-        }
+        expression.processBool(ctx);
 
         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);
-        }
+        expression.processConditional(ctx);
 
         return null;
     }
 
     @Override
     public Void visitAssignment(final AssignmentContext ctx) {
-        final Metadata.ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
-        visit(ctx.extstart());
-        checkWriteCast(expremd);
-        checkWriteBranch(ctx);
+        expression.processAssignment(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();
-        }
+    public Void visitExtstart(final ExtstartContext ctx) {
+        external.processExtstart(ctx);
 
         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);
-        }
+        external.processExtprec(ctx);
 
         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);
+        external.processExtcast(ctx);
 
         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);
-        }
+        external.processExtbrace(ctx);
 
         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);
-        }
+        external.processExtdot(ctx);
 
         return null;
     }
 
     @Override
     public Void visitExttype(final ExttypeContext ctx) {
-        visit(ctx.extdot());
+        external.processExttype(ctx);
 
         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);
-        }
+        external.processExtcall(ctx);
 
         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);
-        }
+        external.processExtvar(ctx);
 
         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);
-        }
+        external.processExtfield(ctx);
 
         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);
-        }
+    public Void visitExtnew(final ExtnewContext ctx) {
+        external.processExtnew(ctx);
 
         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);
-        }
+    public Void visitExtstring(final ExtstringContext ctx) {
+        external.processExtstring(ctx);
 
         return null;
     }
 
     @Override
     public Void visitArguments(final ArgumentsContext ctx) {
-        throw new UnsupportedOperationException(Metadata.error(ctx) + "Unexpected writer state.");
+        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected 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);
+    public Void visitIncrement(final IncrementContext ctx) {
+        expression.processIncrement(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();
-    }
 }

+ 86 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterCaster.java

@@ -0,0 +1,86 @@
+/*
+ * 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.elasticsearch.painless.Definition.Cast;
+import org.elasticsearch.painless.Definition.Transform;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Metadata.ExpressionMetadata;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+class WriterCaster {
+    private final GeneratorAdapter execute;
+
+    WriterCaster(final GeneratorAdapter execute) {
+        this.execute = execute;
+    }
+
+    void checkWriteCast(final ExpressionMetadata sort) {
+        checkWriteCast(sort.source, sort.cast);
+    }
+
+    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(WriterUtility.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);
+        }
+    }
+}

+ 138 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java

@@ -0,0 +1,138 @@
+/*
+ * 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.elasticsearch.script.ScoreAccessor;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.Method;
+
+import java.lang.invoke.MethodType;
+import java.util.Map;
+
+class WriterConstants {
+    final static String BASE_CLASS_NAME = Executable.class.getName();
+    final static String CLASS_NAME      = BASE_CLASS_NAME + "$CompiledPainlessExecutable";
+    final static Type BASE_CLASS_TYPE   = Type.getType(Executable.class);
+    final static Type CLASS_TYPE        = Type.getType("L" + CLASS_NAME.replace(".", "/") + ";");
+
+    final static Method CONSTRUCTOR = getAsmMethod(void.class, "<init>", Definition.class, String.class, String.class);
+    final static Method EXECUTE     = getAsmMethod(Object.class, "execute", Map.class);
+    final static String SIGNATURE   = "(Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)Ljava/lang/Object;";
+
+    final static Type PAINLESS_ERROR_TYPE = Type.getType(PainlessError.class);
+
+    final static Type DEFINITION_TYPE = Type.getType(Definition.class);
+
+    final static Type MAP_TYPE  = Type.getType(Map.class);
+    final static Method MAP_GET = getAsmMethod(Object.class, "get", Object.class);
+
+    final static Type SCORE_ACCESSOR_TYPE    = Type.getType(ScoreAccessor.class);
+    final static Method SCORE_ACCESSOR_FLOAT = getAsmMethod(float.class, "floatValue");
+
+    final static Method DEF_METHOD_CALL = getAsmMethod(
+        Object.class, "methodCall", Object.class, String.class, Definition.class, Object[].class, boolean[].class);
+    final static Method DEF_ARRAY_STORE = getAsmMethod(
+        void.class, "arrayStore", Object.class, Object.class, Object.class, Definition.class, boolean.class, boolean.class);
+    final static Method DEF_ARRAY_LOAD = getAsmMethod(
+        Object.class, "arrayLoad", Object.class, Object.class, Definition.class, boolean.class);
+    final static Method DEF_FIELD_STORE = getAsmMethod(
+        void.class, "fieldStore", Object.class, Object.class, String.class, Definition.class, boolean.class);
+    final static Method DEF_FIELD_LOAD = getAsmMethod(
+        Object.class, "fieldLoad", Object.class, String.class, Definition.class);
+
+    final static Method DEF_NOT_CALL = getAsmMethod(Object.class, "not", Object.class);
+    final static Method DEF_NEG_CALL = getAsmMethod(Object.class, "neg", Object.class);
+    final static Method DEF_MUL_CALL = getAsmMethod(Object.class, "mul", Object.class, Object.class);
+    final static Method DEF_DIV_CALL = getAsmMethod(Object.class, "div", Object.class, Object.class);
+    final static Method DEF_REM_CALL = getAsmMethod(Object.class, "rem", Object.class, Object.class);
+    final static Method DEF_ADD_CALL = getAsmMethod(Object.class, "add", Object.class, Object.class);
+    final static Method DEF_SUB_CALL = getAsmMethod(Object.class, "sub", Object.class, Object.class);
+    final static Method DEF_LSH_CALL = getAsmMethod(Object.class, "lsh", Object.class, Object.class);
+    final static Method DEF_RSH_CALL = getAsmMethod(Object.class, "rsh", Object.class, Object.class);
+    final static Method DEF_USH_CALL = getAsmMethod(Object.class, "ush", Object.class, Object.class);
+    final static Method DEF_AND_CALL = getAsmMethod(Object.class, "and", Object.class, Object.class);
+    final static Method DEF_XOR_CALL = getAsmMethod(Object.class, "xor", Object.class, Object.class);
+    final static Method DEF_OR_CALL  = getAsmMethod(Object.class, "or" , Object.class, Object.class);
+    final static Method DEF_EQ_CALL  = getAsmMethod(boolean.class, "eq" , Object.class, Object.class);
+    final static Method DEF_LT_CALL  = getAsmMethod(boolean.class, "lt" , Object.class, Object.class);
+    final static Method DEF_LTE_CALL = getAsmMethod(boolean.class, "lte", Object.class, Object.class);
+    final static Method DEF_GT_CALL  = getAsmMethod(boolean.class, "gt" , Object.class, Object.class);
+    final static Method DEF_GTE_CALL = getAsmMethod(boolean.class, "gte", Object.class, Object.class);
+
+    final static Type STRINGBUILDER_TYPE = Type.getType(StringBuilder.class);
+
+    final static Method STRINGBUILDER_CONSTRUCTOR    = getAsmMethod(void.class, "<init>");
+    final static Method STRINGBUILDER_APPEND_BOOLEAN = getAsmMethod(StringBuilder.class, "append", boolean.class);
+    final static Method STRINGBUILDER_APPEND_CHAR    = getAsmMethod(StringBuilder.class, "append", char.class);
+    final static Method STRINGBUILDER_APPEND_INT     = getAsmMethod(StringBuilder.class, "append", int.class);
+    final static Method STRINGBUILDER_APPEND_LONG    = getAsmMethod(StringBuilder.class, "append", long.class);
+    final static Method STRINGBUILDER_APPEND_FLOAT   = getAsmMethod(StringBuilder.class, "append", float.class);
+    final static Method STRINGBUILDER_APPEND_DOUBLE  = getAsmMethod(StringBuilder.class, "append", double.class);
+    final static Method STRINGBUILDER_APPEND_STRING  = getAsmMethod(StringBuilder.class, "append", String.class);
+    final static Method STRINGBUILDER_APPEND_OBJECT  = getAsmMethod(StringBuilder.class, "append", Object.class);
+    final static Method STRINGBUILDER_TOSTRING       = getAsmMethod(String.class, "toString");
+
+    final static Method TOINTEXACT_LONG  = getAsmMethod(int.class,  "toIntExact",    long.class);
+    final static Method NEGATEEXACT_INT  = getAsmMethod(int.class,  "negateExact",   int.class);
+    final static Method NEGATEEXACT_LONG = getAsmMethod(long.class, "negateExact",   long.class);
+    final static Method MULEXACT_INT     = getAsmMethod(int.class,  "multiplyExact", int.class,  int.class);
+    final static Method MULEXACT_LONG    = getAsmMethod(long.class, "multiplyExact", long.class, long.class);
+    final static Method ADDEXACT_INT     = getAsmMethod(int.class,  "addExact",      int.class,  int.class);
+    final static Method ADDEXACT_LONG    = getAsmMethod(long.class, "addExact",      long.class, long.class);
+    final static Method SUBEXACT_INT     = getAsmMethod(int.class,  "subtractExact", int.class,  int.class);
+    final static Method SUBEXACT_LONG    = getAsmMethod(long.class, "subtractExact", long.class, long.class);
+
+    final static Method CHECKEQUALS              = getAsmMethod(boolean.class, "checkEquals",              Object.class, Object.class);
+    final static Method TOBYTEEXACT_INT          = getAsmMethod(byte.class,    "toByteExact",              int.class);
+    final static Method TOBYTEEXACT_LONG         = getAsmMethod(byte.class,    "toByteExact",              long.class);
+    final static Method TOBYTEWOOVERFLOW_FLOAT   = getAsmMethod(byte.class,    "toByteWithoutOverflow",    float.class);
+    final static Method TOBYTEWOOVERFLOW_DOUBLE  = getAsmMethod(byte.class,    "toByteWithoutOverflow",    double.class);
+    final static Method TOSHORTEXACT_INT         = getAsmMethod(short.class,   "toShortExact",             int.class);
+    final static Method TOSHORTEXACT_LONG        = getAsmMethod(short.class,   "toShortExact",             long.class);
+    final static Method TOSHORTWOOVERFLOW_FLOAT  = getAsmMethod(short.class,   "toShortWithoutOverflow",   float.class);
+    final static Method TOSHORTWOOVERFLOW_DOUBLE = getAsmMethod(short.class,   "toShortWihtoutOverflow",   double.class);
+    final static Method TOCHAREXACT_INT          = getAsmMethod(char.class,    "toCharExact",              int.class);
+    final static Method TOCHAREXACT_LONG         = getAsmMethod(char.class,    "toCharExact",              long.class);
+    final static Method TOCHARWOOVERFLOW_FLOAT   = getAsmMethod(char.class,    "toCharWithoutOverflow",    float.class);
+    final static Method TOCHARWOOVERFLOW_DOUBLE  = getAsmMethod(char.class,    "toCharWithoutOverflow",    double.class);
+    final static Method TOINTWOOVERFLOW_FLOAT    = getAsmMethod(int.class,     "toIntWithoutOverflow",     float.class);
+    final static Method TOINTWOOVERFLOW_DOUBLE   = getAsmMethod(int.class,     "toIntWithoutOverflow",     double.class);
+    final static Method TOLONGWOOVERFLOW_FLOAT   = getAsmMethod(long.class,    "toLongWithoutOverflow",    float.class);
+    final static Method TOLONGWOOVERFLOW_DOUBLE  = getAsmMethod(long.class,    "toLongWithoutOverflow",    double.class);
+    final static Method TOFLOATWOOVERFLOW_DOUBLE = getAsmMethod(float.class ,  "toFloatWihtoutOverflow",   double.class);
+    final static Method MULWOOVERLOW_FLOAT       = getAsmMethod(float.class,   "multiplyWithoutOverflow",  float.class,  float.class);
+    final static Method MULWOOVERLOW_DOUBLE      = getAsmMethod(double.class,  "multiplyWithoutOverflow",  double.class, double.class);
+    final static Method DIVWOOVERLOW_INT         = getAsmMethod(int.class,     "divideWithoutOverflow",    int.class,    int.class);
+    final static Method DIVWOOVERLOW_LONG        = getAsmMethod(long.class,    "divideWithoutOverflow",    long.class,   long.class);
+    final static Method DIVWOOVERLOW_FLOAT       = getAsmMethod(float.class,   "divideWithoutOverflow",    float.class,  float.class);
+    final static Method DIVWOOVERLOW_DOUBLE      = getAsmMethod(double.class,  "divideWithoutOverflow",    double.class, double.class);
+    final static Method REMWOOVERLOW_FLOAT       = getAsmMethod(float.class,   "remainderWithoutOverflow", float.class,  float.class);
+    final static Method REMWOOVERLOW_DOUBLE      = getAsmMethod(double.class,  "remainderWithoutOverflow", double.class, double.class);
+    final static Method ADDWOOVERLOW_FLOAT       = getAsmMethod(float.class,   "addWithoutOverflow",       float.class,  float.class);
+    final static Method ADDWOOVERLOW_DOUBLE      = getAsmMethod(double.class,  "addWithoutOverflow",       double.class, double.class);
+    final static Method SUBWOOVERLOW_FLOAT       = getAsmMethod(float.class,   "subtractWithoutOverflow",  float.class,  float.class);
+    final static Method SUBWOOVERLOW_DOUBLE      = getAsmMethod(double.class,  "subtractWithoutOverflow",  double.class, double.class);
+
+    private static Method getAsmMethod(final Class<?> rtype, final String name, final Class<?>... ptypes) {
+        return new Method(name, MethodType.methodType(rtype, ptypes).toMethodDescriptorString());
+    }
+
+    private WriterConstants() {}
+}

+ 684 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterExpression.java

@@ -0,0 +1,684 @@
+/*
+ * 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.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Metadata.ExpressionMetadata;
+import org.elasticsearch.painless.PainlessParser.AssignmentContext;
+import org.elasticsearch.painless.PainlessParser.BinaryContext;
+import org.elasticsearch.painless.PainlessParser.BoolContext;
+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.ExpressionContext;
+import org.elasticsearch.painless.PainlessParser.ExternalContext;
+import org.elasticsearch.painless.PainlessParser.FalseContext;
+import org.elasticsearch.painless.PainlessParser.IncrementContext;
+import org.elasticsearch.painless.PainlessParser.NullContext;
+import org.elasticsearch.painless.PainlessParser.NumericContext;
+import org.elasticsearch.painless.PainlessParser.PostincContext;
+import org.elasticsearch.painless.PainlessParser.PreincContext;
+import org.elasticsearch.painless.PainlessParser.TrueContext;
+import org.elasticsearch.painless.PainlessParser.UnaryContext;
+import org.elasticsearch.painless.WriterUtility.Branch;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+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;
+import static org.elasticsearch.painless.WriterConstants.CHECKEQUALS;
+import static org.elasticsearch.painless.WriterConstants.DEF_EQ_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_GTE_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_GT_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_LTE_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_LT_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_NEG_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_NOT_CALL;
+import static org.elasticsearch.painless.WriterConstants.NEGATEEXACT_INT;
+import static org.elasticsearch.painless.WriterConstants.NEGATEEXACT_LONG;
+
+class WriterExpression {
+    private final Metadata metadata;
+    private final Definition definition;
+    private final CompilerSettings settings;
+
+    private final GeneratorAdapter execute;
+
+    private final Writer writer;
+    private final WriterUtility utility;
+    private final WriterCaster caster;
+
+    WriterExpression(final Metadata metadata, final GeneratorAdapter execute, final Writer writer,
+                     final WriterUtility utility, final WriterCaster caster) {
+        this.metadata = metadata;
+        definition = metadata.definition;
+        settings = metadata.settings;
+
+        this.execute = execute;
+
+        this.writer = writer;
+        this.utility = utility;
+        this.caster = caster;
+    }
+
+    void processNumeric(final NumericContext ctx) {
+        final ExpressionMetadata numericemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = numericemd.postConst;
+
+        if (postConst == null) {
+            utility.writeNumeric(ctx, numericemd.preConst);
+            caster.checkWriteCast(numericemd);
+        } else {
+            utility.writeConstant(ctx, postConst);
+        }
+
+        utility.checkWriteBranch(ctx);
+    }
+
+    void processChar(final CharContext ctx) {
+        final ExpressionMetadata charemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = charemd.postConst;
+
+        if (postConst == null) {
+            utility.writeNumeric(ctx, (int)(char)charemd.preConst);
+            caster.checkWriteCast(charemd);
+        } else {
+            utility.writeConstant(ctx, postConst);
+        }
+
+        utility.checkWriteBranch(ctx);
+    }
+
+    void processTrue(final TrueContext ctx) {
+        final ExpressionMetadata trueemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = trueemd.postConst;
+        final Branch branch = utility.getBranch(ctx);
+
+        if (branch == null) {
+            if (postConst == null) {
+                utility.writeBoolean(ctx, true);
+                caster.checkWriteCast(trueemd);
+            } else {
+                utility.writeConstant(ctx, postConst);
+            }
+        } else if (branch.tru != null) {
+            execute.goTo(branch.tru);
+        }
+    }
+
+    void processFalse(final FalseContext ctx) {
+        final ExpressionMetadata falseemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = falseemd.postConst;
+        final Branch branch = utility.getBranch(ctx);
+
+        if (branch == null) {
+            if (postConst == null) {
+                utility.writeBoolean(ctx, false);
+                caster.checkWriteCast(falseemd);
+            } else {
+                utility.writeConstant(ctx, postConst);
+            }
+        } else if (branch.fals != null) {
+            execute.goTo(branch.fals);
+        }
+    }
+
+    void processNull(final NullContext ctx) {
+        final ExpressionMetadata nullemd = metadata.getExpressionMetadata(ctx);
+
+        execute.visitInsn(Opcodes.ACONST_NULL);
+        caster.checkWriteCast(nullemd);
+        utility.checkWriteBranch(ctx);
+    }
+
+    void processExternal(final ExternalContext ctx) {
+        final ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
+        writer.visit(ctx.extstart());
+        caster.checkWriteCast(expremd);
+        utility.checkWriteBranch(ctx);
+    }
+
+
+    void processPostinc(final PostincContext ctx) {
+        final ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
+        writer.visit(ctx.extstart());
+        caster.checkWriteCast(expremd);
+        utility.checkWriteBranch(ctx);
+    }
+
+    void processPreinc(final PreincContext ctx) {
+        final ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
+        writer.visit(ctx.extstart());
+        caster.checkWriteCast(expremd);
+        utility.checkWriteBranch(ctx);
+    }
+
+    void processUnary(final UnaryContext ctx) {
+        final ExpressionMetadata unaryemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = unaryemd.postConst;
+        final Object preConst = unaryemd.preConst;
+        final Branch branch = utility.getBranch(ctx);
+
+        if (postConst != null) {
+            if (ctx.BOOLNOT() != null) {
+                if (branch == null) {
+                    utility.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 {
+                utility.writeConstant(ctx, postConst);
+                utility.checkWriteBranch(ctx);
+            }
+        } else if (preConst != null) {
+            if (branch == null) {
+                utility.writeConstant(ctx, preConst);
+                caster.checkWriteCast(unaryemd);
+            } else {
+                throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+            }
+        } else {
+            final ExpressionContext exprctx = ctx.expression();
+
+            if (ctx.BOOLNOT() != null) {
+                final Branch local = utility.markBranch(ctx, exprctx);
+
+                if (branch == null) {
+                    local.fals = new Label();
+                    final Label aend = new Label();
+
+                    writer.visit(exprctx);
+
+                    execute.push(false);
+                    execute.goTo(aend);
+                    execute.mark(local.fals);
+                    execute.push(true);
+                    execute.mark(aend);
+
+                    caster.checkWriteCast(unaryemd);
+                } else {
+                    local.tru = branch.fals;
+                    local.fals = branch.tru;
+
+                    writer.visit(exprctx);
+                }
+            } else {
+                final org.objectweb.asm.Type type = unaryemd.from.type;
+                final Sort sort = unaryemd.from.sort;
+
+                writer.visit(exprctx);
+
+                if (ctx.BWNOT() != null) {
+                    if (sort == Sort.DEF) {
+                        execute.invokeStatic(definition.defobjType.type, DEF_NOT_CALL);
+                    } else {
+                        if (sort == Sort.INT) {
+                            utility.writeConstant(ctx, -1);
+                        } else if (sort == Sort.LONG) {
+                            utility.writeConstant(ctx, -1L);
+                        } else {
+                            throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected 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(WriterUtility.error(ctx) + "Unexpected state.");
+                            }
+                        }
+                    }
+                } else if (ctx.ADD() == null) {
+                    throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+                }
+
+                caster.checkWriteCast(unaryemd);
+                utility.checkWriteBranch(ctx);
+            }
+        }
+    }
+
+    void processCast(final CastContext ctx) {
+        final ExpressionMetadata castemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = castemd.postConst;
+
+        if (postConst == null) {
+            writer.visit(ctx.expression());
+            caster.checkWriteCast(castemd);
+        } else {
+            utility.writeConstant(ctx, postConst);
+        }
+
+        utility.checkWriteBranch(ctx);
+    }
+
+    void processBinary(final BinaryContext ctx) {
+        final ExpressionMetadata binaryemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = binaryemd.postConst;
+        final Object preConst = binaryemd.preConst;
+        final Branch branch = utility.getBranch(ctx);
+
+        if (postConst != null) {
+            utility.writeConstant(ctx, postConst);
+        } else if (preConst != null) {
+            if (branch == null) {
+                utility.writeConstant(ctx, preConst);
+                caster.checkWriteCast(binaryemd);
+            } else {
+                throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+            }
+        } else if (binaryemd.from.sort == Sort.STRING) {
+            final boolean marked = utility.containsStrings(ctx);
+
+            if (!marked) {
+                utility.writeNewStrings();
+            }
+
+            final ExpressionContext exprctx0 = ctx.expression(0);
+            final ExpressionMetadata expremd0 = metadata.getExpressionMetadata(exprctx0);
+            utility.addStrings(exprctx0);
+            writer.visit(exprctx0);
+
+            if (utility.containsStrings(exprctx0)) {
+                utility.writeAppendStrings(expremd0.from.sort);
+                utility.removeStrings(exprctx0);
+            }
+
+            final ExpressionContext exprctx1 = ctx.expression(1);
+            final ExpressionMetadata expremd1 = metadata.getExpressionMetadata(exprctx1);
+            utility.addStrings(exprctx1);
+            writer.visit(exprctx1);
+
+            if (utility.containsStrings(exprctx1)) {
+                utility.writeAppendStrings(expremd1.from.sort);
+                utility.removeStrings(exprctx1);
+            }
+
+            if (marked) {
+                utility.removeStrings(ctx);
+            } else {
+                utility.writeToStrings();
+            }
+
+            caster.checkWriteCast(binaryemd);
+        } else {
+            final ExpressionContext exprctx0 = ctx.expression(0);
+            final ExpressionContext exprctx1 = ctx.expression(1);
+
+            writer.visit(exprctx0);
+            writer.visit(exprctx1);
+
+            final Type type = binaryemd.from;
+
+            if      (ctx.MUL()   != null) utility.writeBinaryInstruction(ctx, type, MUL);
+            else if (ctx.DIV()   != null) utility.writeBinaryInstruction(ctx, type, DIV);
+            else if (ctx.REM()   != null) utility.writeBinaryInstruction(ctx, type, REM);
+            else if (ctx.ADD()   != null) utility.writeBinaryInstruction(ctx, type, ADD);
+            else if (ctx.SUB()   != null) utility.writeBinaryInstruction(ctx, type, SUB);
+            else if (ctx.LSH()   != null) utility.writeBinaryInstruction(ctx, type, LSH);
+            else if (ctx.USH()   != null) utility.writeBinaryInstruction(ctx, type, USH);
+            else if (ctx.RSH()   != null) utility.writeBinaryInstruction(ctx, type, RSH);
+            else if (ctx.BWAND() != null) utility.writeBinaryInstruction(ctx, type, BWAND);
+            else if (ctx.BWXOR() != null) utility.writeBinaryInstruction(ctx, type, BWXOR);
+            else if (ctx.BWOR()  != null) utility.writeBinaryInstruction(ctx, type, BWOR);
+            else {
+                throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+            }
+
+            caster.checkWriteCast(binaryemd);
+        }
+
+        utility.checkWriteBranch(ctx);
+    }
+
+    void processComp(final CompContext ctx) {
+        final ExpressionMetadata compemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = compemd.postConst;
+        final Object preConst = compemd.preConst;
+        final Branch branch = utility.getBranch(ctx);
+
+        if (postConst != null) {
+            if (branch == null) {
+                utility.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) {
+                utility.writeConstant(ctx, preConst);
+                caster.checkWriteCast(compemd);
+            } else {
+                throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+            }
+        } else {
+            final ExpressionContext exprctx0 = ctx.expression(0);
+            final ExpressionMetadata expremd0 = metadata.getExpressionMetadata(exprctx0);
+
+            final ExpressionContext exprctx1 = ctx.expression(1);
+            final ExpressionMetadata expremd1 = metadata.getExpressionMetadata(exprctx1);
+            final org.objectweb.asm.Type type = expremd1.to.type;
+            final Sort sort1 = expremd1.to.sort;
+
+            writer.visit(exprctx0);
+
+            if (!expremd1.isNull) {
+                writer.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(WriterUtility.error(ctx) + "Unexpected state.");
+                case BOOL:
+                    if      (eq) execute.ifZCmp(GeneratorAdapter.EQ, jump);
+                    else if (ne) execute.ifZCmp(GeneratorAdapter.NE, jump);
+                    else {
+                        throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected 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(WriterUtility.error(ctx) + "Unexpected 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(WriterUtility.error(ctx) + "Unexpected 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(WriterUtility.error(ctx) + "Unexpected state.");
+                    }
+            }
+
+            if (branch == null) {
+                if (writejump) {
+                    execute.push(false);
+                    execute.goTo(end);
+                    execute.mark(jump);
+                    execute.push(true);
+                    execute.mark(end);
+                }
+
+                caster.checkWriteCast(compemd);
+            }
+        }
+    }
+
+    void processBool(final BoolContext ctx) {
+        final ExpressionMetadata boolemd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = boolemd.postConst;
+        final Object preConst = boolemd.preConst;
+        final Branch branch = utility.getBranch(ctx);
+
+        if (postConst != null) {
+            if (branch == null) {
+                utility.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) {
+                utility.writeConstant(ctx, preConst);
+                caster.checkWriteCast(boolemd);
+            } else {
+                throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+            }
+        } else {
+            final ExpressionContext exprctx0 = ctx.expression(0);
+            final ExpressionContext exprctx1 = ctx.expression(1);
+
+            if (branch == null) {
+                if (ctx.BOOLAND() != null) {
+                    final Branch local = utility.markBranch(ctx, exprctx0, exprctx1);
+                    local.fals = new Label();
+                    final Label end = new Label();
+
+                    writer.visit(exprctx0);
+                    writer.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 = utility.markBranch(ctx, exprctx0);
+                    branch0.tru = new Label();
+                    final Branch branch1 = utility.markBranch(ctx, exprctx1);
+                    branch1.fals = new Label();
+                    final Label aend = new Label();
+
+                    writer.visit(exprctx0);
+                    writer.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(WriterUtility.error(ctx) + "Unexpected state.");
+                }
+
+                caster.checkWriteCast(boolemd);
+            } else {
+                if (ctx.BOOLAND() != null) {
+                    final Branch branch0 = utility.markBranch(ctx, exprctx0);
+                    branch0.fals = branch.fals == null ? new Label() : branch.fals;
+                    final Branch branch1 = utility.markBranch(ctx, exprctx1);
+                    branch1.tru = branch.tru;
+                    branch1.fals = branch.fals;
+
+                    writer.visit(exprctx0);
+                    writer.visit(exprctx1);
+
+                    if (branch.fals == null) {
+                        execute.mark(branch0.fals);
+                    }
+                } else if (ctx.BOOLOR() != null) {
+                    final Branch branch0 = utility.markBranch(ctx, exprctx0);
+                    branch0.tru = branch.tru == null ? new Label() : branch.tru;
+                    final Branch branch1 = utility.markBranch(ctx, exprctx1);
+                    branch1.tru = branch.tru;
+                    branch1.fals = branch.fals;
+
+                    writer.visit(exprctx0);
+                    writer.visit(exprctx1);
+
+                    if (branch.tru == null) {
+                        execute.mark(branch0.tru);
+                    }
+                } else {
+                    throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+                }
+            }
+        }
+    }
+
+    void processConditional(final ConditionalContext ctx) {
+        final ExpressionMetadata condemd = metadata.getExpressionMetadata(ctx);
+        final Branch branch = utility.getBranch(ctx);
+
+        final ExpressionContext expr0 = ctx.expression(0);
+        final ExpressionContext expr1 = ctx.expression(1);
+        final ExpressionContext expr2 = ctx.expression(2);
+
+        final Branch local = utility.markBranch(ctx, expr0);
+        local.fals = new Label();
+        local.end = new Label();
+
+        if (branch != null) {
+            utility.copyBranch(branch, expr1, expr2);
+        }
+
+        writer.visit(expr0);
+        writer.visit(expr1);
+        execute.goTo(local.end);
+        execute.mark(local.fals);
+        writer.visit(expr2);
+        execute.mark(local.end);
+
+        if (branch == null) {
+            caster.checkWriteCast(condemd);
+        }
+    }
+
+    void processAssignment(final AssignmentContext ctx) {
+        final ExpressionMetadata expremd = metadata.getExpressionMetadata(ctx);
+        writer.visit(ctx.extstart());
+        caster.checkWriteCast(expremd);
+        utility.checkWriteBranch(ctx);
+    }
+
+    void processIncrement(final IncrementContext ctx) {
+        final ExpressionMetadata incremd = metadata.getExpressionMetadata(ctx);
+        final Object postConst = incremd.postConst;
+
+        if (postConst == null) {
+            utility.writeNumeric(ctx, incremd.preConst);
+            caster.checkWriteCast(incremd);
+        } else {
+            utility.writeConstant(ctx, postConst);
+        }
+
+        utility.checkWriteBranch(ctx);
+    }
+}

+ 769 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterExternal.java

@@ -0,0 +1,769 @@
+/*
+ * 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.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.Type;
+import org.elasticsearch.painless.Metadata.ExpressionMetadata;
+import org.elasticsearch.painless.Metadata.ExtNodeMetadata;
+import org.elasticsearch.painless.Metadata.ExternalMetadata;
+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.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.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.List;
+
+import static org.elasticsearch.painless.PainlessParser.ADD;
+import static org.elasticsearch.painless.PainlessParser.DIV;
+import static org.elasticsearch.painless.PainlessParser.MUL;
+import static org.elasticsearch.painless.PainlessParser.REM;
+import static org.elasticsearch.painless.PainlessParser.SUB;
+import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
+import static org.elasticsearch.painless.WriterConstants.DEFINITION_TYPE;
+import static org.elasticsearch.painless.WriterConstants.DEF_ARRAY_LOAD;
+import static org.elasticsearch.painless.WriterConstants.DEF_ARRAY_STORE;
+import static org.elasticsearch.painless.WriterConstants.DEF_FIELD_LOAD;
+import static org.elasticsearch.painless.WriterConstants.DEF_FIELD_STORE;
+import static org.elasticsearch.painless.WriterConstants.DEF_METHOD_CALL;
+import static org.elasticsearch.painless.WriterConstants.TOBYTEEXACT_INT;
+import static org.elasticsearch.painless.WriterConstants.TOBYTEEXACT_LONG;
+import static org.elasticsearch.painless.WriterConstants.TOBYTEWOOVERFLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.TOBYTEWOOVERFLOW_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.TOCHAREXACT_INT;
+import static org.elasticsearch.painless.WriterConstants.TOCHAREXACT_LONG;
+import static org.elasticsearch.painless.WriterConstants.TOCHARWOOVERFLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.TOCHARWOOVERFLOW_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.TOFLOATWOOVERFLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.TOINTEXACT_LONG;
+import static org.elasticsearch.painless.WriterConstants.TOINTWOOVERFLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.TOINTWOOVERFLOW_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.TOLONGWOOVERFLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.TOLONGWOOVERFLOW_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.TOSHORTEXACT_INT;
+import static org.elasticsearch.painless.WriterConstants.TOSHORTEXACT_LONG;
+import static org.elasticsearch.painless.WriterConstants.TOSHORTWOOVERFLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.TOSHORTWOOVERFLOW_FLOAT;
+
+class WriterExternal {
+    private final Metadata metadata;
+    private final Definition definition;
+    private final CompilerSettings settings;
+
+    private final GeneratorAdapter execute;
+
+    private final Writer writer;
+    private final WriterUtility utility;
+    private final WriterCaster caster;
+
+    WriterExternal(final Metadata metadata, final GeneratorAdapter execute, final Writer writer,
+                   final WriterUtility utility, final WriterCaster caster) {
+        this.metadata = metadata;
+        definition = metadata.definition;
+        settings = metadata.settings;
+
+        this.execute = execute;
+
+        this.writer = writer;
+        this.utility = utility;
+        this.caster = caster;
+    }
+
+    void processExtstart(final ExtstartContext ctx) {
+        final ExternalMetadata startemd = metadata.getExternalMetadata(ctx);
+
+        if (startemd.token == ADD) {
+            final ExpressionMetadata storeemd = metadata.getExpressionMetadata(startemd.storeExpr);
+
+            if (startemd.current.sort == Sort.STRING || storeemd.from.sort == Sort.STRING) {
+                utility.writeNewStrings();
+                utility.addStrings(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) {
+            writer.visit(precctx);
+        } else if (castctx != null) {
+            writer.visit(castctx);
+        } else if (typectx != null) {
+            writer.visit(typectx);
+        } else if (varctx != null) {
+            writer.visit(varctx);
+        } else if (newctx != null) {
+            writer.visit(newctx);
+        } else if (stringctx != null) {
+            writer.visit(stringctx);
+        } else {
+            throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+        }
+    }
+
+    void processExtprec(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) {
+            writer.visit(precctx);
+        } else if (castctx != null) {
+            writer.visit(castctx);
+        } else if (typectx != null) {
+            writer.visit(typectx);
+        } else if (varctx != null) {
+            writer.visit(varctx);
+        } else if (newctx != null) {
+            writer.visit(newctx);
+        } else if (stringctx != null) {
+            writer.visit(stringctx);
+        } else {
+            throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+        }
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (dotctx != null) {
+            writer.visit(dotctx);
+        } else if (bracectx != null) {
+            writer.visit(bracectx);
+        }
+    }
+
+    void processExtcast(final ExtcastContext ctx) {
+        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) {
+            writer.visit(precctx);
+        } else if (castctx != null) {
+            writer.visit(castctx);
+        } else if (typectx != null) {
+            writer.visit(typectx);
+        } else if (varctx != null) {
+            writer.visit(varctx);
+        } else if (newctx != null) {
+            writer.visit(newctx);
+        } else if (stringctx != null) {
+            writer.visit(stringctx);
+        } else {
+            throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+        }
+
+        caster.checkWriteCast(ctx, castenmd.castTo);
+    }
+
+    void processExtbrace(final ExtbraceContext ctx) {
+        final ExpressionContext exprctx = ctx.expression();
+
+        writer.visit(exprctx);
+        writeLoadStoreExternal(ctx);
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (dotctx != null) {
+            writer.visit(dotctx);
+        } else if (bracectx != null) {
+            writer.visit(bracectx);
+        }
+    }
+
+    void processExtdot(final ExtdotContext ctx) {
+        final ExtcallContext callctx = ctx.extcall();
+        final ExtfieldContext fieldctx = ctx.extfield();
+
+        if (callctx != null) {
+            writer.visit(callctx);
+        } else if (fieldctx != null) {
+            writer.visit(fieldctx);
+        }
+    }
+
+    void processExttype(final ExttypeContext ctx) {
+        writer.visit(ctx.extdot());
+    }
+
+    void processExtcall(final ExtcallContext ctx) {
+        writeCallExternal(ctx);
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (dotctx != null) {
+            writer.visit(dotctx);
+        } else if (bracectx != null) {
+            writer.visit(bracectx);
+        }
+    }
+
+    void processExtvar(final ExtvarContext ctx) {
+        writeLoadStoreExternal(ctx);
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (dotctx != null) {
+            writer.visit(dotctx);
+        } else if (bracectx != null) {
+            writer.visit(bracectx);
+        }
+    }
+
+    void processExtfield(final ExtfieldContext ctx) {
+        writeLoadStoreExternal(ctx);
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (dotctx != null) {
+            writer.visit(dotctx);
+        } else if (bracectx != null) {
+            writer.visit(bracectx);
+        }
+    }
+
+    void processExtnew(final ExtnewContext ctx) {
+        writeNewExternal(ctx);
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (dotctx != null) {
+            writer.visit(dotctx);
+        } else if (bracectx != null) {
+            writer.visit(bracectx);
+        }
+    }
+
+    void processExtstring(final ExtstringContext ctx) {
+        final ExtNodeMetadata stringenmd = metadata.getExtNodeMetadata(ctx);
+
+        utility.writeConstant(ctx, stringenmd.target);
+
+        final ExtdotContext dotctx = ctx.extdot();
+        final ExtbraceContext bracectx = ctx.extbrace();
+
+        if (dotctx != null) {
+            writer.visit(dotctx);
+        } else if (bracectx != null) {
+            writer.visit(bracectx);
+        }
+    }
+
+    private void writeLoadStoreExternal(final ParserRuleContext source) {
+        final ExtNodeMetadata sourceenmd = metadata.getExtNodeMetadata(source);
+        final 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(WriterUtility.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 ExpressionMetadata expremd = metadata.getExpressionMetadata(parentemd.storeExpr);
+            final boolean cat = utility.containsStrings(parentemd.storeExpr);
+
+            if (cat) {
+                if (field || name || shortcut) {
+                    execute.dupX1();
+                } else if (array) {
+                    execute.dup2X1();
+                }
+
+                if (maplist) {
+                    if (constant != null) {
+                        utility.writeConstant(source, constant);
+                    }
+
+                    execute.dupX2();
+                }
+
+                writeLoadStoreInstruction(source, false, variable, field, name, array, shortcut);
+                utility.writeAppendStrings(sourceenmd.type.sort);
+                writer.visit(parentemd.storeExpr);
+
+                if (utility.containsStrings(parentemd.storeExpr)) {
+                    utility.writeAppendStrings(expremd.to.sort);
+                    utility.removeStrings(parentemd.storeExpr);
+                }
+
+                utility.writeToStrings();
+                caster.checkWriteCast(source, sourceenmd.castTo);
+
+                if (parentemd.read) {
+                    utility.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) {
+                        utility.writeConstant(source, constant);
+                    }
+
+                    execute.dupX1();
+                }
+
+                writeLoadStoreInstruction(source, false, variable, field, name, array, shortcut);
+
+                if (parentemd.read && parentemd.post) {
+                    utility.writeDup(sourceenmd.type.sort.size, x1, x2);
+                }
+
+                caster.checkWriteCast(source, sourceenmd.castFrom);
+                writer.visit(parentemd.storeExpr);
+
+                utility.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) {
+                    caster.checkWriteCast(source, sourceenmd.castTo);
+                }
+
+                if (parentemd.read && !parentemd.post) {
+                    utility.writeDup(sourceenmd.type.sort.size, x1, x2);
+                }
+
+                writeLoadStoreInstruction(source, true, variable, field, name, array, shortcut);
+            } else {
+                if (constant != null) {
+                    utility.writeConstant(source, constant);
+                }
+
+                writer.visit(parentemd.storeExpr);
+
+                if (parentemd.read) {
+                    utility.writeDup(sourceenmd.type.sort.size, x1, x2);
+                }
+
+                writeLoadStoreInstruction(source, true, variable, field, name, array, shortcut);
+            }
+        } else {
+            if (constant != null) {
+                utility.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 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(WriterUtility.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(WriterUtility.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 ExtNodeMetadata sourceemd = metadata.getExtNodeMetadata(source);
+            final ExternalMetadata parentemd = metadata.getExternalMetadata(sourceemd.parent);
+            final 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(WriterUtility.error(source) + "Cannot load/store void type.");
+        }
+
+        if (type.sort == Sort.DEF) {
+            final ExtbraceContext bracectx = (ExtbraceContext)source;
+            final ExpressionMetadata expremd0 = metadata.getExpressionMetadata(bracectx.expression());
+
+            if (store) {
+                final ExtNodeMetadata braceenmd = metadata.getExtNodeMetadata(bracectx);
+                final ExternalMetadata parentemd = metadata.getExternalMetadata(braceenmd.parent);
+                final 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) {
+            utility.writePop(method.rtn.type.getSize());
+        } else if (!method.rtn.clazz.equals(method.handle.type().returnType())) {
+            execute.checkCast(method.rtn.type);
+        }
+    }
+
+    /**
+     * 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 This will be 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 writeNewExternal(final ExtnewContext source) {
+        final ExtNodeMetadata sourceenmd = metadata.getExtNodeMetadata(source);
+        final 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(WriterUtility.error(source) + "Target not found for new call.");
+        }
+
+        if (makearray) {
+            for (final ExpressionContext exprctx : source.expression()) {
+                writer.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()) {
+                writer.visit(exprctx);
+            }
+
+            final Constructor target = (Constructor)sourceenmd.target;
+            execute.invokeConstructor(target.owner.type, target.method);
+        }
+    }
+
+    private void writeCallExternal(final ExtcallContext source) {
+        final 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(WriterUtility.error(source) + "Target not found for call.");
+        }
+
+        final List<ExpressionContext> arguments = source.arguments().expression();
+
+        if (method) {
+            for (final ExpressionContext exprctx : arguments) {
+                writer.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);
+                writer.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);
+        }
+    }
+}

+ 391 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterStatement.java

@@ -0,0 +1,391 @@
+/*
+ * 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.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Metadata.ExpressionMetadata;
+import org.elasticsearch.painless.Metadata.StatementMetadata;
+import org.elasticsearch.painless.PainlessParser.AfterthoughtContext;
+import org.elasticsearch.painless.PainlessParser.BlockContext;
+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.EmptyscopeContext;
+import org.elasticsearch.painless.PainlessParser.ExprContext;
+import org.elasticsearch.painless.PainlessParser.ExpressionContext;
+import org.elasticsearch.painless.PainlessParser.ForContext;
+import org.elasticsearch.painless.PainlessParser.IfContext;
+import org.elasticsearch.painless.PainlessParser.InitializerContext;
+import org.elasticsearch.painless.PainlessParser.MultipleContext;
+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.TryContext;
+import org.elasticsearch.painless.PainlessParser.WhileContext;
+import org.elasticsearch.painless.WriterUtility.Branch;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
+
+class WriterStatement {
+    private final Metadata metadata;
+
+    private final GeneratorAdapter execute;
+
+    private final Writer writer;
+    private final WriterUtility utility;
+
+    WriterStatement(final Metadata metadata, final GeneratorAdapter execute,
+                    final Writer writer, final WriterUtility utility) {
+        this.metadata = metadata;
+
+        this.execute = execute;
+
+        this.writer = writer;
+        this.utility = utility;
+    }
+
+    void processSource(final SourceContext ctx) {
+        final StatementMetadata sourcesmd = metadata.getStatementMetadata(ctx);
+
+        for (final StatementContext sctx : ctx.statement()) {
+            writer.visit(sctx);
+        }
+
+        if (!sourcesmd.methodEscape) {
+            execute.visitInsn(Opcodes.ACONST_NULL);
+            execute.returnValue();
+        }
+    }
+
+    void processIf(final IfContext ctx) {
+        final ExpressionContext exprctx = ctx.expression();
+        final boolean els = ctx.ELSE() != null;
+        final Branch branch = utility.markBranch(ctx, exprctx);
+        branch.end = new Label();
+        branch.fals = els ? new Label() : branch.end;
+
+        writer.visit(exprctx);
+
+        final BlockContext blockctx0 = ctx.block(0);
+        final StatementMetadata blockmd0 = metadata.getStatementMetadata(blockctx0);
+        writer.visit(blockctx0);
+
+        if (els) {
+            if (!blockmd0.allLast) {
+                execute.goTo(branch.end);
+            }
+
+            execute.mark(branch.fals);
+            writer.visit(ctx.block(1));
+        }
+
+        execute.mark(branch.end);
+    }
+
+    void processWhile(final WhileContext ctx) {
+        final ExpressionContext exprctx = ctx.expression();
+        final Branch branch = utility.markBranch(ctx, exprctx);
+        branch.begin = new Label();
+        branch.end = new Label();
+        branch.fals = branch.end;
+
+        utility.pushJump(branch);
+        execute.mark(branch.begin);
+        writer.visit(exprctx);
+
+        final BlockContext blockctx = ctx.block();
+        boolean allLast = false;
+
+        if (blockctx != null) {
+            final StatementMetadata blocksmd = metadata.getStatementMetadata(blockctx);
+            allLast = blocksmd.allLast;
+            writeLoopCounter(blocksmd.count > 0 ? blocksmd.count : 1);
+            writer.visit(blockctx);
+        } else if (ctx.empty() != null) {
+            writeLoopCounter(1);
+        } else {
+            throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+        }
+
+        if (!allLast) {
+            execute.goTo(branch.begin);
+        }
+
+        execute.mark(branch.end);
+        utility.popJump();
+    }
+
+    void processDo(final DoContext ctx) {
+        final ExpressionContext exprctx = ctx.expression();
+        final Branch branch = utility.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 StatementMetadata blocksmd = metadata.getStatementMetadata(blockctx);
+
+        utility.pushJump(branch);
+        execute.mark(start);
+        writer.visit(blockctx);
+        execute.mark(branch.begin);
+        writer.visit(exprctx);
+        writeLoopCounter(blocksmd.count > 0 ? blocksmd.count : 1);
+        execute.goTo(start);
+        execute.mark(branch.end);
+        utility.popJump();
+    }
+
+    void processFor(final ForContext ctx) {
+        final ExpressionContext exprctx = ctx.expression();
+        final AfterthoughtContext atctx = ctx.afterthought();
+        final Branch branch = utility.markBranch(ctx, exprctx);
+        final Label start = new Label();
+        branch.begin = atctx == null ? start : new Label();
+        branch.end = new Label();
+        branch.fals = branch.end;
+
+        utility.pushJump(branch);
+
+        if (ctx.initializer() != null) {
+            writer.visit(ctx.initializer());
+        }
+
+        execute.mark(start);
+
+        if (exprctx != null) {
+            writer.visit(exprctx);
+        }
+
+        final BlockContext blockctx = ctx.block();
+        boolean allLast = false;
+
+        if (blockctx != null) {
+            StatementMetadata blocksmd = metadata.getStatementMetadata(blockctx);
+            allLast = blocksmd.allLast;
+
+            int count = blocksmd.count > 0 ? blocksmd.count : 1;
+
+            if (atctx != null) {
+                ++count;
+            }
+
+            writeLoopCounter(count);
+            writer.visit(blockctx);
+        } else if (ctx.empty() != null) {
+            writeLoopCounter(1);
+        } else {
+            throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+        }
+
+        if (atctx != null) {
+            execute.mark(branch.begin);
+            writer.visit(atctx);
+        }
+
+        if (atctx != null || !allLast) {
+            execute.goTo(start);
+        }
+
+        execute.mark(branch.end);
+        utility.popJump();
+    }
+
+    void processDecl(final DeclContext ctx) {
+        writer.visit(ctx.declaration());
+    }
+
+    void processContinue() {
+        final Branch jump = utility.peekJump();
+        execute.goTo(jump.begin);
+    }
+
+    void processBreak() {
+        final Branch jump = utility.peekJump();
+        execute.goTo(jump.end);
+    }
+
+    void processReturn(final ReturnContext ctx) {
+        writer.visit(ctx.expression());
+        execute.returnValue();
+    }
+
+    void processTry(final TryContext ctx) {
+        final TrapContext[] trapctxs = new TrapContext[ctx.trap().size()];
+        ctx.trap().toArray(trapctxs);
+        final Branch branch = utility.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 StatementMetadata blocksmd = metadata.getStatementMetadata(blockctx);
+        writer.visit(blockctx);
+
+        if (!blocksmd.allLast) {
+            execute.goTo(end);
+        }
+
+        execute.mark(branch.end);
+
+        for (final TrapContext trapctx : trapctxs) {
+            writer.visit(trapctx);
+        }
+
+        if (!blocksmd.allLast || trapctxs.length > 1) {
+            execute.mark(end);
+        }
+    }
+
+    void processThrow(final ThrowContext ctx) {
+        writer.visit(ctx.expression());
+        execute.throwException();
+    }
+
+    void processExpr(final ExprContext ctx) {
+        final StatementMetadata exprsmd = metadata.getStatementMetadata(ctx);
+        final ExpressionContext exprctx = ctx.expression();
+        final ExpressionMetadata expremd = metadata.getExpressionMetadata(exprctx);
+        writer.visit(exprctx);
+
+        if (exprsmd.methodEscape) {
+            execute.returnValue();
+        } else {
+            utility.writePop(expremd.to.type.getSize());
+        }
+    }
+
+    void processMultiple(final MultipleContext ctx) {
+        for (final StatementContext sctx : ctx.statement()) {
+            writer.visit(sctx);
+        }
+    }
+
+    void processSingle(final SingleContext ctx) {
+        writer.visit(ctx.statement());
+    }
+
+    void processInitializer(InitializerContext ctx) {
+        final DeclarationContext declctx = ctx.declaration();
+        final ExpressionContext exprctx = ctx.expression();
+
+        if (declctx != null) {
+            writer.visit(declctx);
+        } else if (exprctx != null) {
+            final ExpressionMetadata expremd = metadata.getExpressionMetadata(exprctx);
+            writer.visit(exprctx);
+            utility.writePop(expremd.to.type.getSize());
+        } else {
+            throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+        }
+    }
+
+    void processAfterthought(AfterthoughtContext ctx) {
+        final ExpressionContext exprctx = ctx.expression();
+        final ExpressionMetadata expremd = metadata.getExpressionMetadata(exprctx);
+        writer.visit(ctx.expression());
+        utility.writePop(expremd.to.type.getSize());
+    }
+
+    void processDeclaration(DeclarationContext ctx) {
+        for (final DeclvarContext declctx : ctx.declvar()) {
+            writer.visit(declctx);
+        }
+    }
+
+    void processDeclvar(final DeclvarContext ctx) {
+        final 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) {
+            writer.visit(exprctx);
+        }
+
+        switch (sort) {
+            case VOID:   throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected 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);
+    }
+
+    void processTrap(final TrapContext ctx) {
+        final StatementMetadata trapsmd = metadata.getStatementMetadata(ctx);
+
+        final Branch branch = utility.getBranch(ctx);
+        final Label jump = new Label();
+
+        final BlockContext blockctx = ctx.block();
+        final EmptyscopeContext emptyctx = ctx.emptyscope();
+
+        execute.mark(jump);
+        execute.visitVarInsn(trapsmd.exception.type.getOpcode(Opcodes.ISTORE), trapsmd.slot);
+
+        if (blockctx != null) {
+            writer.visit(ctx.block());
+        } else if (emptyctx == null) {
+            throw new IllegalStateException(WriterUtility.error(ctx) + "Unexpected state.");
+        }
+
+        execute.visitTryCatchBlock(branch.begin, branch.end, jump, trapsmd.exception.type.getInternalName());
+
+        if (branch.tru != null && !trapsmd.allLast) {
+            execute.goTo(branch.tru);
+        }
+    }
+
+    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);
+    }
+}

+ 387 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterUtility.java

@@ -0,0 +1,387 @@
+/*
+ * 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.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Type;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+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;
+import static org.elasticsearch.painless.WriterConstants.ADDEXACT_INT;
+import static org.elasticsearch.painless.WriterConstants.ADDEXACT_LONG;
+import static org.elasticsearch.painless.WriterConstants.ADDWOOVERLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.ADDWOOVERLOW_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.DEF_ADD_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_AND_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_DIV_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_LSH_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_MUL_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_OR_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_REM_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_RSH_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_SUB_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_USH_CALL;
+import static org.elasticsearch.painless.WriterConstants.DEF_XOR_CALL;
+import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_INT;
+import static org.elasticsearch.painless.WriterConstants.DIVWOOVERLOW_LONG;
+import static org.elasticsearch.painless.WriterConstants.MULEXACT_INT;
+import static org.elasticsearch.painless.WriterConstants.MULEXACT_LONG;
+import static org.elasticsearch.painless.WriterConstants.MULWOOVERLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.MULWOOVERLOW_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.REMWOOVERLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.REMWOOVERLOW_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_CHAR;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_INT;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_LONG;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_OBJECT;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_STRING;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_CONSTRUCTOR;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TOSTRING;
+import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TYPE;
+import static org.elasticsearch.painless.WriterConstants.SUBEXACT_INT;
+import static org.elasticsearch.painless.WriterConstants.SUBEXACT_LONG;
+import static org.elasticsearch.painless.WriterConstants.SUBWOOVERLOW_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.SUBWOOVERLOW_FLOAT;
+
+class WriterUtility {
+    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;
+        }
+    }
+
+    /**
+     * A utility method to output consistent error messages.
+     * @param ctx The ANTLR node the error occurred in.
+     * @return The error message with tacked on line number and character position.
+     */
+    static String error(final ParserRuleContext ctx) {
+        return "Writer Error [" + ctx.getStart().getLine() + ":" + ctx.getStart().getCharPositionInLine() + "]: ";
+    }
+
+    private final Definition definition;
+    private final CompilerSettings settings;
+
+    private final GeneratorAdapter execute;
+
+    private final Map<ParserRuleContext, Branch> branches = new HashMap<>();
+    private final Deque<Branch> jumps = new ArrayDeque<>();
+    private final Set<ParserRuleContext> strings = new HashSet<>();
+
+    WriterUtility(final Metadata metadata, final GeneratorAdapter execute) {
+        definition = metadata.definition;
+        settings = metadata.settings;
+
+        this.execute = execute;
+    }
+
+    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;
+    }
+
+    void copyBranch(final Branch branch, final ParserRuleContext... nodes) {
+        for (final ParserRuleContext node : nodes) {
+            branches.put(node, branch);
+        }
+    }
+
+    Branch getBranch(final ParserRuleContext source) {
+        return branches.get(source);
+    }
+
+    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);
+            }
+        }
+    }
+
+    void pushJump(final Branch branch) {
+        jumps.push(branch);
+    }
+
+    Branch peekJump() {
+        return jumps.peek();
+    }
+
+    void popJump() {
+        jumps.pop();
+    }
+
+    void addStrings(final ParserRuleContext source) {
+        strings.add(source);
+    }
+
+    boolean containsStrings(final ParserRuleContext source) {
+        return strings.contains(source);
+    }
+
+    void removeStrings(final ParserRuleContext source) {
+        strings.remove(source);
+    }
+
+    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();
+            }
+        }
+    }
+
+    void writePop(final int size) {
+        if (size == 1) {
+            execute.pop();
+        } else if (size == 2) {
+            execute.pop2();
+        }
+    }
+
+    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(WriterUtility.error(source) + "Unexpected state.");
+        }
+    }
+
+    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(WriterUtility.error(source) + "Unexpected state.");
+        }
+    }
+
+    void writeString(final ParserRuleContext source, final Object string) {
+        if (string instanceof String) {
+            execute.push((String)string);
+        } else {
+            throw new IllegalStateException(WriterUtility.error(source) + "Unexpected state.");
+        }
+    }
+
+    void writeBoolean(final ParserRuleContext source, final Object bool) {
+        if (bool instanceof Boolean) {
+            execute.push((boolean)bool);
+        } else {
+            throw new IllegalStateException(WriterUtility.error(source) + "Unexpected state.");
+        }
+    }
+
+    void writeNewStrings() {
+        execute.newInstance(STRINGBUILDER_TYPE);
+        execute.dup();
+        execute.invokeConstructor(STRINGBUILDER_TYPE, STRINGBUILDER_CONSTRUCTOR);
+    }
+
+    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);
+        }
+    }
+
+    void writeToStrings() {
+        execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_TOSTRING);
+    }
+
+    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 it's a 64-bit shift, fix-up the last argument to truncate to 32-bits.
+        // Note that unlike java, this means we still do binary promotion of shifts,
+        // but it keeps things simple, and 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(WriterUtility.error(source) + "Unexpected 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(WriterUtility.error(source) + "Unexpected 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(WriterUtility.error(source) + "Unexpected 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(WriterUtility.error(source) + "Unexpected state.");
+                    }
+
+                    break;
+                default:
+                    throw new IllegalStateException(WriterUtility.error(source) + "Unexpected state.");
+            }
+        } else {
+            if ((sort == Sort.FLOAT || sort == Sort.DOUBLE) &&
+                (token == LSH || token == USH || token == RSH || token == BWAND || token == BWXOR || token == BWOR)) {
+                throw new IllegalStateException(WriterUtility.error(source) + "Unexpected 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(WriterUtility.error(source) + "Unexpected 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(WriterUtility.error(source) + "Unexpected state.");
+                }
+            }
+        }
+    }
+}