Bläddra i källkod

Decouple the ANTLR AST from Painless.

Closes #18286
Jack Conradson 9 år sedan
förälder
incheckning
28164ccf2e
94 ändrade filer med 8476 tillägg och 7791 borttagningar
  1. 2 2
      buildSrc/src/main/resources/checkstyle_suppressions.xml
  2. 6 6
      modules/lang-painless/ant.xml
  3. 1 1
      modules/lang-painless/src/main/antlr/PainlessLexer.g4
  4. 20 24
      modules/lang-painless/src/main/antlr/PainlessParser.g4
  5. 12 442
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Analyzer.java
  6. 346 101
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java
  7. 0 856
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerExpression.java
  8. 0 813
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerExternal.java
  9. 0 281
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerPromoter.java
  10. 0 591
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerStatement.java
  11. 0 191
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerUtility.java
  12. 28 44
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java
  13. 8 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/CompilerSettings.java
  14. 105 164
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java
  15. 306 596
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java
  16. 28 26
      modules/lang-painless/src/main/java/org/elasticsearch/painless/DynamicCallSite.java
  17. 6 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Executable.java
  18. 0 605
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Metadata.java
  19. 3 1
      modules/lang-painless/src/main/java/org/elasticsearch/painless/NeedsScore.java
  20. 61 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Operation.java
  21. 1 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessError.java
  22. 3 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java
  23. 6 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java
  24. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptImpl.java
  25. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Utility.java
  26. 219 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Variables.java
  27. 60 443
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Writer.java
  28. 0 86
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterCaster.java
  29. 117 103
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java
  30. 0 669
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterExpression.java
  31. 0 720
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterExternal.java
  32. 0 390
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterStatement.java
  33. 302 253
      modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterUtility.java
  34. 13 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/ErrorHandlingLexer.java
  35. 12 12
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessLexer.java
  36. 201 194
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessParser.java
  37. 18 25
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessParserBaseVisitor.java
  38. 28 34
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessParserVisitor.java
  39. 17 12
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/ParserErrorStrategy.java
  40. 790 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java
  41. 173 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java
  42. 121 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ALink.java
  43. 39 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java
  44. 125 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java
  45. 528 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java
  46. 139 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java
  47. 47 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java
  48. 60 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java
  49. 310 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java
  50. 528 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java
  51. 94 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java
  52. 97 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java
  53. 63 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java
  54. 70 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java
  55. 56 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java
  56. 102 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java
  57. 234 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java
  58. 71 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java
  59. 98 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java
  60. 120 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCall.java
  61. 78 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java
  62. 74 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java
  63. 94 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java
  64. 67 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java
  65. 136 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LField.java
  66. 105 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java
  67. 104 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java
  68. 98 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewArray.java
  69. 118 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewObj.java
  70. 105 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java
  71. 67 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LString.java
  72. 90 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LVariable.java
  73. 75 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java
  74. 52 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java
  75. 55 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java
  76. 58 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java
  77. 86 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java
  78. 105 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java
  79. 72 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java
  80. 181 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java
  81. 111 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java
  82. 58 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java
  83. 76 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java
  84. 57 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java
  85. 98 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STrap.java
  86. 114 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java
  87. 121 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java
  88. 130 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/package-info.java
  89. 3 4
      modules/lang-painless/src/test/java/org/elasticsearch/painless/BinaryOperatorTests.java
  90. 9 7
      modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java
  91. 36 36
      modules/lang-painless/src/test/java/org/elasticsearch/painless/DefTests.java
  92. 13 13
      modules/lang-painless/src/test/java/org/elasticsearch/painless/ReservedWordTests.java
  93. 18 10
      modules/lang-painless/src/test/java/org/elasticsearch/painless/StringTests.java
  94. 4 4
      modules/lang-painless/src/test/java/org/elasticsearch/painless/WhenThingsGoWrongTests.java

+ 2 - 2
buildSrc/src/main/resources/checkstyle_suppressions.xml

@@ -7,8 +7,8 @@
   <!-- On Windows, Checkstyle matches files using \ path separator -->
 
   <!-- These files are generated by ANTLR so its silly to hold them to our rules. -->
-  <suppress files="org[/\\]elasticsearch[/\\]painless[/\\]PainlessLexer\.java" checks="." />
-  <suppress files="org[/\\]elasticsearch[/\\]painless[/\\]PainlessParser(|BaseVisitor|Visitor)\.java" checks="." />
+  <suppress files="org[/\\]elasticsearch[/\\]painless[/\\]antlr[/\\]PainlessLexer\.java" checks="." />
+  <suppress files="org[/\\]elasticsearch[/\\]painless[/\\]antlr[/\\]PainlessParser(|BaseVisitor|Visitor)\.java" checks="." />
 
   <!-- Hopefully temporary suppression of LineLength on files that don't pass it. We should remove these when we the
     files start to pass. -->

+ 6 - 6
modules/lang-painless/ant.xml

@@ -49,7 +49,7 @@
         <include name="@{grammar}ParserVisitor.java" />
         <include name="@{grammar}ParserBaseVisitor.java" />
       </patternset>
-      <property name="output.path" location="src/main/java/org/elasticsearch/painless"/>
+      <property name="output.path" location="src/main/java/org/elasticsearch/painless/antlr"/>
       <!-- delete parser and lexer so files will be generated -->
       <delete dir="${output.path}">
         <patternset refid="grammar.@{grammar}.patternset"/>
@@ -63,7 +63,7 @@
       <local name="grammar.path"/>
       <local name="output.path"/>
       <property name="grammar.path" location="src/main/antlr"/>
-      <property name="output.path" location="src/main/java/org/elasticsearch/painless"/>
+      <property name="output.path" location="src/main/java/org/elasticsearch/painless/antlr"/>
       <!-- invoke ANTLR4 -->
       <java classname="org.antlr.v4.Tool" fork="true" failonerror="true" classpathref="regenerate.classpath" taskname="antlr">
         <sysproperty key="file.encoding" value="UTF-8"/>
@@ -71,7 +71,7 @@
         <sysproperty key="user.country" value="US"/>
         <sysproperty key="user.variant" value=""/>
         <arg value="-package"/>
-        <arg value="org.elasticsearch.painless"/>
+        <arg value="org.elasticsearch.painless.antlr"/>
         <arg value="-o"/>
         <arg path="${output.path}"/>
         <arg path="${grammar.path}/@{grammar}Lexer.g4"/>
@@ -85,7 +85,7 @@
       <local name="grammar.path"/>
       <local name="output.path"/>
       <property name="grammar.path" location="src/main/antlr"/>
-      <property name="output.path" location="src/main/java/org/elasticsearch/painless"/>
+      <property name="output.path" location="src/main/java/org/elasticsearch/painless/antlr"/>
       <!-- invoke ANTLR4 -->
       <java classname="org.antlr.v4.Tool" fork="true" failonerror="true" classpathref="regenerate.classpath" taskname="antlr">
         <sysproperty key="file.encoding" value="UTF-8"/>
@@ -93,7 +93,7 @@
         <sysproperty key="user.country" value="US"/>
         <sysproperty key="user.variant" value=""/>
         <arg value="-package"/>
-        <arg value="org.elasticsearch.painless"/>
+        <arg value="org.elasticsearch.painless.antlr"/>
         <arg value="-no-listener"/>
         <arg value="-visitor"/>
         <!-- <arg value="-Xlog"/> -->
@@ -110,7 +110,7 @@
       <local name="grammar.path"/>
       <local name="output.path"/>
       <property name="grammar.path" location="src/main/antlr"/>
-      <property name="output.path" location="src/main/java/org/elasticsearch/painless"/>
+      <property name="output.path" location="src/main/java/org/elasticsearch/painless/antlr"/>
       <patternset id="grammar.@{grammar}.patternset">
         <include name="@{grammar}Lexer.java" />
         <include name="@{grammar}Parser.java" />

+ 1 - 1
modules/lang-painless/src/main/antlr/PainlessLexer.g4

@@ -63,7 +63,7 @@ EQR:     '===';
 NE:      '!=';
 NER:     '!==';
 BWAND:   '&';
-BWXOR:   '^';
+XOR:     '^';
 BWOR:    '|';
 BOOLAND: '&&';
 BOOLOR:  '||';

+ 20 - 24
modules/lang-painless/src/main/antlr/PainlessParser.g4

@@ -92,9 +92,9 @@ expression
     |               TRUE                                                # true
     |               FALSE                                               # false
     |               NULL                                                # null
-    | <assoc=right> extstart increment                                  # postinc
-    | <assoc=right> increment extstart                                  # preinc
-    |               extstart                                            # external
+    | <assoc=right> chain ( INCR | DECR )                               # postinc
+    | <assoc=right> ( INCR | DECR ) chain                               # preinc
+    |               chain                                               # read
     | <assoc=right> ( BOOLNOT | BWNOT | ADD | SUB ) expression          # unary
     | <assoc=right> LP decltype RP expression                           # cast
     |               expression ( MUL | DIV | REM ) expression           # binary
@@ -103,39 +103,35 @@ expression
     |               expression ( LT | LTE | GT | GTE ) expression       # comp
     |               expression ( EQ | EQR | NE | NER ) expression       # comp
     |               expression BWAND expression                         # binary
-    |               expression BWXOR expression                         # binary
+    |               expression XOR expression                           # binary
     |               expression BWOR expression                          # binary
     |               expression BOOLAND expression                       # bool
     |               expression BOOLOR expression                        # bool
     | <assoc=right> expression COND expression COLON expression         # conditional
-    | <assoc=right> extstart ( ASSIGN | AADD | ASUB | AMUL | ADIV
+    | <assoc=right> chain ( ASSIGN | AADD | ASUB | AMUL | ADIV
                                       | AREM | AAND | AXOR | AOR
                                       | ALSH | ARSH | AUSH ) expression # assignment
     ;
 
-extstart
-    : extprec
-    | extcast
-    | extvar
-    | extnew
-    | extstring
+chain
+    : linkprec
+    | linkcast
+    | linkvar
+    | linknew
+    | linkstring
     ;
 
-extprec:   LP ( extprec | extcast | extvar | extnew | extstring ) RP ( extdot | extbrace )?;
-extcast:   LP decltype RP ( extprec | extcast | extvar | extnew | extstring );
-extbrace:  LBRACE expression RBRACE ( extdot | extbrace )?;
-extdot:    DOT ( extcall | extfield );
-extcall:   EXTID arguments ( extdot | extbrace )?;
-extvar:    identifier ( extdot | extbrace )?;
-extfield:  ( EXTID | EXTINTEGER ) ( extdot | extbrace )?;
-extnew:    NEW identifier ( ( arguments extdot? ) | ( ( LBRACE expression RBRACE )+ extdot? ) );
-extstring: STRING (extdot | extbrace )?;
+linkprec:   LP ( linkprec | linkcast | linkvar | linknew | linkstring ) RP ( linkdot | linkbrace )?;
+linkcast:   LP decltype RP ( linkprec | linkcast | linkvar | linknew | linkstring );
+linkbrace:  LBRACE expression RBRACE ( linkdot | linkbrace )?;
+linkdot:    DOT ( linkcall | linkfield );
+linkcall:   EXTID arguments ( linkdot | linkbrace )?;
+linkvar:    identifier ( linkdot | linkbrace )?;
+linkfield:  ( EXTID | EXTINTEGER ) ( linkdot | linkbrace )?;
+linknew:    NEW identifier ( ( arguments linkdot? ) | ( ( LBRACE expression RBRACE )+ linkdot? ) );
+linkstring: STRING (linkdot | linkbrace )?;
 
 arguments
     : ( LP ( expression ( COMMA expression )* )? RP )
     ;
 
-increment
-    : INCR
-    | DECR
-    ;

+ 12 - 442
modules/lang-painless/src/main/java/org/elasticsearch/painless/Analyzer.java

@@ -19,450 +19,20 @@
 
 package org.elasticsearch.painless;
 
-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.BoolContext;
-import org.elasticsearch.painless.PainlessParser.BreakContext;
-import org.elasticsearch.painless.PainlessParser.CastContext;
-import org.elasticsearch.painless.PainlessParser.CompContext;
-import org.elasticsearch.painless.PainlessParser.ConditionalContext;
-import org.elasticsearch.painless.PainlessParser.ContinueContext;
-import org.elasticsearch.painless.PainlessParser.DeclContext;
-import org.elasticsearch.painless.PainlessParser.DeclarationContext;
-import org.elasticsearch.painless.PainlessParser.DecltypeContext;
-import org.elasticsearch.painless.PainlessParser.DeclvarContext;
-import org.elasticsearch.painless.PainlessParser.DoContext;
-import org.elasticsearch.painless.PainlessParser.EmptyContext;
-import org.elasticsearch.painless.PainlessParser.EmptyscopeContext;
-import org.elasticsearch.painless.PainlessParser.ExprContext;
-import org.elasticsearch.painless.PainlessParser.ExtbraceContext;
-import org.elasticsearch.painless.PainlessParser.ExtcallContext;
-import org.elasticsearch.painless.PainlessParser.ExtcastContext;
-import org.elasticsearch.painless.PainlessParser.ExtdotContext;
-import org.elasticsearch.painless.PainlessParser.ExternalContext;
-import org.elasticsearch.painless.PainlessParser.ExtfieldContext;
-import org.elasticsearch.painless.PainlessParser.ExtnewContext;
-import org.elasticsearch.painless.PainlessParser.ExtprecContext;
-import org.elasticsearch.painless.PainlessParser.ExtstartContext;
-import org.elasticsearch.painless.PainlessParser.ExtstringContext;
-import org.elasticsearch.painless.PainlessParser.ExtvarContext;
-import org.elasticsearch.painless.PainlessParser.FalseContext;
-import org.elasticsearch.painless.PainlessParser.ForContext;
-import org.elasticsearch.painless.PainlessParser.GenericContext;
-import org.elasticsearch.painless.PainlessParser.IdentifierContext;
-import org.elasticsearch.painless.PainlessParser.IfContext;
-import org.elasticsearch.painless.PainlessParser.IncrementContext;
-import org.elasticsearch.painless.PainlessParser.InitializerContext;
-import org.elasticsearch.painless.PainlessParser.MultipleContext;
-import org.elasticsearch.painless.PainlessParser.NullContext;
-import org.elasticsearch.painless.PainlessParser.NumericContext;
-import org.elasticsearch.painless.PainlessParser.PostincContext;
-import org.elasticsearch.painless.PainlessParser.PrecedenceContext;
-import org.elasticsearch.painless.PainlessParser.PreincContext;
-import org.elasticsearch.painless.PainlessParser.ReturnContext;
-import org.elasticsearch.painless.PainlessParser.SingleContext;
-import org.elasticsearch.painless.PainlessParser.SourceContext;
-import org.elasticsearch.painless.PainlessParser.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.painless.Variables.Reserved;
+import org.elasticsearch.painless.node.SSource;
 
-class Analyzer extends PainlessParserBaseVisitor<Void> {
-    static void analyze(final Metadata metadata) {
-        new Analyzer(metadata);
-    }
-
-    private final AnalyzerStatement statement;
-    private final AnalyzerExpression expression;
-    private final AnalyzerExternal external;
-
-    private Analyzer(final Metadata metadata) {
-        final Definition definition = metadata.definition;
-
-        final AnalyzerUtility utility = new AnalyzerUtility(metadata);
-        final AnalyzerCaster caster = new AnalyzerCaster(definition);
-        final AnalyzerPromoter promoter = new AnalyzerPromoter(definition);
-
-        statement = new AnalyzerStatement(metadata, this, utility, caster);
-        expression = new AnalyzerExpression(metadata, this, caster, promoter);
-        external = new AnalyzerExternal(metadata, this, utility, caster, promoter);
-
-        utility.incrementScope();
-        utility.addVariable(null, "#this", definition.execType);
-        //
-        // reserved words.
-        //
-        // input map of parameters passed to the script.
-        metadata.paramsValueSlot = utility.addVariable(null, "params", definition.smapType).slot;
-        // scorer parameter passed to the script. internal use only.
-        metadata.scorerValueSlot = utility.addVariable(null, "#scorer", definition.objectType).slot;
-        // doc parameter passed to the script.
-        // TODO: currently working as a Map<String,Def>, we can do better?
-        metadata.docValueSlot = utility.addVariable(null, "doc", definition.smapType).slot;
-        // aggregation _value parameter passed to the script
-        metadata.aggregationValueSlot = utility.addVariable(null, "_value", definition.defType).slot;
-        //
-        // reserved words implemented as local variables
-        //
-        // loop counter to catch runaway scripts. internal use only.
-        metadata.loopCounterSlot = utility.addVariable(null, "#loop", definition.intType).slot;
-        // document's score as a read-only double.
-        metadata.scoreValueSlot = utility.addVariable(null, "_score", definition.doubleType).slot;
-        // ctx map set by executable scripts as a read-only map.
-        metadata.ctxValueSlot = utility.addVariable(null, "ctx", definition.smapType).slot;
-
-        metadata.createStatementMetadata(metadata.root);
-        visit(metadata.root);
-
-        utility.decrementScope();
-    }
-
-    @Override
-    public Void visitSource(final SourceContext ctx) {
-        statement.processSource(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitIf(final IfContext ctx) {
-        statement.processIf(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitWhile(final WhileContext ctx) {
-        statement.processWhile(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDo(final DoContext ctx) {
-        statement.processDo(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitFor(final ForContext ctx) {
-        statement.processFor(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDecl(final DeclContext ctx) {
-        statement.processDecl(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitContinue(final ContinueContext ctx) {
-        statement.processContinue(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitBreak(final BreakContext ctx) {
-        statement.processBreak(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitReturn(final ReturnContext ctx) {
-        statement.processReturn(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitTry(final TryContext ctx) {
-        statement.processTry(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitThrow(final ThrowContext ctx) {
-        statement.processThrow(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExpr(final ExprContext ctx) {
-        statement.processExpr(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitMultiple(final MultipleContext ctx) {
-        statement.processMultiple(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitSingle(final SingleContext ctx) {
-        statement.processSingle(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitEmpty(final EmptyContext ctx) {
-        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitEmptyscope(final EmptyscopeContext ctx) {
-        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitInitializer(final InitializerContext ctx) {
-        statement.processInitializer(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitAfterthought(final AfterthoughtContext ctx) {
-        statement.processAfterthought(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDeclaration(final DeclarationContext ctx) {
-        statement.processDeclaration(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDecltype(final DecltypeContext ctx) {
-        statement.processDecltype(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDeclvar(final DeclvarContext ctx) {
-        statement.processDeclvar(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitTrap(final TrapContext ctx) {
-        statement.processTrap(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitIdentifier(IdentifierContext ctx) {
-        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitGeneric(GenericContext ctx) {
-        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitPrecedence(final PrecedenceContext ctx) {
-        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitNumeric(final NumericContext ctx) {
-        expression.processNumeric(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitTrue(final TrueContext ctx) {
-        expression.processTrue(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitFalse(final FalseContext ctx) {
-        expression.processFalse(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitNull(final NullContext ctx) {
-        expression.processNull(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExternal(final ExternalContext ctx) {
-        expression.processExternal(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitPostinc(final PostincContext ctx) {
-        expression.processPostinc(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitPreinc(final PreincContext ctx) {
-        expression.processPreinc(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitUnary(final UnaryContext ctx) {
-        expression.processUnary(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitCast(final CastContext ctx) {
-        expression.processCast(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitBinary(final BinaryContext ctx) {
-        expression.processBinary(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitComp(final CompContext ctx) {
-        expression.processComp(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitBool(final BoolContext ctx) {
-        expression.processBool(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitConditional(final ConditionalContext ctx) {
-        expression.processConditional(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitAssignment(final AssignmentContext ctx) {
-        expression.processAssignment(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtstart(final ExtstartContext ctx) {
-        external.processExtstart(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtprec(final ExtprecContext ctx) {
-        external.processExtprec(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtcast(final ExtcastContext ctx) {
-        external.processExtcast(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtbrace(final ExtbraceContext ctx) {
-        external.processExtbrace(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtdot(final ExtdotContext ctx) {
-        external.processExtdot(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtcall(final ExtcallContext ctx) {
-        external.processExtcall(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtvar(final ExtvarContext ctx) {
-        external.processExtvar(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtfield(final ExtfieldContext ctx) {
-        external.processExtfield(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtnew(final ExtnewContext ctx) {
-        external.processExtnew(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtstring(final ExtstringContext ctx) {
-        external.processExtstring(ctx);
-
-        return null;
-    }
+/**
+ * Runs the analysis phase of compilation using the Painless AST.
+ */
+final class Analyzer {
+    static Variables analyze(final CompilerSettings settings, final Definition definition,
+                                    final Reserved shortcut, final SSource root) {
+        final Variables variables = new Variables(settings, definition, shortcut);
+        root.analyze(settings, definition, variables);
 
-    @Override
-    public Void visitArguments(final ArgumentsContext ctx) {
-        throw new UnsupportedOperationException(AnalyzerUtility.error(ctx) + "Unexpected state.");
+        return variables;
     }
 
-    @Override
-    public Void visitIncrement(final IncrementContext ctx) {
-        expression.processIncrement(ctx);
-
-        return null;
-    }
+    private Analyzer() {}
 }

+ 346 - 101
modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java

@@ -19,46 +19,30 @@
 
 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;
+import java.lang.reflect.InvocationTargetException;
 
-    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.");
-        }
+/**
+ * Used during the analysis phase to collect legal type casts and promotions
+ * for type-checking and later to write necessary casts in the bytecode.
+ */
+public final class AnalyzerCaster {
 
-        if (emd.to != null) {
-            emd.cast = getLegalCast(emd.source, emd.from, emd.to, emd.explicit || !emd.typesafe);
+    public static Cast getLegalCast(final Definition definition,
+                                    final String location, final Type actual, final Type expected, final boolean explicit) {
+        final Cast cast = new Cast(actual, expected);
 
-            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.");
+        if (actual.equals(expected)) {
+            return 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 (actual.sort == Sort.DEF && expected.sort != Sort.VOID || actual.sort != Sort.VOID && expected.sort == Sort.DEF) {
+            final Transform transform = definition.transformsMap.get(cast);
 
             if (transform != null) {
                 return transform;
@@ -67,17 +51,17 @@ class AnalyzerCaster {
             return cast;
         }
 
-        switch (from.sort) {
+        switch (actual.sort) {
             case BOOL:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case OBJECT:
                     case BOOL_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                 }
 
                 break;
             case BYTE:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case SHORT:
                     case INT:
                     case LONG:
@@ -97,17 +81,17 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case CHAR_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case SHORT:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case INT:
                     case LONG:
                     case FLOAT:
@@ -126,18 +110,18 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE_OBJ:
                     case CHAR_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case CHAR:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case INT:
                     case LONG:
                     case FLOAT:
@@ -156,19 +140,19 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE_OBJ:
                     case SHORT_OBJ:
                     case STRING:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case INT:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case LONG:
                     case FLOAT:
                     case DOUBLE:
@@ -186,19 +170,19 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE_OBJ:
                     case SHORT_OBJ:
                     case CHAR_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case LONG:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case FLOAT:
                     case DOUBLE:
                         return cast;
@@ -215,20 +199,20 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE_OBJ:
                     case SHORT_OBJ:
                     case CHAR_OBJ:
                     case INT_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case FLOAT:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case DOUBLE:
                         return cast;
                     case BYTE:
@@ -244,21 +228,21 @@ class AnalyzerCaster {
                     case NUMBER:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE_OBJ:
                     case SHORT_OBJ:
                     case CHAR_OBJ:
                     case INT_OBJ:
                     case LONG_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case DOUBLE:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case BYTE:
                     case SHORT:
                     case CHAR:
@@ -272,7 +256,7 @@ class AnalyzerCaster {
                     case OBJECT:
                     case NUMBER:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE_OBJ:
                     case SHORT_OBJ:
                     case CHAR_OBJ:
@@ -280,7 +264,7 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
@@ -288,7 +272,7 @@ class AnalyzerCaster {
                 break;
             case OBJECT:
             case NUMBER:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case BYTE:
                     case SHORT:
                     case CHAR:
@@ -297,21 +281,21 @@ class AnalyzerCaster {
                     case FLOAT:
                     case DOUBLE:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case BOOL_OBJ:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case BOOL:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                 }
 
                 break;
             case BYTE_OBJ:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case BYTE:
                     case SHORT:
                     case INT:
@@ -323,18 +307,18 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case CHAR:
                     case CHAR_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case SHORT_OBJ:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case SHORT:
                     case INT:
                     case LONG:
@@ -344,20 +328,20 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE:
                     case CHAR:
                     case BYTE_OBJ:
                     case CHAR_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case CHAR_OBJ:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case CHAR:
                     case INT:
                     case LONG:
@@ -367,21 +351,21 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE:
                     case SHORT:
                     case BYTE_OBJ:
                     case SHORT_OBJ:
                     case STRING:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case INT_OBJ:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case INT:
                     case LONG:
                     case FLOAT:
@@ -389,7 +373,7 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE:
                     case SHORT:
                     case CHAR:
@@ -397,20 +381,20 @@ class AnalyzerCaster {
                     case SHORT_OBJ:
                     case CHAR_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case LONG_OBJ:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case LONG:
                     case FLOAT:
                     case DOUBLE:
                     case FLOAT_OBJ:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE:
                     case SHORT:
                     case CHAR:
@@ -420,18 +404,18 @@ class AnalyzerCaster {
                     case CHAR_OBJ:
                     case INT_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case FLOAT_OBJ:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case FLOAT:
                     case DOUBLE:
                     case DOUBLE_OBJ:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE:
                     case SHORT:
                     case CHAR:
@@ -443,16 +427,16 @@ class AnalyzerCaster {
                     case INT_OBJ:
                     case LONG_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case DOUBLE_OBJ:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case DOUBLE:
-                        return checkTransform(source, cast);
+                        return checkTransform(definition, location, cast);
                     case BYTE:
                     case SHORT:
                     case CHAR:
@@ -466,59 +450,57 @@ class AnalyzerCaster {
                     case LONG_OBJ:
                     case FLOAT_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
 
                 break;
             case STRING:
-                switch (to.sort) {
+                switch (expected.sort) {
                     case CHAR:
                     case CHAR_OBJ:
                         if (explicit)
-                            return checkTransform(source, cast);
+                            return checkTransform(definition, location, cast);
 
                         break;
                 }
         }
 
         try {
-            from.clazz.asSubclass(to.clazz);
+            actual.clazz.asSubclass(expected.clazz);
 
             return cast;
         } catch (final ClassCastException cce0) {
             try {
                 if (explicit) {
-                    to.clazz.asSubclass(from.clazz);
+                    expected.clazz.asSubclass(actual.clazz);
 
                     return cast;
                 } else {
                     throw new ClassCastException(
-                        AnalyzerUtility.error(source) + "Cannot cast from [" + from.name + "] to [" + to.name + "].");
+                        "Error" + location + ": Cannot cast from [" + actual.name + "] to [" + expected.name + "].");
                 }
             } catch (final ClassCastException cce1) {
-                throw new ClassCastException(
-                    AnalyzerUtility.error(source) + "Cannot cast from [" + from.name + "] to [" + to.name + "].");
+                throw new ClassCastException("Error" + location + ": Cannot cast from [" + actual.name + "] to [" + expected.name + "].");
             }
         }
     }
 
-    private Transform checkTransform(final ParserRuleContext source, final Cast cast) {
-        final Transform transform = definition.transforms.get(cast);
+    private static Transform checkTransform(final Definition definition, final String location, final Cast cast) {
+        final Transform transform = definition.transformsMap.get(cast);
 
         if (transform == null) {
-            throw new ClassCastException(
-                AnalyzerUtility.error(source) + "Cannot cast from [" + cast.from.name + "] to [" + cast.to.name + "].");
+            throw new ClassCastException("Error" + location + ": Cannot cast from [" + cast.from.name + "] to [" + cast.to.name + "].");
         }
 
         return transform;
     }
 
-    private Object constCast(final ParserRuleContext source, final Object constant, final Cast cast) {
+    public static Object constCast(final String location, final Object constant, final Cast cast) {
         if (cast instanceof Transform) {
             final Transform transform = (Transform)cast;
-            return invokeTransform(source, transform, constant);
+            return invokeTransform(location, transform, constant);
         } else {
             final Sort fsort = cast.from.sort;
             final Sort tsort = cast.to.sort;
@@ -543,17 +525,17 @@ class AnalyzerCaster {
                     case FLOAT:  return number.floatValue();
                     case DOUBLE: return number.doubleValue();
                     default:
-                        throw new IllegalStateException(AnalyzerUtility.error(source) + "Expected numeric type for cast.");
+                        throw new IllegalStateException("Error" + location + ": Cannot cast from " +
+                            "[" + cast.from.clazz.getCanonicalName() + "] to [" + cast.to.clazz.getCanonicalName() + "].");
                 }
             } else {
-                throw new IllegalStateException(AnalyzerUtility.error(source) + "No valid constant cast from " +
-                    "[" + cast.from.clazz.getCanonicalName() + "] to " +
-                    "[" + cast.to.clazz.getCanonicalName() + "].");
+                throw new IllegalStateException("Error" + location + ": Cannot cast from " +
+                    "[" + cast.from.clazz.getCanonicalName() + "] to [" + cast.to.clazz.getCanonicalName() + "].");
             }
         }
     }
 
-    private Object invokeTransform(final ParserRuleContext source, final Transform transform, final Object object) {
+    private static Object invokeTransform(final String location, final Transform transform, final Object object) {
         final Method method = transform.method;
         final java.lang.reflect.Method jmethod = method.reflect;
         final int modifiers = jmethod.getModifiers();
@@ -564,11 +546,274 @@ class AnalyzerCaster {
             } else {
                 return jmethod.invoke(object);
             }
-        } catch (IllegalAccessException | IllegalArgumentException |
-            java.lang.reflect.InvocationTargetException | NullPointerException |
-            ExceptionInInitializerError exception) {
-            throw new IllegalArgumentException(AnalyzerUtility.error(source) +
-                "Cannot cast constant from [" + transform.from.name + "] to [" + transform.to.name + "].");
+        } catch (final IllegalAccessException | IllegalArgumentException |
+            InvocationTargetException | NullPointerException | ExceptionInInitializerError exception) {
+            throw new ClassCastException(
+                "Error" + location + ": Cannot cast from [" + transform.from.name + "] to [" + transform.to.name + "].");
+        }
+    }
+
+    public static Type promoteNumeric(final Definition definition, 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) && 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) {
+            return primitive ? definition.longType : definition.longobjType;
+        } else if (sort == Sort.INT   || sort == Sort.INT_OBJ   ||
+                   sort == Sort.CHAR  || sort == Sort.CHAR_OBJ  ||
+                   sort == Sort.SHORT || sort == Sort.SHORT_OBJ ||
+                   sort == Sort.BYTE  || sort == Sort.BYTE_OBJ) {
+            return primitive ? definition.intType : definition.intobjType;
+        }
+
+        return null;
+    }
+
+    public static Type promoteNumeric(final Definition definition,
+                                      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 ||
+                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;
+            }
+        }
+
+        if (sort0 == Sort.LONG || sort0 == Sort.LONG_OBJ ||
+            sort1 == Sort.LONG || sort1 == Sort.LONG_OBJ) {
+            return primitive ? definition.longType : definition.longobjType;
+        } else if (sort0 == Sort.INT   || sort0 == Sort.INT_OBJ   ||
+                   sort1 == Sort.INT   || sort1 == Sort.INT_OBJ   ||
+                   sort0 == Sort.CHAR  || sort0 == Sort.CHAR_OBJ  ||
+                   sort1 == Sort.CHAR  || sort1 == Sort.CHAR_OBJ  ||
+                   sort0 == Sort.SHORT || sort0 == Sort.SHORT_OBJ ||
+                   sort1 == Sort.SHORT || sort1 == Sort.SHORT_OBJ ||
+                   sort0 == Sort.BYTE  || sort0 == Sort.BYTE_OBJ  ||
+                   sort1 == Sort.BYTE  || sort1 == Sort.BYTE_OBJ) {
+            return primitive ? definition.intType : definition.intobjType;
+        }
+
+        return null;
+    }
+
+    public static Type promoteAdd(final Definition definition, 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(definition, from0, from1, true, true);
+    }
+
+    public static Type promoteXor(final Definition definition, 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(definition, from0, from1, false, true);
+    }
+
+    public static Type promoteEquality(final Definition definition, 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(definition, from0, from1, true, primitive);
+        }
+
+        return definition.objectType;
+    }
+
+    public static Type promoteReference(final Definition definition, 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(definition, from0, from1, true, true);
+            }
         }
+
+        return definition.objectType;
+    }
+
+    public static Type promoteConditional(final Definition definition,
+                                          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;
+                    }
+                }
+            }
+        }
+
+        // TODO: In the rare case we still haven't reached a correct promotion we need
+        //       to calculate the highest upper bound for the two types and return that.
+        //       However, for now we just return objectType that may require an extra cast.
+
+        return definition.objectType;
     }
+
+    private AnalyzerCaster() {}
 }

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

@@ -1,856 +0,0 @@
-/*
- * 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.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 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;
-            }
-        }
-    }
-}

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

@@ -1,813 +0,0 @@
-/*
- * 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.ExtvarContext;
-import org.elasticsearch.painless.PainlessParser.IdentifierContext;
-
-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 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 (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 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 (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 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 (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;
-        } 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.");
-        }
-
-        if (dotctx != null) {
-            metadata.createExtNodeMetadata(parent, dotctx);
-            analyzer.visit(dotctx);
-        } else if (bracectx != null) {
-            metadata.createExtNodeMetadata(parent, bracectx);
-            analyzer.visit(bracectx);
-        }
-    }
-
-    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 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 IdentifierContext idctx = ctx.identifier();
-        final String id = idctx.getText();
-
-        final ExtdotContext dotctx = ctx.extdot();
-        final ExtbraceContext bracectx = ctx.extbrace();
-
-        final boolean type = utility.isValidType(idctx, false);
-
-        if (type) {
-            if (parentemd.current != null || dotctx == null || bracectx != null) {
-                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unexpected static type [" + id + "].");
-            }
-
-            varenmd.type = definition.getType(id);
-            parentemd.current = varenmd.type;
-            parentemd.statik = true;
-
-            metadata.createExtNodeMetadata(parent, dotctx);
-            analyzer.visit(dotctx);
-        } else {
-            utility.isValidIdentifier(idctx, true);
-
-            if (parentemd.current != null) {
-                throw new IllegalStateException(AnalyzerUtility.error(ctx) + "Unexpected variable [" + id + "] load.");
-            }
-
-            varenmd.last = parentemd.scope == 0 && dotctx == null && bracectx == null;
-
-            final Variable variable = utility.getVariable(id);
-
-            if (variable == null) {
-                throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unknown variable [" + id + "].");
-            }
-
-            // special cases: reserved words
-            if ("_score".equals(id) || "_value".equals(id) || "doc".equals(id) || "ctx".equals(id)) {
-                // read-only: don't allow stores to ourself
-                if (varenmd.last && parentemd.storeExpr != null) {
-                    throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Variable [" + id + "] is read-only.");
-                }
-                if ("_score".equals(id)) {
-                    // track if the _score value is ever used, we will invoke Scorer.score() only once if so.
-                    metadata.scoreValueUsed = true;
-                } else if ("ctx".equals(id)) {
-                    // track if ctx value is ever used, we will invoke Map.get() only once if so.
-                    metadata.ctxValueUsed = true;
-                }
-            }
-
-            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.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();
-        newenmd.last = parentemd.scope == 0 && dotctx == null;
-
-        final IdentifierContext idctx = ctx.identifier();
-        final String type = idctx.getText();
-        utility.isValidType(idctx, true);
-
-        if (parentemd.current != null) {
-            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Unexpected new call.");
-        } else if (newenmd.last && parentemd.storeExpr != null) {
-            throw new IllegalArgumentException(AnalyzerUtility.error(ctx) + "Cannot assign a value to a new call.");
-        }
-
-        final Struct struct = definition.structs.get(type);
-
-        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 constructor 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);
-        }
-    }
-
-    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().substring(1, ctx.STRING().getText().length() - 1);
-
-        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);
-            }
-        }
-    }
-}

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

@@ -1,281 +0,0 @@
-/*
- * 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;
-    }
-}

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

@@ -1,591 +0,0 @@
-/*
- * 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.IdentifierContext;
-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 IdentifierContext idctx = ctx.identifier();
-        final String type = ctx.getText();
-
-        utility.isValidType(idctx, true);
-        decltypeemd.from = definition.getType(type);
-    }
-
-    void processDeclvar(final DeclvarContext ctx) {
-        final ExpressionMetadata declvaremd = metadata.getExpressionMetadata(ctx);
-        final IdentifierContext idctx = ctx.identifier();
-        final String identifier = idctx.getText();
-
-        utility.isValidIdentifier(idctx, true);
-        declvaremd.postConst = utility.addVariable(ctx, identifier, 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 IdentifierContext idctx0 = ctx.identifier(0);
-        final String type = idctx0.getText();
-        utility.isValidType(idctx0, true);
-        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 IdentifierContext idctx1 = ctx.identifier(1);
-        final String identifier = idctx1.getText();
-        utility.isValidIdentifier(idctx1, true);
-        trapsmd.slot = utility.addVariable(ctx, identifier, 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.");
-        }
-    }
-}

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

@@ -1,191 +0,0 @@
-/*
- * 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.IdentifierContext;
-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() + "]: ";
-    }
-
-    /**
-     * A utility method to output consistent error messages for invalid types.
-     * @param ctx The ANTLR node the error occurred in.
-     * @param type The invalid type.
-     * @return The error message with tacked on line number and character position.
-     */
-    static String typeError(final ParserRuleContext ctx, final String type) {
-        return error(ctx) + "Invalid type [" + type + "].";
-    }
-
-    /**
-     * A utility method to output consistent error messages for invalid identifiers.
-     * @param ctx The ANTLR node the error occurred in.
-     * @param identifier The invalid identifier.
-     * @return The error message with tacked on line number and character position.
-     */
-    static String identifierError(final ParserRuleContext ctx, final String identifier) {
-        return error(ctx) + "Invalid identifier [" + identifier + "].";
-    }
-
-    /**
-     * 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 Definition definition;
-
-    private final Deque<Integer> scopes = new ArrayDeque<>();
-    private final Deque<Variable> variables = new ArrayDeque<>();
-
-    AnalyzerUtility(final Metadata metadata) {
-        this.definition = metadata.definition;
-    }
-
-    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;
-    }
-
-    boolean isValidType(final IdentifierContext idctx, final boolean error) {
-        boolean valid = definition.structs.containsKey(idctx.getText());
-
-        if (!valid && error) {
-            throw new IllegalArgumentException(typeError(idctx, idctx.getText()));
-        }
-
-        return valid;
-    }
-
-    boolean isValidIdentifier(final IdentifierContext idctx, final boolean error) {
-        boolean valid = !definition.structs.containsKey(idctx.getText()) && idctx.generic() == null;
-
-        if (!valid && error) {
-            throw new IllegalArgumentException(identifierError(idctx, idctx.getText()));
-        }
-
-        return valid;
-    }
-}

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

@@ -19,10 +19,10 @@
 
 package org.elasticsearch.painless;
 
-import org.antlr.v4.runtime.ANTLRInputStream;
-import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.ParserRuleContext;
 import org.elasticsearch.bootstrap.BootstrapInfo;
+import org.elasticsearch.painless.Variables.Reserved;
+import org.elasticsearch.painless.antlr.Walker;
+import org.elasticsearch.painless.node.SSource;
 
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -30,13 +30,16 @@ import java.security.CodeSource;
 import java.security.SecureClassLoader;
 import java.security.cert.Certificate;
 
+import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
+
 /**
- * The Compiler is the entry point for generating a Painless script.  The compiler will generate an ANTLR
- * parse tree based on the source code that is passed in.  Two passes will then be run over the parse tree,
+ * The Compiler is the entry point for generating a Painless script.  The compiler will receive a Painless
+ * tree based on the type of input passed in (currently only ANTLR).  Two passes will then be run over the tree,
  * one for analysis using the {@link Analyzer} and another to generate the actual byte code using ASM in
  * the {@link Writer}.
  */
 final class Compiler {
+
     /**
      * The maximum number of characters allowed in the script source.
      */
@@ -54,7 +57,7 @@ final class Compiler {
         try {
             // Setup the code privileges.
             CODESOURCE = new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[]) null);
-        } catch (MalformedURLException impossible) {
+        } catch (final MalformedURLException impossible) {
             throw new RuntimeException(impossible);
         }
     }
@@ -62,7 +65,7 @@ final class Compiler {
     /**
      * A secure class loader used to define Painless scripts.
      */
-    static class Loader extends SecureClassLoader {
+    static final class Loader extends SecureClassLoader {
         /**
          * @param parent The parent ClassLoader.
          */
@@ -90,64 +93,45 @@ final class Compiler {
      * @return An {@link Executable} Painless script.
      */
     static Executable compile(final Loader loader, final String name, final String source, final CompilerSettings settings) {
-        final byte[] bytes = compile(name, source, Definition.INSTANCE, settings);
-        return createExecutable(loader, Definition.INSTANCE, name, source, bytes);
+        byte[] bytes = compile(source, settings);
+
+        return createExecutable(loader, name, source, bytes);
     }
 
     /**
-     * Compiles the script to bytecode
+     * Runs the two-pass compiler to generate a Painless script.  (Used by the debugger.)
+     * @param source The source code for the script.
+     * @param settings The CompilerSettings to be used during the compilation.
+     * @return The bytes for compilation.
      */
-    static byte[] compile(String name, String source, Definition definition, CompilerSettings settings) {
+    static byte[] compile(final String source, final CompilerSettings settings) {
         if (source.length() > MAXIMUM_SOURCE_LENGTH) {
             throw new IllegalArgumentException("Scripts may be no longer than " + MAXIMUM_SOURCE_LENGTH +
-                    " characters.  The passed in script is " + source.length() + " characters.  Consider using a" +
-                    " plugin if a script longer than this length is a requirement.");
+                " characters.  The passed in script is " + source.length() + " characters.  Consider using a" +
+                " plugin if a script longer than this length is a requirement.");
         }
-        final ParserRuleContext root = createParseTree(source);
-        final Metadata metadata = new Metadata(definition, source, root, settings);
-        Analyzer.analyze(metadata);
-        return Writer.write(metadata);
-    }
-
-    /**
-     * Generates the ANTLR tree from the given source code.  Several methods below, are used
-     * to ensure that the first error generated by ANTLR will cause the compilation to fail rather than
-     * use ANTLR's recovery strategies that may be potentially dangerous.
-     * @param source The source code for the script.
-     * @return The root node for the ANTLR parse tree.
-     */
-    private static ParserRuleContext createParseTree(final String source) {
-        final ANTLRInputStream stream = new ANTLRInputStream(source);
-        final ErrorHandlingLexer lexer = new ErrorHandlingLexer(stream);
-        final PainlessParser parser = new PainlessParser(new CommonTokenStream(lexer));
-        final ParserErrorStrategy strategy = new ParserErrorStrategy();
-
-        lexer.removeErrorListeners();
-        parser.removeErrorListeners();
-        parser.setErrorHandler(strategy);
 
-        ParserRuleContext root = parser.source();
+        final Reserved reserved = new Reserved();
+        final SSource root = Walker.buildPainlessTree(source, reserved);
+        final Variables variables = Analyzer.analyze(settings, Definition.INSTANCE, reserved, root);
 
-        return root;
+        return Writer.write(settings, Definition.INSTANCE, source, variables, root);
     }
 
     /**
      * Generates an {@link Executable} that can run a Painless script.
      * @param loader The {@link Loader} to define the script's class file.
-     * @param definition The Painless API.
      * @param name The name of the script.
      * @param source The source text of the script.
      * @param bytes The ASM generated byte code to define the class with.
      * @return A Painless {@link Executable} script.
      */
-    private static Executable createExecutable(final Loader loader, final Definition definition,
-                                               final String name, final String source, final byte[] bytes) {
+    private static Executable createExecutable(final Loader loader, final String name, final String source, final byte[] bytes) {
         try {
-            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);
+            final Class<? extends Executable> clazz = loader.define(CLASS_NAME, bytes);
+            final java.lang.reflect.Constructor<? extends Executable> constructor = clazz.getConstructor(String.class, String.class);
 
-            return constructor.newInstance(definition, name, source);
+            return constructor.newInstance(name, source);
         } catch (final Exception exception) { // Catch everything to let the user know this is something caused internally.
             throw new IllegalStateException(
                     "An internal error occurred attempting to define the script [" + name + "].", exception);

+ 8 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/CompilerSettings.java

@@ -22,7 +22,8 @@ package org.elasticsearch.painless;
 /**
  * Settings to use when compiling a script.
  */
-final class CompilerSettings {
+public final class CompilerSettings {
+
     /**
      * Constant to be used when specifying numeric overflow when compiling a script.
      */
@@ -52,7 +53,7 @@ final class CompilerSettings {
      * signs, and overflow for floating point types can result in infinite or
      * {@code NaN} values.
      */
-    public boolean getNumericOverflow() {
+    public final boolean getNumericOverflow() {
         return numericOverflow;
     }
 
@@ -60,15 +61,16 @@ final class CompilerSettings {
      * Set {@code true} for numerics to overflow, false to deliver exceptions.
      * @see #getNumericOverflow
      */
-    public void setNumericOverflow(boolean allow) {
+    public final void setNumericOverflow(boolean allow) {
         this.numericOverflow = allow;
     }
 
     /**
      * Returns the value for the cumulative total number of statements that can be made in all loops
-     * in a script before an exception is thrown.  This attempts to prevent infinite loops.
+     * in a script before an exception is thrown.  This attempts to prevent infinite loops.  Note if
+     * the counter is set to 0, no loop counter will be written.
      */
-    public int getMaxLoopCounter() {
+    public final int getMaxLoopCounter() {
         return maxLoopCounter;
     }
 
@@ -76,7 +78,7 @@ final class CompilerSettings {
      * Set the cumulative total number of statements that can be made in all loops.
      * @see #getMaxLoopCounter
      */
-    public void setMaxLoopCounter(int max) {
+    public final void setMaxLoopCounter(int max) {
         this.maxLoopCounter = max;
     }
 }

+ 105 - 164
modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java

@@ -24,8 +24,8 @@ import org.elasticsearch.painless.Definition.RuntimeClass;
 
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
 import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -41,13 +41,85 @@ import java.util.stream.Stream;
  * <p>
  * Dynamic methods, loads, stores, and array/list/map load/stores involve locating the appropriate field
  * or method depending on the receiver's class. For these, we emit an {@code invokedynamic} instruction that,
- * for each new  type encountered will query a corresponding {@code lookupXXX} method to retrieve the appropriate
+ * for each new type encountered will query a corresponding {@code lookupXXX} method to retrieve the appropriate
  * method. In most cases, the {@code lookupXXX} methods here will only be called once for a given call site, because
  * caching ({@link DynamicCallSite}) generally works: usually all objects at any call site will be consistently
  * the same type (or just a few types).  In extreme cases, if there is type explosion, they may be called every
  * single time, but simplicity is still more valuable than performance in this code.
  */
-public class Def {
+public final class Def {
+
+    // TODO: Once Java has a factory for those in java.lang.invoke.MethodHandles, use it:
+
+    /** Helper class for isolating MethodHandles and methods to get the length of arrays
+     * (to emulate a "arraystore" byteoode using MethodHandles).
+     * This should really be a method in {@link MethodHandles} class!
+     */
+    private static final class ArrayLengthHelper {
+        private static final Lookup PRIV_LOOKUP = MethodHandles.lookup();
+
+        private static final Map<Class<?>,MethodHandle> ARRAY_TYPE_MH_MAPPING = Collections.unmodifiableMap(
+            Stream.of(boolean[].class, byte[].class, short[].class, int[].class, long[].class,
+                char[].class, float[].class, double[].class, Object[].class)
+                .collect(Collectors.toMap(Function.identity(), type -> {
+                    try {
+                        return PRIV_LOOKUP.findStatic(PRIV_LOOKUP.lookupClass(), "getArrayLength", MethodType.methodType(int.class, type));
+                    } catch (ReflectiveOperationException e) {
+                        throw new AssertionError(e);
+                    }
+                }))
+        );
+
+        private static final MethodHandle OBJECT_ARRAY_MH = ARRAY_TYPE_MH_MAPPING.get(Object[].class);
+
+        static int getArrayLength(final boolean[] array) { return array.length; }
+        static int getArrayLength(final byte[] array)    { return array.length; }
+        static int getArrayLength(final short[] array)   { return array.length; }
+        static int getArrayLength(final int[] array)     { return array.length; }
+        static int getArrayLength(final long[] array)    { return array.length; }
+        static int getArrayLength(final char[] array)    { return array.length; }
+        static int getArrayLength(final float[] array)   { return array.length; }
+        static int getArrayLength(final double[] array)  { return array.length; }
+        static int getArrayLength(final Object[] array)  { return array.length; }
+
+        static MethodHandle arrayLengthGetter(Class<?> arrayType) {
+            if (!arrayType.isArray()) {
+                throw new IllegalArgumentException("type must be an array");
+            }
+            return (ARRAY_TYPE_MH_MAPPING.containsKey(arrayType)) ?
+                ARRAY_TYPE_MH_MAPPING.get(arrayType) :
+                OBJECT_ARRAY_MH.asType(OBJECT_ARRAY_MH.type().changeParameterType(0, arrayType));
+        }
+
+        private ArrayLengthHelper() {}
+    }
+
+    /** pointer to Map.get(Object) */
+    private static final MethodHandle MAP_GET;
+    /** pointer to Map.put(Object,Object) */
+    private static final MethodHandle MAP_PUT;
+    /** pointer to List.get(int) */
+    private static final MethodHandle LIST_GET;
+    /** pointer to List.set(int,Object) */
+    private static final MethodHandle LIST_SET;
+
+    static {
+        final Lookup lookup = MethodHandles.publicLookup();
+
+        try {
+            MAP_GET  = lookup.findVirtual(Map.class , "get", MethodType.methodType(Object.class, Object.class));
+            MAP_PUT  = lookup.findVirtual(Map.class , "put", MethodType.methodType(Object.class, Object.class, Object.class));
+            LIST_GET = lookup.findVirtual(List.class, "get", MethodType.methodType(Object.class, int.class));
+            LIST_SET = lookup.findVirtual(List.class, "set", MethodType.methodType(Object.class, int.class, Object.class));
+        } catch (final ReflectiveOperationException roe) {
+            throw new AssertionError(roe);
+        }
+    }
+
+    /** Returns an array length getter MethodHandle for the given array type */
+    static MethodHandle arrayLengthGetter(Class<?> arrayType) {
+        return ArrayLengthHelper.arrayLengthGetter(arrayType);
+    }
 
     /**
      * Looks up handle for a dynamic method call.
@@ -65,7 +137,7 @@ public class Def {
      * @return pointer to matching method to invoke. never returns null.
      * @throws IllegalArgumentException if no matching whitelisted method was found.
      */
-    static MethodHandle lookupMethod(Class<?> receiverClass, String name, Definition definition) {
+     static MethodHandle lookupMethod(Class<?> receiverClass, String name, Definition definition) {
         // check whitelist for matching method
         for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
             RuntimeClass struct = definition.runtimeMap.get(clazz);
@@ -77,7 +149,7 @@ public class Def {
                 }
             }
 
-            for (Class<?> iface : clazz.getInterfaces()) {
+            for (final Class<?> iface : clazz.getInterfaces()) {
                 struct = definition.runtimeMap.get(iface);
 
                 if (struct != null) {
@@ -94,78 +166,6 @@ public class Def {
                                            "for class [" + receiverClass.getCanonicalName() + "].");
     }
 
-    /** pointer to Map.get(Object) */
-    private static final MethodHandle MAP_GET;
-    /** pointer to Map.put(Object,Object) */
-    private static final MethodHandle MAP_PUT;
-    /** pointer to List.get(int) */
-    private static final MethodHandle LIST_GET;
-    /** pointer to List.set(int,Object) */
-    private static final MethodHandle LIST_SET;
-    static {
-        Lookup lookup = MethodHandles.publicLookup();
-        try {
-            MAP_GET      = lookup.findVirtual(Map.class, "get",
-                                             MethodType.methodType(Object.class, Object.class));
-            MAP_PUT      = lookup.findVirtual(Map.class, "put",
-                                             MethodType.methodType(Object.class, Object.class, Object.class));
-            LIST_GET     = lookup.findVirtual(List.class, "get",
-                                             MethodType.methodType(Object.class, int.class));
-            LIST_SET     = lookup.findVirtual(List.class, "set",
-                                             MethodType.methodType(Object.class, int.class, Object.class));
-        } catch (ReflectiveOperationException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    // TODO: Once Java has a factory for those in java.lang.invoke.MethodHandles, use it:
-
-    /** Helper class for isolating MethodHandles and methods to get the length of arrays
-     * (to emulate a "arraystore" byteoode using MethodHandles).
-     * This should really be a method in {@link MethodHandles} class!
-     */
-    private static final class ArrayLengthHelper {
-      private ArrayLengthHelper() {}
-
-      private static final Lookup PRIV_LOOKUP = MethodHandles.lookup();
-      private static final Map<Class<?>,MethodHandle> ARRAY_TYPE_MH_MAPPING = Collections.unmodifiableMap(
-        Stream.of(boolean[].class, byte[].class, short[].class, int[].class, long[].class,
-        char[].class, float[].class, double[].class, Object[].class)
-          .collect(Collectors.toMap(Function.identity(), type -> {
-            try {
-              return PRIV_LOOKUP.findStatic(PRIV_LOOKUP.lookupClass(), "getArrayLength", MethodType.methodType(int.class, type));
-            } catch (ReflectiveOperationException e) {
-              throw new AssertionError(e);
-            }
-          }))
-      );
-      private static final MethodHandle OBJECT_ARRAY_MH = ARRAY_TYPE_MH_MAPPING.get(Object[].class);
-
-      static int getArrayLength(boolean[] array) { return array.length; }
-      static int getArrayLength(byte[] array) { return array.length; }
-      static int getArrayLength(short[] array) { return array.length; }
-      static int getArrayLength(int[] array) { return array.length; }
-      static int getArrayLength(long[] array) { return array.length; }
-      static int getArrayLength(char[] array) { return array.length; }
-      static int getArrayLength(float[] array) { return array.length; }
-      static int getArrayLength(double[] array) { return array.length; }
-      static int getArrayLength(Object[] array) { return array.length; }
-
-      public static MethodHandle arrayLengthGetter(Class<?> arrayType) {
-        if (!arrayType.isArray()) {
-          throw new IllegalArgumentException("type must be an array");
-        }
-        return (ARRAY_TYPE_MH_MAPPING.containsKey(arrayType)) ?
-            ARRAY_TYPE_MH_MAPPING.get(arrayType) :
-            OBJECT_ARRAY_MH.asType(OBJECT_ARRAY_MH.type().changeParameterType(0, arrayType));
-      }
-    }
-
-    /** Returns an array length getter MethodHandle for the given array type */
-    public static MethodHandle arrayLengthGetter(Class<?> arrayType) {
-      return ArrayLengthHelper.arrayLengthGetter(arrayType);
-    }
-
     /**
      * Looks up handle for a dynamic field getter (field load)
      * <p>
@@ -298,7 +298,7 @@ public class Def {
             try {
                 int index = Integer.parseInt(name);
                 return MethodHandles.insertArguments(LIST_SET, 1, index);
-            } catch (NumberFormatException exception) {
+            } catch (final NumberFormatException exception) {
                 throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "].");
             }
         }
@@ -345,10 +345,12 @@ public class Def {
                                            "[" + receiverClass.getCanonicalName() + "] as an array.");
     }
 
-    // NOTE: below methods are not cached, instead invoked directly because they are performant.
+    // NOTE: Below methods are not cached, instead invoked directly because they are performant.
+    //       We also check for Long values first when possible since the type is more
+    //       likely to be a Long than a Float.
 
     public static Object not(final Object unary) {
-        if (unary instanceof Double || unary instanceof Float || unary instanceof Long) {
+        if (unary instanceof Double || unary instanceof Long || unary instanceof Float) {
             return ~((Number)unary).longValue();
         } else if (unary instanceof Number) {
             return ~((Number)unary).intValue();
@@ -594,103 +596,40 @@ public class Def {
                 "[" + left.getClass().getCanonicalName() + "] and [" + right.getClass().getCanonicalName() + "].");
     }
 
-    public static Object lsh(final Object left, final Object right) {
-        if (left instanceof Number) {
-            if (right instanceof Number) {
-                if (left instanceof Double || right instanceof Double ||
-                    left instanceof Long || right instanceof Long ||
-                    left instanceof Float || right instanceof Float) {
-                    return ((Number)left).longValue() << ((Number)right).longValue();
-                } else {
-                    return ((Number)left).intValue() << ((Number)right).intValue();
-                }
-            } else if (right instanceof Character) {
-                if (left instanceof Double ||  left instanceof Long || left instanceof Float) {
-                    return ((Number)left).longValue() << (char)right;
-                } else {
-                    return ((Number)left).intValue() << (char)right;
-                }
-            }
+    public static Object lsh(final Object left, final int right) {
+        if (left instanceof Double || left instanceof Long || left instanceof Float) {
+            return ((Number)left).longValue() << right;
+        } else if (left instanceof Number) {
+            return ((Number)left).intValue() << right;
         } else if (left instanceof Character) {
-            if (right instanceof Number) {
-                if (right instanceof Double || right instanceof Long || right instanceof Float) {
-                    return (long)(char)left << ((Number)right).longValue();
-                } else {
-                    return (char)left << ((Number)right).intValue();
-                }
-            } else if (right instanceof Character) {
-                return (char)left << (char)right;
-            }
+            return (char)left << right;
         }
 
-        throw new ClassCastException("Cannot apply [<<] operation to types " +
-                "[" + left.getClass().getCanonicalName() + "] and [" + right.getClass().getCanonicalName() + "].");
+        throw new ClassCastException("Cannot apply [<<] operation to types [" + left.getClass().getCanonicalName() + "] and [int].");
     }
 
-    public static Object rsh(final Object left, final Object right) {
-        if (left instanceof Number) {
-            if (right instanceof Number) {
-                if (left instanceof Double || right instanceof Double ||
-                    left instanceof Long || right instanceof Long ||
-                    left instanceof Float || right instanceof Float) {
-                    return ((Number)left).longValue() >> ((Number)right).longValue();
-                } else {
-                    return ((Number)left).intValue() >> ((Number)right).intValue();
-                }
-            } else if (right instanceof Character) {
-                if (left instanceof Double || left instanceof Long || left instanceof Float) {
-                    return ((Number)left).longValue() >> (char)right;
-                } else {
-                    return ((Number)left).intValue() >> (char)right;
-                }
-            }
+    public static Object rsh(final Object left, final int right) {
+        if (left instanceof Double || left instanceof Long || left instanceof Float) {
+            return ((Number)left).longValue() >> right;
+        } else if (left instanceof Number) {
+            return ((Number)left).intValue() >> right;
         } else if (left instanceof Character) {
-            if (right instanceof Number) {
-                if (right instanceof Double || right instanceof Long || right instanceof Float) {
-                    return (long)(char)left >> ((Number)right).longValue();
-                } else {
-                    return (char)left >> ((Number)right).intValue();
-                }
-            } else if (right instanceof Character) {
-                return (char)left >> (char)right;
-            }
+            return (char)left >> right;
         }
 
-        throw new ClassCastException("Cannot apply [>>] operation to types " +
-                "[" + left.getClass().getCanonicalName() + "] and [" + right.getClass().getCanonicalName() + "].");
+        throw new ClassCastException("Cannot apply [>>] operation to types [" + left.getClass().getCanonicalName() + "] and [int].");
     }
 
-    public static Object ush(final Object left, final Object right) {
-        if (left instanceof Number) {
-            if (right instanceof Number) {
-                if (left instanceof Double || right instanceof Double ||
-                    left instanceof Long || right instanceof Long ||
-                    left instanceof Float || right instanceof Float) {
-                    return ((Number)left).longValue() >>> ((Number)right).longValue();
-                } else {
-                    return ((Number)left).intValue() >>> ((Number)right).intValue();
-                }
-            } else if (right instanceof Character) {
-                if (left instanceof Double || left instanceof Long || left instanceof Float) {
-                    return ((Number)left).longValue() >>> (char)right;
-                } else {
-                    return ((Number)left).intValue() >>> (char)right;
-                }
-            }
+    public static Object ush(final Object left, final int right) {
+        if (left instanceof Double || left instanceof Long || left instanceof Float) {
+            return ((Number)left).longValue() >>> right;
+        } else if (left instanceof Number) {
+            return ((Number)left).intValue() >>> right;
         } else if (left instanceof Character) {
-            if (right instanceof Number) {
-                if (right instanceof Double || right instanceof Long || right instanceof Float) {
-                    return (long)(char)left >>> ((Number)right).longValue();
-                } else {
-                    return (char)left >>> ((Number)right).intValue();
-                }
-            } else if (right instanceof Character) {
-                return (char)left >>> (char)right;
-            }
+            return (char)left >>> right;
         }
 
-        throw new ClassCastException("Cannot apply [>>>] operation to types " +
-                "[" + left.getClass().getCanonicalName() + "] and [" + right.getClass().getCanonicalName() + "].");
+        throw new ClassCastException("Cannot apply [>>>] operation to types [" + left.getClass().getCanonicalName() + "] and [int].");
     }
 
     public static Object and(final Object left, final Object right) {
@@ -1026,6 +965,8 @@ public class Def {
                 "[" + left.getClass().getCanonicalName() + "] and [" + right.getClass().getCanonicalName() + "].");
     }
 
+    // Conversion methods for Def to primitive types.
+
     public static boolean DefToboolean(final Object value) {
         if (value instanceof Boolean) {
             return (boolean)value;

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 306 - 596
modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java


+ 28 - 26
modules/lang-painless/src/main/java/org/elasticsearch/painless/DynamicCallSite.java

@@ -36,33 +36,34 @@ import java.lang.invoke.MutableCallSite;
  * When a new type is encountered at the call site, we lookup from the appropriate
  * whitelist, and cache with a guard. If we encounter too many types, we stop caching.
  * <p>
- * Based on the cascaded inlining cache from the JSR 292 cookbook 
+ * Based on the cascaded inlining cache from the JSR 292 cookbook
  * (https://code.google.com/archive/p/jsr292-cookbook/, BSD license)
  */
 // NOTE: this class must be public, because generated painless classes are in a different package,
 // and it needs to be accessible by that code.
 public final class DynamicCallSite {
+
     // NOTE: these must be primitive types, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
     /** static bootstrap parameter indicating a dynamic method call, e.g. foo.bar(...) */
-    static final int METHOD_CALL = 0;
+    public static final int METHOD_CALL = 0;
     /** static bootstrap parameter indicating a dynamic load (getter), e.g. baz = foo.bar */
-    static final int LOAD = 1;
+    public static final int LOAD = 1;
     /** static bootstrap parameter indicating a dynamic store (setter), e.g. foo.bar = baz */
-    static final int STORE = 2;
+    public static final int STORE = 2;
     /** static bootstrap parameter indicating a dynamic array load, e.g. baz = foo[bar] */
-    static final int ARRAY_LOAD = 3;
+    public static final int ARRAY_LOAD = 3;
     /** static bootstrap parameter indicating a dynamic array store, e.g. foo[bar] = baz */
-    static final int ARRAY_STORE = 4;
-    
+    public static final int ARRAY_STORE = 4;
+
     static class InliningCacheCallSite extends MutableCallSite {
         /** maximum number of types before we go megamorphic */
         static final int MAX_DEPTH = 5;
-        
+
         final Lookup lookup;
         final String name;
         final int flavor;
         int depth;
-        
+
         InliningCacheCallSite(Lookup lookup, String name, MethodType type, int flavor) {
             super(type);
             this.lookup = lookup;
@@ -70,8 +71,8 @@ public final class DynamicCallSite {
             this.flavor = flavor;
         }
     }
-    
-    /** 
+
+    /**
      * invokeDynamic bootstrap method
      * <p>
      * In addition to ordinary parameters, we also take a static parameter {@code flavor} which
@@ -81,45 +82,45 @@ public final class DynamicCallSite {
      */
     public static CallSite bootstrap(Lookup lookup, String name, MethodType type, int flavor) {
         InliningCacheCallSite callSite = new InliningCacheCallSite(lookup, name, type, flavor);
-        
+
         MethodHandle fallback = FALLBACK.bindTo(callSite);
         fallback = fallback.asCollector(Object[].class, type.parameterCount());
         fallback = fallback.asType(type);
-        
+
         callSite.setTarget(fallback);
         return callSite;
     }
-    
-    /** 
+
+    /**
      * guard method for inline caching: checks the receiver's class is the same
      * as the cached class
      */
     static boolean checkClass(Class<?> clazz, Object receiver) {
         return receiver.getClass() == clazz;
     }
-    
+
     /**
      * Does a slow lookup against the whitelist.
      */
     private static MethodHandle lookup(int flavor, Class<?> clazz, String name) {
         switch(flavor) {
-            case METHOD_CALL: 
+            case METHOD_CALL:
                 return Def.lookupMethod(clazz, name, Definition.INSTANCE);
-            case LOAD: 
+            case LOAD:
                 return Def.lookupGetter(clazz, name, Definition.INSTANCE);
             case STORE:
                 return Def.lookupSetter(clazz, name, Definition.INSTANCE);
-            case ARRAY_LOAD: 
+            case ARRAY_LOAD:
                 return Def.lookupArrayLoad(clazz);
             case ARRAY_STORE:
                 return Def.lookupArrayStore(clazz);
             default: throw new AssertionError();
         }
     }
-    
+
     /**
      * Called when a new type is encountered (or, when we have encountered more than {@code MAX_DEPTH}
-     * types at this call site and given up on caching). 
+     * types at this call site and given up on caching).
      */
     static Object fallback(InliningCacheCallSite callSite, Object[] args) throws Throwable {
         MethodType type = callSite.type();
@@ -127,25 +128,26 @@ public final class DynamicCallSite {
         Class<?> receiverClass = receiver.getClass();
         MethodHandle target = lookup(callSite.flavor, receiverClass, callSite.name);
         target = target.asType(type);
-        
+
         if (callSite.depth >= InliningCacheCallSite.MAX_DEPTH) {
             // revert to a vtable call
             callSite.setTarget(target);
             return target.invokeWithArguments(args);
         }
-        
+
         MethodHandle test = CHECK_CLASS.bindTo(receiverClass);
         test = test.asType(test.type().changeParameterType(0, type.parameterType(0)));
-        
+
         MethodHandle guard = MethodHandles.guardWithTest(test, target, callSite.getTarget());
         callSite.depth++;
-        
+
         callSite.setTarget(guard);
         return target.invokeWithArguments(args);
     }
-    
+
     private static final MethodHandle CHECK_CLASS;
     private static final MethodHandle FALLBACK;
+
     static {
         Lookup lookup = MethodHandles.lookup();
         try {

+ 6 - 9
modules/lang-painless/src/main/java/org/elasticsearch/painless/Executable.java

@@ -24,15 +24,15 @@ import org.elasticsearch.search.lookup.LeafDocLookup;
 
 import java.util.Map;
 
+/**
+ * The superclass used to build all Painless scripts on top of.
+ */
 public abstract class Executable {
-    protected final Definition definition;
 
     private final String name;
     private final String source;
 
-    public Executable(final Definition definition, final String name, final String source) {
-        this.definition = definition;
-
+    public Executable(final String name, final String source) {
         this.name = name;
         this.source = source;
     }
@@ -45,9 +45,6 @@ public abstract class Executable {
         return source;
     }
 
-    public Definition getDefinition() {
-        return definition;
-    }
-
-    public abstract Object execute(Map<String, Object> params, Scorer scorer, LeafDocLookup doc, Object value);
+    public abstract Object execute(
+        final Map<String, Object> params, final Scorer scorer, final LeafDocLookup doc, final Object value);
 }

+ 0 - 605
modules/lang-painless/src/main/java/org/elasticsearch/painless/Metadata.java

@@ -1,605 +0,0 @@
-/*
- * 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.Type;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Metadata is a wrapper for all the data that is collected by the {@link Analyzer}.  Each node in the ANTLR parse tree
- * will have one of the types of metadata to store information used either in a different node by the analyzer
- * or by the {@link Writer} during byte code generation.  Metadata also contains several objects passed into the
- * {@link Analyzer} and {@link Writer} used during compilation including the {@link Definition}, the source code,
- * 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.
-     */
-    static class StatementMetadata {
-        /**
-         * The source variable is the ANTLR node used to generate this metadata.
-         */
-        final ParserRuleContext source;
-
-        /**
-         * The lastSource variable will be set to true when the final statement from the root ANTLR node is about
-         * to be visited.  This is used to determine whether or not the auto-return feature is allowed to be used,
-         * and if a null return value needs to be generated automatically since a return value is always required.
-         */
-        boolean lastSource = false;
-
-        /**
-         * The beginLoop variable will be set to true whenever a loop node is initially visited including inner
-         * loops.  This will not be propagated down the parse tree afterwards, though.  This is used to determine
-         * whether or not inLoop should be set further down the tree.  Note that inLoop alone is not enough
-         * information to determine whether we are in the last statement of a loop because we may inside of
-         * multiple loops, so this variable is necessary.
-         */
-        boolean beginLoop = false;
-
-        /**
-         * The inLoop variable is set to true when inside a loop.  This will be propagated down the parse tree.  This
-         * is used to determine whether or not continue and break statements are legal.
-         */
-        boolean inLoop = false;
-
-        /**
-         * The lastLoop variable is set to true when the final statement of a loop is reached.  This will be
-         * propagated down the parse tree until another loop is reached and then will not be propagated further for
-         * the current loop.  This is used to determine whether or not a continue statement is superfluous.
-         */
-        boolean lastLoop = false;
-
-        /**
-         * The methodEscape variable is set to true when a statement would cause the method to potentially exit.  This
-         * includes return, throw, and continuous loop statements.  Note that a catch statement may possibly
-         * reset this to false after a throw statement.  This will be propagated up the tree as far as necessary.
-         * This is used by the {@link Writer} to ensure that superfluous statements aren't unnecessarily written
-         * into generated bytecode.
-         */
-        boolean methodEscape = false;
-
-        /**
-         * The loopEscape variable is set to true when a loop is going to be exited.  This may be caused by a number of
-         * different statements including continue, break, return, etc.  This will only be propagated as far as the
-         * loop node.  This is used to ensure that in certain case an infinite loop will be caught at
-         * compile-time rather than run-time.
-         */
-        boolean loopEscape = false;
-
-        /**
-         * The allLast variable is set whenever a final statement in a block is reached. This includes the end of loop,
-         * if, else, etc.  This will be only propagated to the top of the block statement ANTLR node.
-         * This is used to ensure that there are no unreachable statements within the script.
-         */
-        boolean allLast = false;
-
-        /**
-         * The anyContinue will be set to true when a continue statement is visited.  This will be propagated to the
-         * loop node it's within.  This is used to ensure that in certain case an infinite loop will be caught at
-         * compile-time rather than run-time.
-         */
-        boolean anyContinue = false;
-
-        /**
-         * The anyBreak will be set to true when a break statement is visited.  This will be propagated to the
-         * loop node it's within.  This is used to in conjunction with methodEscape to ensure there are no unreachable
-         * statements within the script.
-         */
-        boolean anyBreak = false;
-
-        /**
-         * The count variable is used as a rudimentary count of statements within a loop.  This will be used in
-         * the {@link Writer} to keep a count of statements that have been executed at run-time to ensure that a loop
-         * will exit if it runs too long.
-         */
-        int count = 0;
-
-        /**
-         * The exception variable is used to store the exception type when a throw node is visited.  This is used by
-         * the {@link Writer} to write the correct type of exception in the generated byte code.
-         */
-        Type exception = null;
-
-        /**
-         * The slot variable is used to store the place on the stack of where a thrown exception will be stored to.
-         * This is used by the {@link Writer}.
-         */
-        int slot = -1;
-
-        /**
-         * Constructor.
-         * @param source The associated ANTLR node.
-         */
-        private StatementMetadata(final ParserRuleContext source) {
-            this.source = source;
-        }
-    }
-
-    /**
-     * ExpressionMetadata is used to store metadata mostly about constants and casting
-     * for ANTLR nodes related to mathematical operations.
-     */
-    static class ExpressionMetadata {
-        /**
-         * The source variable is the ANTLR node used to generate this metadata.
-         */
-        final ParserRuleContext source;
-
-        /**
-         * The read variable is used to determine whether or not the value of an expression will be read from.
-         * This is set to false when the expression is the left-hand side of an assignment that is not chained or
-         * when a method call is made alone.  This will propagate down the tree as far as necessary.
-         * The {@link Writer} uses this to determine when a value may need to be popped from the stack
-         * such as when a method call returns a value that is never read.
-         */
-        boolean read = true;
-
-        /**
-         * The statement variable is set true when an expression is a complete meaning that there is some sort
-         * of effect on a variable or a method call is made.  This will propagate up the tree as far as necessary.
-         * This prevents statements that have no effect on the output of a script from being executed.
-         */
-        boolean statement = false;
-
-        /**
-         * The preConst variable is set to a non-null value when a constant statement is made in a script.  This is
-         * used to track the constant value prior to any casts being made on an ANTLR node.
-         */
-        Object preConst = null;
-
-        /**
-         * The postConst variable is set to a non-null value when a cast is made on a node where a preConst variable
-         * has already been set when the cast would leave the constant as a non-object value except in the case of a
-         * String.  This will be propagated up the tree and used to simplify constants when possible such as making
-         * the value of 2*2 be 4 in the * node, so that the {@link Writer} only has to push a 4 onto the stack.
-         */
-        Object postConst = null;
-
-        /**
-         * The isNull variable is set to true when a null constant statement is made in the script.  This allows the
-         * {@link Writer} to potentially shortcut certain comparison operations.
-         */
-        boolean isNull = false;
-
-        /**
-         * The to variable is used to track what an ANTLR node's value should be cast to.  This is set on every ANTLR
-         * node in the tree, and used by the {@link Writer} to make a run-time cast if necessary in the byte code.
-         * This is also used by the {@link Analyzer} to determine if a cast is legal.
-         */
-        Type to = null;
-
-        /**
-         * The from variable is used to track what an ANTLR node's value should be cast from.  This is set on every
-         * ANTLR node in the tree independent of other nodes.  This is used by the {@link Analyzer} to determine if a
-         * cast is legal.
-         */
-        Type from = null;
-
-        /**
-         * The explicit variable is set to true when a cast is explicitly made in the script.  This tracks whether
-         * or not a cast is a legal up cast.
-         */
-        boolean explicit = false;
-
-        /**
-         * The typesafe variable is set to true when a dynamic type is used as part of an expression.  This propagates
-         * up the tree to the top of the expression.  This allows for implicit up casts throughout the expression and
-         * is used by the {@link Analyzer}.
-         */
-        boolean typesafe = true;
-
-        /**
-         * This is set to the combination of the to and from variables at the end of each node visit in the
-         * {@link Analyzer}.  This is set on every ANTLR node in the tree independent of other nodes, and is
-         * used by {@link Writer} to make a run-time cast if necessary in the byte code.
-         */
-        Cast cast = null;
-
-        /**
-         * Constructor.
-         * @param source The associated ANTLR node.
-         */
-        private ExpressionMetadata(final ParserRuleContext source) {
-            this.source = source;
-        }
-    }
-
-    /**
-     * ExternalMetadata is used to store metadata about the overall state of a variable/method chain such as
-     * '(int)x.get(3)' where each piece of that chain is broken into it's indiviual pieces and stored in
-     * {@link ExtNodeMetadata}.
-     */
-    static class ExternalMetadata {
-        /**
-         * The source variable is the ANTLR node used to generate this metadata.
-         */
-        final ParserRuleContext source;
-
-        /**
-         * The read variable is set to true when the value of a variable/method chain is going to be read from.
-         * This is used by the {@link Analyzer} to determine if this variable/method chain will be in a standalone
-         * statement.
-         */
-        boolean read = false;
-
-        /**
-         * The storeExpr variable is set to the right-hand side of an assignment in the variable/method chain if
-         * necessary.  This is used by the {@link Analyzer} to set the proper metadata for a read versus a write,
-         * and is used by the {@link Writer} to determine if a bytecode operation should be a load or a store.
-         */
-        ParserRuleContext storeExpr = null;
-
-        /**
-         * The token variable is set to a constant value of the operator type (+, -, etc.) when a compound assignment
-         * is being visited.  This is also used by the increment and decrement operators.  This is used by both the
-         * {@link Analyzer} and {@link Writer} to correctly handle the compound assignment.
-         */
-        int token = 0;
-
-        /**
-         * The pre variable is set to true when pre-increment or pre-decrement is visited.  This is used by both the
-         * {@link Analyzer} and {@link Writer} to correctly handle any reads of the variable/method chain that are
-         * necessary.
-         */
-        boolean pre = false;
-
-        /**
-         * The post variable is set to true when post-increment or post-decrement is visited. This is used by both the
-         * {@link Analyzer} and {@link Writer} to correctly handle any reads of the variable/method chain that are
-         * necessary.
-         */
-        boolean post = false;
-
-        /**
-         * The scope variable is incremented and decremented when a precedence node is visited as part of a
-         * variable/method chain.  This is used by the {@link Analyzer} to determine when the final piece of the
-         * variable/method chain has been reached.
-         */
-        int scope = 0;
-
-        /**
-         * The current variable is set to whatever the current type is within the visited node of the variable/method
-         * chain.  This changes as the nodes for the variable/method are walked through.  This is used by the
-         * {@link Analyzer} to make decisions about whether or not a cast is legal, and what methods are available
-         * for that specific type.
-         */
-        Type current = null;
-
-        /**
-         * The statik variable is set to true when a variable/method chain begins with static type.  This is used by
-         * the {@link Analyzer} to determine what methods/members are available for that specific type.
-         */
-        boolean statik = false;
-
-        /**
-         * The statement variable is set to true when a variable/method chain can be standalone statement.  This is
-         * used by the {@link Analyzer} to error out if there a variable/method chain that is not a statement.
-         */
-        boolean statement = false;
-
-        /**
-         * The constant variable is set when a String constant is part of the variable/method chain.  String is a
-         * special case because methods/members need to be able to be called on a String constant, so this can't be
-         * only as part of {@link ExpressionMetadata}.  This is used by the {@link Writer} to write out the String
-         * constant in the byte code.
-         */
-        Object constant = null;
-
-        /**
-         * Constructor.
-         * @param source The associated ANTLR node.
-         */
-        private ExternalMetadata(final ParserRuleContext source) {
-            this.source = source;
-        }
-    }
-
-    static class ExtNodeMetadata {
-        /**
-         * The parent variable is top-level ANTLR node of the variable/method chain.  This is used to retrieve the
-         * ExternalMetadata for the variable/method chain this ExtNodeMetadata is a piece of.
-         */
-        final ParserRuleContext parent;
-
-        /**
-         * The source variable is the ANTLR node used to generate this metadata.
-         */
-        final ParserRuleContext source;
-
-        /**
-         * The target variable is set to a value based on the type of ANTLR node that is visited.  This is used by
-         * {@link Writer} to determine whether a cast, store, load, or method call should be written in byte code
-         * depending on what the target variable is.
-         */
-        Object target = null;
-
-        /**
-         * The last variable is set to true when the last ANTLR node of the variable/method chain is visted.  This is
-         * used by the {@link Writer} in conjuction with the storeExpr variable to determine whether or not a store
-         * needs to be written as opposed to a load.
-         */
-        boolean last = false;
-
-        /**
-         * The type variable is set to the type that a visited node ends with.  This is used by both the
-         * {@link Analyzer} and {@link Writer} to make decisions about compound assignments, String constants, and
-         * shortcuts.
-         */
-        Type type = null;
-
-        /**
-         * The promote variable is set to the type of a promotion within a compound assignment.  Compound assignments
-         * may require promotion between the left-hand side variable and right-hand side value.  This is used by the
-         * {@link Writer} to make the correct decision about the byte code operation.
-         */
-        Type promote = null;
-
-        /**
-         * The castFrom variable is set during a compound assignment.  This is used by the {@link Writer} to
-         * cast the values to the promoted type during a compound assignment.
-         */
-        Cast castFrom = null;
-
-        /**
-         * The castTo variable is set during an explicit cast in a variable/method chain or during a compound
-         * assignment.  This is used by the {@link Writer} to either do an explicit cast, or cast the values
-         * from the promoted type back to the original type during a compound assignment.
-         */
-        Cast castTo = null;
-
-        /**
-         * Constructor.
-         * @param parent The top-level ANTLR node for the variable/method chain.
-         * @param source The associated ANTLR node.
-         */
-        private ExtNodeMetadata(final ParserRuleContext parent, final ParserRuleContext source) {
-            this.parent = parent;
-            this.source = source;
-        }
-    }
-
-    /**
-     * Acts as both the Painless API and white-list for what types and methods are allowed.
-     */
-    final Definition definition;
-
-    /**
-     * The original text of the input script.  This is used to write out the source code into
-     * the byte code file for debugging purposes.
-     */
-    final String source;
-
-    /**
-     * Toot node of the ANTLR tree for the Painless script.
-     */
-    final ParserRuleContext root;
-
-    /**
-     * Used to determine certain compile-time constraints such as whether or not numeric overflow is allowed
-     * and how many statements are allowed before a loop will throw an exception.
-     */
-    final CompilerSettings settings;
-
-    /**
-     * Used to determine what slot the input variable is stored in.  This is used in the {@link Writer} whenever
-     * the input variable is accessed.
-     */
-    int paramsValueSlot = -1;
-
-    /**
-     * Used to determine what slot the Scorer variable is stored in.  This is used in the {@link Writer} to load
-     * _score from it, if _score will be accessed by the script.
-     */
-    int scorerValueSlot = -1;
-    
-    /**
-     * Used to determine what slot the _value variable is scored in. 
-     */
-    int aggregationValueSlot = -1;
-
-    /**
-     * Used to determine what slot the loopCounter variable is stored in.  This is used n the {@link Writer} whenever
-     * the loop variable is accessed.
-     */
-    int loopCounterSlot = -1;
-
-    /**
-     * Used to determine what slot the _score variable is stored in.  This is used in the {@link Writer} whenever
-     * the score variable is accessed.
-     */
-    int scoreValueSlot = -1;
-
-    /**
-     * Used to determine if the _score variable is actually used.  This is used to know if we should call
-     * Scorer.score() once and cache into a local variable, and expose NeedsScore interface (to allow query caching)
-     */
-    boolean scoreValueUsed = false;
-    
-    /**
-     * Used to determine what slot the doc variable is stored in.  This is used in the {@link Writer} whenever
-     * the doc variable is accessed.
-     */
-    int docValueSlot = -1;
-    
-    /**
-     * Used to determine what slot the ctx variable is stored in.  This is used in the {@link Writer} whenever
-     * the ctx variable is accessed.
-     */
-    int ctxValueSlot = -1;
-    
-    /**
-     * Used to determine if the ctx variable is actually used.  This is used to determine if we should call
-     * Map.get once and store into a local variable on startup.
-     */
-    boolean ctxValueUsed = false;
-
-    /**
-     * Maps the relevant ANTLR node to its metadata.
-     */
-    private final Map<ParserRuleContext, StatementMetadata> statementMetadata = new HashMap<>();
-
-    /**
-     * Maps the relevant ANTLR node to its metadata.
-     */
-    private final Map<ParserRuleContext, ExpressionMetadata> expressionMetadata = new HashMap<>();
-
-    /**
-     * Maps the relevant ANTLR node to its metadata.
-     */
-    private final Map<ParserRuleContext, ExternalMetadata> externalMetadata = new HashMap<>();
-
-    /**
-     * Maps the relevant ANTLR node to its metadata.
-     */
-    private final Map<ParserRuleContext, ExtNodeMetadata> extNodeMetadata = new HashMap<>();
-
-    /**
-     * Constructor.
-     * @param definition The Painless definition.
-     * @param source The source text for the script.
-     * @param root The root ANTLR node.
-     * @param settings The compile-time settings.
-     */
-    Metadata(final Definition definition, final String source, final ParserRuleContext root, final CompilerSettings settings) {
-        this.definition = definition;
-        this.source = source;
-        this.root = root;
-        this.settings = settings;
-    }
-
-    /**
-     * Creates a new StatementMetadata and stores it in the statementMetadata map.
-     * @param source The ANTLR node for this metadata.
-     * @return The new StatementMetadata.
-     */
-    StatementMetadata createStatementMetadata(final ParserRuleContext source) {
-        final StatementMetadata sourcesmd = new StatementMetadata(source);
-        statementMetadata.put(source, sourcesmd);
-
-        return sourcesmd;
-    }
-
-    /**
-     * Retrieves StatementMetadata from the statementMetadata map.
-     * @param source The ANTLR node for this metadata.
-     * @return The retrieved StatementMetadata.
-     */
-    StatementMetadata getStatementMetadata(final ParserRuleContext source) {
-        final StatementMetadata sourcesmd = statementMetadata.get(source);
-
-        if (sourcesmd == null) {
-            throw new IllegalStateException("Statement metadata does not exist at" +
-                " the parse node with text [" + source.getText() + "].");
-        }
-
-        return sourcesmd;
-    }
-
-    /**
-     * Creates a new ExpressionMetadata and stores it in the expressionMetadata map.
-     * @param source The ANTLR node for this metadata.
-     * @return The new ExpressionMetadata.
-     */
-    ExpressionMetadata createExpressionMetadata(ParserRuleContext source) {
-        final ExpressionMetadata sourceemd = new ExpressionMetadata(source);
-        expressionMetadata.put(source, sourceemd);
-
-        return sourceemd;
-    }
-
-    /**
-     * Retrieves ExpressionMetadata from the expressionMetadata map.
-     * @param source The ANTLR node for this metadata.
-     * @return The retrieved ExpressionMetadata.
-     */
-    ExpressionMetadata getExpressionMetadata(final ParserRuleContext source) {
-        final ExpressionMetadata sourceemd = expressionMetadata.get(source);
-
-        if (sourceemd == null) {
-            throw new IllegalStateException("Expression metadata does not exist at" +
-                " the parse node with text [" + source.getText() + "].");
-        }
-
-        return sourceemd;
-    }
-
-    /**
-     * Creates a new ExternalMetadata and stores it in the externalMetadata map.
-     * @param source The ANTLR node for this metadata.
-     * @return The new ExternalMetadata.
-     */
-    ExternalMetadata createExternalMetadata(final ParserRuleContext source) {
-        final ExternalMetadata sourceemd = new ExternalMetadata(source);
-        externalMetadata.put(source, sourceemd);
-
-        return sourceemd;
-    }
-
-    /**
-     * Retrieves ExternalMetadata from the externalMetadata map.
-     * @param source The ANTLR node for this metadata.
-     * @return The retrieved ExternalMetadata.
-     */
-    ExternalMetadata getExternalMetadata(final ParserRuleContext source) {
-        final ExternalMetadata sourceemd = externalMetadata.get(source);
-
-        if (sourceemd == null) {
-            throw new IllegalStateException("External metadata does not exist at" +
-                " the parse node with text [" + source.getText() + "].");
-        }
-
-        return sourceemd;
-    }
-
-    /**
-     * Creates a new ExtNodeMetadata and stores it in the extNodeMetadata map.
-     * @param source The ANTLR node for this metadata.
-     * @return The new ExtNodeMetadata.
-     */
-    ExtNodeMetadata createExtNodeMetadata(final ParserRuleContext parent, final ParserRuleContext source) {
-        final ExtNodeMetadata sourceemd = new ExtNodeMetadata(parent, source);
-        extNodeMetadata.put(source, sourceemd);
-
-        return sourceemd;
-    }
-
-    /**
-     * Retrieves ExtNodeMetadata from the extNodeMetadata map.
-     * @param source The ANTLR node for this metadata.
-     * @return The retrieved ExtNodeMetadata.
-     */
-    ExtNodeMetadata getExtNodeMetadata(final ParserRuleContext source) {
-        final ExtNodeMetadata sourceemd = extNodeMetadata.get(source);
-
-        if (sourceemd == null) {
-            throw new IllegalStateException("External metadata does not exist at" +
-                " the parse node with text [" + source.getText() + "].");
-        }
-
-        return sourceemd;
-    }
-}

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

@@ -18,6 +18,8 @@
  */
 package org.elasticsearch.painless;
 
-/** Marker interface that a generated {@link Executable} uses the {@code _score} value */
+/**
+ * Marker interface that a generated {@link Executable} uses the {@code _score} value
+ */
 public interface NeedsScore {
 }

+ 61 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/Operation.java

@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * Provides a way to represent operations independently of ASM, to keep ASM
+ * contained to only the writing phase of compilation.  Note there are also
+ * a few extra operations not in ASM that are used internally by the
+ * Painless tree.
+ */
+public enum Operation {
+
+    MUL     ( "+"   ),
+    DIV     ( "/"   ),
+    REM     ( "%"   ),
+    ADD     ( "+"   ),
+    SUB     ( "-"   ),
+    LSH     ( "<<"  ),
+    RSH     ( ">>"  ),
+    USH     ( ">>>" ),
+    BWNOT   ( "~"   ),
+    BWAND   ( "&"   ),
+    XOR     ( "^"   ),
+    BWOR    ( "|"   ),
+    NOT     ( "!"   ),
+    AND     ( "&&"  ),
+    OR      ( "||"  ),
+    LT      ( "<"   ),
+    LTE     ( "<="  ),
+    GT      ( ">"   ),
+    GTE     ( ">="  ),
+    EQ      ( "=="  ),
+    EQR     ( "===" ),
+    NE      ( "!="  ),
+    NER     ( "!==" ),
+    INCR    ( "++"  ),
+    DECR    ( "--"  );
+
+    public final String symbol;
+
+    Operation(final String symbol) {
+        this.symbol = symbol;
+    }
+}

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

@@ -28,6 +28,7 @@ package org.elasticsearch.painless;
  */
 @SuppressWarnings("serial")
 public class PainlessError extends Error {
+
     /**
      * Constructor.
      * @param message The error message.

+ 3 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java

@@ -23,6 +23,9 @@ import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.script.ScriptEngineRegistry;
 import org.elasticsearch.script.ScriptModule;
 
+/**
+ * Registers Painless as a plugin.
+ */
 public final class PainlessPlugin extends Plugin {
 
     @Override

+ 6 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java

@@ -24,6 +24,7 @@ import org.elasticsearch.SpecialPermission;
 import org.elasticsearch.common.component.AbstractComponent;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.painless.Compiler.Loader;
 import org.elasticsearch.script.CompiledScript;
 import org.elasticsearch.script.ExecutableScript;
 import org.elasticsearch.script.LeafSearchScript;
@@ -45,7 +46,7 @@ import java.util.Map;
 /**
  * Implementation of a ScriptEngine for the Painless language.
  */
-public class PainlessScriptEngineService extends AbstractComponent implements ScriptEngineService {
+public final class PainlessScriptEngineService extends AbstractComponent implements ScriptEngineService {
 
     /**
      * Standard name of the Painless language.
@@ -60,7 +61,7 @@ public class PainlessScriptEngineService extends AbstractComponent implements Sc
     /**
      * Standard extension of the Painless language.
      */
-    public static final String EXTENSION = "pain";
+    public static final String EXTENSION = "painless";
 
     /**
      * Standard list of extensions for the Painless language.  (There is only one.)
@@ -166,10 +167,10 @@ public class PainlessScriptEngineService extends AbstractComponent implements Sc
         }
 
         // Create our loader (which loads compiled code with no permissions).
-        final Compiler.Loader loader = AccessController.doPrivileged(new PrivilegedAction<Compiler.Loader>() {
+        final Loader loader = AccessController.doPrivileged(new PrivilegedAction<Loader>() {
             @Override
-            public Compiler.Loader run() {
-                return new Compiler.Loader(getClass().getClassLoader());
+            public Loader run() {
+                return new Loader(getClass().getClassLoader());
             }
         });
 

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptImpl.java

@@ -33,6 +33,7 @@ import java.util.Map;
  * to run a previously compiled Painless script.
  */
 final class ScriptImpl implements ExecutableScript, LeafSearchScript {
+
     /**
      * The Painless Executable script that can be run.
      */
@@ -47,7 +48,7 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
      * The lookup is used to access search field values at run-time.
      */
     private final LeafSearchLookup lookup;
-    
+
     /**
      * the 'doc' object accessed by the script, if available.
      */
@@ -97,7 +98,11 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
     public void setNextVar(final String name, final Object value) {
         variables.put(name, value);
     }
-    
+
+    /**
+     * Set the next aggregation value.
+     * @param value Per-document value, typically a String, Long, or Double.
+     */
     @Override
     public void setNextAggregationValue(Object value) {
         this.aggregationValue = value;

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/Utility.java

@@ -19,7 +19,12 @@
 
 package org.elasticsearch.painless;
 
+/**
+ * A set of methods for non-native boxing and non-native
+ * exact math operations used at both compile-time and runtime.
+ */
 public class Utility {
+
     public static boolean NumberToboolean(final Number value) {
         return value.longValue() != 0;
     }
@@ -818,11 +823,11 @@ public class Utility {
     }
 
     public static boolean checkEquals(final Object left, final Object right) {
-        if (left != null && right != null) {
+        if (left != null) {
             return left.equals(right);
         }
 
-        return left == null && right == null;
+        return right == null || right.equals(null);
     }
 
     private Utility() {}

+ 219 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/Variables.java

@@ -0,0 +1,219 @@
+/*
+ * 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.Type;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+/**
+ * Tracks variables across compilation phases.
+ */
+public final class Variables {
+
+    /**
+     * Tracks reserved variables.  Must be given to any source of input
+     * prior to beginning the analysis phase so that reserved variables
+     * are known ahead of time to assign appropriate slots without
+     * being wasteful.
+     */
+    public static final class Reserved {
+        public static final String THIS   = "#this";
+        public static final String PARAMS = "params";
+        public static final String SCORER = "#scorer";
+        public static final String DOC    = "doc";
+        public static final String VALUE  = "_value";
+        public static final String SCORE  = "_score";
+        public static final String CTX    = "ctx";
+        public static final String LOOP   = "#loop";
+
+        boolean score = false;
+        boolean ctx = false;
+        boolean loop = false;
+
+        public void markReserved(final String name) {
+            if (SCORE.equals(name)) {
+                score = true;
+            } else if (CTX.equals(name)) {
+                ctx = true;
+            }
+        }
+
+        public boolean isReserved(final String name) {
+            return name.equals(THIS) || name.equals(PARAMS) || name.equals(SCORER) || name.equals(DOC) ||
+                name.equals(VALUE) || name.equals(SCORE) || name.equals(CTX) || name.equals(LOOP);
+         }
+
+        public void usesLoop() {
+            loop = true;
+        }
+    }
+
+    public static final class Variable {
+        public final String location;
+        public final String name;
+        public final Type type;
+        public final int slot;
+        public final boolean readonly;
+
+        public boolean read = false;
+
+        private Variable(final String location, final String name, final Type type, final int slot, final boolean readonly) {
+            this.location = location;
+            this.name = name;
+            this.type = type;
+            this.slot = slot;
+            this.readonly = readonly;
+        }
+    }
+
+    private final Definition definition;
+    final Reserved reserved;
+
+    private final Deque<Integer> scopes = new ArrayDeque<>();
+    private final Deque<Variable> variables = new ArrayDeque<>();
+
+    public Variables(final CompilerSettings settings, final Definition definition, final Reserved reserved) {
+        this.definition = definition;
+        this.reserved = reserved;
+
+        incrementScope();
+
+        // Method variables.
+
+        // This reference.  Internal use only.
+        addVariable("[" + Reserved.THIS + "]"  , definition.execType.name, Reserved.THIS  , true, true);
+
+        // Input map of variables passed to the script.  TODO: Rename to 'params' since that will be its use.
+        addVariable("[" + Reserved.PARAMS + "]", definition.smapType.name, Reserved.PARAMS, true, true);
+
+        // Scorer parameter passed to the script.  Internal use only.
+        addVariable("[" + Reserved.SCORER + "]", definition.defType.name , Reserved.SCORER, true, true);
+
+        // Doc parameter passed to the script. TODO: Currently working as a Map<String,Def>, we can do better?
+        addVariable("[" + Reserved.DOC + "]"   , definition.smapType.name, Reserved.DOC   , true, true);
+
+        // Aggregation _value parameter passed to the script.
+        addVariable("[" + Reserved.VALUE + "]" , definition.defType.name , Reserved.VALUE , true, true);
+
+        // Shortcut variables.
+
+        // Document's score as a read-only double.
+        if (reserved.score) {
+            addVariable("[" + Reserved.SCORE + "]", definition.doubleType.name, Reserved.SCORE, true, true);
+        }
+
+        // The ctx map set by executable scripts as a read-only map.
+        if (reserved.ctx) {
+            addVariable("[" + Reserved.CTX + "]", definition.smapType.name, Reserved.CTX, true, true);
+        }
+
+        // Loop counter to catch infinite loops.  Internal use only.
+        if (reserved.loop && settings.getMaxLoopCounter() > 0) {
+            addVariable("[" + Reserved.LOOP + "]", definition.intType.name, Reserved.LOOP, true, true);
+        }
+    }
+
+    public void incrementScope() {
+        scopes.push(0);
+    }
+
+    public void decrementScope() {
+        int remove = scopes.pop();
+
+        while (remove > 0) {
+            final Variable variable = variables.pop();
+
+            if (variable.read) {
+                throw new IllegalArgumentException("Error [" + variable.location + "]: Variable [" + variable.name + "] never used.");
+            }
+
+            --remove;
+        }
+    }
+
+    public Variable getVariable(final String location, final String name) {
+        final Iterator<Variable> itr = variables.iterator();
+
+        while (itr.hasNext()) {
+            final Variable variable = itr.next();
+
+            if (variable.name.equals(name)) {
+                return variable;
+            }
+        }
+
+        if (location != null) {
+            throw new IllegalArgumentException("Error " + location + ": Variable [" + name + "] not defined.");
+        }
+
+        return null;
+    }
+
+    public Variable addVariable(final String location, final String typestr, final String name,
+                                final boolean readonly, final boolean reserved) {
+        if (!reserved && this.reserved.isReserved(name)) {
+            throw new IllegalArgumentException("Error " + location + ": Variable name [" + name + "] is reserved.");
+        }
+
+        if (getVariable(null, name) != null) {
+            throw new IllegalArgumentException("Error " + location + ": Variable name [" + name + "] already defined.");
+        }
+
+        final Type type;
+
+        try {
+            type = definition.getType(typestr);
+        } catch (final IllegalArgumentException exception) {
+            throw new IllegalArgumentException("Error " + location + ": Not a type [" + typestr + "].");
+        }
+
+        boolean legal = !name.contains("<");
+
+        try {
+            definition.getType(name);
+            legal = false;
+        } catch (final IllegalArgumentException exception) {
+            // Do nothing.
+        }
+
+        if (!legal) {
+            throw new IllegalArgumentException("Error " + location + ": Variable name [" + name + "] cannot be a type.");
+        }
+
+        final Variable previous = variables.peekFirst();
+        int slot = 0;
+
+        if (previous != null) {
+            slot = previous.slot + previous.type.type.getSize();
+        }
+
+        final Variable variable = new Variable(location, name, type, slot, readonly);
+        variables.push(variable);
+
+        final int update = scopes.pop() + 1;
+        scopes.push(update);
+
+        return variable;
+    }
+}
+

+ 60 - 443
modules/lang-painless/src/main/java/org/elasticsearch/painless/Writer.java

@@ -19,58 +19,9 @@
 
 package org.elasticsearch.painless;
 
-import org.antlr.v4.runtime.tree.ParseTree;
-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.BoolContext;
-import org.elasticsearch.painless.PainlessParser.BreakContext;
-import org.elasticsearch.painless.PainlessParser.CastContext;
-import org.elasticsearch.painless.PainlessParser.CompContext;
-import org.elasticsearch.painless.PainlessParser.ConditionalContext;
-import org.elasticsearch.painless.PainlessParser.ContinueContext;
-import org.elasticsearch.painless.PainlessParser.DeclContext;
-import org.elasticsearch.painless.PainlessParser.DeclarationContext;
-import org.elasticsearch.painless.PainlessParser.DecltypeContext;
-import org.elasticsearch.painless.PainlessParser.DeclvarContext;
-import org.elasticsearch.painless.PainlessParser.DoContext;
-import org.elasticsearch.painless.PainlessParser.EmptyContext;
-import org.elasticsearch.painless.PainlessParser.EmptyscopeContext;
-import org.elasticsearch.painless.PainlessParser.ExprContext;
-import org.elasticsearch.painless.PainlessParser.ExtbraceContext;
-import org.elasticsearch.painless.PainlessParser.ExtcallContext;
-import org.elasticsearch.painless.PainlessParser.ExtcastContext;
-import org.elasticsearch.painless.PainlessParser.ExtdotContext;
-import org.elasticsearch.painless.PainlessParser.ExternalContext;
-import org.elasticsearch.painless.PainlessParser.ExtfieldContext;
-import org.elasticsearch.painless.PainlessParser.ExtnewContext;
-import org.elasticsearch.painless.PainlessParser.ExtprecContext;
-import org.elasticsearch.painless.PainlessParser.ExtstartContext;
-import org.elasticsearch.painless.PainlessParser.ExtstringContext;
-import org.elasticsearch.painless.PainlessParser.ExtvarContext;
-import org.elasticsearch.painless.PainlessParser.FalseContext;
-import org.elasticsearch.painless.PainlessParser.ForContext;
-import org.elasticsearch.painless.PainlessParser.GenericContext;
-import org.elasticsearch.painless.PainlessParser.IdentifierContext;
-import org.elasticsearch.painless.PainlessParser.IfContext;
-import org.elasticsearch.painless.PainlessParser.IncrementContext;
-import org.elasticsearch.painless.PainlessParser.InitializerContext;
-import org.elasticsearch.painless.PainlessParser.MultipleContext;
-import org.elasticsearch.painless.PainlessParser.NullContext;
-import org.elasticsearch.painless.PainlessParser.NumericContext;
-import org.elasticsearch.painless.PainlessParser.PostincContext;
-import org.elasticsearch.painless.PainlessParser.PrecedenceContext;
-import org.elasticsearch.painless.PainlessParser.PreincContext;
-import org.elasticsearch.painless.PainlessParser.ReturnContext;
-import org.elasticsearch.painless.PainlessParser.SingleContext;
-import org.elasticsearch.painless.PainlessParser.SourceContext;
-import org.elasticsearch.painless.PainlessParser.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.painless.Variables.Reserved;
+import org.elasticsearch.painless.Variables.Variable;
+import org.elasticsearch.painless.node.SSource;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.commons.GeneratorAdapter;
@@ -79,45 +30,44 @@ 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;
 
-class Writer extends PainlessParserBaseVisitor<Void> {
-    static byte[] write(Metadata metadata) {
-        final Writer writer = new Writer(metadata);
+/**
+ * Runs the writing phase of compilation using the Painless AST.
+ */
+final class Writer {
+
+    static byte[] write(final CompilerSettings settings, final Definition definition,
+                               final String source, final Variables variables, final SSource root) {
+        final Writer writer = new Writer(settings, definition, source, variables, root);
 
         return writer.getBytes();
     }
 
-    private final Metadata metadata;
-    private final ParseTree root;
-    private final String source;
     private final CompilerSettings settings;
+    private final Definition definition;
+    private final String source;
+    private final Variables variables;
+    private final SSource root;
 
     private final ClassWriter writer;
-    private final GeneratorAdapter execute;
-
-    private final WriterStatement statement;
-    private final WriterExpression expression;
-    private final WriterExternal external;
+    private final GeneratorAdapter adapter;
 
-    private Writer(final Metadata metadata) {
-        this.metadata = metadata;
-        root = metadata.root;
-        source = metadata.source;
-        settings = metadata.settings;
+    private Writer(final CompilerSettings settings, final Definition definition,
+                     final String source, final Variables variables, final SSource root) {
+        this.settings = settings;
+        this.definition = definition;
+        this.source = source;
+        this.variables = variables;
+        this.root = root;
 
         writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
 
         writeBegin();
         writeConstructor();
 
-        execute = new GeneratorAdapter(Opcodes.ACC_PUBLIC, EXECUTE, null, null, writer);
-
-        final WriterUtility utility = new WriterUtility(metadata, execute);
-        final WriterCaster caster = new WriterCaster(execute);
-
-        statement = new WriterStatement(metadata, execute, this, utility);
-        expression = new WriterExpression(metadata, execute, this, utility, caster);
-        external = new WriterExternal(metadata, execute, this, utility, caster);
+        adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC, EXECUTE, null, null, writer);
 
         writeExecute();
         writeEnd();
@@ -130,12 +80,9 @@ class Writer extends PainlessParserBaseVisitor<Void> {
         final String name = CLASS_TYPE.getInternalName();
 
         // apply marker interface NeedsScore if we use the score!
-        final String interfaces[];
-        if (metadata.scoreValueUsed) {
-            interfaces = new String[] { WriterConstants.NEEDS_SCORE_TYPE.getInternalName() };
-        } else {
-            interfaces = null;
-        }
+        final String interfaces[] = variables.reserved.score ?
+            new String[] { WriterConstants.NEEDS_SCORE_TYPE.getInternalName() } : null;
+
         writer.visit(version, access, name, null, base, interfaces);
         writer.visitSource(source, null);
     }
@@ -150,29 +97,43 @@ class Writer extends PainlessParserBaseVisitor<Void> {
     }
 
     private void writeExecute() {
-        if (metadata.scoreValueUsed) {
+        if (variables.reserved.score) {
             // if the _score value is used, we do this once:
-            //   final double _score = scorer.score();
-            execute.visitVarInsn(Opcodes.ALOAD, metadata.scorerValueSlot);
-            execute.invokeVirtual(WriterConstants.SCORER_TYPE, WriterConstants.SCORER_SCORE);
-            execute.visitInsn(Opcodes.F2D);
-            execute.visitVarInsn(Opcodes.DSTORE, metadata.scoreValueSlot);
+            // final double _score = scorer.score();
+            final Variable scorer = variables.getVariable(null, Reserved.SCORER);
+            final Variable score = variables.getVariable(null, Reserved.SCORE);
+
+            adapter.visitVarInsn(Opcodes.ALOAD, scorer.slot);
+            adapter.invokeVirtual(WriterConstants.SCORER_TYPE, WriterConstants.SCORER_SCORE);
+            adapter.visitInsn(Opcodes.F2D);
+            adapter.visitVarInsn(Opcodes.DSTORE, score.slot);
         }
-        
-        if (metadata.ctxValueUsed) {
+
+        if (variables.reserved.ctx) {
             // if the _ctx value is used, we do this once:
-            //   final Map<String,Object> ctx = input.get("ctx");
-            execute.visitVarInsn(Opcodes.ALOAD, metadata.paramsValueSlot);
-            execute.push("ctx");
-            execute.invokeInterface(WriterConstants.MAP_TYPE, WriterConstants.MAP_GET);
-            execute.visitVarInsn(Opcodes.ASTORE, metadata.ctxValueSlot);
+            // final Map<String,Object> ctx = input.get("ctx");
+
+            final Variable input = variables.getVariable(null, Reserved.PARAMS);
+            final Variable ctx = variables.getVariable(null, Reserved.CTX);
+
+            adapter.visitVarInsn(Opcodes.ALOAD, input.slot);
+            adapter.push(Reserved.CTX);
+            adapter.invokeInterface(MAP_TYPE, MAP_GET);
+            adapter.visitVarInsn(Opcodes.ASTORE, ctx.slot);
         }
 
-        execute.push(settings.getMaxLoopCounter());
-        execute.visitVarInsn(Opcodes.ISTORE, metadata.loopCounterSlot);
+        if (variables.reserved.loop) {
+            // if there is infinite loop protection, we do this once:
+            // int #loop = settings.getMaxLoopCounter()
+
+            final Variable loop = variables.getVariable(null, Reserved.LOOP);
 
-        visit(root);
-        execute.endMethod();
+            adapter.push(settings.getMaxLoopCounter());
+            adapter.visitVarInsn(Opcodes.ISTORE, loop.slot);
+        }
+
+        root.write(settings, definition, adapter);
+        adapter.endMethod();
     }
 
     private void writeEnd() {
@@ -182,348 +143,4 @@ class Writer extends PainlessParserBaseVisitor<Void> {
     private byte[] getBytes() {
         return writer.toByteArray();
     }
-
-    @Override
-    public Void visitSource(final SourceContext ctx) {
-        statement.processSource(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitIf(final IfContext ctx) {
-        statement.processIf(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitWhile(final WhileContext ctx) {
-        statement.processWhile(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDo(final DoContext ctx) {
-        statement.processDo(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitFor(final ForContext ctx) {
-        statement.processFor(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDecl(final DeclContext ctx) {
-        statement.processDecl(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitContinue(final ContinueContext ctx) {
-        statement.processContinue();
-
-        return null;
-    }
-
-    @Override
-    public Void visitBreak(final BreakContext ctx) {
-        statement.processBreak();
-
-        return null;
-    }
-
-    @Override
-    public Void visitReturn(final ReturnContext ctx) {
-        statement.processReturn(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitTry(final TryContext ctx) {
-        statement.processTry(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitThrow(final ThrowContext ctx) {
-        statement.processThrow(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExpr(final ExprContext ctx) {
-        statement.processExpr(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitMultiple(final MultipleContext ctx) {
-        statement.processMultiple(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitSingle(final SingleContext ctx) {
-        statement.processSingle(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitEmpty(final EmptyContext ctx) {
-        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    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(final AfterthoughtContext ctx) {
-        statement.processAfterthought(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDeclaration(DeclarationContext ctx) {
-        statement.processDeclaration(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitDecltype(final DecltypeContext ctx) {
-        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitDeclvar(final DeclvarContext ctx) {
-        statement.processDeclvar(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitTrap(final TrapContext ctx) {
-        statement.processTrap(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitIdentifier(IdentifierContext ctx) {
-        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitGeneric(GenericContext ctx) {
-        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitPrecedence(final PrecedenceContext ctx) {
-        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitNumeric(final NumericContext ctx) {
-        expression.processNumeric(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitTrue(final TrueContext ctx) {
-        expression.processTrue(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitFalse(final FalseContext ctx) {
-        expression.processFalse(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitNull(final NullContext ctx) {
-        expression.processNull(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExternal(final ExternalContext ctx) {
-        expression.processExternal(ctx);
-
-        return null;
-    }
-
-
-    @Override
-    public Void visitPostinc(final PostincContext ctx) {
-        expression.processPostinc(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitPreinc(final PreincContext ctx) {
-        expression.processPreinc(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitUnary(final UnaryContext ctx) {
-        expression.processUnary(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitCast(final CastContext ctx) {
-        expression.processCast(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitBinary(final BinaryContext ctx) {
-        expression.processBinary(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitComp(final CompContext ctx) {
-        expression.processComp(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitBool(final BoolContext ctx) {
-        expression.processBool(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitConditional(final ConditionalContext ctx) {
-        expression.processConditional(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitAssignment(final AssignmentContext ctx) {
-        expression.processAssignment(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtstart(final ExtstartContext ctx) {
-        external.processExtstart(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtprec(final ExtprecContext ctx) {
-        external.processExtprec(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtcast(final ExtcastContext ctx) {
-        external.processExtcast(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtbrace(final ExtbraceContext ctx) {
-        external.processExtbrace(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtdot(final ExtdotContext ctx) {
-        external.processExtdot(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtcall(final ExtcallContext ctx) {
-        external.processExtcall(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtvar(final ExtvarContext ctx) {
-        external.processExtvar(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtfield(final ExtfieldContext ctx) {
-        external.processExtfield(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtnew(final ExtnewContext ctx) {
-        external.processExtnew(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitExtstring(final ExtstringContext ctx) {
-        external.processExtstring(ctx);
-
-        return null;
-    }
-
-    @Override
-    public Void visitArguments(final ArgumentsContext ctx) {
-        throw new UnsupportedOperationException(WriterUtility.error(ctx) + "Unexpected state.");
-    }
-
-    @Override
-    public Void visitIncrement(final IncrementContext ctx) {
-        expression.processIncrement(ctx);
-
-        return null;
-    }
 }

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

@@ -1,86 +0,0 @@
-/*
- * 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);
-        }
-    }
-}

+ 117 - 103
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java

@@ -31,116 +31,130 @@ import java.lang.invoke.MethodHandles;
 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, Scorer.class, LeafDocLookup.class, Object.class);
+/**
+ * General pool of constants used during the writing phase of compilation.
+ */
+public final class WriterConstants {
 
-    final static Type PAINLESS_ERROR_TYPE = Type.getType(PainlessError.class);
+    public final static String BASE_CLASS_NAME = Executable.class.getName();
+    public final static String CLASS_NAME      = BASE_CLASS_NAME + "$CompiledPainlessExecutable";
+    public final static Type BASE_CLASS_TYPE   = Type.getType(Executable.class);
+    public final static Type CLASS_TYPE        = Type.getType("L" + CLASS_NAME.replace(".", "/") + ";");
 
-    final static Type DEFINITION_TYPE = Type.getType(Definition.class);
-    
-    final static Type NEEDS_SCORE_TYPE = Type.getType(NeedsScore.class);
+    public final static Method CONSTRUCTOR = getAsmMethod(void.class, "<init>", String.class, String.class);
+    public final static Method EXECUTE     =
+        getAsmMethod(Object.class, "execute", Map.class, Scorer.class, LeafDocLookup.class, Object.class);
 
-    final static Type OBJECT_TYPE = Type.getType(Object.class);
+    public final static Type PAINLESS_ERROR_TYPE = Type.getType(PainlessError.class);
 
-    final static Type SCORER_TYPE = Type.getType(Scorer.class);
-    final static Method SCORER_SCORE = getAsmMethod(float.class, "score");
+    public final static Type NEEDS_SCORE_TYPE = Type.getType(NeedsScore.class);
+    public final static Type SCORER_TYPE = Type.getType(Scorer.class);
+    public final static Method SCORER_SCORE = getAsmMethod(float.class, "score");
 
-    final static Type MAP_TYPE  = Type.getType(Map.class);
-    final static Method MAP_GET = getAsmMethod(Object.class, "get", Object.class);
+    public final static Type MAP_TYPE  = Type.getType(Map.class);
+    public final static Method MAP_GET = getAsmMethod(Object.class, "get", Object.class);
 
     /** dynamic callsite bootstrap signature */
-    final static MethodType DEF_BOOTSTRAP_TYPE = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, 
-                                                                       String.class, MethodType.class, int.class);
-    final static Handle DEF_BOOTSTRAP_HANDLE = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(DynamicCallSite.class),
-                                                          "bootstrap", WriterConstants.DEF_BOOTSTRAP_TYPE.toMethodDescriptorString());
-
-    final static String DEF_DYNAMIC_LOAD_FIELD_DESC = MethodType.methodType(Object.class, Object.class)
-        .toMethodDescriptorString();
-    final static String DEF_DYNAMIC_STORE_FIELD_DESC = MethodType.methodType(void.class, Object.class, Object.class)
-        .toMethodDescriptorString();
-    final static String DEF_DYNAMIC_ARRAY_LOAD_DESC = MethodType.methodType(Object.class, Object.class, Object.class)
-        .toMethodDescriptorString();
-    final static String DEF_DYNAMIC_ARRAY_STORE_DESC = MethodType.methodType(void.class, Object.class, Object.class, Object.class)
-        .toMethodDescriptorString();
-
-    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);
+    public final static MethodType DEF_BOOTSTRAP_TYPE =
+        MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class);
+    public final static Handle DEF_BOOTSTRAP_HANDLE =
+        new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(DynamicCallSite.class),
+            "bootstrap", DEF_BOOTSTRAP_TYPE.toMethodDescriptorString());
+
+    public final static String DEF_DYNAMIC_LOAD_FIELD_DESC =
+        MethodType.methodType(Object.class, Object.class).toMethodDescriptorString();
+    public final static String DEF_DYNAMIC_STORE_FIELD_DESC =
+        MethodType.methodType(void.class, Object.class, Object.class).toMethodDescriptorString();
+    public final static String DEF_DYNAMIC_ARRAY_LOAD_DESC =
+        MethodType.methodType(Object.class, Object.class, Object.class).toMethodDescriptorString();
+    public final static String DEF_DYNAMIC_ARRAY_STORE_DESC =
+        MethodType.methodType(void.class, Object.class, Object.class, Object.class).toMethodDescriptorString();
+
+    public final static Method DEF_NOT_CALL = getAsmMethod(Object.class, "not", Object.class);
+    public final static Method DEF_NEG_CALL = getAsmMethod(Object.class, "neg", Object.class);
+    public final static Method DEF_MUL_CALL = getAsmMethod(Object.class, "mul", Object.class, Object.class);
+    public final static Method DEF_DIV_CALL = getAsmMethod(Object.class, "div", Object.class, Object.class);
+    public final static Method DEF_REM_CALL = getAsmMethod(Object.class, "rem", Object.class, Object.class);
+    public final static Method DEF_ADD_CALL = getAsmMethod(Object.class, "add", Object.class, Object.class);
+    public final static Method DEF_SUB_CALL = getAsmMethod(Object.class, "sub", Object.class, Object.class);
+    public final static Method DEF_LSH_CALL = getAsmMethod(Object.class, "lsh", Object.class, int.class);
+    public final static Method DEF_RSH_CALL = getAsmMethod(Object.class, "rsh", Object.class, int.class);
+    public final static Method DEF_USH_CALL = getAsmMethod(Object.class, "ush", Object.class, int.class);
+    public final static Method DEF_AND_CALL = getAsmMethod(Object.class, "and", Object.class, Object.class);
+    public final static Method DEF_XOR_CALL = getAsmMethod(Object.class, "xor", Object.class, Object.class);
+    public final static Method DEF_OR_CALL  = getAsmMethod(Object.class, "or" , Object.class, Object.class);
+    public final static Method DEF_EQ_CALL  = getAsmMethod(boolean.class, "eq" , Object.class, Object.class);
+    public final static Method DEF_LT_CALL  = getAsmMethod(boolean.class, "lt" , Object.class, Object.class);
+    public final static Method DEF_LTE_CALL = getAsmMethod(boolean.class, "lte", Object.class, Object.class);
+    public final static Method DEF_GT_CALL  = getAsmMethod(boolean.class, "gt" , Object.class, Object.class);
+    public final static Method DEF_GTE_CALL = getAsmMethod(boolean.class, "gte", Object.class, Object.class);
+
+    public final static Type STRINGBUILDER_TYPE = Type.getType(StringBuilder.class);
+
+    public final static Method STRINGBUILDER_CONSTRUCTOR    = getAsmMethod(void.class, "<init>");
+    public final static Method STRINGBUILDER_APPEND_BOOLEAN = getAsmMethod(StringBuilder.class, "append", boolean.class);
+    public final static Method STRINGBUILDER_APPEND_CHAR    = getAsmMethod(StringBuilder.class, "append", char.class);
+    public final static Method STRINGBUILDER_APPEND_INT     = getAsmMethod(StringBuilder.class, "append", int.class);
+    public final static Method STRINGBUILDER_APPEND_LONG    = getAsmMethod(StringBuilder.class, "append", long.class);
+    public final static Method STRINGBUILDER_APPEND_FLOAT   = getAsmMethod(StringBuilder.class, "append", float.class);
+    public final static Method STRINGBUILDER_APPEND_DOUBLE  = getAsmMethod(StringBuilder.class, "append", double.class);
+    public final static Method STRINGBUILDER_APPEND_STRING  = getAsmMethod(StringBuilder.class, "append", String.class);
+    public final static Method STRINGBUILDER_APPEND_OBJECT  = getAsmMethod(StringBuilder.class, "append", Object.class);
+    public final static Method STRINGBUILDER_TOSTRING       = getAsmMethod(String.class, "toString");
+
+    public final static Method TOINTEXACT_LONG  = getAsmMethod(int.class,  "toIntExact",    long.class);
+    public final static Method NEGATEEXACT_INT  = getAsmMethod(int.class,  "negateExact",   int.class);
+    public final static Method NEGATEEXACT_LONG = getAsmMethod(long.class, "negateExact",   long.class);
+    public final static Method MULEXACT_INT     = getAsmMethod(int.class,  "multiplyExact", int.class,  int.class);
+    public final static Method MULEXACT_LONG    = getAsmMethod(long.class, "multiplyExact", long.class, long.class);
+    public final static Method ADDEXACT_INT     = getAsmMethod(int.class,  "addExact",      int.class,  int.class);
+    public final static Method ADDEXACT_LONG    = getAsmMethod(long.class, "addExact",      long.class, long.class);
+    public final static Method SUBEXACT_INT     = getAsmMethod(int.class,  "subtractExact", int.class,  int.class);
+    public final static Method SUBEXACT_LONG    = getAsmMethod(long.class, "subtractExact", long.class, long.class);
+
+    public final static Method CHECKEQUALS              =
+        getAsmMethod(boolean.class, "checkEquals",              Object.class, Object.class);
+    public final static Method TOBYTEEXACT_INT          = getAsmMethod(byte.class,    "toByteExact",              int.class);
+    public final static Method TOBYTEEXACT_LONG         = getAsmMethod(byte.class,    "toByteExact",              long.class);
+    public final static Method TOBYTEWOOVERFLOW_FLOAT   = getAsmMethod(byte.class,    "toByteWithoutOverflow",    float.class);
+    public final static Method TOBYTEWOOVERFLOW_DOUBLE  = getAsmMethod(byte.class,    "toByteWithoutOverflow",    double.class);
+    public final static Method TOSHORTEXACT_INT         = getAsmMethod(short.class,   "toShortExact",             int.class);
+    public final static Method TOSHORTEXACT_LONG        = getAsmMethod(short.class,   "toShortExact",             long.class);
+    public final static Method TOSHORTWOOVERFLOW_FLOAT  = getAsmMethod(short.class,   "toShortWithoutOverflow",   float.class);
+    public final static Method TOSHORTWOOVERFLOW_DOUBLE = getAsmMethod(short.class,   "toShortWihtoutOverflow",   double.class);
+    public final static Method TOCHAREXACT_INT          = getAsmMethod(char.class,    "toCharExact",              int.class);
+    public final static Method TOCHAREXACT_LONG         = getAsmMethod(char.class,    "toCharExact",              long.class);
+    public final static Method TOCHARWOOVERFLOW_FLOAT   = getAsmMethod(char.class,    "toCharWithoutOverflow",    float.class);
+    public final static Method TOCHARWOOVERFLOW_DOUBLE  = getAsmMethod(char.class,    "toCharWithoutOverflow",    double.class);
+    public final static Method TOINTWOOVERFLOW_FLOAT    = getAsmMethod(int.class,     "toIntWithoutOverflow",     float.class);
+    public final static Method TOINTWOOVERFLOW_DOUBLE   = getAsmMethod(int.class,     "toIntWithoutOverflow",     double.class);
+    public final static Method TOLONGWOOVERFLOW_FLOAT   = getAsmMethod(long.class,    "toLongWithoutOverflow",    float.class);
+    public final static Method TOLONGWOOVERFLOW_DOUBLE  = getAsmMethod(long.class,    "toLongWithoutOverflow",    double.class);
+    public final static Method TOFLOATWOOVERFLOW_DOUBLE = getAsmMethod(float.class ,  "toFloatWihtoutOverflow",   double.class);
+    public final static Method MULWOOVERLOW_FLOAT       =
+        getAsmMethod(float.class,   "multiplyWithoutOverflow",  float.class,  float.class);
+    public final static Method MULWOOVERLOW_DOUBLE      =
+        getAsmMethod(double.class,  "multiplyWithoutOverflow",  double.class, double.class);
+    public final static Method DIVWOOVERLOW_INT         =
+        getAsmMethod(int.class,     "divideWithoutOverflow",    int.class,    int.class);
+    public final static Method DIVWOOVERLOW_LONG        =
+        getAsmMethod(long.class,    "divideWithoutOverflow",    long.class,   long.class);
+    public final static Method DIVWOOVERLOW_FLOAT       =
+        getAsmMethod(float.class,   "divideWithoutOverflow",    float.class,  float.class);
+    public final static Method DIVWOOVERLOW_DOUBLE      =
+        getAsmMethod(double.class,  "divideWithoutOverflow",    double.class, double.class);
+    public final static Method REMWOOVERLOW_FLOAT       =
+        getAsmMethod(float.class,   "remainderWithoutOverflow", float.class,  float.class);
+    public final static Method REMWOOVERLOW_DOUBLE      =
+        getAsmMethod(double.class,  "remainderWithoutOverflow", double.class, double.class);
+    public final static Method ADDWOOVERLOW_FLOAT       =
+        getAsmMethod(float.class,   "addWithoutOverflow",       float.class,  float.class);
+    public final static Method ADDWOOVERLOW_DOUBLE      =
+        getAsmMethod(double.class,  "addWithoutOverflow",       double.class, double.class);
+    public final static Method SUBWOOVERLOW_FLOAT       =
+        getAsmMethod(float.class,   "subtractWithoutOverflow",  float.class,  float.class);
+    public 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());

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

@@ -1,669 +0,0 @@
-/*
- * 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.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 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);
-    }
-}

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

@@ -1,720 +0,0 @@
-/*
- * 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.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.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.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 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 (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 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 (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 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 (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 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();
-
-        if (dotctx != null) {
-            writer.visit(dotctx);
-        }
-    }
-
-    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);
-
-        if (sourceenmd.target == null) {
-            return;
-        }
-
-        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 (maplist && constant != null) {
-                    utility.writeConstant(source, constant);
-                }
-
-                if (field || name || (shortcut && !maplist)) {
-                    execute.dupX1();
-                } else if (array || maplist) {
-                    execute.dup2X1();
-                }
-
-                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 (maplist && constant != null) {
-                    utility.writeConstant(source, constant);
-                }
-
-                if (field || name || (shortcut && !maplist)) {
-                    execute.dup();
-                } else if (array || maplist) {
-                    execute.dup2();
-                }
-
-                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, 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) {
-            execute.visitInvokeDynamicInsn(name, WriterConstants.DEF_DYNAMIC_STORE_FIELD_DESC,
-                                           WriterConstants.DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.STORE });
-        } else {
-            execute.visitInvokeDynamicInsn(name, WriterConstants.DEF_DYNAMIC_LOAD_FIELD_DESC,
-                                           WriterConstants.DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.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) {
-            if (store) {
-                execute.visitInvokeDynamicInsn("arrayStore", WriterConstants.DEF_DYNAMIC_ARRAY_STORE_DESC,
-                                           WriterConstants.DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.ARRAY_STORE });
-            } else {
-                execute.visitInvokeDynamicInsn("arrayLoad", WriterConstants.DEF_DYNAMIC_ARRAY_LOAD_DESC,
-                                           WriterConstants.DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.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 {
-            writeDynamicCallExternal(source);
-        }
-    }
-
-    private void writeDynamicCallExternal(final ExtcallContext source) {
-        final ExtNodeMetadata sourceenmd = metadata.getExtNodeMetadata(source);
-        final List<ExpressionContext> arguments = source.arguments().expression();
-
-        StringBuilder signature = new StringBuilder();
-        signature.append('(');
-        // first parameter is the receiver, we never know its type: always Object
-        signature.append(WriterConstants.OBJECT_TYPE.getDescriptor());
-
-        for (int i = 0; i < arguments.size(); i++) {
-            ExpressionMetadata arg = metadata.getExpressionMetadata(arguments.get(i));
-            // disable any implicit casts/conversion for arguments, let invokeDynamic take care
-            arg.to = arg.from;
-            arg.cast = new Cast(arg.from, arg.from);
-            signature.append(arg.from.type.getDescriptor());
-            writer.visit(arguments.get(i));
-        }
-        signature.append(')');
-        // return value: currently always Object. making this better may be tricky...
-        signature.append(WriterConstants.OBJECT_TYPE.getDescriptor());
-        execute.visitInvokeDynamicInsn((String)sourceenmd.target, signature.toString(),
-                                       WriterConstants.DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.METHOD_CALL });
-    }
-}

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

@@ -1,390 +0,0 @@
-/*
- * 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.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;
-        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);
-    }
-}

+ 302 - 253
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterUtility.java

@@ -19,31 +19,14 @@
 
 package org.elasticsearch.painless;
 
-import org.antlr.v4.runtime.ParserRuleContext;
+import org.elasticsearch.painless.Definition.Cast;
 import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Transform;
 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;
@@ -67,6 +50,7 @@ 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.PAINLESS_ERROR_TYPE;
 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;
@@ -84,304 +68,369 @@ 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);
+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;
+
+/**
+ * Set of methods used during the writing phase of compilation
+ * shared by the nodes of the Painless tree.
+ */
+public final class WriterUtility {
+
+    public static void writeLoopCounter(final GeneratorAdapter adapter, final int slot, final int count) {
+        if (slot > -1) {
+            final Label end = new Label();
+
+            adapter.iinc(slot, -count);
+            adapter.visitVarInsn(Opcodes.ILOAD, slot);
+            adapter.push(0);
+            adapter.ifICmp(GeneratorAdapter.GT, end);
+            adapter.throwException(PAINLESS_ERROR_TYPE,
+                "The maximum number of statements that can be executed in a loop has been reached.");
+            adapter.mark(end);
         }
     }
 
-    Branch getBranch(final ParserRuleContext source) {
-        return branches.get(source);
-    }
-
-    void checkWriteBranch(final ParserRuleContext source) {
-        final Branch branch = getBranch(source);
+    public static void writeCast(final GeneratorAdapter adapter, final Cast cast) {
+        if (cast instanceof Transform) {
+            final Transform transform = (Transform)cast;
 
-        if (branch != null) {
-            if (branch.tru != null) {
-                execute.visitJumpInsn(Opcodes.IFNE, branch.tru);
-            } else if (branch.fals != null) {
-                execute.visitJumpInsn(Opcodes.IFEQ, branch.fals);
+            if (transform.upcast != null) {
+                adapter.checkCast(transform.upcast.type);
             }
-        }
-    }
-
-    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();
+            if (java.lang.reflect.Modifier.isStatic(transform.method.reflect.getModifiers())) {
+                adapter.invokeStatic(transform.method.owner.type, transform.method.method);
+            } else if (java.lang.reflect.Modifier.isInterface(transform.method.owner.clazz.getModifiers())) {
+                adapter.invokeInterface(transform.method.owner.type, transform.method.method);
             } else {
-                execute.dup2();
+                adapter.invokeVirtual(transform.method.owner.type, transform.method.method);
             }
-        }
-    }
-
-    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.");
-        }
-    }
+            if (transform.downcast != null) {
+                adapter.checkCast(transform.downcast.type);
+            }
+        } else if (cast != null) {
+            final Type from = cast.from;
+            final Type to = cast.to;
 
-    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.");
-        }
-    }
+            if (from.equals(to)) {
+                return;
+            }
 
-    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.");
+            if (from.sort.numeric && from.sort.primitive && to.sort.numeric && to.sort.primitive) {
+                adapter.cast(from.type, to.type);
+            } else {
+                try {
+                    from.clazz.asSubclass(to.clazz);
+                } catch (ClassCastException exception) {
+                    adapter.checkCast(to.type);
+                }
+            }
         }
     }
 
-    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.");
+    public static void writeBranch(final GeneratorAdapter adapter, final Label tru, final Label fals) {
+        if (tru != null) {
+            adapter.visitJumpInsn(Opcodes.IFNE, tru);
+        } else if (fals != null) {
+            adapter.visitJumpInsn(Opcodes.IFEQ, fals);
         }
     }
 
-    void writeNewStrings() {
-        execute.newInstance(STRINGBUILDER_TYPE);
-        execute.dup();
-        execute.invokeConstructor(STRINGBUILDER_TYPE, STRINGBUILDER_CONSTRUCTOR);
+    public static void writeNewStrings(final GeneratorAdapter adapter) {
+        adapter.newInstance(STRINGBUILDER_TYPE);
+        adapter.dup();
+        adapter.invokeConstructor(STRINGBUILDER_TYPE, STRINGBUILDER_CONSTRUCTOR);
     }
 
-    void writeAppendStrings(final Sort sort) {
+    public static void writeAppendStrings(final GeneratorAdapter adapter, 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 BOOL:   adapter.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_BOOLEAN); break;
+            case CHAR:   adapter.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);
+            case INT:    adapter.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_INT);     break;
+            case LONG:   adapter.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_LONG);    break;
+            case FLOAT:  adapter.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_FLOAT);   break;
+            case DOUBLE: adapter.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_DOUBLE);  break;
+            case STRING: adapter.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_STRING);  break;
+            default:     adapter.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_OBJECT);
         }
     }
 
-    void writeToStrings() {
-        execute.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_TOSTRING);
+    public static void writeToStrings(final GeneratorAdapter adapter) {
+        adapter.invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_TOSTRING);
     }
 
-    void writeBinaryInstruction(final ParserRuleContext source, final Type type, final int token) {
+    public static void writeBinaryInstruction(final CompilerSettings settings, final Definition definition,
+                                              final GeneratorAdapter adapter, final String location,
+                                              final Type type, final Operation operation) {
         final Sort sort = type.sort;
-        final boolean exact = !settings.getNumericOverflow() &&
+        boolean exact = !settings.getNumericOverflow() &&
             ((sort == Sort.INT || sort == Sort.LONG) &&
-                (token == MUL || token == DIV || token == ADD || token == SUB) ||
+                (operation == Operation.MUL || operation == Operation.DIV ||
+                    operation == Operation.ADD || operation == Operation.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);
-        }
+                    (operation == Operation.MUL || operation == Operation.DIV || operation == Operation.REM ||
+                        operation == Operation.ADD || operation == Operation.SUB));
 
         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.");
+                    switch (operation) {
+                        case MUL: adapter.invokeStatic(definition.mathType.type,    MULEXACT_INT);     break;
+                        case DIV: adapter.invokeStatic(definition.utilityType.type, DIVWOOVERLOW_INT); break;
+                        case ADD: adapter.invokeStatic(definition.mathType.type,    ADDEXACT_INT);     break;
+                        case SUB: adapter.invokeStatic(definition.mathType.type,    SUBEXACT_INT);     break;
                     }
 
                     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.");
+                    switch (operation) {
+                        case MUL: adapter.invokeStatic(definition.mathType.type,    MULEXACT_LONG);     break;
+                        case DIV: adapter.invokeStatic(definition.utilityType.type, DIVWOOVERLOW_LONG); break;
+                        case ADD: adapter.invokeStatic(definition.mathType.type,    ADDEXACT_LONG);     break;
+                        case SUB: adapter.invokeStatic(definition.mathType.type,    SUBEXACT_LONG);     break;
                     }
 
                     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;
+                    switch (operation) {
+                        case MUL: adapter.invokeStatic(definition.utilityType.type, MULWOOVERLOW_FLOAT); break;
+                        case DIV: adapter.invokeStatic(definition.utilityType.type, DIVWOOVERLOW_FLOAT); break;
+                        case REM: adapter.invokeStatic(definition.utilityType.type, REMWOOVERLOW_FLOAT); break;
+                        case ADD: adapter.invokeStatic(definition.utilityType.type, ADDWOOVERLOW_FLOAT); break;
+                        case SUB: adapter.invokeStatic(definition.utilityType.type, SUBWOOVERLOW_FLOAT); break;
                         default:
-                            throw new IllegalStateException(WriterUtility.error(source) + "Unexpected state.");
+                            throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
                     }
 
                     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;
+                    switch (operation) {
+                        case MUL: adapter.invokeStatic(definition.utilityType.type, MULWOOVERLOW_DOUBLE); break;
+                        case DIV: adapter.invokeStatic(definition.utilityType.type, DIVWOOVERLOW_DOUBLE); break;
+                        case REM: adapter.invokeStatic(definition.utilityType.type, REMWOOVERLOW_DOUBLE); break;
+                        case ADD: adapter.invokeStatic(definition.utilityType.type, ADDWOOVERLOW_DOUBLE); break;
+                        case SUB: adapter.invokeStatic(definition.utilityType.type, SUBWOOVERLOW_DOUBLE); break;
                         default:
-                            throw new IllegalStateException(WriterUtility.error(source) + "Unexpected state.");
+                            throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
                     }
 
                     break;
                 default:
-                    throw new IllegalStateException(WriterUtility.error(source) + "Unexpected state.");
+                    throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
             }
         } 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.");
+                (operation == Operation.LSH || operation == Operation.USH ||
+                    operation == Operation.RSH || operation == Operation.BWAND ||
+                    operation == Operation.XOR || operation == Operation.BWOR)) {
+                throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
             }
 
             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;
+                switch (operation) {
+                    case MUL:   adapter.invokeStatic(definition.defobjType.type, DEF_MUL_CALL); break;
+                    case DIV:   adapter.invokeStatic(definition.defobjType.type, DEF_DIV_CALL); break;
+                    case REM:   adapter.invokeStatic(definition.defobjType.type, DEF_REM_CALL); break;
+                    case ADD:   adapter.invokeStatic(definition.defobjType.type, DEF_ADD_CALL); break;
+                    case SUB:   adapter.invokeStatic(definition.defobjType.type, DEF_SUB_CALL); break;
+                    case LSH:   adapter.invokeStatic(definition.defobjType.type, DEF_LSH_CALL); break;
+                    case USH:   adapter.invokeStatic(definition.defobjType.type, DEF_RSH_CALL); break;
+                    case RSH:   adapter.invokeStatic(definition.defobjType.type, DEF_USH_CALL); break;
+                    case BWAND: adapter.invokeStatic(definition.defobjType.type, DEF_AND_CALL); break;
+                    case XOR:   adapter.invokeStatic(definition.defobjType.type, DEF_XOR_CALL); break;
+                    case BWOR:  adapter.invokeStatic(definition.defobjType.type, DEF_OR_CALL);  break;
                     default:
-                        throw new IllegalStateException(WriterUtility.error(source) + "Unexpected state.");
+                        throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
                 }
             } 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;
+                switch (operation) {
+                    case MUL:   adapter.math(GeneratorAdapter.MUL,  type.type); break;
+                    case DIV:   adapter.math(GeneratorAdapter.DIV,  type.type); break;
+                    case REM:   adapter.math(GeneratorAdapter.REM,  type.type); break;
+                    case ADD:   adapter.math(GeneratorAdapter.ADD,  type.type); break;
+                    case SUB:   adapter.math(GeneratorAdapter.SUB,  type.type); break;
+                    case LSH:   adapter.math(GeneratorAdapter.SHL,  type.type); break;
+                    case USH:   adapter.math(GeneratorAdapter.USHR, type.type); break;
+                    case RSH:   adapter.math(GeneratorAdapter.SHR,  type.type); break;
+                    case BWAND: adapter.math(GeneratorAdapter.AND,  type.type); break;
+                    case XOR:   adapter.math(GeneratorAdapter.XOR,  type.type); break;
+                    case BWOR:  adapter.math(GeneratorAdapter.OR,   type.type); break;
                     default:
-                        throw new IllegalStateException(WriterUtility.error(source) + "Unexpected state.");
+                        throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
                 }
             }
         }
     }
+
+    /**
+     * 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.
+     */
+    public static boolean writeExactInstruction(
+        final Definition definition, final GeneratorAdapter adapter, final Sort fsort, final Sort tsort) {
+        if (fsort == Sort.DOUBLE) {
+            if (tsort == Sort.FLOAT) {
+                adapter.invokeStatic(definition.utilityType.type, TOFLOATWOOVERFLOW_DOUBLE);
+            } else if (tsort == Sort.FLOAT_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOFLOATWOOVERFLOW_DOUBLE);
+                adapter.checkCast(definition.floatobjType.type);
+            } else if (tsort == Sort.LONG) {
+                adapter.invokeStatic(definition.utilityType.type, TOLONGWOOVERFLOW_DOUBLE);
+            } else if (tsort == Sort.LONG_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOLONGWOOVERFLOW_DOUBLE);
+                adapter.checkCast(definition.longobjType.type);
+            } else if (tsort == Sort.INT) {
+                adapter.invokeStatic(definition.utilityType.type, TOINTWOOVERFLOW_DOUBLE);
+            } else if (tsort == Sort.INT_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOINTWOOVERFLOW_DOUBLE);
+                adapter.checkCast(definition.intobjType.type);
+            } else if (tsort == Sort.CHAR) {
+                adapter.invokeStatic(definition.utilityType.type, TOCHARWOOVERFLOW_DOUBLE);
+            } else if (tsort == Sort.CHAR_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOCHARWOOVERFLOW_DOUBLE);
+                adapter.checkCast(definition.charobjType.type);
+            } else if (tsort == Sort.SHORT) {
+                adapter.invokeStatic(definition.utilityType.type, TOSHORTWOOVERFLOW_DOUBLE);
+            } else if (tsort == Sort.SHORT_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOSHORTWOOVERFLOW_DOUBLE);
+                adapter.checkCast(definition.shortobjType.type);
+            } else if (tsort == Sort.BYTE) {
+                adapter.invokeStatic(definition.utilityType.type, TOBYTEWOOVERFLOW_DOUBLE);
+            } else if (tsort == Sort.BYTE_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOBYTEWOOVERFLOW_DOUBLE);
+                adapter.checkCast(definition.byteobjType.type);
+            } else {
+                return false;
+            }
+        } else if (fsort == Sort.FLOAT) {
+            if (tsort == Sort.LONG) {
+                adapter.invokeStatic(definition.utilityType.type, TOLONGWOOVERFLOW_FLOAT);
+            } else if (tsort == Sort.LONG_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOLONGWOOVERFLOW_FLOAT);
+                adapter.checkCast(definition.longobjType.type);
+            } else if (tsort == Sort.INT) {
+                adapter.invokeStatic(definition.utilityType.type, TOINTWOOVERFLOW_FLOAT);
+            } else if (tsort == Sort.INT_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOINTWOOVERFLOW_FLOAT);
+                adapter.checkCast(definition.intobjType.type);
+            } else if (tsort == Sort.CHAR) {
+                adapter.invokeStatic(definition.utilityType.type, TOCHARWOOVERFLOW_FLOAT);
+            } else if (tsort == Sort.CHAR_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOCHARWOOVERFLOW_FLOAT);
+                adapter.checkCast(definition.charobjType.type);
+            } else if (tsort == Sort.SHORT) {
+                adapter.invokeStatic(definition.utilityType.type, TOSHORTWOOVERFLOW_FLOAT);
+            } else if (tsort == Sort.SHORT_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOSHORTWOOVERFLOW_FLOAT);
+                adapter.checkCast(definition.shortobjType.type);
+            } else if (tsort == Sort.BYTE) {
+                adapter.invokeStatic(definition.utilityType.type, TOBYTEWOOVERFLOW_FLOAT);
+            } else if (tsort == Sort.BYTE_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOBYTEWOOVERFLOW_FLOAT);
+                adapter.checkCast(definition.byteobjType.type);
+            } else {
+                return false;
+            }
+        } else if (fsort == Sort.LONG) {
+            if (tsort == Sort.INT) {
+                adapter.invokeStatic(definition.mathType.type, TOINTEXACT_LONG);
+            } else if (tsort == Sort.INT_OBJ) {
+                adapter.invokeStatic(definition.mathType.type, TOINTEXACT_LONG);
+                adapter.checkCast(definition.intobjType.type);
+            } else if (tsort == Sort.CHAR) {
+                adapter.invokeStatic(definition.utilityType.type, TOCHAREXACT_LONG);
+            } else if (tsort == Sort.CHAR_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOCHAREXACT_LONG);
+                adapter.checkCast(definition.charobjType.type);
+            } else if (tsort == Sort.SHORT) {
+                adapter.invokeStatic(definition.utilityType.type, TOSHORTEXACT_LONG);
+            } else if (tsort == Sort.SHORT_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOSHORTEXACT_LONG);
+                adapter.checkCast(definition.shortobjType.type);
+            } else if (tsort == Sort.BYTE) {
+                adapter.invokeStatic(definition.utilityType.type, TOBYTEEXACT_LONG);
+            } else if (tsort == Sort.BYTE_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOBYTEEXACT_LONG);
+                adapter.checkCast(definition.byteobjType.type);
+            } else {
+                return false;
+            }
+        } else if (fsort == Sort.INT) {
+            if (tsort == Sort.CHAR) {
+                adapter.invokeStatic(definition.utilityType.type, TOCHAREXACT_INT);
+            } else if (tsort == Sort.CHAR_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOCHAREXACT_INT);
+                adapter.checkCast(definition.charobjType.type);
+            } else if (tsort == Sort.SHORT) {
+                adapter.invokeStatic(definition.utilityType.type, TOSHORTEXACT_INT);
+            } else if (tsort == Sort.SHORT_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOSHORTEXACT_INT);
+                adapter.checkCast(definition.shortobjType.type);
+            } else if (tsort == Sort.BYTE) {
+                adapter.invokeStatic(definition.utilityType.type, TOBYTEEXACT_INT);
+            } else if (tsort == Sort.BYTE_OBJ) {
+                adapter.invokeStatic(definition.utilityType.type, TOBYTEEXACT_INT);
+                adapter.checkCast(definition.byteobjType.type);
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    public static void writeDup(final GeneratorAdapter adapter, final int size, final int xsize) {
+        if (size == 1) {
+            if (xsize == 2) {
+                adapter.dupX2();
+            } else if (xsize == 1) {
+                adapter.dupX1();
+            } else {
+                adapter.dup();
+            }
+        } else if (size == 2) {
+            if (xsize == 2) {
+                adapter.dup2X2();
+            } else if (xsize == 1) {
+                adapter.dup2X1();
+            } else {
+                adapter.dup2();
+            }
+        }
+    }
+
+    public static void writePop(final GeneratorAdapter adapter, final int size) {
+        if (size == 1) {
+            adapter.pop();
+        } else if (size == 2) {
+            adapter.pop2();
+        }
+    }
+
+    private WriterUtility() {}
 }

+ 13 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/ErrorHandlingLexer.java → modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/ErrorHandlingLexer.java

@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.elasticsearch.painless;
+package org.elasticsearch.painless.antlr;
 
 import org.antlr.v4.runtime.CharStream;
 import org.antlr.v4.runtime.LexerNoViableAltException;
@@ -25,21 +25,26 @@ import org.antlr.v4.runtime.misc.Interval;
 
 import java.text.ParseException;
 
-class ErrorHandlingLexer extends PainlessLexer {
-    public ErrorHandlingLexer(CharStream charStream) {
+/**
+ * A lexer that will override the default error behavior to fail on the first error.
+ */
+final class ErrorHandlingLexer extends PainlessLexer {
+
+    ErrorHandlingLexer(final CharStream charStream) {
         super(charStream);
     }
 
     @Override
-    public void recover(LexerNoViableAltException lnvae) {
-        CharStream charStream = lnvae.getInputStream();
-        int startIndex = lnvae.getStartIndex();
-        String text = charStream.getText(Interval.of(startIndex, charStream.index()));
+    public void recover(final LexerNoViableAltException lnvae) {
+        final CharStream charStream = lnvae.getInputStream();
+        final int startIndex = lnvae.getStartIndex();
+        final String text = charStream.getText(Interval.of(startIndex, charStream.index()));
 
-        ParseException parseException = new ParseException("Error [" + _tokenStartLine + ":" +
+        final ParseException parseException = new ParseException("Error [" + _tokenStartLine + ":" +
                 _tokenStartCharPositionInLine + "]: unexpected character [" +
                 getErrorDisplay(text) + "].",  _tokenStartCharIndex);
         parseException.initCause(lnvae);
+
         throw new RuntimeException(parseException);
     }
 }

+ 12 - 12
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessLexer.java → modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessLexer.java

@@ -1,5 +1,5 @@
 // ANTLR GENERATED CODE: DO NOT EDIT
-package org.elasticsearch.painless;
+package org.elasticsearch.painless.antlr;
 import org.antlr.v4.runtime.Lexer;
 import org.antlr.v4.runtime.CharStream;
 import org.antlr.v4.runtime.Token;
@@ -21,7 +21,7 @@ class PainlessLexer extends Lexer {
     COMMA=10, SEMICOLON=11, IF=12, ELSE=13, WHILE=14, DO=15, FOR=16, CONTINUE=17, 
     BREAK=18, RETURN=19, NEW=20, TRY=21, CATCH=22, THROW=23, BOOLNOT=24, BWNOT=25, 
     MUL=26, DIV=27, REM=28, ADD=29, SUB=30, LSH=31, RSH=32, USH=33, LT=34, 
-    LTE=35, GT=36, GTE=37, EQ=38, EQR=39, NE=40, NER=41, BWAND=42, BWXOR=43, 
+    LTE=35, GT=36, GTE=37, EQ=38, EQR=39, NE=40, NER=41, BWAND=42, XOR=43, 
     BWOR=44, BOOLAND=45, BOOLOR=46, COND=47, COLON=48, INCR=49, DECR=50, ASSIGN=51, 
     AADD=52, ASUB=53, AMUL=54, ADIV=55, AREM=56, AAND=57, AXOR=58, AOR=59, 
     ALSH=60, ARSH=61, AUSH=62, OCTAL=63, HEX=64, INTEGER=65, DECIMAL=66, STRING=67, 
@@ -36,11 +36,11 @@ class PainlessLexer extends Lexer {
     "COMMA", "SEMICOLON", "IF", "ELSE", "WHILE", "DO", "FOR", "CONTINUE", 
     "BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", "BOOLNOT", "BWNOT", 
     "MUL", "DIV", "REM", "ADD", "SUB", "LSH", "RSH", "USH", "LT", "LTE", "GT", 
-    "GTE", "EQ", "EQR", "NE", "NER", "BWAND", "BWXOR", "BWOR", "BOOLAND", 
-    "BOOLOR", "COND", "COLON", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", "AMUL", 
-    "ADIV", "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", "OCTAL", 
-    "HEX", "INTEGER", "DECIMAL", "STRING", "TRUE", "FALSE", "NULL", "ID", 
-    "EXTINTEGER", "EXTID"
+    "GTE", "EQ", "EQR", "NE", "NER", "BWAND", "XOR", "BWOR", "BOOLAND", "BOOLOR", 
+    "COND", "COLON", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", "AMUL", "ADIV", 
+    "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", "OCTAL", "HEX", 
+    "INTEGER", "DECIMAL", "STRING", "TRUE", "FALSE", "NULL", "ID", "EXTINTEGER", 
+    "EXTID"
   };
 
   private static final String[] _LITERAL_NAMES = {
@@ -58,11 +58,11 @@ class PainlessLexer extends Lexer {
     "DOT", "COMMA", "SEMICOLON", "IF", "ELSE", "WHILE", "DO", "FOR", "CONTINUE", 
     "BREAK", "RETURN", "NEW", "TRY", "CATCH", "THROW", "BOOLNOT", "BWNOT", 
     "MUL", "DIV", "REM", "ADD", "SUB", "LSH", "RSH", "USH", "LT", "LTE", "GT", 
-    "GTE", "EQ", "EQR", "NE", "NER", "BWAND", "BWXOR", "BWOR", "BOOLAND", 
-    "BOOLOR", "COND", "COLON", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", "AMUL", 
-    "ADIV", "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", "OCTAL", 
-    "HEX", "INTEGER", "DECIMAL", "STRING", "TRUE", "FALSE", "NULL", "ID", 
-    "EXTINTEGER", "EXTID"
+    "GTE", "EQ", "EQR", "NE", "NER", "BWAND", "XOR", "BWOR", "BOOLAND", "BOOLOR", 
+    "COND", "COLON", "INCR", "DECR", "ASSIGN", "AADD", "ASUB", "AMUL", "ADIV", 
+    "AREM", "AAND", "AXOR", "AOR", "ALSH", "ARSH", "AUSH", "OCTAL", "HEX", 
+    "INTEGER", "DECIMAL", "STRING", "TRUE", "FALSE", "NULL", "ID", "EXTINTEGER", 
+    "EXTID"
   };
   public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
 

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 201 - 194
modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessParser.java


+ 18 - 25
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessParserBaseVisitor.java → modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessParserBaseVisitor.java

@@ -1,5 +1,5 @@
 // ANTLR GENERATED CODE: DO NOT EDIT
-package org.elasticsearch.painless;
+package org.elasticsearch.painless.antlr;
 import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor;
 
 /**
@@ -186,6 +186,13 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
    * {@link #visitChildren} on {@code ctx}.</p>
    */
   @Override public T visitComp(PainlessParser.CompContext ctx) { return visitChildren(ctx); }
+  /**
+   * {@inheritDoc}
+   *
+   * <p>The default implementation returns the result of calling
+   * {@link #visitChildren} on {@code ctx}.</p>
+   */
+  @Override public T visitRead(PainlessParser.ReadContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
@@ -256,13 +263,6 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
    * {@link #visitChildren} on {@code ctx}.</p>
    */
   @Override public T visitCast(PainlessParser.CastContext ctx) { return visitChildren(ctx); }
-  /**
-   * {@inheritDoc}
-   *
-   * <p>The default implementation returns the result of calling
-   * {@link #visitChildren} on {@code ctx}.</p>
-   */
-  @Override public T visitExternal(PainlessParser.ExternalContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
@@ -290,70 +290,70 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtstart(PainlessParser.ExtstartContext ctx) { return visitChildren(ctx); }
+  @Override public T visitChain(PainlessParser.ChainContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtprec(PainlessParser.ExtprecContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinkprec(PainlessParser.LinkprecContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtcast(PainlessParser.ExtcastContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinkcast(PainlessParser.LinkcastContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtbrace(PainlessParser.ExtbraceContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinkbrace(PainlessParser.LinkbraceContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtdot(PainlessParser.ExtdotContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinkdot(PainlessParser.LinkdotContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtcall(PainlessParser.ExtcallContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinkcall(PainlessParser.LinkcallContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtvar(PainlessParser.ExtvarContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinkvar(PainlessParser.LinkvarContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtfield(PainlessParser.ExtfieldContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinkfield(PainlessParser.LinkfieldContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtnew(PainlessParser.ExtnewContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinknew(PainlessParser.LinknewContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
    * <p>The default implementation returns the result of calling
    * {@link #visitChildren} on {@code ctx}.</p>
    */
-  @Override public T visitExtstring(PainlessParser.ExtstringContext ctx) { return visitChildren(ctx); }
+  @Override public T visitLinkstring(PainlessParser.LinkstringContext ctx) { return visitChildren(ctx); }
   /**
    * {@inheritDoc}
    *
@@ -361,11 +361,4 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
    * {@link #visitChildren} on {@code ctx}.</p>
    */
   @Override public T visitArguments(PainlessParser.ArgumentsContext ctx) { return visitChildren(ctx); }
-  /**
-   * {@inheritDoc}
-   *
-   * <p>The default implementation returns the result of calling
-   * {@link #visitChildren} on {@code ctx}.</p>
-   */
-  @Override public T visitIncrement(PainlessParser.IncrementContext ctx) { return visitChildren(ctx); }
 }

+ 28 - 34
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessParserVisitor.java → modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessParserVisitor.java

@@ -1,5 +1,5 @@
 // ANTLR GENERATED CODE: DO NOT EDIT
-package org.elasticsearch.painless;
+package org.elasticsearch.painless.antlr;
 import org.antlr.v4.runtime.tree.ParseTreeVisitor;
 
 /**
@@ -174,6 +174,13 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
    * @return the visitor result
    */
   T visitComp(PainlessParser.CompContext ctx);
+  /**
+   * Visit a parse tree produced by the {@code read}
+   * labeled alternative in {@link PainlessParser#expression}.
+   * @param ctx the parse tree
+   * @return the visitor result
+   */
+  T visitRead(PainlessParser.ReadContext ctx);
   /**
    * Visit a parse tree produced by the {@code bool}
    * labeled alternative in {@link PainlessParser#expression}.
@@ -244,13 +251,6 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
    * @return the visitor result
    */
   T visitCast(PainlessParser.CastContext ctx);
-  /**
-   * Visit a parse tree produced by the {@code external}
-   * labeled alternative in {@link PainlessParser#expression}.
-   * @param ctx the parse tree
-   * @return the visitor result
-   */
-  T visitExternal(PainlessParser.ExternalContext ctx);
   /**
    * Visit a parse tree produced by the {@code null}
    * labeled alternative in {@link PainlessParser#expression}.
@@ -273,75 +273,69 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
    */
   T visitTrue(PainlessParser.TrueContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extstart}.
+   * Visit a parse tree produced by {@link PainlessParser#chain}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtstart(PainlessParser.ExtstartContext ctx);
+  T visitChain(PainlessParser.ChainContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extprec}.
+   * Visit a parse tree produced by {@link PainlessParser#linkprec}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtprec(PainlessParser.ExtprecContext ctx);
+  T visitLinkprec(PainlessParser.LinkprecContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extcast}.
+   * Visit a parse tree produced by {@link PainlessParser#linkcast}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtcast(PainlessParser.ExtcastContext ctx);
+  T visitLinkcast(PainlessParser.LinkcastContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extbrace}.
+   * Visit a parse tree produced by {@link PainlessParser#linkbrace}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtbrace(PainlessParser.ExtbraceContext ctx);
+  T visitLinkbrace(PainlessParser.LinkbraceContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extdot}.
+   * Visit a parse tree produced by {@link PainlessParser#linkdot}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtdot(PainlessParser.ExtdotContext ctx);
+  T visitLinkdot(PainlessParser.LinkdotContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extcall}.
+   * Visit a parse tree produced by {@link PainlessParser#linkcall}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtcall(PainlessParser.ExtcallContext ctx);
+  T visitLinkcall(PainlessParser.LinkcallContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extvar}.
+   * Visit a parse tree produced by {@link PainlessParser#linkvar}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtvar(PainlessParser.ExtvarContext ctx);
+  T visitLinkvar(PainlessParser.LinkvarContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extfield}.
+   * Visit a parse tree produced by {@link PainlessParser#linkfield}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtfield(PainlessParser.ExtfieldContext ctx);
+  T visitLinkfield(PainlessParser.LinkfieldContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extnew}.
+   * Visit a parse tree produced by {@link PainlessParser#linknew}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtnew(PainlessParser.ExtnewContext ctx);
+  T visitLinknew(PainlessParser.LinknewContext ctx);
   /**
-   * Visit a parse tree produced by {@link PainlessParser#extstring}.
+   * Visit a parse tree produced by {@link PainlessParser#linkstring}.
    * @param ctx the parse tree
    * @return the visitor result
    */
-  T visitExtstring(PainlessParser.ExtstringContext ctx);
+  T visitLinkstring(PainlessParser.LinkstringContext ctx);
   /**
    * Visit a parse tree produced by {@link PainlessParser#arguments}.
    * @param ctx the parse tree
    * @return the visitor result
    */
   T visitArguments(PainlessParser.ArgumentsContext ctx);
-  /**
-   * Visit a parse tree produced by {@link PainlessParser#increment}.
-   * @param ctx the parse tree
-   * @return the visitor result
-   */
-  T visitIncrement(PainlessParser.IncrementContext ctx);
 }

+ 17 - 12
modules/lang-painless/src/main/java/org/elasticsearch/painless/ParserErrorStrategy.java → modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/ParserErrorStrategy.java

@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.elasticsearch.painless;
+package org.elasticsearch.painless.antlr;
 
 import org.antlr.v4.runtime.DefaultErrorStrategy;
 import org.antlr.v4.runtime.InputMismatchException;
@@ -28,10 +28,14 @@ import org.antlr.v4.runtime.Token;
 
 import java.text.ParseException;
 
-class ParserErrorStrategy extends DefaultErrorStrategy {
+/**
+ * An error strategy that will override the default error behavior to fail on the first parser error.
+ */
+final class ParserErrorStrategy extends DefaultErrorStrategy {
+
     @Override
-    public void recover(Parser recognizer, RecognitionException re) {
-        Token token = re.getOffendingToken();
+    public void recover(final Parser recognizer, final RecognitionException re) {
+        final Token token = re.getOffendingToken();
         String message;
 
         if (token == null) {
@@ -52,23 +56,24 @@ class ParserErrorStrategy extends DefaultErrorStrategy {
                     " unexpected token near [" + getTokenErrorDisplay(token) + "].";
         }
 
-        ParseException parseException = new ParseException(message, token == null ? -1 : token.getStartIndex());
+        final ParseException parseException = new ParseException(message, token == null ? -1 : token.getStartIndex());
         parseException.initCause(re);
 
         throw new RuntimeException(parseException);
     }
 
     @Override
-    public Token recoverInline(Parser recognizer) throws RecognitionException {
-        Token token = recognizer.getCurrentToken();
-        String message = "Error[" + token.getLine() + ":" + token.getCharPositionInLine() + "]:" +
-                " unexpected token [" + getTokenErrorDisplay(token) + "]" +
-                " was expecting one of [" + recognizer.getExpectedTokens().toString(recognizer.getVocabulary()) + "].";
-        ParseException parseException = new ParseException(message, token.getStartIndex());
+    public Token recoverInline(final Parser recognizer) throws RecognitionException {
+        final Token token = recognizer.getCurrentToken();
+        final String message = "Error[" + token.getLine() + ":" + token.getCharPositionInLine() + "]:" +
+            " unexpected token [" + getTokenErrorDisplay(token) + "]" +
+            " was expecting one of [" + recognizer.getExpectedTokens().toString(recognizer.getVocabulary()) + "].";
+        final ParseException parseException = new ParseException(message, token.getStartIndex());
+
         throw new RuntimeException(parseException);
     }
 
     @Override
-    public void sync(Parser recognizer) {
+    public void sync(final Parser recognizer) {
     }
 }

+ 790 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java

@@ -0,0 +1,790 @@
+/*
+ * 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.antlr;
+
+import org.antlr.v4.runtime.ANTLRInputStream;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.elasticsearch.painless.Operation;
+import org.elasticsearch.painless.Variables.Reserved;
+import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ArgumentsContext;
+import org.elasticsearch.painless.antlr.PainlessParser.AssignmentContext;
+import org.elasticsearch.painless.antlr.PainlessParser.BinaryContext;
+import org.elasticsearch.painless.antlr.PainlessParser.BreakContext;
+import org.elasticsearch.painless.antlr.PainlessParser.CastContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ConditionalContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ContinueContext;
+import org.elasticsearch.painless.antlr.PainlessParser.DeclContext;
+import org.elasticsearch.painless.antlr.PainlessParser.DeclarationContext;
+import org.elasticsearch.painless.antlr.PainlessParser.DecltypeContext;
+import org.elasticsearch.painless.antlr.PainlessParser.DeclvarContext;
+import org.elasticsearch.painless.antlr.PainlessParser.DoContext;
+import org.elasticsearch.painless.antlr.PainlessParser.EmptyContext;
+import org.elasticsearch.painless.antlr.PainlessParser.EmptyscopeContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ExprContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ExpressionContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinkbraceContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinkcallContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinkcastContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinkdotContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ReadContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinkfieldContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinknewContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinkprecContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ChainContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinkstringContext;
+import org.elasticsearch.painless.antlr.PainlessParser.LinkvarContext;
+import org.elasticsearch.painless.antlr.PainlessParser.FalseContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ForContext;
+import org.elasticsearch.painless.antlr.PainlessParser.GenericContext;
+import org.elasticsearch.painless.antlr.PainlessParser.IdentifierContext;
+import org.elasticsearch.painless.antlr.PainlessParser.IfContext;
+import org.elasticsearch.painless.antlr.PainlessParser.InitializerContext;
+import org.elasticsearch.painless.antlr.PainlessParser.MultipleContext;
+import org.elasticsearch.painless.antlr.PainlessParser.NullContext;
+import org.elasticsearch.painless.antlr.PainlessParser.NumericContext;
+import org.elasticsearch.painless.antlr.PainlessParser.PostincContext;
+import org.elasticsearch.painless.antlr.PainlessParser.PrecedenceContext;
+import org.elasticsearch.painless.antlr.PainlessParser.PreincContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ReturnContext;
+import org.elasticsearch.painless.antlr.PainlessParser.SingleContext;
+import org.elasticsearch.painless.antlr.PainlessParser.SourceContext;
+import org.elasticsearch.painless.antlr.PainlessParser.StatementContext;
+import org.elasticsearch.painless.antlr.PainlessParser.ThrowContext;
+import org.elasticsearch.painless.antlr.PainlessParser.TrapContext;
+import org.elasticsearch.painless.antlr.PainlessParser.TrueContext;
+import org.elasticsearch.painless.antlr.PainlessParser.TryContext;
+import org.elasticsearch.painless.antlr.PainlessParser.UnaryContext;
+import org.elasticsearch.painless.antlr.PainlessParser.WhileContext;
+import org.elasticsearch.painless.node.AExpression;
+import org.elasticsearch.painless.node.ALink;
+import org.elasticsearch.painless.node.ANode;
+import org.elasticsearch.painless.node.AStatement;
+import org.elasticsearch.painless.node.EBinary;
+import org.elasticsearch.painless.node.EBool;
+import org.elasticsearch.painless.node.EBoolean;
+import org.elasticsearch.painless.node.EExplicit;
+import org.elasticsearch.painless.node.EChain;
+import org.elasticsearch.painless.node.EComp;
+import org.elasticsearch.painless.node.EConditional;
+import org.elasticsearch.painless.node.EDecimal;
+import org.elasticsearch.painless.node.ENull;
+import org.elasticsearch.painless.node.ENumeric;
+import org.elasticsearch.painless.node.EUnary;
+import org.elasticsearch.painless.node.LBrace;
+import org.elasticsearch.painless.node.LCall;
+import org.elasticsearch.painless.node.LCast;
+import org.elasticsearch.painless.node.LField;
+import org.elasticsearch.painless.node.LNewArray;
+import org.elasticsearch.painless.node.LNewObj;
+import org.elasticsearch.painless.node.LString;
+import org.elasticsearch.painless.node.LVariable;
+import org.elasticsearch.painless.node.SBlock;
+import org.elasticsearch.painless.node.SBreak;
+import org.elasticsearch.painless.node.SContinue;
+import org.elasticsearch.painless.node.SDeclBlock;
+import org.elasticsearch.painless.node.SDeclaration;
+import org.elasticsearch.painless.node.SDo;
+import org.elasticsearch.painless.node.SExpression;
+import org.elasticsearch.painless.node.SFor;
+import org.elasticsearch.painless.node.SIfElse;
+import org.elasticsearch.painless.node.SReturn;
+import org.elasticsearch.painless.node.SSource;
+import org.elasticsearch.painless.node.SThrow;
+import org.elasticsearch.painless.node.STrap;
+import org.elasticsearch.painless.node.STry;
+import org.elasticsearch.painless.node.SWhile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts the ANTLR tree to a Painless tree.
+ */
+public final class Walker extends PainlessParserBaseVisitor<ANode> {
+
+    public static SSource buildPainlessTree(final String source, final Reserved reserved) {
+        return new Walker(source, reserved).source;
+    }
+
+    private final Reserved reserved;
+    private final SSource source;
+
+    private Walker(final String source, final Reserved reserved) {
+        this.reserved = reserved;
+        this.source = (SSource)visit(buildAntlrTree(source));
+    }
+
+    private SourceContext buildAntlrTree(final String source) {
+        final ANTLRInputStream stream = new ANTLRInputStream(source);
+        final PainlessLexer lexer = new ErrorHandlingLexer(stream);
+        final PainlessParser parser = new PainlessParser(new CommonTokenStream(lexer));
+        final ParserErrorStrategy strategy = new ParserErrorStrategy();
+
+        lexer.removeErrorListeners();
+        parser.removeErrorListeners();
+        parser.setErrorHandler(strategy);
+
+        return parser.source();
+    }
+
+    private String location(final ParserRuleContext ctx) {
+        return "[ " + ctx.getStart().getLine() + " : " + ctx.getStart().getCharPositionInLine() + " ]";
+    }
+
+    @Override
+    public ANode visitSource(final SourceContext ctx) {
+        final List<AStatement> statements = new ArrayList<>();
+
+        for (final StatementContext statement : ctx.statement()) {
+            statements.add((AStatement)visit(statement));
+        }
+
+        return new SSource(location(ctx), statements);
+    }
+
+    @Override
+    public ANode visitIf(final IfContext ctx) {
+        final AExpression condition = (AExpression)visit(ctx.expression());
+        final AStatement ifblock = (AStatement)visit(ctx.block(0));
+        final AStatement elseblock = ctx.block(1) == null ? null : (AStatement)visit(ctx.block(1));
+
+        return new SIfElse(location(ctx), condition, ifblock, elseblock);
+    }
+
+    @Override
+    public ANode visitWhile(final WhileContext ctx) {
+        final AExpression condition = (AExpression)visit(ctx.expression());
+        final AStatement block = ctx.block() == null ? null : (AStatement)visit(ctx.block());
+
+        reserved.usesLoop();
+
+        return new SWhile(location(ctx), condition, block);
+    }
+
+    @Override
+    public ANode visitDo(final DoContext ctx) {
+        final AStatement block = ctx.block() == null ? null : (AStatement)visit(ctx.block());
+        final AExpression condition = (AExpression)visit(ctx.expression());
+
+        reserved.usesLoop();
+
+        return new SDo(location(ctx), block, condition);
+    }
+
+    @Override
+    public ANode visitFor(final ForContext ctx) {
+        final ANode intializer = ctx.initializer() == null ? null : visit(ctx.initializer());
+        final AExpression condition = ctx.expression() == null ? null : (AExpression)visit(ctx.expression());
+        final AExpression afterthought = ctx.afterthought() == null ? null : (AExpression)visit(ctx.afterthought());
+        final AStatement block = ctx.block() == null ? null : (AStatement)visit(ctx.block());
+
+        reserved.usesLoop();
+
+        return new SFor(location(ctx), intializer, condition, afterthought, block);
+    }
+
+    @Override
+    public ANode visitDecl(final DeclContext ctx) {
+        return visit(ctx.declaration());
+    }
+
+    @Override
+    public ANode visitContinue(final ContinueContext ctx) {
+        return new SContinue(location(ctx));
+    }
+
+    @Override
+    public ANode visitBreak(final BreakContext ctx) {
+        return new SBreak(location(ctx));
+    }
+
+    @Override
+    public ANode visitReturn(final ReturnContext ctx) {
+        final AExpression expression = (AExpression)visit(ctx.expression());
+
+        return new SReturn(location(ctx), expression);
+    }
+
+    @Override
+    public ANode visitTry(final TryContext ctx) {
+        final AStatement block = (AStatement)visit(ctx.block());
+        final List<STrap> traps = new ArrayList<>();
+
+        for (final TrapContext trap : ctx.trap()) {
+            traps.add((STrap)visit(trap));
+        }
+
+        return new STry(location(ctx), block, traps);
+    }
+
+    @Override
+    public ANode visitThrow(final ThrowContext ctx) {
+        final AExpression expression = (AExpression)visit(ctx.expression());
+
+        return new SThrow(location(ctx), expression);
+    }
+
+    @Override
+    public ANode visitExpr(final ExprContext ctx) {
+        final AExpression expression = (AExpression)visit(ctx.expression());
+
+        return new SExpression(location(ctx), expression);
+    }
+
+    @Override
+    public ANode visitMultiple(final MultipleContext ctx) {
+        final List<AStatement> statements = new ArrayList<>();
+
+        for (final StatementContext statement : ctx.statement()) {
+            statements.add((AStatement)visit(statement));
+        }
+
+        return new SBlock(location(ctx), statements);
+    }
+
+    @Override
+    public ANode visitSingle(final SingleContext ctx) {
+        final List<AStatement> statements = new ArrayList<>();
+        statements.add((AStatement)visit(ctx.statement()));
+
+        return new SBlock(location(ctx), statements);
+    }
+
+    @Override
+    public ANode visitEmpty(final EmptyContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    @Override
+    public ANode visitEmptyscope(final EmptyscopeContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    @Override
+    public ANode visitInitializer(final InitializerContext ctx) {
+        if (ctx.declaration() != null) {
+            return visit(ctx.declaration());
+        } else if (ctx.expression() != null) {
+            return visit(ctx.expression());
+        }
+
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    @Override
+    public ANode visitAfterthought(final AfterthoughtContext ctx) {
+        return visit(ctx.expression());
+    }
+
+    @Override
+    public ANode visitDeclaration(final DeclarationContext ctx) {
+        final String type = ctx.decltype().getText();
+        final List<SDeclaration> declarations = new ArrayList<>();
+
+        for (final DeclvarContext declvar : ctx.declvar()) {
+            final String name = declvar.identifier().getText();
+            final AExpression expression = declvar.expression() == null ? null : (AExpression)visit(declvar.expression());
+            declarations.add(new SDeclaration(location(ctx), type, name, expression));
+        }
+
+        return new SDeclBlock(location(ctx), declarations);
+    }
+
+    @Override
+    public ANode visitDecltype(final DecltypeContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    @Override
+    public ANode visitDeclvar(final DeclvarContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    @Override
+    public ANode visitTrap(final TrapContext ctx) {
+        final String type = ctx.identifier(0).getText();
+        final String name = ctx.identifier(1).getText();
+        final AStatement block = ctx.block() == null ? null : (AStatement)visit(ctx.block());
+
+        return new STrap(location(ctx), type, name, block);
+    }
+
+    @Override
+    public ANode visitIdentifier(final IdentifierContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    @Override
+    public ANode visitGeneric(final GenericContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    @Override
+    public ANode visitPrecedence(final PrecedenceContext ctx) {
+        return visit(ctx.expression());
+    }
+
+    @Override
+    public ANode visitNumeric(final NumericContext ctx) {
+        final boolean negate = ctx.parent instanceof UnaryContext && ((UnaryContext)ctx.parent).SUB() != null;
+
+        if (ctx.DECIMAL() != null) {
+            return new EDecimal(location(ctx), (negate ? "-" : "") + ctx.DECIMAL().getText());
+        } else if (ctx.HEX() != null) {
+            return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.HEX().getText().substring(2), 16);
+        } else if (ctx.INTEGER() != null) {
+            return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.INTEGER().getText(), 10);
+        } else if (ctx.OCTAL() != null) {
+            return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.OCTAL().getText().substring(1), 8);
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+    }
+
+    @Override
+    public ANode visitTrue(final TrueContext ctx) {
+        return new EBoolean(location(ctx), true);
+    }
+
+    @Override
+    public ANode visitFalse(FalseContext ctx) {
+        return new EBoolean(location(ctx), false);
+    }
+
+    @Override
+    public ANode visitNull(final NullContext ctx) {
+        return new ENull(location(ctx));
+    }
+
+    @Override
+    public ANode visitPostinc(final PostincContext ctx) {
+        final List<ALink> links = new ArrayList<>();
+        final Operation operation;
+
+        visitChain(ctx.chain(), links);
+
+        if (ctx.INCR() != null) {
+            operation = Operation.INCR;
+        } else if (ctx.DECR() != null) {
+            operation = Operation.DECR;
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        return new EChain(location(ctx), links, false, true, operation, null);
+    }
+
+    @Override
+    public ANode visitPreinc(final PreincContext ctx) {
+        final List<ALink> links = new ArrayList<>();
+        final Operation operation;
+
+        visitChain(ctx.chain(), links);
+
+        if (ctx.INCR() != null) {
+            operation = Operation.INCR;
+        } else if (ctx.DECR() != null) {
+            operation = Operation.DECR;
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        return new EChain(location(ctx), links, true, false, operation, null);
+    }
+
+    @Override
+    public ANode visitRead(final ReadContext ctx) {
+        final List<ALink> links = new ArrayList<>();
+
+        visitChain(ctx.chain(), links);
+
+        return new EChain(location(ctx), links, false, false, null, null);
+    }
+
+    @Override
+    public ANode visitUnary(final UnaryContext ctx) {
+        if (ctx.SUB() != null && ctx.expression() instanceof NumericContext) {
+            return visit(ctx.expression());
+        } else {
+            final Operation operation;
+
+            if (ctx.BOOLNOT() != null) {
+                operation = Operation.NOT;
+            } else if (ctx.BWNOT() != null) {
+                operation = Operation.BWNOT;
+            } else if (ctx.ADD() != null) {
+                operation = Operation.ADD;
+            } else if (ctx.SUB() != null) {
+                operation = Operation.SUB;
+            } else {
+                throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+            }
+
+            return new EUnary(location(ctx), operation, (AExpression)visit(ctx.expression()));
+        }
+    }
+
+    @Override
+    public ANode visitCast(final CastContext ctx) {
+        return new EExplicit(location(ctx), ctx.decltype().getText(), (AExpression)visit(ctx.expression()));
+    }
+
+    @Override
+    public ANode visitBinary(final BinaryContext ctx) {
+        final AExpression left = (AExpression)visit(ctx.expression(0));
+        final AExpression right = (AExpression)visit(ctx.expression(1));
+        final Operation operation;
+
+        if (ctx.MUL() != null) {
+            operation = Operation.MUL;
+        } else if (ctx.DIV() != null) {
+            operation = Operation.DIV;
+        } else if (ctx.REM() != null) {
+            operation = Operation.REM;
+        } else if (ctx.ADD() != null) {
+            operation = Operation.ADD;
+        } else if (ctx.SUB() != null) {
+            operation = Operation.SUB;
+        } else if (ctx.LSH() != null) {
+            operation = Operation.LSH;
+        } else if (ctx.RSH() != null) {
+            operation = Operation.RSH;
+        } else if (ctx.USH() != null) {
+            operation = Operation.USH;
+        } else if (ctx.BWAND() != null) {
+            operation = Operation.BWAND;
+        } else if (ctx.XOR() != null) {
+            operation = Operation.XOR;
+        } else if (ctx.BWOR() != null) {
+            operation = Operation.BWOR;
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        return new EBinary(location(ctx), operation, left, right);
+    }
+
+    @Override
+    public ANode visitComp(PainlessParser.CompContext ctx) {
+        final AExpression left = (AExpression)visit(ctx.expression(0));
+        final AExpression right = (AExpression)visit(ctx.expression(1));
+        final Operation operation;
+
+        if (ctx.LT() != null) {
+            operation = Operation.LT;
+        } else if (ctx.LTE() != null) {
+            operation = Operation.LTE;
+        } else if (ctx.GT() != null) {
+            operation = Operation.GT;
+        } else if (ctx.GTE() != null) {
+            operation = Operation.GTE;
+        } else if (ctx.EQ() != null) {
+            operation = Operation.EQ;
+        } else if (ctx.EQR() != null) {
+            operation = Operation.EQR;
+        } else if (ctx.NE() != null) {
+            operation = Operation.NE;
+        } else if (ctx.NER() != null) {
+            operation = Operation.NER;
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        return new EComp(location(ctx), operation, left, right);
+    }
+
+    @Override
+    public ANode visitBool(PainlessParser.BoolContext ctx) {
+        final AExpression left = (AExpression)visit(ctx.expression(0));
+        final AExpression right = (AExpression)visit(ctx.expression(1));
+        final Operation operation;
+
+        if (ctx.BOOLAND() != null) {
+            operation = Operation.AND;
+        } else if (ctx.BOOLOR() != null) {
+            operation = Operation.OR;
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        return new EBool(location(ctx), operation, left, right);
+    }
+
+
+    @Override
+    public ANode visitConditional(final ConditionalContext ctx) {
+        final AExpression condition = (AExpression)visit(ctx.expression(0));
+        final AExpression left = (AExpression)visit(ctx.expression(1));
+        final AExpression right = (AExpression)visit(ctx.expression(2));
+
+        return new EConditional(location(ctx), condition, left, right);
+    }
+
+    @Override
+    public ANode visitAssignment(final AssignmentContext ctx) {
+        final List<ALink> links = new ArrayList<>();
+        final Operation operation;
+
+        visitChain(ctx.chain(), links);
+
+        if (ctx.AMUL() != null) {
+            operation = Operation.MUL;
+        } else if (ctx.ADIV() != null) {
+            operation = Operation.DIV;
+        } else if (ctx.AREM() != null) {
+            operation = Operation.REM;
+        } else if (ctx.AADD() != null) {
+            operation = Operation.ADD;
+        } else if (ctx.ASUB() != null) {
+            operation = Operation.SUB;
+        } else if (ctx.ALSH() != null) {
+            operation = Operation.LSH;
+        } else if (ctx.ARSH() != null) {
+            operation = Operation.RSH;
+        } else if (ctx.AUSH() != null) {
+            operation = Operation.USH;
+        } else if (ctx.AAND() != null) {
+            operation = Operation.BWAND;
+        } else if (ctx.AXOR() != null) {
+            operation = Operation.XOR;
+        } else if (ctx.AOR() != null) {
+            operation = Operation.BWOR;
+        } else {
+            operation = null;
+        }
+
+        return new EChain(location(ctx), links, false, false, operation, (AExpression)visit(ctx.expression()));
+    }
+
+    private void visitChain(final ChainContext ctx, final List<ALink> links) {
+        if (ctx.linkprec() != null) {
+            visitLinkprec(ctx.linkprec(), links);
+        } else if (ctx.linkcast() != null) {
+            visitLinkcast(ctx.linkcast(), links);
+        } else if (ctx.linkvar() != null) {
+            visitLinkvar(ctx.linkvar(), links);
+        } else if (ctx.linknew() != null) {
+            visitLinknew(ctx.linknew(), links);
+        } else if (ctx.linkstring() != null) {
+            visitLinkstring(ctx.linkstring(), links);
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+    }
+
+    @Override
+    public ANode visitChain(final ChainContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinkprec(final LinkprecContext ctx, final List<ALink> links) {
+        if (ctx.linkprec() != null) {
+            visitLinkprec(ctx.linkprec(), links);
+        } else if (ctx.linkcast() != null) {
+            visitLinkcast(ctx.linkcast(), links);
+        } else if (ctx.linkvar() != null) {
+            visitLinkvar(ctx.linkvar(), links);
+        } else if (ctx.linknew() != null) {
+            visitLinknew(ctx.linknew(), links);
+        } else if (ctx.linkstring() != null) {
+            visitLinkstring(ctx.linkstring(), links);
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        if (ctx.linkbrace() != null) {
+            visitLinkbrace(ctx.linkbrace(), links);
+        } else if (ctx.linkdot() != null) {
+            visitLinkdot(ctx.linkdot(), links);
+        }
+    }
+
+    @Override
+    public ANode visitLinkprec(final LinkprecContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinkcast(final LinkcastContext ctx, final List<ALink> links) {
+        if (ctx.linkprec() != null) {
+            visitLinkprec(ctx.linkprec(), links);
+        } else if (ctx.linkcast() != null) {
+            visitLinkcast(ctx.linkcast(), links);
+        } else if (ctx.linkvar() != null) {
+            visitLinkvar(ctx.linkvar(), links);
+        } else if (ctx.linknew() != null) {
+            visitLinknew(ctx.linknew(), links);
+        } else if (ctx.linkstring() != null) {
+            visitLinkstring(ctx.linkstring(), links);
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        links.add(new LCast(location(ctx), ctx.decltype().getText()));
+    }
+
+    @Override
+    public ANode visitLinkcast(final LinkcastContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinkbrace(final LinkbraceContext ctx, final List<ALink> links) {
+        links.add(new LBrace(location(ctx), (AExpression)visit(ctx.expression())));
+
+        if (ctx.linkbrace() != null) {
+            visitLinkbrace(ctx.linkbrace(), links);
+        } else if (ctx.linkdot() != null) {
+            visitLinkdot(ctx.linkdot(), links);
+        }
+    }
+
+    @Override
+    public ANode visitLinkbrace(final LinkbraceContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinkdot(final LinkdotContext ctx, final List<ALink> links) {
+        if (ctx.linkcall() != null) {
+            visitLinkcall(ctx.linkcall(), links);
+        } else if (ctx.linkfield() != null) {
+            visitLinkfield(ctx.linkfield(), links);
+        }
+    }
+
+    @Override
+    public ANode visitLinkdot(final LinkdotContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinkcall(final LinkcallContext ctx, final List<ALink> links) {
+        final List<AExpression> arguments = new ArrayList<>();
+
+        for (final ExpressionContext expression : ctx.arguments().expression()) {
+            arguments.add((AExpression)visit(expression));
+        }
+
+        links.add(new LCall(location(ctx), ctx.EXTID().getText(), arguments));
+
+        if (ctx.linkbrace() != null) {
+            visitLinkbrace(ctx.linkbrace(), links);
+        } else if (ctx.linkdot() != null) {
+            visitLinkdot(ctx.linkdot(), links);
+        }
+    }
+
+    @Override
+    public ANode visitLinkcall(final LinkcallContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinkvar(final LinkvarContext ctx, final List<ALink> links) {
+        final String name = ctx.identifier().getText();
+
+        reserved.markReserved(name);
+
+        links.add(new LVariable(location(ctx), name));
+
+        if (ctx.linkbrace() != null) {
+            visitLinkbrace(ctx.linkbrace(), links);
+        } else if (ctx.linkdot() != null) {
+            visitLinkdot(ctx.linkdot(), links);
+        }
+    }
+
+    @Override
+    public ANode visitLinkvar(final LinkvarContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinkfield(final LinkfieldContext ctx, final List<ALink> links) {
+        final String value;
+
+        if (ctx.EXTID() != null) {
+            value = ctx.EXTID().getText();
+        } else if (ctx.EXTINTEGER() != null) {
+            value = ctx.EXTINTEGER().getText();
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        links.add(new LField(location(ctx), value));
+
+        if (ctx.linkbrace() != null) {
+            visitLinkbrace(ctx.linkbrace(), links);
+        } else if (ctx.linkdot() != null) {
+            visitLinkdot(ctx.linkdot(), links);
+        }
+    }
+
+    @Override
+    public ANode visitLinkfield(final LinkfieldContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinknew(final LinknewContext ctx, final List<ALink> links) {
+        final List<AExpression> arguments = new ArrayList<>();
+
+        if (ctx.arguments() != null) {
+            for (final ExpressionContext expression : ctx.arguments().expression()) {
+                arguments.add((AExpression)visit(expression));
+            }
+
+            links.add(new LNewObj(location(ctx), ctx.identifier().getText(), arguments));
+        } else if (ctx.expression().size() > 0) {
+            for (final ExpressionContext expression : ctx.expression()) {
+                arguments.add((AExpression)visit(expression));
+            }
+
+            links.add(new LNewArray(location(ctx), ctx.identifier().getText(), arguments));
+        } else {
+            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+        }
+
+        if (ctx.linkdot() != null) {
+            visitLinkdot(ctx.linkdot(), links);
+        }
+    }
+
+    @Override
+    public ANode visitLinknew(final LinknewContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    private void visitLinkstring(final LinkstringContext ctx, final List<ALink> links) {
+        links.add(new LString(location(ctx), ctx.STRING().getText().substring(1, ctx.STRING().getText().length() - 1)));
+
+        if (ctx.linkbrace() != null) {
+            visitLinkbrace(ctx.linkbrace(), links);
+        } else if (ctx.linkdot() != null) {
+            visitLinkdot(ctx.linkdot(), links);
+        }
+    }
+
+    @Override
+    public ANode visitLinkstring(final LinkstringContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+
+    @Override
+    public ANode visitArguments(final ArgumentsContext ctx) {
+        throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+    }
+}

+ 173 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java

@@ -0,0 +1,173 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Cast;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.AnalyzerCaster;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * The superclass for all E* (expression) nodes.
+ */
+public abstract class AExpression extends ANode {
+
+    /**
+     * Set to false when an expression will not be read from such as
+     * a basic assignment.  Note this variable is always set by the parent
+     * as input.
+     */
+    protected boolean read = true;
+
+    /**
+     * Set to true when an expression can be considered a stand alone
+     * statement.  Used to prevent extraneous bytecode. This is always
+     * set by the node as output.
+     */
+    protected boolean statement = false;
+
+    /**
+     * Set to the expected type this node needs to be.  Note this variable
+     * is always set by the parent as input and should never be read from.
+     */
+    protected Type expected = null;
+
+    /**
+     * Set to the actual type this node is.  Note this variable is always
+     * set by the node as output and should only be read from outside of the
+     * node itself.  <b>Also, actual can always be read after a cast is
+     * called on this node to get the type of the node after the cast.</b>
+     */
+    protected Type actual = null;
+
+    /**
+     * Set by {@link EExplicit} if a cast made on an expression node should be
+     * explicit.
+     */
+    protected boolean explicit = false;
+
+    /**
+     * Set to the value of the constant this expression node represents if
+     * and only if the node represents a constant.  If this is not null
+     * this node will be replaced by an {@link EConstant} during casting
+     * if it's not already one.
+     */
+    protected Object constant = null;
+
+    /**
+     * Set to true by {@link ENull} to represent a null value.
+     */
+    protected boolean isNull = false;
+
+    /**
+     * If an expression represents a branch statement, represents the jump should
+     * the expression evaluate to a true value.  It should always be the case that only
+     * one of tru and fals are non-null or both are null.  Only used during the writing phase.
+     */
+    protected Label tru = null;
+
+    /**
+     * If an expression represents a branch statement, represents the jump should
+     * the expression evaluate to a false value.  It should always be the case that only
+     * one of tru and fals are non-null or both are null.  Only used during the writing phase.
+     */
+    protected Label fals = null;
+
+    public AExpression(final String location) {
+        super(location);
+    }
+
+    /**
+     * Checks for errors and collects data for the writing phase.
+     */
+    abstract void analyze(final CompilerSettings settings, final Definition definition, final Variables variables);
+
+    /**
+     * Writes ASM based on the data collected during the analysis phase.
+     */
+    abstract void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter);
+
+    /**
+     * Inserts {@link ECast} nodes into the tree for implicit casts.  Also replaces
+     * nodes with the constant variable set to a non-null value with {@link EConstant}.
+     * @return The new child node for the parent node calling this method.
+     */
+    AExpression cast(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        final Cast cast = AnalyzerCaster.getLegalCast(definition, location, actual, expected, explicit);
+
+        if (cast == null) {
+            if (constant == null || this instanceof EConstant) {
+                return this;
+            } else {
+                final EConstant econstant = new EConstant(location, constant);
+                econstant.analyze(settings, definition, variables);
+
+                if (!expected.equals(econstant.actual)) {
+                    throw new IllegalStateException(error("Illegal tree structure."));
+                }
+
+                return econstant;
+            }
+        } else {
+            if (constant == null) {
+                final ECast ecast = new ECast(location, this, cast);
+                ecast.statement = statement;
+                ecast.actual = expected;
+                ecast.isNull = isNull;
+
+                return ecast;
+            } else {
+                if (expected.sort.constant) {
+                    constant = AnalyzerCaster.constCast(location, constant, cast);
+
+                    final EConstant econstant = new EConstant(location, constant);
+                    econstant.analyze(settings, definition, variables);
+
+                    if (!expected.equals(econstant.actual)) {
+                        throw new IllegalStateException(error("Illegal tree structure."));
+                    }
+
+                    return econstant;
+                } else if (this instanceof EConstant) {
+                    final ECast ecast = new ECast(location, this, cast);
+                    ecast.actual = expected;
+
+                    return ecast;
+                } else {
+                    final EConstant econstant = new EConstant(location, constant);
+                    econstant.analyze(settings, definition, variables);
+
+                    if (!actual.equals(econstant.actual)) {
+                        throw new IllegalStateException(error("Illegal tree structure."));
+                    }
+
+                    final ECast ecast = new ECast(location, econstant, cast);
+                    ecast.actual = expected;
+
+                    return ecast;
+                }
+            }
+        }
+    }
+}

+ 121 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ALink.java

@@ -0,0 +1,121 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * The superclass for all L* (link) nodes.
+ */
+public abstract class ALink extends ANode {
+
+    /**
+     * Size is set to a value based on this link's size on the stack.  This is
+     * used during the writing phase to dup stack values from this link as
+     * necessary during certain store operations.
+     */
+    final int size;
+
+    /**
+     * Set to false only if the link is not going to be read from.
+     */
+    boolean load = true;
+
+    /**
+     * Set to true only if the link is going to be written to and
+     * is the final link in a chain.
+     */
+    boolean store = false;
+
+    /**
+     * Set to true if this link represents a statik type to be accessed.
+     */
+    boolean statik = false;
+
+    /**
+     * Set by the parent chain to type of the previous link or null if
+     * there was no previous link.
+     */
+    Type before = null;
+
+    /**
+     * Set by the link to be the type after the link has been loaded/stored.
+     */
+    Type after = null;
+
+    /**
+     * Set to true if this link could be a stand-alone statement.
+     */
+    boolean statement = false;
+
+    /**
+     * Used by {@link LString} to set the value of the String constant.  Also
+     * used by shortcuts to represent a constant key.
+     */
+    String string = null;
+
+    ALink(final String location, final int size) {
+        super(location);
+
+        this.size = size;
+    }
+
+    /**
+     * Checks for errors and collects data for the writing phase.
+     * @return Possibly returns a different {@link ALink} node if a type is
+     * def or a shortcut is used. Otherwise, returns itself.  This will be
+     * updated into the {@link EChain} node's list of links.
+     */
+    abstract ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables);
+
+    /**
+     * Write values before a load/store occurs such as an array index.
+     */
+    abstract void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter);
+
+    /**
+     * Write a load for the specific link type.
+     */
+    abstract void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter);
+
+    /**
+     * Write a store for the specific link type.
+     */
+    abstract void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter);
+
+    /**
+     * Used to copy link data from one to another during analysis in the case of replacement.
+     */
+    final ALink copy(final ALink link) {
+        load       = link.load;
+        store      = link.store;
+        statik     = link.statik;
+        before     = link.before;
+        after      = link.after;
+        statement  = link.statement;
+        string     = link.string;
+
+        return this;
+    }
+}

+ 39 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java

@@ -0,0 +1,39 @@
+/*
+ * 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.node;
+
+/**
+ * The superclass for all other nodes.
+ */
+public abstract class ANode {
+
+    /**
+     * The location in the original source to be printed in error messages.
+     */
+    final String location;
+
+    ANode(final String location) {
+        this.location = location;
+    }
+
+    public String error(final String message) {
+        return "Error " + location  + ": " + message;
+    }
+}

+ 125 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java

@@ -0,0 +1,125 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * The superclass for all S* (statement) nodes.
+ */
+public abstract class AStatement extends ANode {
+
+    /**
+     * Set to true when the final statement in an {@link SSource} is reached.
+     * Used to determine whether or not an auto-return is necessary.
+     */
+    boolean lastSource = false;
+
+    /**
+     * Set to true when a loop begins.  Used by {@link SBlock} to help determine
+     * when the final statement of a loop is reached.
+     */
+    boolean beginLoop = false;
+
+    /**
+     * Set to true when inside a loop.  Used by {@link SBreak} and {@link SContinue}
+     * to determine if a break/continue statement is legal.
+     */
+    boolean inLoop = false;
+
+    /**
+     * Set to true when on the last statement of a loop.  Used by {@link SContinue}
+     * to prevent extraneous continue statements.
+     */
+    boolean lastLoop = false;
+
+    /**
+     * Set to true if a statement would cause the method to exit.  Used to
+     * determine whether or not an auto-return is necessary.
+     */
+    boolean methodEscape = false;
+
+    /**
+     * Set to true if a statement would cause a loop to exit.  Used to
+     * prevent unreachable statements.
+     */
+    boolean loopEscape = false;
+
+    /**
+     * Set to true if all current paths escape from the current {@link SBlock}.
+     * Used during the analysis phase to prevent unreachable statements and
+     * the writing phase to prevent extraneous bytecode gotos from being written.
+     */
+    boolean allEscape = false;
+
+    /**
+     * Set to true if any continue statement occurs in a loop.  Used to prevent
+     * unnecessary infinite loops.
+     */
+    boolean anyContinue = false;
+
+    /**
+     * Set to true if any break statement occurs in a loop.  Used to prevent
+     * extraneous loops.
+     */
+    boolean anyBreak = false;
+
+    /**
+     * Set to the loop counter variable slot as a shortcut if loop statements
+     * are being counted.
+     */
+    int loopCounterSlot = -1;
+
+    /**
+     * Set to the approximate number of statements in a loop block to prevent
+     * infinite loops during runtime.
+     */
+    int statementCount = 0;
+
+    /**
+     * Set to the beginning of a loop so a continue statement knows where to
+     * jump to.  Only used during the writing phase.
+     */
+    Label continu = null;
+
+    /**
+     * Set to the beginning of a loop so a break statement knows where to
+     * jump to.  Only used during the writing phase.
+     */
+    Label brake = null;
+
+    AStatement(final String location) {
+        super(location);
+    }
+
+    /**
+     * Checks for errors and collects data for the writing phase.
+     */
+    abstract void analyze(final CompilerSettings settings, final Definition definition, final Variables variables);
+
+    /**
+     * Writes ASM based on the data collected during the analysis phase.
+     */
+    abstract void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter);
+}

+ 528 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java

@@ -0,0 +1,528 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.AnalyzerCaster;
+import org.elasticsearch.painless.Operation;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a binary math expression.
+ */
+public final class EBinary extends AExpression {
+
+    final Operation operation;
+    AExpression left;
+    AExpression right;
+
+    boolean cat = false;
+
+    public EBinary(final String location, final Operation operation, final AExpression left, final AExpression right) {
+        super(location);
+
+        this.operation = operation;
+        this.left = left;
+        this.right = right;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (operation == Operation.MUL) {
+            analyzeMul(settings, definition, variables);
+        } else if (operation == Operation.DIV) {
+            analyzeDiv(settings, definition, variables);
+        } else if (operation == Operation.REM) {
+            analyzeRem(settings, definition, variables);
+        } else if (operation == Operation.ADD) {
+            analyzeAdd(settings, definition, variables);
+        } else if (operation == Operation.SUB) {
+            analyzeSub(settings, definition, variables);
+        } else if (operation == Operation.LSH) {
+            analyzeLSH(settings, definition, variables);
+        } else if (operation == Operation.RSH) {
+            analyzeRSH(settings, definition, variables);
+        } else if (operation == Operation.USH) {
+            analyzeUSH(settings, definition, variables);
+        } else if (operation == Operation.BWAND) {
+            analyzeBWAnd(settings, definition, variables);
+        } else if (operation == Operation.XOR) {
+            analyzeXor(settings, definition, variables);
+        } else if (operation == Operation.BWOR) {
+            analyzeBWOr(settings, definition, variables);
+        } else {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        }
+    }
+
+    private void analyzeMul(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply multiply [*] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final boolean overflow = settings.getNumericOverflow();
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = overflow ? (int)left.constant * (int)right.constant :
+                    Math.multiplyExact((int)left.constant, (int)right.constant);
+            } else if (sort == Sort.LONG) {
+                constant = overflow ? (long)left.constant * (long)right.constant :
+                    Math.multiplyExact((long)left.constant, (long)right.constant);
+            } else if (sort == Sort.FLOAT) {
+                constant = overflow ? (float)left.constant * (float)right.constant :
+                    org.elasticsearch.painless.Utility.multiplyWithoutOverflow((float)left.constant, (float)right.constant);
+            } else if (sort == Sort.DOUBLE) {
+                constant = overflow ? (double)left.constant * (double)right.constant :
+                    org.elasticsearch.painless.Utility.multiplyWithoutOverflow((double)left.constant, (double)right.constant);
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeDiv(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply divide [/] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final boolean overflow = settings.getNumericOverflow();
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = overflow ? (int)left.constant / (int)right.constant :
+                    org.elasticsearch.painless.Utility.divideWithoutOverflow((int)left.constant, (int)right.constant);
+            } else if (sort == Sort.LONG) {
+                constant = overflow ? (long)left.constant / (long)right.constant :
+                    org.elasticsearch.painless.Utility.divideWithoutOverflow((long)left.constant, (long)right.constant);
+            } else if (sort == Sort.FLOAT) {
+                constant = overflow ? (float)left.constant / (float)right.constant :
+                    org.elasticsearch.painless.Utility.divideWithoutOverflow((float)left.constant, (float)right.constant);
+            } else if (sort == Sort.DOUBLE) {
+                constant = overflow ? (double)left.constant / (double)right.constant :
+                    org.elasticsearch.painless.Utility.divideWithoutOverflow((double)left.constant, (double)right.constant);
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeRem(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply remainder [%] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final boolean overflow = settings.getNumericOverflow();
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant % (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant % (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = overflow ? (float)left.constant % (float)right.constant :
+                    org.elasticsearch.painless.Utility.remainderWithoutOverflow((float)left.constant, (float)right.constant);
+            } else if (sort == Sort.DOUBLE) {
+                constant = overflow ? (double)left.constant % (double)right.constant :
+                    org.elasticsearch.painless.Utility.remainderWithoutOverflow((double)left.constant, (double)right.constant);
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeAdd(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteAdd(definition, left.actual, right.actual);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply add [+] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        final Sort sort = promote.sort;
+
+        if (sort == Sort.STRING) {
+            left.expected = left.actual;
+
+            if (left instanceof EBinary && ((EBinary)left).operation == Operation.ADD && left.actual.sort == Sort.STRING) {
+                ((EBinary)left).cat = true;
+            }
+
+            right.expected = right.actual;
+
+            if (right instanceof EBinary && ((EBinary)right).operation == Operation.ADD && right.actual.sort == Sort.STRING) {
+                ((EBinary)right).cat = true;
+            }
+        } else {
+            left.expected = promote;
+            right.expected = promote;
+        }
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final boolean overflow = settings.getNumericOverflow();
+
+            if (sort == Sort.INT) {
+                constant = overflow ? (int)left.constant + (int)right.constant :
+                    Math.addExact((int)left.constant, (int)right.constant);
+            } else if (sort == Sort.LONG) {
+                constant = overflow ? (long)left.constant + (long)right.constant :
+                    Math.addExact((long)left.constant, (long)right.constant);
+            } else if (sort == Sort.FLOAT) {
+                constant = overflow ? (float)left.constant + (float)right.constant :
+                    org.elasticsearch.painless.Utility.addWithoutOverflow((float)left.constant, (float)right.constant);
+            } else if (sort == Sort.DOUBLE) {
+                constant = overflow ? (double)left.constant + (double)right.constant :
+                    org.elasticsearch.painless.Utility.addWithoutOverflow((double)left.constant, (double)right.constant);
+            } else if (sort == Sort.STRING) {
+                constant = "" + left.constant + right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeSub(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply subtract [-] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final boolean overflow = settings.getNumericOverflow();
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = overflow ? (int)left.constant - (int)right.constant :
+                    Math.subtractExact((int)left.constant, (int)right.constant);
+            } else if (sort == Sort.LONG) {
+                constant = overflow ? (long)left.constant - (long)right.constant :
+                    Math.subtractExact((long)left.constant, (long)right.constant);
+            } else if (sort == Sort.FLOAT) {
+                constant = overflow ? (float)left.constant - (float)right.constant :
+                    org.elasticsearch.painless.Utility.subtractWithoutOverflow((float)left.constant, (float)right.constant);
+            } else if (sort == Sort.DOUBLE) {
+                constant = overflow ? (double)left.constant - (double)right.constant :
+                    org.elasticsearch.painless.Utility.subtractWithoutOverflow((double)left.constant, (double)right.constant);
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeLSH(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, false, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply left shift [<<] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = definition.intType;
+        right.explicit = true;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant << (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant << (int)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeRSH(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, false, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply right shift [>>] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = definition.intType;
+        right.explicit = true;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant >> (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant >> (int)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeUSH(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, false, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply unsigned shift [>>>] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = definition.intType;
+        right.explicit = true;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant >>> (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant >>> (int)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeBWAnd(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, false, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply and [&] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant & (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant & (long)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeXor(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteXor(definition, left.actual, right.actual);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply xor [^] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.BOOL) {
+                constant = (boolean)left.constant ^ (boolean)right.constant;
+            } else if (sort == Sort.INT) {
+                constant = (int)left.constant ^ (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant ^ (long)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    private void analyzeBWOr(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, false, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply or [|] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant | (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant | (long)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (actual.sort == Sort.STRING && operation == Operation.ADD) {
+            if (!cat) {
+                WriterUtility.writeNewStrings(adapter);
+            }
+
+            left.write(settings, definition, adapter);
+
+            if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) {
+                WriterUtility.writeAppendStrings(adapter, left.actual.sort);
+            }
+
+            right.write(settings, definition, adapter);
+
+            if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) {
+                WriterUtility.writeAppendStrings(adapter, right.actual.sort);
+            }
+
+            if (!cat) {
+                WriterUtility.writeToStrings(adapter);
+            }
+        } else {
+            left.write(settings, definition, adapter);
+            right.write(settings, definition, adapter);
+
+            WriterUtility.writeBinaryInstruction(settings, definition, adapter, location, actual, operation);
+        }
+
+        WriterUtility.writeBranch(adapter, tru, fals);
+    }
+}

+ 139 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java

@@ -0,0 +1,139 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Operation;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a boolean expression.
+ */
+public final class EBool extends AExpression {
+
+    final Operation operation;
+    AExpression left;
+    AExpression right;
+
+    public EBool(final String location, final Operation operation, final AExpression left, final AExpression right) {
+        super(location);
+
+        this.operation = operation;
+        this.left = left;
+        this.right = right;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.expected = definition.booleanType;
+        left.analyze(settings, definition, variables);
+        left = left.cast(settings, definition, variables);
+
+        right.expected = definition.booleanType;
+        right.analyze(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            if (operation == Operation.AND) {
+                constant = (boolean)left.constant && (boolean)right.constant;
+            } else if (operation == Operation.OR) {
+                constant = (boolean)left.constant || (boolean)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (tru != null || fals != null) {
+            if (operation == Operation.AND) {
+                final Label localfals = fals == null ? new Label() : fals;
+
+                left.fals = localfals;
+                right.tru = tru;
+                right.fals = fals;
+
+                left.write(settings, definition, adapter);
+                right.write(settings, definition, adapter);
+
+                if (fals == null) {
+                    adapter.mark(localfals);
+                }
+            } else if (operation == Operation.OR) {
+                final Label localtru = tru == null ? new Label() : tru;
+
+                left.tru = localtru;
+                right.tru = tru;
+                right.fals = fals;
+
+                left.write(settings, definition, adapter);
+                right.write(settings, definition, adapter);
+
+                if (tru == null) {
+                    adapter.mark(localtru);
+                }
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        } else {
+            if (operation == Operation.AND) {
+                final Label localfals = new Label();
+                final Label end = new Label();
+
+                left.fals = localfals;
+                right.fals = localfals;
+
+                left.write(settings, definition, adapter);
+                right.write(settings, definition, adapter);
+
+                adapter.push(true);
+                adapter.goTo(end);
+                adapter.mark(localfals);
+                adapter.push(false);
+                adapter.mark(end);
+            } else if (operation == Operation.OR) {
+                final Label localtru = new Label();
+                final Label localfals = new Label();
+                final Label end = new Label();
+
+                left.tru = localtru;
+                right.fals = localfals;
+
+                left.write(settings, definition, adapter);
+                right.write(settings, definition, adapter);
+
+                adapter.mark(localtru);
+                adapter.push(true);
+                adapter.goTo(end);
+                adapter.mark(localfals);
+                adapter.push(false);
+                adapter.mark(end);
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+    }
+}

+ 47 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java

@@ -0,0 +1,47 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a boolean constant.
+ */
+public final class EBoolean extends AExpression {
+
+    public EBoolean(final String location, final boolean constant) {
+        super(location);
+
+        this.constant = constant;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        actual = definition.booleanType;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalArgumentException(error("Illegal tree structure."));
+    }
+}

+ 60 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java

@@ -0,0 +1,60 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Cast;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents an implicit cast in most cases, though it will replace
+ * explicit casts in the tree for simplicity.  (Internal only.)
+ */
+final class ECast extends AExpression {
+
+    final String type;
+    AExpression child;
+
+    Cast cast = null;
+
+    ECast(final String location, final AExpression child, final Cast cast) {
+        super(location);
+
+        this.type = null;
+        this.child = child;
+
+        this.cast = cast;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        throw new IllegalStateException(error("Illegal tree structure."));
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        child.write(settings, definition, adapter);
+        WriterUtility.writeCast(adapter, cast);
+        WriterUtility.writeBranch(adapter, tru, fals);
+    }
+}

+ 310 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java

@@ -0,0 +1,310 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Cast;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.AnalyzerCaster;
+import org.elasticsearch.painless.Operation;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.List;
+
+/**
+ * Represents the entirety of a variable/method chain for read/write operations.
+ */
+public final class EChain extends AExpression {
+
+    final List<ALink> links;
+    final boolean pre;
+    final boolean post;
+    Operation operation;
+    AExpression expression;
+
+    boolean cat = false;
+    Type promote = null;
+    boolean exact = false;
+    Cast there = null;
+    Cast back = null;
+
+    public EChain(final String location, final List<ALink> links,
+                  final boolean pre, final boolean post, final Operation operation, final AExpression expression) {
+        super(location);
+
+        this.links = links;
+        this.pre = pre;
+        this.post = post;
+        this.operation = operation;
+        this.expression = expression;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        analyzeLinks(settings, definition, variables);
+        analyzeIncrDecr();
+
+        if (operation != null) {
+            analyzeCompound(settings, definition, variables);
+        } else if (expression != null) {
+            analyzeWrite(settings, definition, variables);
+        } else {
+            analyzeRead();
+        }
+    }
+
+    private void analyzeLinks(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        ALink previous = null;
+        int index = 0;
+
+        while (index < links.size()) {
+            final ALink current = links.get(index);
+
+            if (previous != null) {
+                current.before = previous.after;
+
+                if (index == 1) {
+                    current.statik = previous.statik;
+                }
+            }
+
+            if (index == links.size() - 1) {
+                current.load = read;
+                current.store = expression != null || pre || post;
+            }
+
+            final ALink analyzed = current.analyze(settings, definition, variables);
+
+            if (analyzed == null) {
+                links.remove(index);
+            } else {
+                if (analyzed != current) {
+                    links.set(index, analyzed);
+                }
+
+                previous = analyzed;
+                ++index;
+            }
+        }
+
+        if (links.get(0).statik) {
+            links.remove(0);
+        }
+    }
+
+    private void analyzeIncrDecr() {
+        final ALink last = links.get(links.size() - 1);
+
+        if (pre && post) {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        } else if (pre || post) {
+            if (expression != null) {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+
+            final Sort sort = last.after.sort;
+
+            if (operation == Operation.INCR) {
+                if (sort == Sort.DOUBLE) {
+                    expression = new EConstant(location, 1D);
+                } else if (sort == Sort.FLOAT) {
+                    expression = new EConstant(location, 1F);
+                } else if (sort == Sort.LONG) {
+                    expression = new EConstant(location, 1L);
+                } else {
+                    expression = new EConstant(location, 1);
+                }
+
+                operation = Operation.ADD;
+            } else if (operation == Operation.DECR) {
+                if (sort == Sort.DOUBLE) {
+                    expression = new EConstant(location, 1D);
+                } else if (sort == Sort.FLOAT) {
+                    expression = new EConstant(location, 1F);
+                } else if (sort == Sort.LONG) {
+                    expression = new EConstant(location, 1L);
+                } else {
+                    expression = new EConstant(location, 1);
+                }
+
+                operation = Operation.SUB;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+    }
+
+    private void analyzeCompound(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        final ALink last = links.get(links.size() - 1);
+
+        expression.analyze(settings, definition, variables);
+
+        if (operation == Operation.MUL) {
+            promote = AnalyzerCaster.promoteNumeric(definition, last.after, expression.actual, true, true);
+        } else if (operation == Operation.DIV) {
+            promote = AnalyzerCaster.promoteNumeric(definition, last.after, expression.actual, true, true);
+        } else if (operation == Operation.REM) {
+            promote = AnalyzerCaster.promoteNumeric(definition, last.after, expression.actual, true, true);
+        } else if (operation == Operation.ADD) {
+            promote = AnalyzerCaster.promoteAdd(definition, last.after, expression.actual);
+        } else if (operation == Operation.SUB) {
+            promote = AnalyzerCaster.promoteNumeric(definition, last.after, expression.actual, true, true);
+        } else if (operation == Operation.LSH) {
+            promote = AnalyzerCaster.promoteNumeric(definition, last.after, false, true);
+        } else if (operation == Operation.RSH) {
+            promote = AnalyzerCaster.promoteNumeric(definition, last.after, false, true);
+        } else if (operation == Operation.USH) {
+            promote = AnalyzerCaster.promoteNumeric(definition, last.after, false, true);
+        } else if (operation == Operation.BWAND) {
+            promote = AnalyzerCaster.promoteXor(definition, last.after, expression.actual);
+        } else if (operation == Operation.XOR) {
+            promote = AnalyzerCaster.promoteXor(definition, last.after, expression.actual);
+        } else if (operation == Operation.BWOR) {
+            promote = AnalyzerCaster.promoteXor(definition, last.after, expression.actual);
+        } else {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        }
+
+        if (promote == null) {
+            throw new ClassCastException("Cannot apply compound assignment " +
+                "[" + operation.symbol + "=] to types [" + last.after + "] and [" + expression.actual + "].");
+        }
+
+        cat = operation == Operation.ADD && promote.sort == Sort.STRING;
+
+        if (cat) {
+            if (expression instanceof EBinary && ((EBinary)expression).operation == Operation.ADD &&
+                expression.actual.sort == Sort.STRING) {
+                ((EBinary)expression).cat = true;
+            }
+
+            expression.expected = expression.actual;
+        } else if (operation == Operation.LSH || operation == Operation.RSH || operation == Operation.USH) {
+            expression.expected = definition.intType;
+            expression.explicit = true;
+        } else {
+            expression.expected = promote;
+        }
+
+        expression = expression.cast(settings, definition, variables);
+
+        exact = !settings.getNumericOverflow() &&
+            (operation == Operation.MUL || operation == Operation.DIV || operation == Operation.REM ||
+                operation == Operation.ADD || operation == Operation.SUB);
+        there = AnalyzerCaster.getLegalCast(definition, location, last.after, promote, false);
+        back = AnalyzerCaster.getLegalCast(definition, location, promote, last.after, true);
+
+        statement = true;
+        actual = read ? last.after : definition.voidType;
+    }
+
+    private void analyzeWrite(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        final ALink last = links.get(links.size() - 1);
+
+        expression.expected = last.after;
+        expression.analyze(settings, definition, variables);
+        expression = expression.cast(settings, definition, variables);
+
+        statement = true;
+        actual = read ? last.after : definition.voidType;
+    }
+
+    private void analyzeRead() {
+        final ALink last = links.get(links.size() - 1);
+
+        constant = last.string;
+        statement = last.statement;
+        actual = last.after;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (cat) {
+            WriterUtility.writeNewStrings(adapter);
+        }
+
+        final ALink last = links.get(links.size() - 1);
+
+        for (final ALink link : links) {
+            link.write(settings, definition, adapter);
+
+            if (link == last && link.store) {
+                if (cat) {
+                    WriterUtility.writeDup(adapter, link.size, 1);
+                    link.load(settings, definition, adapter);
+                    WriterUtility.writeAppendStrings(adapter, link.after.sort);
+
+                    expression.write(settings, definition, adapter);
+
+                    if (!(expression instanceof EBinary) ||
+                        ((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) {
+                        WriterUtility.writeAppendStrings(adapter, expression.actual.sort);
+                    }
+
+                    WriterUtility.writeToStrings(adapter);
+                    WriterUtility.writeCast(adapter, back);
+
+                    if (link.load) {
+                        WriterUtility.writeDup(adapter, link.after.sort.size, link.size);
+                    }
+
+                    link.store(settings, definition, adapter);
+                } else if (operation != null) {
+                    WriterUtility.writeDup(adapter, link.size, 0);
+                    link.load(settings, definition, adapter);
+
+                    if (link.load && post) {
+                        WriterUtility.writeDup(adapter, link.after.sort.size, link.size);
+                    }
+
+                    WriterUtility.writeCast(adapter, there);
+                    expression.write(settings, definition, adapter);
+                    WriterUtility.writeBinaryInstruction(settings, definition, adapter, location, promote, operation);
+
+                    if (!exact || !WriterUtility.writeExactInstruction(definition, adapter, promote.sort, link.after.sort)) {
+                        WriterUtility.writeCast(adapter, back);
+                    }
+
+                    if (link.load && !post) {
+                        WriterUtility.writeDup(adapter, link.after.sort.size, link.size);
+                    }
+
+                    link.store(settings, definition, adapter);
+                } else {
+                    expression.write(settings, definition, adapter);
+
+                    if (link.load) {
+                        WriterUtility.writeDup(adapter, link.after.sort.size, link.size);
+                    }
+
+                    link.store(settings, definition, adapter);
+                }
+            } else {
+                link.load(settings, definition, adapter);
+            }
+        }
+
+        WriterUtility.writeBranch(adapter, tru, fals);
+    }
+}

+ 528 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java

@@ -0,0 +1,528 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.AnalyzerCaster;
+import org.elasticsearch.painless.Operation;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+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;
+
+/**
+ * Represents a comparison expression.
+ */
+public final class EComp extends AExpression {
+
+    final Operation operation;
+    AExpression left;
+    AExpression right;
+
+    public EComp(final String location, final Operation operation, final AExpression left, final AExpression right) {
+        super(location);
+
+        this.operation = operation;
+        this.left = left;
+        this.right = right;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (operation == Operation.EQ) {
+            analyzeEq(settings, definition, variables);
+        } else if (operation == Operation.EQR) {
+            analyzeEqR(settings, definition, variables);
+        } else if (operation == Operation.NE) {
+            analyzeNE(settings, definition, variables);
+        } else if (operation == Operation.NER) {
+            analyzeNER(settings, definition, variables);
+        } else if (operation == Operation.GTE) {
+            analyzeGTE(settings, definition, variables);
+        } else if (operation == Operation.GT) {
+            analyzeGT(settings, definition, variables);
+        } else if (operation == Operation.LTE) {
+            analyzeLTE(settings, definition, variables);
+        } else if (operation == Operation.LT) {
+            analyzeLT(settings, definition, variables);
+        } else {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        }
+    }
+
+    private void analyzeEq(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteEquality(definition, left.actual, right.actual);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply equals [==] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.isNull && right.isNull) {
+            throw new IllegalArgumentException(error("Extraneous comparison of null constants."));
+        }
+
+        if ((left.constant != null || left.isNull) && (right.constant != null || right.isNull)) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.BOOL) {
+                constant = (boolean)left.constant == (boolean)right.constant;
+            } else if (sort == Sort.INT) {
+                constant = (int)left.constant == (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant == (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = (float)left.constant == (float)right.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = (double)left.constant == (double)right.constant;
+            } else if (!left.isNull) {
+                constant = left.constant.equals(right.constant);
+            } else if (!right.isNull) {
+                constant = right.constant.equals(null);
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    private void analyzeEqR(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteReference(definition, left.actual, right.actual);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply reference equals [===] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.isNull && right.isNull) {
+            throw new IllegalArgumentException(error("Extraneous comparison of null constants."));
+        }
+
+        if ((left.constant != null || left.isNull) && (right.constant != null || right.isNull)) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.BOOL) {
+                constant = (boolean)left.constant == (boolean)right.constant;
+            } else if (sort == Sort.INT) {
+                constant = (int)left.constant == (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant == (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = (float)left.constant == (float)right.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = (double)left.constant == (double)right.constant;
+            } else {
+                constant = left.constant == right.constant;
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    private void analyzeNE(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteEquality(definition, left.actual, right.actual);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply not equals [!=] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.isNull && right.isNull) {
+            throw new IllegalArgumentException(error("Extraneous comparison of null constants."));
+        }
+
+        if ((left.constant != null || left.isNull) && (right.constant != null || right.isNull)) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.BOOL) {
+                constant = (boolean)left.constant != (boolean)right.constant;
+            } else if (sort == Sort.INT) {
+                constant = (int)left.constant != (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant != (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = (float)left.constant != (float)right.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = (double)left.constant != (double)right.constant;
+            } else if (!left.isNull) {
+                constant = !left.constant.equals(right.constant);
+            } else if (!right.isNull) {
+                constant = !right.constant.equals(null);
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    private void analyzeNER(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteReference(definition, left.actual, right.actual);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply reference not equals [!==] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.isNull && right.isNull) {
+            throw new IllegalArgumentException(error("Extraneous comparison of null constants."));
+        }
+
+        if ((left.constant != null || left.isNull) && (right.constant != null || right.isNull)) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.BOOL) {
+                constant = (boolean)left.constant != (boolean)right.constant;
+            } else if (sort == Sort.INT) {
+                constant = (int)left.constant != (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant != (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = (float)left.constant != (float)right.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = (double)left.constant != (double)right.constant;
+            } else {
+                constant = left.constant != right.constant;
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    private void analyzeGTE(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply greater than or equals [>=] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant >= (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant >= (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = (float)left.constant >= (float)right.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = (double)left.constant >= (double)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    private void analyzeGT(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply greater than [>] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant > (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant > (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = (float)left.constant > (float)right.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = (double)left.constant > (double)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    private void analyzeLTE(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply less than or equals [<=] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant <= (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant <= (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = (float)left.constant <= (float)right.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = (double)left.constant <= (double)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    private void analyzeLT(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, left.actual, right.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply less than [>=] to types " +
+                "[" + left.actual.name + "] and [" + right.actual.name + "]."));
+        }
+
+        left.expected = promote;
+        right.expected = promote;
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+
+        if (left.constant != null && right.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = (int)left.constant < (int)right.constant;
+            } else if (sort == Sort.LONG) {
+                constant = (long)left.constant < (long)right.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = (float)left.constant < (float)right.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = (double)left.constant < (double)right.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = definition.booleanType;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final boolean branch = tru != null || fals != null;
+        final org.objectweb.asm.Type rtype = right.actual.type;
+        final Sort rsort = right.actual.sort;
+
+        left.write(settings, definition, adapter);
+
+        if (!right.isNull) {
+            right.write(settings, definition, adapter);
+        }
+
+        final Label jump = tru != null ? tru : fals != null ? fals : new Label();
+        final Label end = new Label();
+
+        final boolean eq = (operation == Operation.EQ || operation == Operation.EQR) && (tru != null || fals == null) ||
+            (operation == Operation.NE || operation == Operation.NER) && fals != null;
+        final boolean ne = (operation == Operation.NE || operation == Operation.NER) && (tru != null || fals == null) ||
+            (operation == Operation.EQ || operation == Operation.EQR) && fals != null;
+        final boolean lt  = operation == Operation.LT  && (tru != null || fals == null) || operation == Operation.GTE && fals != null;
+        final boolean lte = operation == Operation.LTE && (tru != null || fals == null) || operation == Operation.GT  && fals != null;
+        final boolean gt  = operation == Operation.GT  && (tru != null || fals == null) || operation == Operation.LTE && fals != null;
+        final boolean gte = operation == Operation.GTE && (tru != null || fals == null) || operation == Operation.LT  && fals != null;
+
+        boolean writejump = true;
+
+        switch (rsort) {
+            case VOID:
+            case BYTE:
+            case SHORT:
+            case CHAR:
+                throw new IllegalStateException(error("Illegal tree structure."));
+            case BOOL:
+                if      (eq) adapter.ifZCmp(GeneratorAdapter.EQ, jump);
+                else if (ne) adapter.ifZCmp(GeneratorAdapter.NE, jump);
+                else {
+                    throw new IllegalStateException(error("Illegal tree structure."));
+                }
+
+                break;
+            case INT:
+            case LONG:
+            case FLOAT:
+            case DOUBLE:
+                if      (eq)  adapter.ifCmp(rtype, GeneratorAdapter.EQ, jump);
+                else if (ne)  adapter.ifCmp(rtype, GeneratorAdapter.NE, jump);
+                else if (lt)  adapter.ifCmp(rtype, GeneratorAdapter.LT, jump);
+                else if (lte) adapter.ifCmp(rtype, GeneratorAdapter.LE, jump);
+                else if (gt)  adapter.ifCmp(rtype, GeneratorAdapter.GT, jump);
+                else if (gte) adapter.ifCmp(rtype, GeneratorAdapter.GE, jump);
+                else {
+                    throw new IllegalStateException(error("Illegal tree structure."));
+                }
+
+                break;
+            case DEF:
+                if (eq) {
+                    if (right.isNull) {
+                        adapter.ifNull(jump);
+                    } else if (!left.isNull && operation == Operation.EQ) {
+                        adapter.invokeStatic(definition.defobjType.type, DEF_EQ_CALL);
+                    } else {
+                        adapter.ifCmp(rtype, GeneratorAdapter.EQ, jump);
+                    }
+                } else if (ne) {
+                    if (right.isNull) {
+                        adapter.ifNonNull(jump);
+                    } else if (!left.isNull && operation == Operation.NE) {
+                        adapter.invokeStatic(definition.defobjType.type, DEF_EQ_CALL);
+                        adapter.ifZCmp(GeneratorAdapter.EQ, jump);
+                    } else {
+                        adapter.ifCmp(rtype, GeneratorAdapter.NE, jump);
+                    }
+                } else if (lt) {
+                    adapter.invokeStatic(definition.defobjType.type, DEF_LT_CALL);
+                } else if (lte) {
+                    adapter.invokeStatic(definition.defobjType.type, DEF_LTE_CALL);
+                } else if (gt) {
+                    adapter.invokeStatic(definition.defobjType.type, DEF_GT_CALL);
+                } else if (gte) {
+                    adapter.invokeStatic(definition.defobjType.type, DEF_GTE_CALL);
+                } else {
+                    throw new IllegalStateException(error("Illegal tree structure."));
+                }
+
+                writejump = left.isNull || ne || operation == Operation.EQR;
+
+                if (branch && !writejump) {
+                    adapter.ifZCmp(GeneratorAdapter.NE, jump);
+                }
+
+                break;
+            default:
+                if (eq) {
+                    if (right.isNull) {
+                        adapter.ifNull(jump);
+                    } else if (operation == Operation.EQ) {
+                        adapter.invokeStatic(definition.utilityType.type, CHECKEQUALS);
+
+                        if (branch) {
+                            adapter.ifZCmp(GeneratorAdapter.NE, jump);
+                        }
+
+                        writejump = false;
+                    } else {
+                        adapter.ifCmp(rtype, GeneratorAdapter.EQ, jump);
+                    }
+                } else if (ne) {
+                    if (right.isNull) {
+                        adapter.ifNonNull(jump);
+                    } else if (operation == Operation.NE) {
+                        adapter.invokeStatic(definition.utilityType.type, CHECKEQUALS);
+                        adapter.ifZCmp(GeneratorAdapter.EQ, jump);
+                    } else {
+                        adapter.ifCmp(rtype, GeneratorAdapter.NE, jump);
+                    }
+                } else {
+                    throw new IllegalStateException(error("Illegal tree structure."));
+                }
+        }
+
+        if (!branch && writejump) {
+            adapter.push(false);
+            adapter.goTo(end);
+            adapter.mark(jump);
+            adapter.push(true);
+            adapter.mark(end);
+        }
+    }
+}

+ 94 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java

@@ -0,0 +1,94 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.AnalyzerCaster;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Respresents a conditional expression.
+ */
+public final class EConditional extends AExpression {
+
+    AExpression condition;
+    AExpression left;
+    AExpression right;
+
+    public EConditional(final String location, final AExpression condition, final AExpression left, final AExpression right) {
+        super(location);
+
+        this.condition = condition;
+        this.left = left;
+        this.right = right;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        condition.expected = definition.booleanType;
+        condition.analyze(settings, definition, variables);
+        condition = condition.cast(settings, definition, variables);
+
+        if (condition.constant != null) {
+            throw new IllegalArgumentException(error("Extraneous conditional statement."));
+        }
+
+        left.expected = expected;
+        left.explicit = explicit;
+        right.expected = expected;
+        right.explicit = explicit;
+        actual = expected;
+
+        left.analyze(settings, definition, variables);
+        right.analyze(settings, definition, variables);
+
+        if (expected == null) {
+            final Type promote = AnalyzerCaster.promoteConditional(definition, left.actual, right.actual, left.constant, right.constant);
+
+            left.expected = promote;
+            right.expected = promote;
+            actual = promote;
+        }
+
+        left = left.cast(settings, definition, variables);
+        right = right.cast(settings, definition, variables);
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final Label localfals = new Label();
+        final Label end = new Label();
+
+        condition.fals = localfals;
+        left.tru = right.tru = tru;
+        left.fals = right.fals = fals;
+
+        condition.write(settings, definition, adapter);
+        left.write(settings, definition, adapter);
+        adapter.goTo(end);
+        adapter.mark(localfals);
+        right.write(settings, definition, adapter);
+        adapter.mark(end);
+    }
+}

+ 97 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java

@@ -0,0 +1,97 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Respresents a constant.  Note this replaces any other expression
+ * node with a constant value set during a cast.  (Internal only.)
+ */
+final class EConstant extends AExpression {
+
+    EConstant(final String location, final Object constant) {
+        super(location);
+
+        this.constant = constant;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (constant instanceof String) {
+            actual = definition.stringType;
+        } else if (constant instanceof Double) {
+            actual = definition.doubleType;
+        } else if (constant instanceof Float) {
+            actual = definition.floatType;
+        } else if (constant instanceof Long) {
+            actual = definition.longType;
+        } else if (constant instanceof Integer) {
+            actual = definition.intType;
+        } else if (constant instanceof Character) {
+            actual = definition.charType;
+        } else if (constant instanceof Short) {
+            actual = definition.shortType;
+        } else if (constant instanceof Byte) {
+            actual = definition.byteType;
+        } else if (constant instanceof Boolean) {
+            actual = definition.booleanType;
+        } else {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        }
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final Sort sort = actual.sort;
+
+        switch (sort) {
+            case STRING: adapter.push((String)constant);  break;
+            case DOUBLE: adapter.push((double)constant);  break;
+            case FLOAT:  adapter.push((float)constant);   break;
+            case LONG:   adapter.push((long)constant);    break;
+            case INT:    adapter.push((int)constant);     break;
+            case CHAR:   adapter.push((char)constant);    break;
+            case SHORT:  adapter.push((short)constant);   break;
+            case BYTE:   adapter.push((byte)constant);    break;
+            case BOOL:
+                if (tru != null && (boolean)constant) {
+                    adapter.goTo(tru);
+                } else if (fals != null && !(boolean)constant) {
+                    adapter.goTo(fals);
+                } else if (tru == null && fals == null) {
+                    adapter.push((boolean)constant);
+                }
+
+                break;
+            default:
+                throw new IllegalStateException(error("Illegal tree structure."));
+        }
+
+        if (sort != Sort.BOOL) {
+            WriterUtility.writeBranch(adapter, tru, fals);
+        }
+    }
+}

+ 63 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java

@@ -0,0 +1,63 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Respresents a decimal constant.
+ */
+public final class EDecimal extends AExpression {
+
+    final String value;
+
+    public EDecimal(final String location, final String value) {
+        super(location);
+
+        this.value = value;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (value.endsWith("f") || value.endsWith("F")) {
+            try {
+                constant = Float.parseFloat(value.substring(0, value.length() - 1));
+                actual = definition.floatType;
+            } catch (final NumberFormatException exception) {
+                throw new IllegalArgumentException(error("Invalid float constant [" + value + "]."));
+            }
+        } else {
+            try {
+                constant = Double.parseDouble(value);
+                actual = definition.doubleType;
+            } catch (final NumberFormatException exception) {
+                throw new IllegalArgumentException(error("Invalid double constant [" + value + "]."));
+            }
+        }
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalArgumentException(error("Illegal tree structure."));
+    }
+}

+ 70 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java

@@ -0,0 +1,70 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Cast;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents an explicit cast.
+ */
+public final class EExplicit extends AExpression {
+
+    final String type;
+    AExpression child;
+
+    Cast cast = null;
+
+    public EExplicit(final String location, final String type, final AExpression child) {
+        super(location);
+
+        this.type = type;
+        this.child = child;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        try {
+            actual = definition.getType(this.type);
+        } catch (final IllegalArgumentException exception) {
+            throw new IllegalArgumentException(error("Not a type [" + this.type + "]."));
+        }
+
+        child.expected = actual;
+        child.explicit = true;
+        child.analyze(settings, definition, variables);
+        child = child.cast(settings, definition, variables);
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalArgumentException(error("Illegal tree structure."));
+    }
+
+    AExpression cast(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        child.expected = expected;
+        child.explicit = explicit;
+
+        return child.cast(settings, definition, variables);
+    }
+}

+ 56 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java

@@ -0,0 +1,56 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a null constant.
+ */
+public final class ENull extends AExpression {
+
+    public ENull(final String location) {
+        super(location);
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        isNull = true;
+
+        if (expected != null) {
+            if (expected.sort.primitive) {
+                throw new IllegalArgumentException(error("Cannot cast null to a primitive type [" + expected.name + "]."));
+            }
+
+            actual = expected;
+        } else {
+            actual = definition.objectType;
+        }
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.visitInsn(Opcodes.ACONST_NULL);
+    }
+}

+ 102 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java

@@ -0,0 +1,102 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Respresents a non-decimal numeric constant.
+ */
+public final class ENumeric extends AExpression {
+
+    final String value;
+    int radix;
+
+    public ENumeric(final String location, final String value, final int radix) {
+        super(location);
+
+        this.value = value;
+        this.radix = radix;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (value.endsWith("d") || value.endsWith("D")) {
+            if (radix != 10) {
+                throw new IllegalStateException(error("Invalid tree structure."));
+            }
+
+            try {
+                constant = Double.parseDouble(value.substring(0, value.length() - 1));
+                actual = definition.doubleType;
+            } catch (final NumberFormatException exception) {
+                throw new IllegalArgumentException(error("Invalid double constant [" + value + "]."));
+            }
+        } else if (value.endsWith("f") || value.endsWith("F")) {
+            if (radix != 10) {
+                throw new IllegalStateException(error("Invalid tree structure."));
+            }
+
+            try {
+                constant = Float.parseFloat(value.substring(0, value.length() - 1));
+                actual = definition.floatType;
+            } catch (final NumberFormatException exception) {
+                throw new IllegalArgumentException(error("Invalid float constant [" + value + "]."));
+            }
+        } else if (value.endsWith("l") || value.endsWith("L")) {
+            try {
+                constant = Long.parseLong(value.substring(0, value.length() - 1), radix);
+                actual = definition.longType;
+            } catch (final NumberFormatException exception) {
+                throw new IllegalArgumentException(error("Invalid long constant [" + value + "]."));
+            }
+        } else {
+            try {
+                final Sort sort = expected == null ? Sort.INT : expected.sort;
+                final int integer = Integer.parseInt(value, radix);
+
+                if (sort == Sort.BYTE && integer >= Byte.MIN_VALUE && integer <= Byte.MAX_VALUE) {
+                    constant = (byte)integer;
+                    actual = definition.byteType;
+                } else if (sort == Sort.CHAR && integer >= Character.MIN_VALUE && integer <= Character.MAX_VALUE) {
+                    constant = (char)integer;
+                    actual = definition.charType;
+                } else if (sort == Sort.SHORT && integer >= Short.MIN_VALUE && integer <= Short.MAX_VALUE) {
+                    constant = (short)integer;
+                    actual = definition.shortType;
+                } else {
+                    constant = integer;
+                    actual = definition.intType;
+                }
+            } catch (final NumberFormatException exception) {
+                throw new IllegalArgumentException(error("Invalid int constant [" + value + "]."));
+            }
+        }
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalArgumentException(error("Illegal tree structure."));
+    }
+}

+ 234 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java

@@ -0,0 +1,234 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.AnalyzerCaster;
+import org.elasticsearch.painless.Operation;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+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;
+
+/**
+ * Represents a unary math expression.
+ */
+public final class EUnary extends AExpression {
+
+    Operation operation;
+    AExpression child;
+
+    public EUnary(final String location, final Operation operation, final AExpression child) {
+        super(location);
+
+        this.operation = operation;
+        this.child = child;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (operation == Operation.NOT) {
+            analyzeNot(settings, definition, variables);
+        } else if (operation == Operation.BWNOT) {
+            analyzeBWNot(settings, definition, variables);
+        } else if (operation == Operation.ADD) {
+            analyzerAdd(settings, definition, variables);
+        } else if (operation == Operation.SUB) {
+            analyzerSub(settings, definition, variables);
+        } else {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        }
+    }
+
+    void analyzeNot(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        child.expected = definition.booleanType;
+        child.analyze(settings, definition, variables);
+        child = child.cast(settings, definition, variables);
+
+        if (child.constant != null) {
+            constant = !(boolean)child.constant;
+        }
+
+        actual = definition.booleanType;
+    }
+
+    void analyzeBWNot(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        child.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, child.actual, false, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply not [~] to type [" + child.actual.name + "]."));
+        }
+
+        child.expected = promote;
+        child = child.cast(settings, definition, variables);
+
+        if (child.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = ~(int)child.constant;
+            } else if (sort == Sort.LONG) {
+                constant = ~(long)child.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    void analyzerAdd(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        child.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, child.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply positive [+] to type [" + child.actual.name + "]."));
+        }
+
+        child.expected = promote;
+        child = child.cast(settings, definition, variables);
+
+        if (child.constant != null) {
+            final Sort sort = promote.sort;
+
+            if (sort == Sort.INT) {
+                constant = +(int)child.constant;
+            } else if (sort == Sort.LONG) {
+                constant = +(long)child.constant;
+            } else if (sort == Sort.FLOAT) {
+                constant = +(float)child.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = +(double)child.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    void analyzerSub(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        child.analyze(settings, definition, variables);
+
+        final Type promote = AnalyzerCaster.promoteNumeric(definition, child.actual, true, true);
+
+        if (promote == null) {
+            throw new ClassCastException(error("Cannot apply negative [-] to type [" + child.actual.name + "]."));
+        }
+
+        child.expected = promote;
+        child = child.cast(settings, definition, variables);
+
+        if (child.constant != null) {
+            final boolean overflow = settings.getNumericOverflow();
+            final Sort sort = promote.sort;
+
+
+            if (sort == Sort.INT) {
+                constant = overflow ? -(int)child.constant : Math.negateExact((int)child.constant);
+            } else if (sort == Sort.LONG) {
+                constant = overflow ? -(long)child.constant : Math.negateExact((long)child.constant);
+            } else if (sort == Sort.FLOAT) {
+                constant = -(float)child.constant;
+            } else if (sort == Sort.DOUBLE) {
+                constant = -(double)child.constant;
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        actual = promote;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (operation == Operation.NOT) {
+            if (tru == null && fals == null) {
+                final Label localfals = new Label();
+                final Label end = new Label();
+
+                child.fals = localfals;
+                child.write(settings, definition, adapter);
+
+                adapter.push(false);
+                adapter.goTo(end);
+                adapter.mark(localfals);
+                adapter.push(true);
+                adapter.mark(end);
+            } else {
+                child.tru = fals;
+                child.fals = tru;
+                child.write(settings, definition, adapter);
+            }
+        } else {
+            final org.objectweb.asm.Type type = actual.type;
+            final Sort sort = actual.sort;
+
+            child.write(settings, definition, adapter);
+
+            if (operation == Operation.BWNOT) {
+                if (sort == Sort.DEF) {
+                    adapter.invokeStatic(definition.defobjType.type, DEF_NOT_CALL);
+                } else {
+                    if (sort == Sort.INT) {
+                        adapter.push(-1);
+                    } else if (sort == Sort.LONG) {
+                        adapter.push(-1L);
+                    } else {
+                        throw new IllegalStateException(error("Illegal tree structure."));
+                    }
+
+                    adapter.math(GeneratorAdapter.XOR, type);
+                }
+            } else if (operation == Operation.SUB) {
+                if (sort == Sort.DEF) {
+                    adapter.invokeStatic(definition.defobjType.type, DEF_NEG_CALL);
+                } else {
+                    if (settings.getNumericOverflow()) {
+                        adapter.math(GeneratorAdapter.NEG, type);
+                    } else {
+                        if (sort == Sort.INT) {
+                            adapter.invokeStatic(definition.mathType.type, NEGATEEXACT_INT);
+                        } else if (sort == Sort.LONG) {
+                            adapter.invokeStatic(definition.mathType.type, NEGATEEXACT_LONG);
+                        } else {
+                            throw new IllegalStateException(error("Illegal tree structure."));
+                        }
+                    }
+                }
+            } else if (operation != Operation.ADD) {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+
+            WriterUtility.writeBranch(adapter, tru, fals);
+        }
+    }
+}

+ 71 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java

@@ -0,0 +1,71 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents an array length field load.
+ */
+public final class LArrayLength extends ALink {
+
+    final String value;
+
+    LArrayLength(final String location, final String value) {
+        super(location, -1);
+
+        this.value = value;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if ("length".equals(value)) {
+            if (!load) {
+                throw new IllegalArgumentException(error("Must read array field [length]."));
+            } else if (store) {
+                throw new IllegalArgumentException(error("Cannot write to read-only array field [length]."));
+            }
+
+            after = definition.intType;
+        } else {
+            throw new IllegalArgumentException(error("Illegal field access [" + value + "]."));
+        }
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.arrayLength();
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalStateException(error("Illegal tree structure."));
+    }
+}

+ 98 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java

@@ -0,0 +1,98 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents an array load/store or defers to possible shortcuts.
+ */
+public final class LBrace extends ALink {
+
+    AExpression index;
+
+    public LBrace(final String location, final AExpression index) {
+        super(location, 2);
+
+        this.index = index;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (before == null) {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        }
+
+        final Sort sort = before.sort;
+
+        if (sort == Sort.ARRAY) {
+            index.expected = definition.intType;
+            index.analyze(settings, definition, variables);
+            index = index.cast(settings, definition, variables);
+
+            after = definition.getType(before.struct, before.dimensions - 1);
+
+            return this;
+        } else if (sort == Sort.DEF) {
+            return new LDefArray(location, index).copy(this).analyze(settings, definition, variables);
+        } else {
+            try {
+                before.clazz.asSubclass(Map.class);
+
+                return new LMapShortcut(location, index).copy(this).analyze(settings, definition, variables);
+            } catch (final ClassCastException exception) {
+                // Do nothing.
+            }
+
+            try {
+                before.clazz.asSubclass(List.class);
+
+                return new LListShortcut(location, index).copy(this).analyze(settings, definition, variables);
+            } catch (final ClassCastException exception) {
+                // Do nothing.
+            }
+        }
+
+        throw new IllegalArgumentException(error("Illegal array access on type [" + before.name + "]."));
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        index.write(settings, definition, adapter);
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.arrayLoad(after.type);
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.arrayStore(after.type);
+    }
+
+}

+ 120 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCall.java

@@ -0,0 +1,120 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Method;
+import org.elasticsearch.painless.Definition.Struct;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.List;
+
+/**
+ * Represents a method call or deferes to a def call.
+ */
+public final class LCall extends ALink {
+
+    final String name;
+    final List<AExpression> arguments;
+
+    Method method = null;
+
+    public LCall(final String location, final String name, final List<AExpression> arguments) {
+        super(location, -1);
+
+        this.name = name;
+        this.arguments = arguments;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (before == null) {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        } else if (before.sort == Definition.Sort.ARRAY) {
+            throw new IllegalArgumentException(error("Illegal call [" + name + "] on array type."));
+        } else if (store) {
+            throw new IllegalArgumentException(error("Cannot assign a value to a call [" + name + "]."));
+        }
+
+        final Struct struct = before.struct;
+        method = statik ? struct.functions.get(name) : struct.methods.get(name);
+
+        if (method != null) {
+            final Definition.Type[] types = new Definition.Type[method.arguments.size()];
+            method.arguments.toArray(types);
+
+            if (method.arguments.size() != arguments.size()) {
+                throw new IllegalArgumentException(error("When calling [" + name + "] on type [" + struct.name + "]" +
+                    " expected [" + method.arguments.size() + "] arguments, but found [" + arguments.size() + "]."));
+            }
+
+            for (int argument = 0; argument < arguments.size(); ++argument) {
+                final AExpression expression = arguments.get(argument);
+
+                expression.expected = types[argument];
+                expression.analyze(settings, definition, variables);
+                arguments.set(argument, expression.cast(settings, definition, variables));
+            }
+
+            statement = true;
+            after = method.rtn;
+
+            return this;
+        } else if (before.sort == Definition.Sort.DEF) {
+            final ALink link = new LDefCall(location, name, arguments);
+            link.copy(this);
+
+            return link.analyze(settings, definition, variables);
+        }
+
+        throw new IllegalArgumentException(error("Unknown call [" + name + "] on type [" + struct.name + "]."));
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        for (final AExpression argument : arguments) {
+            argument.write(settings, definition, adapter);
+        }
+
+        if (java.lang.reflect.Modifier.isStatic(method.reflect.getModifiers())) {
+            adapter.invokeStatic(method.owner.type, method.method);
+        } else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) {
+            adapter.invokeInterface(method.owner.type, method.method);
+        } else {
+            adapter.invokeVirtual(method.owner.type, method.method);
+        }
+
+        if (!method.rtn.clazz.equals(method.handle.type().returnType())) {
+            adapter.checkCast(method.rtn.type);
+        }
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalStateException(error("Illegal tree structure."));
+    }
+}

+ 78 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java

@@ -0,0 +1,78 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Cast;
+import org.elasticsearch.painless.AnalyzerCaster;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a cast made in a variable/method chain.
+ */
+public final class LCast extends ALink {
+
+    final String type;
+
+    Cast cast = null;
+
+    public LCast(final String location, final String type) {
+        super(location, -1);
+
+        this.type = type;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (before == null) {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        } else if (store) {
+            throw new IllegalArgumentException(error("Cannot assign a value to a cast."));
+        }
+
+        try {
+            after = definition.getType(type);
+        } catch (final IllegalArgumentException exception) {
+            throw new IllegalArgumentException(error("Not a type [" + type + "]."));
+        }
+
+        cast = AnalyzerCaster.getLegalCast(definition, location, before, after, true);
+
+        return cast != null ? this : null;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        WriterUtility.writeCast(adapter, cast);
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalStateException(error("Illegal tree structure."));
+    }
+}

+ 74 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java

@@ -0,0 +1,74 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.DynamicCallSite;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
+import static org.elasticsearch.painless.WriterConstants.DEF_DYNAMIC_ARRAY_LOAD_DESC;
+import static org.elasticsearch.painless.WriterConstants.DEF_DYNAMIC_ARRAY_STORE_DESC;
+
+/**
+ * Represents an array load/store or shortcut on a def type.  (Internal only.)
+ */
+final class LDefArray extends ALink {
+
+    AExpression index;
+
+    LDefArray(final String location, final AExpression index) {
+        super(location, 0);
+
+        this.index = index;
+    }
+
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        index.expected = definition.objectType;
+        index.analyze(settings, definition, variables);
+        index = index.cast(settings, definition, variables);
+
+        after = definition.defType;
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        index.write(settings, definition, adapter);
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.visitInvokeDynamicInsn(
+            "arrayLoad", DEF_DYNAMIC_ARRAY_LOAD_DESC, DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.ARRAY_LOAD });
+
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.visitInvokeDynamicInsn(
+            "arrayStore", DEF_DYNAMIC_ARRAY_STORE_DESC, DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.ARRAY_STORE });
+    }
+}

+ 94 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java

@@ -0,0 +1,94 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.DynamicCallSite;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.List;
+
+import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
+
+/**
+ * Represents a method call made on a def type. (Internal only.)
+ */
+final class LDefCall extends ALink {
+
+    final String name;
+    final List<AExpression> arguments;
+
+    LDefCall(final String location, final String name, final List<AExpression> arguments) {
+        super(location, -1);
+
+        this.name = name;
+        this.arguments = arguments;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        for (int argument = 0; argument < arguments.size(); ++argument) {
+            final AExpression expression = arguments.get(argument);
+
+            expression.analyze(settings, definition, variables);
+            expression.expected = expression.actual;
+            arguments.set(argument, expression.cast(settings, definition, variables));
+        }
+
+        statement = true;
+        after = definition.defType;
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final StringBuilder signature = new StringBuilder();
+
+        signature.append('(');
+        // first parameter is the receiver, we never know its type: always Object
+        signature.append(definition.defType.type.getDescriptor());
+
+        // TODO: remove our explicit conversions and feed more type information for return value,
+        // it can avoid some unnecessary boxing etc.
+        for (final AExpression argument : arguments) {
+            signature.append(argument.actual.type.getDescriptor());
+            argument.write(settings, definition, adapter);
+        }
+
+        signature.append(')');
+        // return value
+        signature.append(definition.defType.type.getDescriptor());
+
+        adapter.visitInvokeDynamicInsn(name, signature.toString(), DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.METHOD_CALL });
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalStateException(error("Illegal tree structure."));
+    }
+}

+ 67 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java

@@ -0,0 +1,67 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.DynamicCallSite;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
+import static org.elasticsearch.painless.WriterConstants.DEF_DYNAMIC_LOAD_FIELD_DESC;
+import static org.elasticsearch.painless.WriterConstants.DEF_DYNAMIC_STORE_FIELD_DESC;
+
+/**
+ * Represents a field load/store or shortcut on a def type.  (Internal only.)
+ */
+final class LDefField extends ALink {
+
+    final String value;
+
+    LDefField(final String location, final String value) {
+        super(location, 1);
+
+        this.value = value;
+    }
+
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        after = definition.defType;
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.visitInvokeDynamicInsn(value, DEF_DYNAMIC_LOAD_FIELD_DESC, DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.LOAD });
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.visitInvokeDynamicInsn(value, DEF_DYNAMIC_STORE_FIELD_DESC, DEF_BOOTSTRAP_HANDLE, new Object[] { DynamicCallSite.STORE });
+    }
+}

+ 136 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LField.java

@@ -0,0 +1,136 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Field;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Struct;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a field load/store or defers to a possible shortcuts.
+ */
+public final class LField extends ALink {
+
+    final String value;
+
+    Field field;
+
+    public LField(final String location, final String value) {
+        super(location, 1);
+
+        this.value = value;
+    }
+
+    @Override
+    ALink analyze(CompilerSettings settings, Definition definition, Variables variables) {
+        if (before == null) {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        }
+
+        final Sort sort = before.sort;
+
+        if (sort == Sort.ARRAY) {
+            return new LArrayLength(location, value).copy(this).analyze(settings, definition, variables);
+        } else if (sort == Sort.DEF) {
+            return new LDefField(location, value).copy(this).analyze(settings, definition, variables);
+        }
+
+        final Struct struct = before.struct;
+        field = 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(error(
+                    "Cannot write to read-only field [" + value + "] for type [" + struct.name + "]."));
+            }
+
+            after = field.type;
+
+            return this;
+        } else {
+            final boolean shortcut =
+                struct.methods.containsKey("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1)) ||
+                struct.methods.containsKey("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1));
+
+            if (shortcut) {
+                return new LShortcut(location, value).copy(this).analyze(settings, definition, variables);
+            } else {
+                final EConstant index = new EConstant(location, value);
+                index.analyze(settings, definition, variables);
+
+                try {
+                    before.clazz.asSubclass(Map.class);
+
+                    return new LMapShortcut(location, index).copy(this).analyze(settings, definition, variables);
+                } catch (final ClassCastException exception) {
+                    // Do nothing.
+                }
+
+                try {
+                    before.clazz.asSubclass(List.class);
+
+                    return new LListShortcut(location, index).copy(this).analyze(settings, definition, variables);
+                } catch (final ClassCastException exception) {
+                    // Do nothing.
+                }
+            }
+        }
+
+        throw new IllegalArgumentException(error("Unknown field [" + value + "] for type [" + struct.name + "]."));
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (java.lang.reflect.Modifier.isStatic(field.reflect.getModifiers())) {
+            adapter.getStatic(field.owner.type, field.reflect.getName(), field.type.type);
+
+            if (!field.generic.clazz.equals(field.type.clazz)) {
+                adapter.checkCast(field.generic.type);
+            }
+        } else {
+            adapter.getField(field.owner.type, field.reflect.getName(), field.type.type);
+
+            if (!field.generic.clazz.equals(field.type.clazz)) {
+                adapter.checkCast(field.generic.type);
+            }
+        }
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (java.lang.reflect.Modifier.isStatic(field.reflect.getModifiers())) {
+            adapter.putStatic(field.owner.type, field.reflect.getName(), field.type.type);
+        } else {
+            adapter.putField(field.owner.type, field.reflect.getName(), field.type.type);
+        }
+    }
+}

+ 105 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java

@@ -0,0 +1,105 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Method;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a list load/store shortcut.  (Internal only.)
+ */
+final class LListShortcut extends ALink {
+
+    AExpression index;
+    Method getter;
+    Method setter;
+
+    LListShortcut(final String location, final AExpression index) {
+        super(location, 2);
+
+        this.index = index;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        getter = before.struct.methods.get("get");
+        setter = before.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(error("Illegal list get shortcut for type [" + before.name + "]."));
+        }
+
+        if (setter != null && (setter.arguments.size() != 2 || setter.arguments.get(0).sort != Sort.INT)) {
+            throw new IllegalArgumentException(error("Illegal list set shortcut for type [" + before.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(error("Shortcut argument types must match."));
+        }
+
+        if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
+            index.expected = definition.intType;
+            index.analyze(settings, definition, variables);
+            index = index.cast(settings, definition, variables);
+
+            after = setter != null ? setter.arguments.get(1) : getter.rtn;
+        } else {
+            throw new IllegalArgumentException(error("Illegal list shortcut for type [" + before.name + "]."));
+        }
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        index.write(settings, definition, adapter);
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
+            adapter.invokeInterface(getter.owner.type, getter.method);
+        } else {
+            adapter.invokeVirtual(getter.owner.type, getter.method);
+        }
+
+        if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
+            adapter.checkCast(getter.rtn.type);
+        }
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
+            adapter.invokeInterface(setter.owner.type, setter.method);
+        } else {
+            adapter.invokeVirtual(setter.owner.type, setter.method);
+        }
+
+        WriterUtility.writePop(adapter, setter.rtn.sort.size);
+    }
+}

+ 104 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java

@@ -0,0 +1,104 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Method;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a map load/store shortcut. (Internal only.)
+ */
+final class LMapShortcut extends ALink {
+
+    AExpression index;
+    Method getter;
+    Method setter;
+
+    LMapShortcut(final String location, final AExpression index) {
+        super(location, 2);
+
+        this.index = index;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        getter = before.struct.methods.get("get");
+        setter = before.struct.methods.get("put");
+
+        if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1)) {
+            throw new IllegalArgumentException(error("Illegal map get shortcut for type [" + before.name + "]."));
+        }
+
+        if (setter != null && setter.arguments.size() != 2) {
+            throw new IllegalArgumentException(error("Illegal map set shortcut for type [" + before.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(error("Shortcut argument types must match."));
+        }
+
+        if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
+            index.expected = setter != null ? setter.arguments.get(0) : getter.arguments.get(0);
+            index.analyze(settings, definition, variables);
+            index = index.cast(settings, definition, variables);
+
+            after = setter != null ? setter.arguments.get(1) : getter.rtn;
+        } else {
+            throw new IllegalArgumentException(error("Illegal map shortcut for type [" + before.name + "]."));
+        }
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        index.write(settings, definition, adapter);
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
+            adapter.invokeInterface(getter.owner.type, getter.method);
+        } else {
+            adapter.invokeVirtual(getter.owner.type, getter.method);
+        }
+
+        if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
+            adapter.checkCast(getter.rtn.type);
+        }
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
+            adapter.invokeInterface(setter.owner.type, setter.method);
+        } else {
+            adapter.invokeVirtual(setter.owner.type, setter.method);
+        }
+
+        WriterUtility.writePop(adapter, setter.rtn.sort.size);
+    }
+}

+ 98 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewArray.java

@@ -0,0 +1,98 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.List;
+
+/**
+ * Represents an array instantiation.
+ */
+public final class LNewArray extends ALink {
+
+    final String type;
+    final List<AExpression> arguments;
+
+    public LNewArray(final String location, final String type, final List<AExpression> arguments) {
+        super(location, -1);
+
+        this.type = type;
+        this.arguments = arguments;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (before != null) {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        } else if (store) {
+            throw new IllegalArgumentException(error("Cannot assign a value to a new array."));
+        } else if (!load) {
+            throw new IllegalArgumentException(error("A newly created array must be assigned."));
+        }
+
+        final Type type;
+
+        try {
+            type = definition.getType(this.type);
+        } catch (final IllegalArgumentException exception) {
+            throw new IllegalArgumentException(error("Not a type [" + this.type + "]."));
+        }
+
+        for (int argument = 0; argument < arguments.size(); ++argument) {
+            final AExpression expression = arguments.get(argument);
+
+            expression.expected = definition.intType;
+            expression.analyze(settings, definition, variables);
+            arguments.set(argument, expression.cast(settings, definition, variables));
+        }
+
+        after = definition.getType(type.struct, arguments.size());
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        for (final AExpression argument : arguments) {
+            argument.write(settings, definition, adapter);
+        }
+
+        if (arguments.size() > 1) {
+            adapter.visitMultiANewArrayInsn(after.type.getDescriptor(), after.type.getDimensions());
+        } else {
+            adapter.newArray(definition.getType(after.struct, 0).type);
+        }
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalStateException(error("Illegal tree structure."));
+    }
+}

+ 118 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewObj.java

@@ -0,0 +1,118 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Constructor;
+import org.elasticsearch.painless.Definition.Struct;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.List;
+
+/**
+ * Respresents and object instantiation.
+ */
+public final class LNewObj extends ALink {
+
+    final String type;
+    final List<AExpression> arguments;
+
+    Constructor constructor;
+
+    public LNewObj(final String location, final String type, final List<AExpression> arguments) {
+        super(location, -1);
+
+        this.type = type;
+        this.arguments = arguments;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (before != null) {
+            throw new IllegalStateException(error("Illegal tree structure"));
+        } else if (store) {
+            throw new IllegalArgumentException(error("Cannot assign a value to a new call."));
+        }
+
+        final Type type;
+
+        try {
+            type = definition.getType(this.type);
+        } catch (final IllegalArgumentException exception) {
+            throw new IllegalArgumentException(error("Not a type [" + this.type + "]."));
+        }
+
+        final Struct struct = type.struct;
+        constructor = struct.constructors.get("new");
+
+        if (constructor != null) {
+            final Type[] types = new Type[constructor.arguments.size()];
+            constructor.arguments.toArray(types);
+
+            if (constructor.arguments.size() != arguments.size()) {
+                throw new IllegalArgumentException(error("When calling constructor on type [" + struct.name + "]" +
+                    " expected [" + constructor.arguments.size() + "] arguments, but found [" + arguments.size() + "]."));
+            }
+
+            for (int argument = 0; argument < arguments.size(); ++argument) {
+                final AExpression expression = arguments.get(argument);
+
+                expression.expected = types[argument];
+                expression.analyze(settings, definition, variables);
+                arguments.set(argument, expression.cast(settings, definition, variables));
+            }
+
+            statement = true;
+            after = type;
+        } else {
+            throw new IllegalArgumentException(error("Unknown new call on type [" + struct.name + "]."));
+        }
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.newInstance(after.type);
+
+        if (load) {
+            adapter.dup();
+        }
+
+        for (final AExpression argument : arguments) {
+            argument.write(settings, definition, adapter);
+        }
+
+        adapter.invokeConstructor(constructor.owner.type, constructor.method);
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalStateException(error("Illegal tree structure."));
+    }
+}

+ 105 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java

@@ -0,0 +1,105 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Method;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Definition.Struct;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a field load/store shortcut.  (Internal only.)
+ */
+final class LShortcut extends ALink {
+
+    final String value;
+
+    Method getter = null;
+    Method setter = null;
+
+    LShortcut(final String location, final String value) {
+        super(location, 1);
+
+        this.value = value;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        final Struct struct = before.struct;
+
+        getter = struct.methods.get("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1));
+        setter = struct.methods.get("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1));
+
+        if (getter != null && (getter.rtn.sort == Sort.VOID || !getter.arguments.isEmpty())) {
+            throw new IllegalArgumentException(error(
+                "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(error(
+                "Illegal set shortcut on field [" + value + "] for type [" + struct.name + "]."));
+        }
+
+        if (getter != null && setter != null && setter.arguments.get(0) != getter.rtn) {
+            throw new IllegalArgumentException(error("Shortcut argument types must match."));
+        }
+
+        if ((getter != null || setter != null) && (!load || getter != null) && (!store || setter != null)) {
+            after = setter != null ? setter.arguments.get(0) : getter.rtn;
+        } else {
+            throw new IllegalArgumentException(error("Illegal shortcut on field [" + value + "] for type [" + struct.name + "]."));
+        }
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
+            adapter.invokeInterface(getter.owner.type, getter.method);
+        } else {
+            adapter.invokeVirtual(getter.owner.type, getter.method);
+        }
+
+        if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
+            adapter.checkCast(getter.rtn.type);
+        }
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
+            adapter.invokeInterface(setter.owner.type, setter.method);
+        } else {
+            adapter.invokeVirtual(setter.owner.type, setter.method);
+        }
+
+        WriterUtility.writePop(adapter, setter.rtn.sort.size);
+    }
+}

+ 67 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LString.java

@@ -0,0 +1,67 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a string constant.
+ */
+public final class LString extends ALink {
+
+    public LString(final String location, final String string) {
+        super(location, -1);
+
+        this.string = string;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (before != null) {
+            throw new IllegalStateException("Illegal tree structure.");
+        } else if (store) {
+            throw new IllegalArgumentException(error("Cannot write to read-only String constant [" + string + "]."));
+        } else if (!load) {
+            throw new IllegalArgumentException(error("Must read String constant [" + string + "]."));
+        }
+
+        after = definition.stringType;
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.push(string);
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        throw new IllegalStateException(error("Illegal tree structure."));
+    }
+}

+ 90 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LVariable.java

@@ -0,0 +1,90 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.Variables.Variable;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a variable load/store.
+ */
+public final class LVariable extends ALink {
+
+    final String name;
+
+    int slot;
+
+    public LVariable(final String location, final String name) {
+        super(location, 0);
+
+        this.name = name;
+    }
+
+    @Override
+    ALink analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (before != null) {
+            throw new IllegalStateException(error("Illegal tree structure."));
+        }
+
+        Type type = null;
+
+        try {
+            type = definition.getType(name);
+        } catch (final IllegalArgumentException exception) {
+            // Do nothing.
+        }
+
+        if (type != null) {
+            statik = true;
+            after = type;
+        } else {
+            final Variable variable = variables.getVariable(location, name);
+
+            if (store && variable.readonly) {
+                throw new IllegalArgumentException(error("Variable [" + variable.name + "] is read-only."));
+            }
+
+            slot = variable.slot;
+            after = variable.type;
+        }
+
+        return this;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        // Do nothing.
+    }
+
+    @Override
+    void load(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), slot);
+    }
+
+    @Override
+    void store(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), slot);
+    }
+}

+ 75 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java

@@ -0,0 +1,75 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a set of statements as a branch of control-flow.
+ */
+public final class SBlock extends AStatement {
+
+    final List<AStatement> statements;
+
+    public SBlock(final String location, final List<AStatement> statements) {
+        super(location);
+
+        this.statements = Collections.unmodifiableList(statements);
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        final AStatement last = statements.get(statements.size() - 1);
+
+        for (final AStatement statement : statements) {
+            if (allEscape) {
+                throw new IllegalArgumentException(error("Unreachable statement."));
+            }
+
+            statement.inLoop = inLoop;
+            statement.lastSource = lastSource && statement == last;
+            statement.lastLoop = (beginLoop || lastLoop) && statement == last;
+
+            statement.analyze(settings, definition, variables);
+
+            methodEscape = statement.methodEscape;
+            loopEscape = statement.loopEscape;
+            allEscape = statement.allEscape;
+            anyContinue |= statement.anyContinue;
+            anyBreak |= statement.anyBreak;
+            statementCount += statement.statementCount;
+        }
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        for (final AStatement statement : statements) {
+            statement.continu = continu;
+            statement.brake = brake;
+            statement.write(settings, definition, adapter);
+        }
+    }
+}

+ 52 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java

@@ -0,0 +1,52 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a break statement.
+ */
+public final class SBreak extends AStatement {
+
+    public SBreak(final String location) {
+        super(location);
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (!inLoop) {
+            throw new IllegalArgumentException(error("Break statement outside of a loop."));
+        }
+
+        loopEscape = true;
+        allEscape = true;
+        anyBreak = true;
+        statementCount = 1;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.goTo(brake);
+    }
+}

+ 55 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java

@@ -0,0 +1,55 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a continue statement.
+ */
+public final class SContinue extends AStatement {
+
+    public SContinue(final String location) {
+        super(location);
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        if (!inLoop) {
+            throw new IllegalArgumentException(error("Continue statement outside of a loop."));
+        }
+
+        if (lastLoop) {
+            throw new IllegalArgumentException(error("Extraneous continue statement."));
+        }
+
+        allEscape = true;
+        anyContinue = true;
+        statementCount = 1;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        adapter.goTo(continu);
+    }
+}

+ 58 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java

@@ -0,0 +1,58 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a series of declarations.
+ */
+public final class SDeclBlock extends AStatement {
+
+    final List<SDeclaration> declarations;
+
+    public SDeclBlock(final String location, final List<SDeclaration> declarations) {
+        super(location);
+
+        this.declarations = Collections.unmodifiableList(declarations);
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        for (final SDeclaration declaration : declarations) {
+            declaration.analyze(settings, definition, variables);
+        }
+
+        statementCount = declarations.size();
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        for (final SDeclaration declaration : declarations) {
+            declaration.write(settings, definition, adapter);
+        }
+    }
+}

+ 86 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.Variables.Variable;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a single variable declaration.
+ */
+public final class SDeclaration extends AStatement {
+
+    final String type;
+    final String name;
+    AExpression expression;
+
+    Variable variable;
+
+    public SDeclaration(final String location, final String type, final String name, final AExpression expression) {
+        super(location);
+
+        this.type = type;
+        this.name = name;
+        this.expression = expression;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        variable = variables.addVariable(location, type, name, false, false);
+
+        if (expression != null) {
+            expression.expected = variable.type;
+            expression.analyze(settings, definition, variables);
+            expression = expression.cast(settings, definition, variables);
+        }
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final org.objectweb.asm.Type type = variable.type.type;
+        final Sort sort = variable.type.sort;
+
+        final boolean initialize = expression == null;
+
+        if (!initialize) {
+            expression.write(settings, definition, adapter);
+        }
+
+        switch (sort) {
+            case VOID:   throw new IllegalStateException(error("Illegal tree structure."));
+            case BOOL:
+            case BYTE:
+            case SHORT:
+            case CHAR:
+            case INT:    if (initialize) adapter.push(0);    break;
+            case LONG:   if (initialize) adapter.push(0L);   break;
+            case FLOAT:  if (initialize) adapter.push(0.0F); break;
+            case DOUBLE: if (initialize) adapter.push(0.0);  break;
+            default:     if (initialize) adapter.visitInsn(Opcodes.ACONST_NULL);
+        }
+
+        adapter.visitVarInsn(type.getOpcode(Opcodes.ISTORE), variable.slot);
+    }
+}

+ 105 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java

@@ -0,0 +1,105 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a do-while loop.
+ */
+public final class SDo extends AStatement {
+
+    final AStatement block;
+    AExpression condition;
+
+    public SDo(final String location, final AStatement block, final AExpression condition) {
+        super(location);
+
+        this.condition = condition;
+        this.block = block;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        variables.incrementScope();
+
+        block.beginLoop = true;
+        block.inLoop = true;
+
+        block.analyze(settings, definition, variables);
+
+        if (block.loopEscape && !block.anyContinue) {
+            throw new IllegalArgumentException(error("Extraneous do while loop."));
+        }
+
+        condition.expected = definition.booleanType;
+        condition.analyze(settings, definition, variables);
+        condition = condition.cast(settings, definition, variables);
+
+        if (condition.constant != null) {
+            final boolean continuous = (boolean)condition.constant;
+
+            if (!continuous) {
+                throw new IllegalArgumentException(error("Extraneous do while loop."));
+            }
+
+            if (!block.anyBreak) {
+                methodEscape = true;
+                allEscape = true;
+            }
+        }
+
+        statementCount = 1;
+
+        if (settings.getMaxLoopCounter() > 0) {
+            loopCounterSlot = variables.getVariable(location, "#loop").slot;
+        }
+
+        variables.decrementScope();
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final Label start = new Label();
+        final Label begin = new Label();
+        final Label end = new Label();
+
+        adapter.mark(start);
+
+        block.continu = begin;
+        block.brake = end;
+        block.write(settings, definition, adapter);
+
+        adapter.mark(begin);
+
+        condition.fals = end;
+        condition.write(settings, definition, adapter);
+
+        WriterUtility.writeLoopCounter(adapter, loopCounterSlot, Math.max(1, block.statementCount));
+
+        adapter.goTo(start);
+        adapter.mark(end);
+    }
+}

+ 72 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java

@@ -0,0 +1,72 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents the top-level node for an expression as a statement.
+ */
+public final class SExpression extends AStatement {
+
+    AExpression expression;
+
+    public SExpression(final String location, final AExpression expression) {
+        super(location);
+
+        this.expression = expression;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        expression.read = lastSource;
+        expression.analyze(settings, definition, variables);
+
+        if (!lastSource && !expression.statement) {
+            throw new IllegalArgumentException(error("Not a statement."));
+        }
+
+        final boolean rtn = lastSource && expression.actual.sort != Sort.VOID;
+
+        expression.expected = rtn ? definition.objectType : expression.actual;
+        expression = expression.cast(settings, definition, variables);
+
+        methodEscape = rtn;
+        loopEscape = rtn;
+        allEscape = rtn;
+        statementCount = 1;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        expression.write(settings, definition, adapter);
+
+        if (methodEscape) {
+            adapter.returnValue();
+        } else {
+            WriterUtility.writePop(adapter, expression.expected.sort.size);
+        }
+    }
+}

+ 181 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java

@@ -0,0 +1,181 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a for loop.
+ */
+public final class SFor extends AStatement {
+
+    ANode initializer;
+    AExpression condition;
+    AExpression afterthought;
+    final AStatement block;
+
+    public SFor(final String location,
+                final ANode initializer, final AExpression condition, final AExpression afterthought, final AStatement block) {
+        super(location);
+
+        this.initializer = initializer;
+        this.condition = condition;
+        this.afterthought = afterthought;
+        this.block = block;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        variables.incrementScope();
+
+        boolean continuous = false;
+
+        if (initializer != null) {
+            if (initializer instanceof SDeclBlock) {
+                ((SDeclBlock)initializer).analyze(settings, definition, variables);
+            } else if (initializer instanceof AExpression) {
+                final AExpression initializer = (AExpression)this.initializer;
+
+                initializer.read = false;
+                initializer.analyze(settings, definition, variables);
+
+                if (!initializer.statement) {
+                    throw new IllegalArgumentException(initializer.error("Not a statement."));
+                }
+            } else {
+                throw new IllegalStateException(error("Illegal tree structure."));
+            }
+        }
+
+        if (condition != null) {
+
+            condition.expected = definition.booleanType;
+            condition.analyze(settings, definition, variables);
+            condition = condition.cast(settings, definition, variables);
+
+            if (condition.constant != null) {
+                continuous = (boolean)condition.constant;
+
+                if (!continuous) {
+                    throw new IllegalArgumentException(error("Extraneous for loop."));
+                }
+
+                if (block == null) {
+                    throw new IllegalArgumentException(error("For loop has no escape."));
+                }
+            }
+        } else {
+            continuous = true;
+        }
+
+        if (afterthought != null) {
+            afterthought.read = false;
+            afterthought.analyze(settings, definition, variables);
+
+            if (!afterthought.statement) {
+                throw new IllegalArgumentException(afterthought.error("Not a statement."));
+            }
+        }
+
+        int count = 1;
+
+        if (block != null) {
+            block.beginLoop = true;
+            block.inLoop = true;
+
+            block.analyze(settings, definition, variables);
+
+            if (block.loopEscape && !block.anyContinue) {
+                throw new IllegalArgumentException(error("Extraneous for loop."));
+            }
+
+            if (continuous && !block.anyBreak) {
+                methodEscape = true;
+                allEscape = true;
+            }
+
+            block.statementCount = Math.max(count, block.statementCount);
+        }
+
+        statementCount = 1;
+
+        if (settings.getMaxLoopCounter() > 0) {
+            loopCounterSlot = variables.getVariable(location, "#loop").slot;
+        }
+
+        variables.decrementScope();
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final Label start = new Label();
+        final Label begin = afterthought == null ? start : new Label();
+        final Label end = new Label();
+
+        if (initializer instanceof SDeclBlock) {
+            ((SDeclBlock)initializer).write(settings, definition, adapter);
+        } else if (initializer instanceof AExpression) {
+            AExpression initializer = (AExpression)this.initializer;
+
+            initializer.write(settings, definition, adapter);
+            WriterUtility.writePop(adapter, initializer.expected.sort.size);
+        }
+
+        adapter.mark(start);
+
+        if (condition != null) {
+            condition.fals = end;
+            condition.write(settings, definition, adapter);
+        }
+
+        boolean allEscape = false;
+
+        if (block != null) {
+            allEscape = block.allEscape;
+
+            int statementCount = Math.max(1, block.statementCount);
+
+            if (afterthought != null) {
+                ++statementCount;
+            }
+
+            WriterUtility.writeLoopCounter(adapter, loopCounterSlot, statementCount);
+            block.write(settings, definition, adapter);
+        } else {
+            WriterUtility.writeLoopCounter(adapter, loopCounterSlot, 1);
+        }
+
+        if (afterthought != null) {
+            adapter.mark(begin);
+            afterthought.write(settings, definition, adapter);
+        }
+
+        if (afterthought != null || !allEscape) {
+            adapter.goTo(start);
+        }
+
+        adapter.mark(end);
+    }
+}

+ 111 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java

@@ -0,0 +1,111 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents an if/else block.
+ */
+public final class SIfElse extends AStatement {
+
+    AExpression condition;
+    final AStatement ifblock;
+    final AStatement elseblock;
+
+    public SIfElse(final String location, final AExpression condition, final AStatement ifblock, final AStatement elseblock) {
+        super(location);
+
+        this.condition = condition;
+        this.ifblock = ifblock;
+        this.elseblock = elseblock;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        condition.expected = definition.booleanType;
+        condition.analyze(settings, definition, variables);
+        condition = condition.cast(settings, definition, variables);
+
+        if (condition.constant != null) {
+            throw new IllegalArgumentException(error("Extraneous if statement."));
+        }
+
+        ifblock.lastSource = lastSource;
+        ifblock.inLoop = inLoop;
+        ifblock.lastLoop = lastLoop;
+
+        variables.incrementScope();
+        ifblock.analyze(settings, definition, variables);
+        variables.decrementScope();
+
+        anyContinue = ifblock.anyContinue;
+        anyBreak = ifblock.anyBreak;
+        statementCount = ifblock.statementCount;
+
+        if (elseblock != null) {
+            elseblock.lastSource = lastSource;
+            elseblock.inLoop = inLoop;
+            elseblock.lastLoop = lastLoop;
+
+            variables.incrementScope();
+            elseblock.analyze(settings, definition, variables);
+            variables.decrementScope();
+
+            methodEscape = ifblock.methodEscape && elseblock.methodEscape;
+            loopEscape = ifblock.loopEscape && elseblock.loopEscape;
+            allEscape = ifblock.allEscape && elseblock.allEscape;
+            anyContinue |= elseblock.anyContinue;
+            anyBreak |= elseblock.anyBreak;
+            statementCount = Math.max(ifblock.statementCount, elseblock.statementCount);
+        }
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final Label end = new Label();
+        final Label fals = elseblock != null ? new Label() : end;
+
+        condition.fals = fals;
+        condition.write(settings, definition, adapter);
+
+        ifblock.continu = continu;
+        ifblock.brake = brake;
+        ifblock.write(settings, definition, adapter);
+
+        if (elseblock != null) {
+            if (!ifblock.allEscape) {
+                adapter.goTo(end);
+            }
+
+            adapter.mark(fals);
+
+            elseblock.continu = continu;
+            elseblock.brake = brake;
+            elseblock.write(settings, definition, adapter);
+        }
+
+        adapter.mark(end);
+    }
+}

+ 58 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java

@@ -0,0 +1,58 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a return statement.
+ */
+public final class SReturn extends AStatement {
+
+    AExpression expression;
+
+    public SReturn(final String location, final AExpression expression) {
+        super(location);
+
+        this.expression = expression;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        expression.expected = definition.objectType;
+        expression.analyze(settings, definition, variables);
+        expression = expression.cast(settings, definition, variables);
+
+        methodEscape = true;
+        loopEscape = true;
+        allEscape = true;
+
+        statementCount = 1;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        expression.write(settings, definition, adapter);
+        adapter.returnValue();
+    }
+}

+ 76 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java

@@ -0,0 +1,76 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The root of all Painless trees.  Contains a series of statements.
+ */
+public final class SSource extends AStatement {
+
+    final List<AStatement> statements;
+
+    public SSource(final String location, final List<AStatement> statements) {
+        super(location);
+
+        this.statements = Collections.unmodifiableList(statements);
+    }
+
+    @Override
+    public void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        variables.incrementScope();
+
+        final AStatement last = statements.get(statements.size() - 1);
+
+        for (final AStatement statement : statements) {
+            if (allEscape) {
+                throw new IllegalArgumentException(error("Unreachable statement."));
+            }
+
+            statement.lastSource = statement == last;
+            statement.analyze(settings, definition, variables);
+
+            methodEscape = statement.methodEscape;
+            allEscape = statement.allEscape;
+        }
+
+        variables.decrementScope();
+    }
+
+    @Override
+    public void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        for (final AStatement statement : statements) {
+            statement.write(settings, definition, adapter);
+        }
+
+        if (!methodEscape) {
+            adapter.visitInsn(Opcodes.ACONST_NULL);
+            adapter.returnValue();
+        }
+    }
+}

+ 57 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java

@@ -0,0 +1,57 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a throw statement.
+ */
+public final class SThrow extends AStatement {
+
+    AExpression expression;
+
+    public SThrow(final String location, final AExpression expression) {
+        super(location);
+
+        this.expression = expression;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        expression.expected = definition.exceptionType;
+        expression.analyze(settings, definition, variables);
+        expression = expression.cast(settings, definition, variables);
+
+        methodEscape = true;
+        loopEscape = true;
+        allEscape = true;
+        statementCount = 1;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        expression.write(settings, definition, adapter);
+        adapter.throwException();
+    }
+}

+ 98 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STrap.java

@@ -0,0 +1,98 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.Variables.Variable;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a catch block as part of a try-catch block.
+ */
+public final class STrap extends AStatement {
+
+    final String type;
+    final String name;
+    final AStatement block;
+
+    Variable variable;
+
+    Label begin;
+    Label end;
+    Label exception;
+
+    public STrap(final String location, final String type, final String name, final AStatement block) {
+        super(location);
+
+        this.type = type;
+        this.name = name;
+        this.block = block;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        variable = variables.addVariable(location, type, name, true, false);
+
+        try {
+            variable.type.clazz.asSubclass(Exception.class);
+        } catch (final ClassCastException cce) {
+            throw new ClassCastException(error("Not an exception type [" + variable.type.name + "]."));
+        }
+
+        if (block != null) {
+            block.lastSource = lastSource;
+            block.inLoop = inLoop;
+            block.lastLoop = lastLoop;
+
+            block.analyze(settings, definition, variables);
+
+            methodEscape = block.methodEscape;
+            loopEscape = block.loopEscape;
+            allEscape = block.allEscape;
+            anyContinue = block.anyContinue;
+            anyBreak = block.anyBreak;
+            statementCount = block.statementCount;
+        }
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final Label jump = new Label();
+
+        adapter.mark(jump);
+        adapter.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot);
+
+        if (block != null) {
+            block.continu = continu;
+            block.brake = brake;
+            block.write(settings, definition, adapter);
+        }
+
+        adapter.visitTryCatchBlock(begin, end, jump, variable.type.type.getInternalName());
+
+        if (exception != null && !block.allEscape) {
+            adapter.goTo(exception);
+        }
+    }
+}

+ 114 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java

@@ -0,0 +1,114 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents the try block as part of a try-catch block.
+ */
+public final class STry extends AStatement {
+
+    final AStatement block;
+    final List<STrap> traps;
+
+    public STry(final String location, final AStatement block, final List<STrap> traps) {
+        super(location);
+
+        this.block = block;
+        this.traps = Collections.unmodifiableList(traps);
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        block.lastSource = lastSource;
+        block.inLoop = inLoop;
+        block.lastLoop = lastLoop;
+
+        variables.incrementScope();
+        block.analyze(settings, definition, variables);
+        variables.decrementScope();
+
+        methodEscape = block.methodEscape;
+        loopEscape = block.loopEscape;
+        allEscape = block.allEscape;
+        anyContinue = block.anyContinue;
+        anyBreak = block.anyBreak;
+
+        int statementCount = 0;
+
+        for (final STrap trap : traps) {
+            trap.lastSource = lastSource;
+            trap.inLoop = inLoop;
+            trap.lastLoop = lastLoop;
+
+            variables.incrementScope();
+            trap.analyze(settings, definition, variables);
+            variables.decrementScope();
+
+            methodEscape &= trap.methodEscape;
+            loopEscape &= trap.loopEscape;
+            allEscape &= trap.allEscape;
+            anyContinue |= trap.anyContinue;
+            anyBreak |= trap.anyBreak;
+
+            statementCount = Math.max(statementCount, trap.statementCount);
+        }
+
+        this.statementCount = block.statementCount + statementCount;
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final Label begin = new Label();
+        final Label end = new Label();
+        final Label exception = new Label();
+
+        adapter.mark(begin);
+
+        block.continu = continu;
+        block.brake = brake;
+        block.write(settings, definition, adapter);
+
+        if (!block.allEscape) {
+            adapter.goTo(exception);
+        }
+
+        adapter.mark(end);
+
+        for (final STrap trap : traps) {
+            trap.begin = begin;
+            trap.end = end;
+            trap.exception = traps.size() > 1 ? exception : null;
+            trap.write(settings, definition, adapter);
+        }
+
+        if (!block.allEscape || traps.size() > 1) {
+            adapter.mark(exception);
+        }
+    }
+}

+ 121 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java

@@ -0,0 +1,121 @@
+/*
+ * 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.node;
+
+import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.WriterUtility;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.commons.GeneratorAdapter;
+
+/**
+ * Represents a while loop.
+ */
+public final class SWhile extends AStatement {
+
+    AExpression condition;
+    final AStatement block;
+
+    public SWhile(final String location, final AExpression condition, final AStatement block) {
+        super(location);
+
+        this.condition = condition;
+        this.block = block;
+    }
+
+    @Override
+    void analyze(final CompilerSettings settings, final Definition definition, final Variables variables) {
+        variables.incrementScope();
+
+        condition.expected = definition.booleanType;
+        condition.analyze(settings, definition, variables);
+        condition = condition.cast(settings, definition, variables);
+
+        boolean continuous = false;
+
+        if (condition.constant != null) {
+            continuous = (boolean)condition.constant;
+
+            if (!continuous) {
+                throw new IllegalArgumentException(error("Extraneous while loop."));
+            }
+
+            if (block == null) {
+                throw new IllegalArgumentException(error("While loop has no escape."));
+            }
+        }
+
+        int count = 1;
+
+        if (block != null) {
+            block.beginLoop = true;
+            block.inLoop = true;
+
+            block.analyze(settings, definition, variables);
+
+            if (block.loopEscape && !block.anyContinue) {
+                throw new IllegalArgumentException(error("Extranous while loop."));
+            }
+
+            if (continuous && !block.anyBreak) {
+                methodEscape = true;
+                allEscape = true;
+            }
+
+            block.statementCount = Math.max(count, block.statementCount);
+        }
+
+        statementCount = 1;
+
+        if (settings.getMaxLoopCounter() > 0) {
+            loopCounterSlot = variables.getVariable(location, "#loop").slot;
+        }
+
+        variables.decrementScope();
+    }
+
+    @Override
+    void write(final CompilerSettings settings, final Definition definition, final GeneratorAdapter adapter) {
+        final Label begin = new Label();
+        final Label end = new Label();
+
+        adapter.mark(begin);
+
+        condition.fals = end;
+        condition.write(settings, definition, adapter);
+
+        if (block != null) {
+            WriterUtility.writeLoopCounter(adapter, loopCounterSlot, Math.max(1, block.statementCount));
+
+            block.continu = begin;
+            block.brake = end;
+            block.write(settings, definition, adapter);
+        } else {
+            WriterUtility.writeLoopCounter(adapter, loopCounterSlot, 1);
+        }
+
+        if (block == null || !block.allEscape) {
+            adapter.goTo(begin);
+        }
+
+        adapter.mark(end);
+    }
+}

+ 130 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/package-info.java

@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+/**
+ * A painless tree is composed of the node classes found in this package.
+ * <p>
+ * The following are the types of nodes:
+ * A* (abstract) - These are the abstract nodes that are the superclasses for the other types.
+ * S* (statement) - These are nodes that represent a statement in Painless.  These are the highest level nodes.
+ * E* (expression) - These are nodess that represent an expression in Painless.  These are the middle level nodes.
+ * L* (link) - These are nodes that respresent a piece of a variable/method chain.  The are the lowest level nodes.
+ * <p>
+ * The following is a brief description of each node:
+ * {@link org.elasticsearch.painless.node.AExpression} - The superclass for all E* (expression) nodes.
+ * {@link org.elasticsearch.painless.node.ALink} - The superclass for all L* (link) nodes.
+ * {@link org.elasticsearch.painless.node.ANode} - The superclass for all other nodes.
+ * {@link org.elasticsearch.painless.node.AStatement} - The superclass for all S* (statement) nodes.
+ * {@link org.elasticsearch.painless.node.EBinary} - Represents a binary math expression.
+ * {@link org.elasticsearch.painless.node.EBool} - Represents a boolean expression.
+ * {@link org.elasticsearch.painless.node.EBoolean} - Represents a boolean constant.
+ * {@link org.elasticsearch.painless.node.ECast} - Represents an implicit cast in most cases.  (Internal only.)
+ * {@link org.elasticsearch.painless.node.EChain} - Represents the entirety of a variable/method chain for read/write operations.
+ * {@link org.elasticsearch.painless.node.EComp} - Represents a comparison expression.
+ * {@link org.elasticsearch.painless.node.EConditional} - Respresents a conditional expression.
+ * {@link org.elasticsearch.painless.node.EConstant} - Respresents a constant.  (Internal only.)
+ * {@link org.elasticsearch.painless.node.EDecimal} - Respresents a decimal constant.
+ * {@link org.elasticsearch.painless.node.EExplicit} - Represents an explicit cast.
+ * {@link org.elasticsearch.painless.node.ENull} - Represents a null constant.
+ * {@link org.elasticsearch.painless.node.ENumeric} - Respresents a non-decimal numeric constant.
+ * {@link org.elasticsearch.painless.node.EUnary} - Represents a unary math expression.
+ * {@link org.elasticsearch.painless.node.LArrayLength} - Represents an array length field load.
+ * {@link org.elasticsearch.painless.node.LBrace} - Represents an array load/store or defers to possible shortcuts.
+ * {@link org.elasticsearch.painless.node.LCall} - Represents a method call or deferes to a def call.
+ * {@link org.elasticsearch.painless.node.LCast} - Represents a cast made in a variable/method chain.
+ * {@link org.elasticsearch.painless.node.LDefArray} - Represents an array load/store or shortcut on a def type.  (Internal only.)
+ * {@link org.elasticsearch.painless.node.LDefCall} - Represents a method call made on a def type. (Internal only.)
+ * {@link org.elasticsearch.painless.node.LDefField} - Represents a field load/store or shortcut on a def type.  (Internal only.)
+ * {@link org.elasticsearch.painless.node.LField} - Represents a field load/store or defers to a possible shortcuts.
+ * {@link org.elasticsearch.painless.node.LListShortcut} - Represents a list load/store shortcut.  (Internal only.)
+ * {@link org.elasticsearch.painless.node.LMapShortcut} - Represents a map load/store shortcut. (Internal only.)
+ * {@link org.elasticsearch.painless.node.LNewArray} - Represents an array instantiation.
+ * {@link org.elasticsearch.painless.node.LNewObj} - Respresents and object instantiation.
+ * {@link org.elasticsearch.painless.node.LShortcut} - Represents a field load/store shortcut.  (Internal only.)
+ * {@link org.elasticsearch.painless.node.LString} - Represents a string constant.
+ * {@link org.elasticsearch.painless.node.LVariable} - Represents a variable load/store.
+ * {@link org.elasticsearch.painless.node.SBlock} - Represents a set of statements as a branch of control-flow.
+ * {@link org.elasticsearch.painless.node.SBreak} - Represents a break statement.
+ * {@link org.elasticsearch.painless.node.SContinue} - Represents a continue statement.
+ * {@link org.elasticsearch.painless.node.SDeclaration} - Represents a single variable declaration.
+ * {@link org.elasticsearch.painless.node.SDeclBlock} - Represents a series of declarations.
+ * {@link org.elasticsearch.painless.node.SDo} - Represents a do-while loop.
+ * {@link org.elasticsearch.painless.node.SExpression} - Represents the top-level node for an expression as a statement.
+ * {@link org.elasticsearch.painless.node.SFor} - Represents a for loop.
+ * {@link org.elasticsearch.painless.node.SIfElse} - Represents an if/else block.
+ * {@link org.elasticsearch.painless.node.SReturn} - Represents a return statement.
+ * {@link org.elasticsearch.painless.node.SSource} - The root of all Painless trees.  Contains a series of statements.
+ * {@link org.elasticsearch.painless.node.SThrow} - Represents a throw statement.
+ * {@link org.elasticsearch.painless.node.STrap} - Represents a catch block as part of a try-catch block.
+ * {@link org.elasticsearch.painless.node.STry} - Represents the try block as part of a try-catch block.
+ * {@link org.elasticsearch.painless.node.SWhile} - Represents a while loop.
+ * <p>
+ * Note that internal nodes are generated during the analysis phase by modifying the tree on-the-fly
+ * for clarity of development and convenience during the writing phase.
+ * <p>
+ * All Painless trees must start with an SSource node at the root.  Each node has a constructor that requires
+ * all of its values and children be passed in at the time of instantiation.  This means that Painless trees
+ * are build bottom-up; however, this helps enforce tree structure to be correct and fits naturally with a
+ * standard recurvise-descent parser.
+ * <p>
+ * Generally, statement nodes have member data that evaluate legal control-flow during the analysis phase.
+ * The typical order for statement nodes is for each node to call analyze on it's children during the analysis phase
+ * and write on it's children during the writing phase.  No modifications are made to the structure of statement nodes.
+ * <p>
+ * Generally, expression nodes have member data that evaluate static types.  The typical order for an expression node
+ * during the analysis phase looks like the following:
+ * {@code
+ * For known expected types:
+ *
+ * expression.child.expected = expectedType      // set the known expected type
+ *
+ * expression.child.analyze(...)                 // analyze the child node to set the child's actual type
+ *
+ * expression.child = expression.child.cast(...) // add an implicit cast node if the child node's
+ *                                               // actual type is not the expected type and set the
+ *                                               // expression's child to the implicit cast node
+ *
+ * For unknown expected types that need promotion:
+ *
+ * expression.child.analyze(...)                 // analyze the child node to set the child's actual type
+ *
+ * Type promote = Caster.promote(...)            // get the promotion type for the child based on
+ *                                               // the current operation and child's actual type
+ *
+ * expression.child.expected = promote           // set the expected type to the promotion type
+ *
+ * expression.child = expression.child.cast(...) // add an implicit cast node if the child node's
+ *                                               // actual type is not the expected type and set the
+ *                                               // expression's child to the implicit cast node
+ * }
+ * Expression nodes just call each child during the writing phase.
+ * <p>
+ * Generally, link nodes have member data that help keep track of items necessary to do a
+ * load/store on a variable/field/method.  Analysis of link nodes happens in a chain node
+ * where each link node will be analysed with the chain node acting as a bridge to pass the
+ * previous link's after type to the next link's before type.  Upon analysis completion, a link
+ * will return either itself or another link node depending on if a shortcut or def type was found.
+ * Cast nodes as links will return null and be removed from the chain node if the cast is
+ * unnecessary.  Link nodes have three methods for writing -- write, load, and store.  The write
+ * method is always once called before a load/store to give links a chance to write any values
+ * such as array indices before the load/store happens.  Load is called to read a link node, and
+ * store is called to write a link node.  Note that store will only ever be called on the final
+ * link node in a chain, all previous links will be considered loads.
+ */
+package org.elasticsearch.painless.node;

+ 3 - 4
modules/lang-painless/src/test/java/org/elasticsearch/painless/BinaryOperatorTests.java

@@ -55,13 +55,12 @@ public class BinaryOperatorTests extends ScriptTestCase {
     }
 
     public void testLongShiftsConst() {
-        // note: we always promote the results of shifts too (unlike java)
         assertEquals(1L << 2, exec("return 1L << 2;"));
-        assertEquals(1L << 2L, exec("return 1 << 2L;"));
-        assertEquals(4L >> 2L, exec("return 4 >> 2L;"));
+        assertEquals(1 << 2L, exec("return 1 << 2L;"));
+        assertEquals(4 >> 2L, exec("return 4 >> 2L;"));
         assertEquals(4L >> 2, exec("return 4L >> 2;"));
         assertEquals(-1L >>> 29, exec("return -1L >>> 29;"));
-        assertEquals(-1L >>> 29L, exec("return -1 >>> 29L;"));
+        assertEquals(-1 >>> 29L, exec("return -1 >>> 29L;"));
     }
 
     public void testMixedTypes() {

+ 9 - 7
modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java

@@ -32,21 +32,23 @@ import java.nio.charset.StandardCharsets;
 final class Debugger {
 
     /** compiles source to bytecode, and returns debugging output */
-    static String toString(String source) {
+    static String toString(final String source) {
         return toString(source, new CompilerSettings());
     }
 
     /** compiles to bytecode, and returns debugging output */
-    static String toString(String source, CompilerSettings settings) {
-        byte[] bytes = Compiler.compile("debugger", source, Definition.INSTANCE, settings);
-        ByteArrayOutputStream output = new ByteArrayOutputStream();
-        PrintWriter outputWriter = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
-        ClassReader reader = new ClassReader(bytes);
+    static String toString(final String source, final CompilerSettings settings) {
+        final byte[] bytes = Compiler.compile(source, settings);
+        final ByteArrayOutputStream output = new ByteArrayOutputStream();
+        final PrintWriter outputWriter = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
+        final ClassReader reader = new ClassReader(bytes);
+
         reader.accept(new TraceClassVisitor(outputWriter), 0);
         outputWriter.flush();
+
         try {
             return output.toString("UTF-8");
-        } catch (UnsupportedEncodingException e) {
+        } catch (final UnsupportedEncodingException e) {
             throw new RuntimeException(e);
         }
     }

+ 36 - 36
modules/lang-painless/src/test/java/org/elasticsearch/painless/DefTests.java

@@ -401,26 +401,26 @@ public class DefTests extends ScriptTestCase {
         assertEquals(2L, exec("def x = (float)1 def y = (int)1 return x << y"));
         assertEquals(2L, exec("def x = (double)1 def y = (int)1 return x << y"));
 
-        assertEquals(2L, exec("def x = (byte)1 def y = (long)1 return x << y"));
-        assertEquals(2L, exec("def x = (short)1 def y = (long)1 return x << y"));
-        assertEquals(2L, exec("def x = (char)1 def y = (long)1 return x << y"));
-        assertEquals(2L, exec("def x = (int)1 def y = (long)1 return x << y"));
+        assertEquals(2, exec("def x = (byte)1 def y = (long)1 return x << y"));
+        assertEquals(2, exec("def x = (short)1 def y = (long)1 return x << y"));
+        assertEquals(2, exec("def x = (char)1 def y = (long)1 return x << y"));
+        assertEquals(2, exec("def x = (int)1 def y = (long)1 return x << y"));
         assertEquals(2L, exec("def x = (long)1 def y = (long)1 return x << y"));
         assertEquals(2L, exec("def x = (float)1 def y = (long)1 return x << y"));
         assertEquals(2L, exec("def x = (double)1 def y = (long)1 return x << y"));
 
-        assertEquals(2L, exec("def x = (byte)1 def y = (float)1 return x << y"));
-        assertEquals(2L, exec("def x = (short)1 def y = (float)1 return x << y"));
-        assertEquals(2L, exec("def x = (char)1 def y = (float)1 return x << y"));
-        assertEquals(2L, exec("def x = (int)1 def y = (float)1 return x << y"));
+        assertEquals(2, exec("def x = (byte)1 def y = (float)1 return x << y"));
+        assertEquals(2, exec("def x = (short)1 def y = (float)1 return x << y"));
+        assertEquals(2, exec("def x = (char)1 def y = (float)1 return x << y"));
+        assertEquals(2, exec("def x = (int)1 def y = (float)1 return x << y"));
         assertEquals(2L, exec("def x = (long)1 def y = (float)1 return x << y"));
         assertEquals(2L, exec("def x = (float)1 def y = (float)1 return x << y"));
         assertEquals(2L, exec("def x = (double)1 def y = (float)1 return x << y"));
 
-        assertEquals(2L, exec("def x = (byte)1 def y = (double)1 return x << y"));
-        assertEquals(2L, exec("def x = (short)1 def y = (double)1 return x << y"));
-        assertEquals(2L, exec("def x = (char)1 def y = (double)1 return x << y"));
-        assertEquals(2L, exec("def x = (int)1 def y = (double)1 return x << y"));
+        assertEquals(2, exec("def x = (byte)1 def y = (double)1 return x << y"));
+        assertEquals(2, exec("def x = (short)1 def y = (double)1 return x << y"));
+        assertEquals(2, exec("def x = (char)1 def y = (double)1 return x << y"));
+        assertEquals(2, exec("def x = (int)1 def y = (double)1 return x << y"));
         assertEquals(2L, exec("def x = (long)1 def y = (double)1 return x << y"));
         assertEquals(2L, exec("def x = (float)1 def y = (double)1 return x << y"));
         assertEquals(2L, exec("def x = (double)1 def y = (double)1 return x << y"));
@@ -467,26 +467,26 @@ public class DefTests extends ScriptTestCase {
         assertEquals(2L, exec("def x = (float)4 def y = (int)1 return x >> y"));
         assertEquals(2L, exec("def x = (double)4 def y = (int)1 return x >> y"));
 
-        assertEquals(2L, exec("def x = (byte)4 def y = (long)1 return x >> y"));
-        assertEquals(2L, exec("def x = (short)4 def y = (long)1 return x >> y"));
-        assertEquals(2L, exec("def x = (char)4 def y = (long)1 return x >> y"));
-        assertEquals(2L, exec("def x = (int)4 def y = (long)1 return x >> y"));
+        assertEquals(2, exec("def x = (byte)4 def y = (long)1 return x >> y"));
+        assertEquals(2, exec("def x = (short)4 def y = (long)1 return x >> y"));
+        assertEquals(2, exec("def x = (char)4 def y = (long)1 return x >> y"));
+        assertEquals(2, exec("def x = (int)4 def y = (long)1 return x >> y"));
         assertEquals(2L, exec("def x = (long)4 def y = (long)1 return x >> y"));
         assertEquals(2L, exec("def x = (float)4 def y = (long)1 return x >> y"));
         assertEquals(2L, exec("def x = (double)4 def y = (long)1 return x >> y"));
 
-        assertEquals(2L, exec("def x = (byte)4 def y = (float)1 return x >> y"));
-        assertEquals(2L, exec("def x = (short)4 def y = (float)1 return x >> y"));
-        assertEquals(2L, exec("def x = (char)4 def y = (float)1 return x >> y"));
-        assertEquals(2L, exec("def x = (int)4 def y = (float)1 return x >> y"));
+        assertEquals(2, exec("def x = (byte)4 def y = (float)1 return x >> y"));
+        assertEquals(2, exec("def x = (short)4 def y = (float)1 return x >> y"));
+        assertEquals(2, exec("def x = (char)4 def y = (float)1 return x >> y"));
+        assertEquals(2, exec("def x = (int)4 def y = (float)1 return x >> y"));
         assertEquals(2L, exec("def x = (long)4 def y = (float)1 return x >> y"));
         assertEquals(2L, exec("def x = (float)4 def y = (float)1 return x >> y"));
         assertEquals(2L, exec("def x = (double)4 def y = (float)1 return x >> y"));
 
-        assertEquals(2L, exec("def x = (byte)4 def y = (double)1 return x >> y"));
-        assertEquals(2L, exec("def x = (short)4 def y = (double)1 return x >> y"));
-        assertEquals(2L, exec("def x = (char)4 def y = (double)1 return x >> y"));
-        assertEquals(2L, exec("def x = (int)4 def y = (double)1 return x >> y"));
+        assertEquals(2, exec("def x = (byte)4 def y = (double)1 return x >> y"));
+        assertEquals(2, exec("def x = (short)4 def y = (double)1 return x >> y"));
+        assertEquals(2, exec("def x = (char)4 def y = (double)1 return x >> y"));
+        assertEquals(2, exec("def x = (int)4 def y = (double)1 return x >> y"));
         assertEquals(2L, exec("def x = (long)4 def y = (double)1 return x >> y"));
         assertEquals(2L, exec("def x = (float)4 def y = (double)1 return x >> y"));
         assertEquals(2L, exec("def x = (double)4 def y = (double)1 return x >> y"));
@@ -533,26 +533,26 @@ public class DefTests extends ScriptTestCase {
         assertEquals(2L, exec("def x = (float)4 def y = (int)1 return x >>> y"));
         assertEquals(2L, exec("def x = (double)4 def y = (int)1 return x >>> y"));
 
-        assertEquals(2L, exec("def x = (byte)4 def y = (long)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (short)4 def y = (long)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (char)4 def y = (long)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (int)4 def y = (long)1 return x >>> y"));
+        assertEquals(2, exec("def x = (byte)4 def y = (long)1 return x >>> y"));
+        assertEquals(2, exec("def x = (short)4 def y = (long)1 return x >>> y"));
+        assertEquals(2, exec("def x = (char)4 def y = (long)1 return x >>> y"));
+        assertEquals(2, exec("def x = (int)4 def y = (long)1 return x >>> y"));
         assertEquals(2L, exec("def x = (long)4 def y = (long)1 return x >>> y"));
         assertEquals(2L, exec("def x = (float)4 def y = (long)1 return x >>> y"));
         assertEquals(2L, exec("def x = (double)4 def y = (long)1 return x >>> y"));
 
-        assertEquals(2L, exec("def x = (byte)4 def y = (float)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (short)4 def y = (float)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (char)4 def y = (float)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (int)4 def y = (float)1 return x >>> y"));
+        assertEquals(2, exec("def x = (byte)4 def y = (float)1 return x >>> y"));
+        assertEquals(2, exec("def x = (short)4 def y = (float)1 return x >>> y"));
+        assertEquals(2, exec("def x = (char)4 def y = (float)1 return x >>> y"));
+        assertEquals(2, exec("def x = (int)4 def y = (float)1 return x >>> y"));
         assertEquals(2L, exec("def x = (long)4 def y = (float)1 return x >>> y"));
         assertEquals(2L, exec("def x = (float)4 def y = (float)1 return x >>> y"));
         assertEquals(2L, exec("def x = (double)4 def y = (float)1 return x >>> y"));
 
-        assertEquals(2L, exec("def x = (byte)4 def y = (double)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (short)4 def y = (double)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (char)4 def y = (double)1 return x >>> y"));
-        assertEquals(2L, exec("def x = (int)4 def y = (double)1 return x >>> y"));
+        assertEquals(2, exec("def x = (byte)4 def y = (double)1 return x >>> y"));
+        assertEquals(2, exec("def x = (short)4 def y = (double)1 return x >>> y"));
+        assertEquals(2, exec("def x = (char)4 def y = (double)1 return x >>> y"));
+        assertEquals(2, exec("def x = (int)4 def y = (double)1 return x >>> y"));
         assertEquals(2L, exec("def x = (long)4 def y = (double)1 return x >>> y"));
         assertEquals(2L, exec("def x = (float)4 def y = (double)1 return x >>> y"));
         assertEquals(2L, exec("def x = (double)4 def y = (double)1 return x >>> y"));

+ 13 - 13
modules/lang-painless/src/test/java/org/elasticsearch/painless/ReservedWordTests.java

@@ -24,15 +24,15 @@ import java.util.HashMap;
 
 /** Tests for special reserved words such as _score */
 public class ReservedWordTests extends ScriptTestCase {
-    
+
     /** check that we can't declare a variable of _score, its really reserved! */
     public void testScoreVar() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
             exec("int _score = 5; return _score;");
         });
-        assertTrue(expected.getMessage().contains("Variable name [_score] already defined"));
+        assertTrue(expected.getMessage().contains("Variable name [_score] is reserved"));
     }
-    
+
     /** check that we can't write to _score, its read-only! */
     public void testScoreStore() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
@@ -40,15 +40,15 @@ public class ReservedWordTests extends ScriptTestCase {
         });
         assertTrue(expected.getMessage().contains("Variable [_score] is read-only"));
     }
-    
+
     /** check that we can't declare a variable of doc, its really reserved! */
     public void testDocVar() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
             exec("int doc = 5; return doc;");
         });
-        assertTrue(expected.getMessage().contains("Variable name [doc] already defined"));
+        assertTrue(expected.getMessage().contains("Variable name [doc] is reserved"));
     }
-    
+
     /** check that we can't write to doc, its read-only! */
     public void testDocStore() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
@@ -56,15 +56,15 @@ public class ReservedWordTests extends ScriptTestCase {
         });
         assertTrue(expected.getMessage().contains("Variable [doc] is read-only"));
     }
-    
+
     /** check that we can't declare a variable of ctx, its really reserved! */
     public void testCtxVar() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
             exec("int ctx = 5; return ctx;");
         });
-        assertTrue(expected.getMessage().contains("Variable name [ctx] already defined"));
+        assertTrue(expected.getMessage().contains("Variable name [ctx] is reserved"));
     }
-    
+
     /** check that we can't write to ctx, its read-only! */
     public void testCtxStore() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
@@ -72,20 +72,20 @@ public class ReservedWordTests extends ScriptTestCase {
         });
         assertTrue(expected.getMessage().contains("Variable [ctx] is read-only"));
     }
-    
+
     /** check that we can modify its contents though */
     public void testCtxStoreMap() {
         assertEquals(5, exec("ctx.foo = 5; return ctx.foo;", Collections.singletonMap("ctx", new HashMap<String,Object>())));
     }
-    
+
     /** check that we can't declare a variable of _value, its really reserved! */
     public void testAggregationValueVar() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
             exec("int _value = 5; return _value;");
         });
-        assertTrue(expected.getMessage().contains("Variable name [_value] already defined"));
+        assertTrue(expected.getMessage().contains("Variable name [_value] is reserved"));
     }
-    
+
     /** check that we can't write to _value, its read-only! */
     public void testAggregationValueStore() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {

+ 18 - 10
modules/lang-painless/src/test/java/org/elasticsearch/painless/StringTests.java

@@ -132,26 +132,30 @@ public class StringTests extends ScriptTestCase {
 
         try {
             assertEquals("cc", exec("return (String)(char)\"cc\""));
-        } catch (final IllegalArgumentException ise) {
-            ise.getMessage().contains("Cannot cast constant from [String] to [char].");
+            fail();
+        } catch (final ClassCastException cce) {
+            assertTrue(cce.getMessage().contains("Cannot cast from [String] to [char]."));
         }
 
         try {
             assertEquals("cc", exec("return (String)(char)'cc'"));
-        } catch (final IllegalArgumentException ise) {
-            ise.getMessage().contains("Cannot cast constant from [String] to [char].");
+            fail();
+        } catch (final ClassCastException cce) {
+            assertTrue(cce.getMessage().contains("Cannot cast from [String] to [char]."));
         }
 
         try {
             assertEquals('c', exec("String s = \"cc\" (char)s"));
+            fail();
         } catch (final ClassCastException cce) {
-            cce.getMessage().contains("Cannot cast [String] with length greater than one to [char].");
+            assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
         }
 
         try {
             assertEquals('c', exec("String s = 'cc' (char)s"));
+            fail();
         } catch (final ClassCastException cce) {
-            cce.getMessage().contains("Cannot cast [String] with length greater than one to [char].");
+            assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
         }
 
         assertEquals('c', exec("return (Character)\"c\""));
@@ -164,26 +168,30 @@ public class StringTests extends ScriptTestCase {
 
         try {
             assertEquals("cc", exec("return (String)(Character)\"cc\""));
+            fail();
         } catch (final ClassCastException ise) {
-            ise.getMessage().contains("Cannot cast [String] with length greater than one to [Character].");
+            assertTrue(ise.getMessage().contains("Cannot cast [String] with length greater than one to [Character]."));
         }
 
         try {
             assertEquals("cc", exec("return (String)(Character)'cc'"));
+            fail();
         } catch (final ClassCastException ise) {
-            ise.getMessage().contains("Cannot cast [String] with length greater than one to [Character].");
+            assertTrue(ise.getMessage().contains("Cannot cast [String] with length greater than one to [Character]."));
         }
 
         try {
             assertEquals('c', exec("String s = \"cc\" (Character)s"));
+            fail();
         } catch (final ClassCastException cce) {
-            cce.getMessage().contains("Cannot cast [String] with length greater than one to [Character].");
+            assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [Character]."));
         }
 
         try {
             assertEquals('c', exec("String s = 'cc' (Character)s"));
+            fail();
         } catch (final ClassCastException cce) {
-            cce.getMessage().contains("Cannot cast [String] with length greater than one to [Character].");
+            assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [Character]."));
         }
     }
 }

+ 4 - 4
modules/lang-painless/src/test/java/org/elasticsearch/painless/WhenThingsGoWrongTests.java

@@ -98,7 +98,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
             exec("try { int x } catch (PainlessError error) {}");
             fail("should have hit ParseException");
         });
-        assertTrue(parseException.getMessage().contains("Invalid type [PainlessError]."));
+        assertTrue(parseException.getMessage().contains("Not a type [PainlessError]."));
     }
 
     public void testLoopLimits() {
@@ -126,20 +126,20 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
         // ok
         assertEquals(0, exec(new String(exactlyAtLimit)));
     }
-    
+
     public void testIllegalDynamicMethod() {
         IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
             exec("def x = 'test'; return x.getClass().toString()");
         });
         assertTrue(expected.getMessage().contains("Unable to find dynamic method"));
     }
-    
+
     public void testDynamicNPE() {
         expectThrows(NullPointerException.class, () -> {
             exec("def x = null; return x.toString()");
         });
     }
-    
+
     public void testDynamicWrongArgs() {
         expectThrows(WrongMethodTypeException.class, () -> {
             exec("def x = new ArrayList(); return x.get('bogus');");

Vissa filer visades inte eftersom för många filer har ändrats