Refactored Painless link nodes into expression nodes to simplify
load/store operations of variable/method chains. Closes #19459
This commit is contained in:
parent
fb45f6a8a8
commit
9d87314105
|
@ -95,80 +95,95 @@ delimiter
|
|||
| EOF
|
||||
;
|
||||
|
||||
// Note we return the boolean s. This is returned as true
|
||||
// if secondaries (postfixes) are allowed, otherwise, false.
|
||||
// This prevents illegal secondaries from being appended to
|
||||
// expressions using precedence that aren't variable/method chains.
|
||||
expression returns [boolean s = true]
|
||||
: u = unary[false] { $s = $u.s; } # single
|
||||
| expression ( MUL | DIV | REM ) expression { $s = false; } # binary
|
||||
| expression ( ADD | SUB ) expression { $s = false; } # binary
|
||||
| expression ( FIND | MATCH ) expression { $s = false; } # binary
|
||||
| expression ( LSH | RSH | USH ) expression { $s = false; } # binary
|
||||
| expression ( LT | LTE | GT | GTE ) expression { $s = false; } # comp
|
||||
| expression INSTANCEOF decltype { $s = false; } # instanceof
|
||||
| expression ( EQ | EQR | NE | NER ) expression { $s = false; } # comp
|
||||
| expression BWAND expression { $s = false; } # binary
|
||||
| expression XOR expression { $s = false; } # binary
|
||||
| expression BWOR expression { $s = false; } # binary
|
||||
| expression BOOLAND expression { $s = false; } # bool
|
||||
| expression BOOLOR expression { $s = false; } # bool
|
||||
| <assoc=right> expression COND e0 = expression COLON e1 = expression { $s = $e0.s && $e1.s; } # conditional
|
||||
// TODO: Should we allow crazy syntax like (x = 5).call()?
|
||||
// Other crazy syntaxes work, but this one requires
|
||||
// a complete restructure of the rules as EChain isn't
|
||||
// designed to handle more postfixes after an assignment.
|
||||
| <assoc=right> chain[true] ( ASSIGN | AADD | ASUB | AMUL |
|
||||
ADIV | AREM | AAND | AXOR |
|
||||
AOR | ALSH | ARSH | AUSH ) expression { $s = false; } # assignment
|
||||
expression
|
||||
: unary # single
|
||||
| expression ( MUL | DIV | REM ) expression # binary
|
||||
| expression ( ADD | SUB ) expression # binary
|
||||
| expression ( FIND | MATCH ) expression # binary
|
||||
| expression ( LSH | RSH | USH ) expression # binary
|
||||
| expression ( LT | LTE | GT | GTE ) expression # comp
|
||||
| expression INSTANCEOF decltype # instanceof
|
||||
| expression ( EQ | EQR | NE | NER ) expression # comp
|
||||
| expression BWAND 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> expression ( ASSIGN | AADD | ASUB | AMUL |
|
||||
ADIV | AREM | AAND | AXOR |
|
||||
AOR | ALSH | ARSH | AUSH ) expression # assignment
|
||||
;
|
||||
|
||||
// Note we take in the boolean c. This is used to indicate
|
||||
// whether or not this rule was called when we are already
|
||||
// processing a variable/method chain. This prevents the chain
|
||||
// from being applied to rules where it wouldn't be allowed.
|
||||
unary[boolean c] returns [boolean s = true]
|
||||
: { !$c }? ( INCR | DECR ) chain[true] # pre
|
||||
| { !$c }? chain[true] (INCR | DECR ) # post
|
||||
| { !$c }? chain[false] # read
|
||||
| { !$c }? ( OCTAL | HEX | INTEGER | DECIMAL ) { $s = false; } # numeric
|
||||
| { !$c }? TRUE { $s = false; } # true
|
||||
| { !$c }? FALSE { $s = false; } # false
|
||||
| { !$c }? NULL { $s = false; } # null
|
||||
| { !$c }? listinitializer { $s = false; } # listinit
|
||||
| { !$c }? mapinitializer { $s = false; } # mapinit
|
||||
| { !$c }? ( BOOLNOT | BWNOT | ADD | SUB ) unary[false] # operator
|
||||
| LP decltype RP unary[$c] # cast
|
||||
unary
|
||||
: ( INCR | DECR ) chain # pre
|
||||
| chain (INCR | DECR ) # post
|
||||
| chain # read
|
||||
| ( BOOLNOT | BWNOT | ADD | SUB ) unary # operator
|
||||
| LP decltype RP unary # cast
|
||||
;
|
||||
|
||||
chain[boolean c]
|
||||
: p = primary[$c] secondary[$p.s]* # dynamic
|
||||
| decltype dot secondary[true]* # static
|
||||
| arrayinitializer # newarray
|
||||
chain
|
||||
: primary postfix* # dynamic
|
||||
| decltype postdot postfix* # static
|
||||
| arrayinitializer # newarray
|
||||
;
|
||||
|
||||
primary[boolean c] returns [boolean s = true]
|
||||
: { !$c }? LP e = expression RP { $s = $e.s; } # exprprec
|
||||
| { $c }? LP unary[true] RP # chainprec
|
||||
| STRING # string
|
||||
| REGEX # regex
|
||||
| ID # variable
|
||||
| ID arguments # calllocal
|
||||
| NEW TYPE arguments # newobject
|
||||
primary
|
||||
: LP expression RP # precedence
|
||||
| ( OCTAL | HEX | INTEGER | DECIMAL ) # numeric
|
||||
| TRUE # true
|
||||
| FALSE # false
|
||||
| NULL # null
|
||||
| STRING # string
|
||||
| REGEX # regex
|
||||
| listinitializer # listinit
|
||||
| mapinitializer # mapinit
|
||||
| ID # variable
|
||||
| ID arguments # calllocal
|
||||
| NEW TYPE arguments # newobject
|
||||
;
|
||||
|
||||
secondary[boolean s]
|
||||
: { $s }? dot
|
||||
| { $s }? brace
|
||||
postfix
|
||||
: callinvoke
|
||||
| fieldaccess
|
||||
| braceaccess
|
||||
;
|
||||
|
||||
dot
|
||||
: DOT DOTID arguments # callinvoke
|
||||
| DOT ( DOTID | DOTINTEGER ) # fieldaccess
|
||||
postdot
|
||||
: callinvoke
|
||||
| fieldaccess
|
||||
;
|
||||
|
||||
brace
|
||||
: LBRACE expression RBRACE # braceaccess
|
||||
callinvoke
|
||||
: DOT DOTID arguments
|
||||
;
|
||||
|
||||
fieldaccess
|
||||
: DOT ( DOTID | DOTINTEGER )
|
||||
;
|
||||
|
||||
braceaccess
|
||||
: LBRACE expression RBRACE
|
||||
;
|
||||
|
||||
arrayinitializer
|
||||
: NEW TYPE ( LBRACE expression RBRACE )+ ( postdot postfix* )? # newstandardarray
|
||||
| NEW TYPE LBRACE RBRACE LBRACK ( expression ( COMMA expression )* )? SEMICOLON? RBRACK postfix* # newinitializedarray
|
||||
;
|
||||
|
||||
listinitializer
|
||||
: LBRACE expression ( COMMA expression)* RBRACE
|
||||
| LBRACE RBRACE
|
||||
;
|
||||
|
||||
mapinitializer
|
||||
: LBRACE maptoken ( COMMA maptoken )* RBRACE
|
||||
| LBRACE COLON RBRACE
|
||||
;
|
||||
|
||||
maptoken
|
||||
: expression COLON expression
|
||||
;
|
||||
|
||||
arguments
|
||||
|
@ -190,49 +205,10 @@ lamtype
|
|||
;
|
||||
|
||||
funcref
|
||||
: classFuncref
|
||||
| constructorFuncref
|
||||
| capturingFuncref
|
||||
| localFuncref
|
||||
: TYPE REF ID # classfuncref // reference to a static or instance method,
|
||||
// e.g. ArrayList::size or Integer::compare
|
||||
| decltype REF NEW # constructorfuncref // reference to a constructor, e.g. ArrayList::new
|
||||
| ID REF ID # capturingfuncref // reference to an instance method, e.g. object::toString
|
||||
// currently limited to capture of a simple variable (id).
|
||||
| THIS REF ID # localfuncref // reference to a local function, e.g. this::myfunc
|
||||
;
|
||||
|
||||
// reference to a static or instance method, e.g. ArrayList::size or Integer::compare
|
||||
classFuncref
|
||||
: TYPE REF ID
|
||||
;
|
||||
|
||||
// reference to a constructor, e.g. ArrayList::new
|
||||
// currently limited to simple non-array types
|
||||
constructorFuncref
|
||||
: decltype REF NEW
|
||||
;
|
||||
|
||||
// reference to an instance method, e.g. object::toString
|
||||
// currently limited to capture of a simple variable (id).
|
||||
capturingFuncref
|
||||
: ID REF ID
|
||||
;
|
||||
|
||||
// reference to a local function, e.g. this::myfunc
|
||||
localFuncref
|
||||
: THIS REF ID
|
||||
;
|
||||
|
||||
arrayinitializer
|
||||
: NEW TYPE (LBRACE expression RBRACE)+ (dot secondary[true]*)? # newstandardarray
|
||||
| NEW TYPE LBRACE RBRACE LBRACK ( expression ( COMMA expression )* )? SEMICOLON? RBRACK # newinitializedarray
|
||||
;
|
||||
|
||||
listinitializer
|
||||
: LBRACE expression ( COMMA expression)* RBRACE
|
||||
| LBRACE RBRACE
|
||||
;
|
||||
|
||||
mapinitializer
|
||||
: LBRACE maptoken ( COMMA maptoken )* RBRACE
|
||||
| LBRACE COLON RBRACE
|
||||
;
|
||||
|
||||
maptoken
|
||||
: expression COLON expression
|
||||
;
|
|
@ -91,43 +91,47 @@ public final class Definition {
|
|||
public static final Type MATCHER_TYPE = getType("Matcher");
|
||||
|
||||
public enum Sort {
|
||||
VOID( void.class , 0 , true , false , false , false ),
|
||||
BOOL( boolean.class , 1 , true , true , false , true ),
|
||||
BYTE( byte.class , 1 , true , false , true , true ),
|
||||
SHORT( short.class , 1 , true , false , true , true ),
|
||||
CHAR( char.class , 1 , true , false , true , true ),
|
||||
INT( int.class , 1 , true , false , true , true ),
|
||||
LONG( long.class , 2 , true , false , true , true ),
|
||||
FLOAT( float.class , 1 , true , false , true , true ),
|
||||
DOUBLE( double.class , 2 , true , false , true , true ),
|
||||
VOID( void.class , Void.class , null , 0 , true , false , false , false ),
|
||||
BOOL( boolean.class , Boolean.class , null , 1 , true , true , false , true ),
|
||||
BYTE( byte.class , Byte.class , null , 1 , true , false , true , true ),
|
||||
SHORT( short.class , Short.class , null , 1 , true , false , true , true ),
|
||||
CHAR( char.class , Character.class , null , 1 , true , false , true , true ),
|
||||
INT( int.class , Integer.class , null , 1 , true , false , true , true ),
|
||||
LONG( long.class , Long.class , null , 2 , true , false , true , true ),
|
||||
FLOAT( float.class , Float.class , null , 1 , true , false , true , true ),
|
||||
DOUBLE( double.class , Double.class , null , 2 , true , false , true , true ),
|
||||
|
||||
VOID_OBJ( Void.class , 1 , true , false , false , false ),
|
||||
BOOL_OBJ( Boolean.class , 1 , false , true , false , false ),
|
||||
BYTE_OBJ( Byte.class , 1 , false , false , true , false ),
|
||||
SHORT_OBJ( Short.class , 1 , false , false , true , false ),
|
||||
CHAR_OBJ( Character.class , 1 , false , false , true , false ),
|
||||
INT_OBJ( Integer.class , 1 , false , false , true , false ),
|
||||
LONG_OBJ( Long.class , 1 , false , false , true , false ),
|
||||
FLOAT_OBJ( Float.class , 1 , false , false , true , false ),
|
||||
DOUBLE_OBJ( Double.class , 1 , false , false , true , false ),
|
||||
VOID_OBJ( Void.class , null , void.class , 1 , true , false , false , false ),
|
||||
BOOL_OBJ( Boolean.class , null , boolean.class , 1 , false , true , false , false ),
|
||||
BYTE_OBJ( Byte.class , null , byte.class , 1 , false , false , true , false ),
|
||||
SHORT_OBJ( Short.class , null , short.class , 1 , false , false , true , false ),
|
||||
CHAR_OBJ( Character.class , null , char.class , 1 , false , false , true , false ),
|
||||
INT_OBJ( Integer.class , null , int.class , 1 , false , false , true , false ),
|
||||
LONG_OBJ( Long.class , null , long.class , 1 , false , false , true , false ),
|
||||
FLOAT_OBJ( Float.class , null , float.class , 1 , false , false , true , false ),
|
||||
DOUBLE_OBJ( Double.class , null , double.class , 1 , false , false , true , false ),
|
||||
|
||||
NUMBER( Number.class , 1 , false , false , false , false ),
|
||||
STRING( String.class , 1 , false , false , false , true ),
|
||||
NUMBER( Number.class , null , null , 1 , false , false , false , false ),
|
||||
STRING( String.class , null , null , 1 , false , false , false , true ),
|
||||
|
||||
OBJECT( null , 1 , false , false , false , false ),
|
||||
DEF( null , 1 , false , false , false , false ),
|
||||
ARRAY( null , 1 , false , false , false , false );
|
||||
OBJECT( null , null , null , 1 , false , false , false , false ),
|
||||
DEF( null , null , null , 1 , false , false , false , false ),
|
||||
ARRAY( null , null , null , 1 , false , false , false , false );
|
||||
|
||||
public final Class<?> clazz;
|
||||
public final Class<?> boxed;
|
||||
public final Class<?> unboxed;
|
||||
public final int size;
|
||||
public final boolean primitive;
|
||||
public final boolean bool;
|
||||
public final boolean numeric;
|
||||
public final boolean constant;
|
||||
|
||||
Sort(final Class<?> clazz, final int size, final boolean primitive,
|
||||
final boolean bool, final boolean numeric, final boolean constant) {
|
||||
Sort(final Class<?> clazz, final Class<?> boxed, final Class<?> unboxed, final int size,
|
||||
final boolean primitive, final boolean bool, final boolean numeric, final boolean constant) {
|
||||
this.clazz = clazz;
|
||||
this.boxed = boxed;
|
||||
this.unboxed = unboxed;
|
||||
this.size = size;
|
||||
this.bool = bool;
|
||||
this.primitive = primitive;
|
||||
|
@ -204,8 +208,8 @@ public final class Definition {
|
|||
this.modifiers = modifiers;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Returns MethodType for this method.
|
||||
* <p>
|
||||
* This works even for user-defined Methods (where the MethodHandle is null).
|
||||
|
@ -252,7 +256,7 @@ public final class Definition {
|
|||
}
|
||||
return MethodType.methodType(returnValue, params);
|
||||
}
|
||||
|
||||
|
||||
public void write(MethodWriter writer) {
|
||||
final org.objectweb.asm.Type type;
|
||||
if (augmentation) {
|
||||
|
@ -803,7 +807,7 @@ public final class Definition {
|
|||
|
||||
final Class<?> implClass;
|
||||
final Class<?>[] params;
|
||||
|
||||
|
||||
if (augmentation == false) {
|
||||
implClass = owner.clazz;
|
||||
params = new Class<?>[args.length];
|
||||
|
@ -818,7 +822,7 @@ public final class Definition {
|
|||
params[count+1] = args[count].clazz;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final java.lang.reflect.Method reflect;
|
||||
|
||||
try {
|
||||
|
|
|
@ -1,49 +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.antlr;
|
||||
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.LexerNoViableAltException;
|
||||
import org.antlr.v4.runtime.misc.Interval;
|
||||
import org.elasticsearch.painless.Location;
|
||||
|
||||
/**
|
||||
* A lexer that will override the default error behavior to fail on the first error.
|
||||
*/
|
||||
final class ErrorHandlingLexer extends PainlessLexer {
|
||||
final String sourceName;
|
||||
|
||||
ErrorHandlingLexer(CharStream charStream, String sourceName) {
|
||||
super(charStream);
|
||||
this.sourceName = sourceName;
|
||||
// Replace the TokenFactory with a stashing wrapper so we can do token-level lookbehind for regex detection
|
||||
_factory = new StashingTokenFactory<>(_factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
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()));
|
||||
|
||||
Location location = new Location(sourceName, _tokenStartCharIndex);
|
||||
throw location.createError(new IllegalArgumentException("unexpected character [" + getErrorDisplay(text) + "].", lnvae));
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -263,48 +263,6 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
|
|||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitRead(PainlessParser.ReadContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitNumeric(PainlessParser.NumericContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitTrue(PainlessParser.TrueContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitFalse(PainlessParser.FalseContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitNull(PainlessParser.NullContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitListinit(PainlessParser.ListinitContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitMapinit(PainlessParser.MapinitContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -346,14 +304,35 @@ 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 visitExprprec(PainlessParser.ExprprecContext ctx) { return visitChildren(ctx); }
|
||||
@Override public T visitPrecedence(PainlessParser.PrecedenceContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitChainprec(PainlessParser.ChainprecContext ctx) { return visitChildren(ctx); }
|
||||
@Override public T visitNumeric(PainlessParser.NumericContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitTrue(PainlessParser.TrueContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitFalse(PainlessParser.FalseContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitNull(PainlessParser.NullContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -368,6 +347,20 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
|
|||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitRegex(PainlessParser.RegexContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitListinit(PainlessParser.ListinitContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitMapinit(PainlessParser.MapinitContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -395,7 +388,14 @@ 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 visitSecondary(PainlessParser.SecondaryContext ctx) { return visitChildren(ctx); }
|
||||
@Override public T visitPostfix(PainlessParser.PostfixContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitPostdot(PainlessParser.PostdotContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -417,69 +417,6 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
|
|||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitBraceaccess(PainlessParser.BraceaccessContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@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 visitArgument(PainlessParser.ArgumentContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLambda(PainlessParser.LambdaContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLamtype(PainlessParser.LamtypeContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitFuncref(PainlessParser.FuncrefContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitClassFuncref(PainlessParser.ClassFuncrefContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitConstructorFuncref(PainlessParser.ConstructorFuncrefContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitCapturingFuncref(PainlessParser.CapturingFuncrefContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLocalFuncref(PainlessParser.LocalFuncrefContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -515,4 +452,60 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
|
|||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitMaptoken(PainlessParser.MaptokenContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@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 visitArgument(PainlessParser.ArgumentContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLambda(PainlessParser.LambdaContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLamtype(PainlessParser.LamtypeContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitClassfuncref(PainlessParser.ClassfuncrefContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitConstructorfuncref(PainlessParser.ConstructorfuncrefContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitCapturingfuncref(PainlessParser.CapturingfuncrefContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitLocalfuncref(PainlessParser.LocalfuncrefContext ctx) { return visitChildren(ctx); }
|
||||
}
|
||||
|
|
|
@ -249,48 +249,6 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
|
|||
* @return the visitor result
|
||||
*/
|
||||
T visitRead(PainlessParser.ReadContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code numeric}
|
||||
* labeled alternative in {@link PainlessParser#unary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitNumeric(PainlessParser.NumericContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code true}
|
||||
* labeled alternative in {@link PainlessParser#unary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitTrue(PainlessParser.TrueContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code false}
|
||||
* labeled alternative in {@link PainlessParser#unary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitFalse(PainlessParser.FalseContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code null}
|
||||
* labeled alternative in {@link PainlessParser#unary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitNull(PainlessParser.NullContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code listinit}
|
||||
* labeled alternative in {@link PainlessParser#unary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitListinit(PainlessParser.ListinitContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code mapinit}
|
||||
* labeled alternative in {@link PainlessParser#unary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitMapinit(PainlessParser.MapinitContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code operator}
|
||||
* labeled alternative in {@link PainlessParser#unary}.
|
||||
|
@ -327,19 +285,40 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
|
|||
*/
|
||||
T visitNewarray(PainlessParser.NewarrayContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code exprprec}
|
||||
* Visit a parse tree produced by the {@code precedence}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitExprprec(PainlessParser.ExprprecContext ctx);
|
||||
T visitPrecedence(PainlessParser.PrecedenceContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code chainprec}
|
||||
* Visit a parse tree produced by the {@code numeric}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitChainprec(PainlessParser.ChainprecContext ctx);
|
||||
T visitNumeric(PainlessParser.NumericContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code true}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitTrue(PainlessParser.TrueContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code false}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitFalse(PainlessParser.FalseContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code null}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitNull(PainlessParser.NullContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code string}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
|
@ -354,6 +333,20 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
|
|||
* @return the visitor result
|
||||
*/
|
||||
T visitRegex(PainlessParser.RegexContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code listinit}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitListinit(PainlessParser.ListinitContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code mapinit}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitMapinit(PainlessParser.MapinitContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code variable}
|
||||
* labeled alternative in {@link PainlessParser#primary}.
|
||||
|
@ -376,86 +369,35 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
|
|||
*/
|
||||
T visitNewobject(PainlessParser.NewobjectContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#secondary}.
|
||||
* Visit a parse tree produced by {@link PainlessParser#postfix}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitSecondary(PainlessParser.SecondaryContext ctx);
|
||||
T visitPostfix(PainlessParser.PostfixContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code callinvoke}
|
||||
* labeled alternative in {@link PainlessParser#dot}.
|
||||
* Visit a parse tree produced by {@link PainlessParser#postdot}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitPostdot(PainlessParser.PostdotContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#callinvoke}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitCallinvoke(PainlessParser.CallinvokeContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code fieldaccess}
|
||||
* labeled alternative in {@link PainlessParser#dot}.
|
||||
* Visit a parse tree produced by {@link PainlessParser#fieldaccess}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitFieldaccess(PainlessParser.FieldaccessContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code braceaccess}
|
||||
* labeled alternative in {@link PainlessParser#brace}.
|
||||
* Visit a parse tree produced by {@link PainlessParser#braceaccess}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitBraceaccess(PainlessParser.BraceaccessContext 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#argument}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitArgument(PainlessParser.ArgumentContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#lambda}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLambda(PainlessParser.LambdaContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#lamtype}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLamtype(PainlessParser.LamtypeContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#funcref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitFuncref(PainlessParser.FuncrefContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#classFuncref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitClassFuncref(PainlessParser.ClassFuncrefContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#constructorFuncref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitConstructorFuncref(PainlessParser.ConstructorFuncrefContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#capturingFuncref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitCapturingFuncref(PainlessParser.CapturingFuncrefContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#localFuncref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLocalFuncref(PainlessParser.LocalFuncrefContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code newstandardarray}
|
||||
* labeled alternative in {@link PainlessParser#arrayinitializer}.
|
||||
|
@ -488,4 +430,56 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
|
|||
* @return the visitor result
|
||||
*/
|
||||
T visitMaptoken(PainlessParser.MaptokenContext 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#argument}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitArgument(PainlessParser.ArgumentContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#lambda}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLambda(PainlessParser.LambdaContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by {@link PainlessParser#lamtype}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLamtype(PainlessParser.LamtypeContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code classfuncref}
|
||||
* labeled alternative in {@link PainlessParser#funcref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitClassfuncref(PainlessParser.ClassfuncrefContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code constructorfuncref}
|
||||
* labeled alternative in {@link PainlessParser#funcref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitConstructorfuncref(PainlessParser.ConstructorfuncrefContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code capturingfuncref}
|
||||
* labeled alternative in {@link PainlessParser#funcref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitCapturingfuncref(PainlessParser.CapturingfuncrefContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code localfuncref}
|
||||
* labeled alternative in {@link PainlessParser#funcref}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitLocalfuncref(PainlessParser.LocalfuncrefContext ctx);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,39 +19,47 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Location;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The superclass for all E* (expression) nodes.
|
||||
* The superclass for all E* (expression) and P* (postfix) nodes.
|
||||
*/
|
||||
public abstract class AExpression extends ANode {
|
||||
|
||||
/**
|
||||
* Prefix is the predecessor to this node in a variable chain.
|
||||
* This is used to analyze and write variable chains in a
|
||||
* more natural order since the parent node of a variable
|
||||
* chain will want the data from the final postfix to be
|
||||
* analyzed.
|
||||
*/
|
||||
AExpression prefix;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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;
|
||||
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;
|
||||
Type expected = null;
|
||||
|
||||
/**
|
||||
* Set to the actual type this node is. Note this variable is always
|
||||
|
@ -59,19 +67,19 @@ public abstract class AExpression extends ANode {
|
|||
* 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;
|
||||
Type actual = null;
|
||||
|
||||
/**
|
||||
* Set by {@link EExplicit} if a cast made on an expression node should be
|
||||
* explicit.
|
||||
*/
|
||||
protected boolean explicit = false;
|
||||
boolean explicit = false;
|
||||
|
||||
/**
|
||||
* Set to true if a cast is allowed to boxed/unboxed. This is used
|
||||
* for method arguments because casting may be required.
|
||||
*/
|
||||
protected boolean internal = false;
|
||||
boolean internal = false;
|
||||
|
||||
/**
|
||||
* Set to the value of the constant this expression node represents if
|
||||
|
@ -79,40 +87,30 @@ public abstract class AExpression extends ANode {
|
|||
* this node will be replaced by an {@link EConstant} during casting
|
||||
* if it's not already one.
|
||||
*/
|
||||
protected Object constant = null;
|
||||
Object constant = null;
|
||||
|
||||
/**
|
||||
* Set to true by {@link ENull} to represent a null value.
|
||||
*/
|
||||
protected boolean isNull = false;
|
||||
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.
|
||||
* Standard constructor with location used for error tracking.
|
||||
*/
|
||||
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(Location location) {
|
||||
AExpression(Location location) {
|
||||
super(location);
|
||||
|
||||
prefix = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for errors and collects data for the writing phase.
|
||||
* This constructor is used by variable/method chains when postfixes are specified.
|
||||
*/
|
||||
abstract void analyze(Locals locals);
|
||||
AExpression(Location location, AExpression prefix) {
|
||||
super(location);
|
||||
|
||||
/**
|
||||
* Writes ASM based on the data collected during the analysis phase.
|
||||
*/
|
||||
abstract void write(MethodWriter writer, Globals globals);
|
||||
this.prefix = Objects.requireNonNull(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts {@link ECast} nodes into the tree for implicit casts. Also replaces
|
||||
|
@ -120,7 +118,7 @@ public abstract class AExpression extends ANode {
|
|||
* @return The new child node for the parent node calling this method.
|
||||
*/
|
||||
AExpression cast(Locals locals) {
|
||||
final Cast cast = AnalyzerCaster.getLegalCast(location, actual, expected, explicit, internal);
|
||||
Cast cast = AnalyzerCaster.getLegalCast(location, actual, expected, explicit, internal);
|
||||
|
||||
if (cast == null) {
|
||||
if (constant == null || this instanceof EConstant) {
|
||||
|
|
|
@ -1,121 +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.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* 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(Location location, 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(Locals locals);
|
||||
|
||||
/**
|
||||
* Write values before a load/store occurs such as an array index.
|
||||
*/
|
||||
abstract void write(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Write a load for the specific link type.
|
||||
*/
|
||||
abstract void load(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Write a store for the specific link type.
|
||||
*/
|
||||
abstract void store(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Used to copy link data from one to another during analysis in the case of replacement.
|
||||
*/
|
||||
final ALink copy(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;
|
||||
}
|
||||
}
|
|
@ -19,24 +19,31 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The superclass for all other nodes.
|
||||
* The superclass for all nodes.
|
||||
*/
|
||||
public abstract class ANode {
|
||||
|
||||
/**
|
||||
* The identifier of the script and character offset used for debugging and errors.
|
||||
*/
|
||||
final Location location;
|
||||
|
||||
/**
|
||||
* Standard constructor with location used for error tracking.
|
||||
*/
|
||||
ANode(Location location) {
|
||||
this.location = Objects.requireNonNull(location);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds all variable names referenced to the variable set.
|
||||
* <p>
|
||||
|
@ -44,8 +51,18 @@ public abstract class ANode {
|
|||
* @param variables set of variables referenced (any scope)
|
||||
*/
|
||||
abstract void extractVariables(Set<String> variables);
|
||||
|
||||
public RuntimeException createError(RuntimeException exception) {
|
||||
|
||||
/**
|
||||
* Checks for errors and collects data for the writing phase.
|
||||
*/
|
||||
abstract void analyze(Locals locals);
|
||||
|
||||
/**
|
||||
* Writes ASM based on the data collected during the analysis phase.
|
||||
*/
|
||||
abstract void write(MethodWriter writer, Globals globals);
|
||||
|
||||
RuntimeException createError(RuntimeException exception) {
|
||||
return location.createError(exception);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,9 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
/**
|
||||
* The superclass for all S* (statement) nodes.
|
||||
|
@ -110,17 +107,10 @@ public abstract class AStatement extends ANode {
|
|||
*/
|
||||
Label brake = null;
|
||||
|
||||
/**
|
||||
* Standard constructor with location used for error tracking.
|
||||
*/
|
||||
AStatement(Location location) {
|
||||
super(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for errors and collects data for the writing phase.
|
||||
*/
|
||||
abstract void analyze(Locals locals);
|
||||
|
||||
/**
|
||||
* Writes ASM based on the data collected during the analysis phase.
|
||||
*/
|
||||
abstract void write(MethodWriter writer, Globals globals);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The super class for an expression that can store a value in local memory.
|
||||
*/
|
||||
abstract class AStoreable extends AExpression {
|
||||
|
||||
/**
|
||||
* Set to true when this node is an lhs-expression and will be storing
|
||||
* a value from an rhs-expression.
|
||||
*/
|
||||
boolean write = false;
|
||||
|
||||
/**
|
||||
* Standard constructor with location used for error tracking.
|
||||
*/
|
||||
AStoreable(Location location) {
|
||||
super(location);
|
||||
|
||||
prefix = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor is used by variable/method chains when postfixes are specified.
|
||||
*/
|
||||
AStoreable(Location location, AExpression prefix) {
|
||||
super(location);
|
||||
|
||||
this.prefix = Objects.requireNonNull(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value based on the number of elements previously placed on the
|
||||
* stack to load/store a certain piece of a variable/method chain. This is
|
||||
* used during the writing phase to dup stack values from this storeable as
|
||||
* necessary during certain store operations.
|
||||
* <p>
|
||||
* Examples:
|
||||
* {@link EVariable} returns 0 because it requires nothing extra to perform
|
||||
* a load/store
|
||||
* {@link PSubField} returns 1 because it requires the name of the field as
|
||||
* an index on the stack to perform a load/store
|
||||
* {@link PSubBrace} returns 2 because it requires both the variable slot and
|
||||
* an index into the array on the stack to perform a
|
||||
* load/store
|
||||
*/
|
||||
abstract int accessElementCount();
|
||||
|
||||
/**
|
||||
* Returns true if this node or a sub-node of this node can be optimized with
|
||||
* rhs actual type to avoid an unnecessary cast.
|
||||
*/
|
||||
abstract boolean isDefOptimized();
|
||||
|
||||
/**
|
||||
* If this node or a sub-node of this node uses dynamic calls then
|
||||
* actual will be set to this value. This is used for an optimization
|
||||
* during assignment to def type targets.
|
||||
*/
|
||||
abstract void updateActual(Type actual);
|
||||
|
||||
/**
|
||||
* Called before a storeable node is loaded or stored. Used to load prefixes and
|
||||
* push load/store constants onto the stack if necessary.
|
||||
*/
|
||||
abstract void setup(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Called to load a storable used for compound assignments.
|
||||
*/
|
||||
abstract void load(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Called to store a storabable to local memory.
|
||||
*/
|
||||
abstract void store(MethodWriter writer, Globals globals);
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* 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.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
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.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an assignment with the lhs and rhs as child nodes.
|
||||
*/
|
||||
public final class EAssignment extends AExpression {
|
||||
|
||||
private AExpression lhs;
|
||||
private AExpression rhs;
|
||||
private final boolean pre;
|
||||
private final boolean post;
|
||||
private Operation operation;
|
||||
|
||||
private boolean cat = false;
|
||||
private Type promote = null;
|
||||
private Type shiftDistance; // for shifts, the RHS is promoted independently
|
||||
private Cast there = null;
|
||||
private Cast back = null;
|
||||
|
||||
public EAssignment(Location location, AExpression lhs, AExpression rhs, boolean pre, boolean post, Operation operation) {
|
||||
super(location);
|
||||
|
||||
this.lhs = Objects.requireNonNull(lhs);
|
||||
this.rhs = rhs;
|
||||
this.pre = pre;
|
||||
this.post = post;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
lhs.extractVariables(variables);
|
||||
rhs.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
analyzeLHS(locals);
|
||||
analyzeIncrDecr();
|
||||
|
||||
if (operation != null) {
|
||||
analyzeCompound(locals);
|
||||
} else if (rhs != null) {
|
||||
analyzeSimple(locals);
|
||||
} else {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeLHS(Locals locals) {
|
||||
if (lhs instanceof AStoreable) {
|
||||
AStoreable lhs = (AStoreable)this.lhs;
|
||||
|
||||
lhs.read = read;
|
||||
lhs.write = true;
|
||||
lhs.analyze(locals);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Left-hand side cannot be assigned a value.");
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeIncrDecr() {
|
||||
if (pre && post) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
} else if (pre || post) {
|
||||
if (rhs != null) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
Sort sort = lhs.actual.sort;
|
||||
|
||||
if (operation == Operation.INCR) {
|
||||
if (sort == Sort.DOUBLE) {
|
||||
rhs = new EConstant(location, 1D);
|
||||
} else if (sort == Sort.FLOAT) {
|
||||
rhs = new EConstant(location, 1F);
|
||||
} else if (sort == Sort.LONG) {
|
||||
rhs = new EConstant(location, 1L);
|
||||
} else {
|
||||
rhs = new EConstant(location, 1);
|
||||
}
|
||||
|
||||
operation = Operation.ADD;
|
||||
} else if (operation == Operation.DECR) {
|
||||
if (sort == Sort.DOUBLE) {
|
||||
rhs = new EConstant(location, 1D);
|
||||
} else if (sort == Sort.FLOAT) {
|
||||
rhs = new EConstant(location, 1F);
|
||||
} else if (sort == Sort.LONG) {
|
||||
rhs = new EConstant(location, 1L);
|
||||
} else {
|
||||
rhs = new EConstant(location, 1);
|
||||
}
|
||||
|
||||
operation = Operation.SUB;
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeCompound(Locals locals) {
|
||||
rhs.analyze(locals);
|
||||
|
||||
boolean shift = false;
|
||||
|
||||
if (operation == Operation.MUL) {
|
||||
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
|
||||
} else if (operation == Operation.DIV) {
|
||||
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
|
||||
} else if (operation == Operation.REM) {
|
||||
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
|
||||
} else if (operation == Operation.ADD) {
|
||||
promote = AnalyzerCaster.promoteAdd(lhs.actual, rhs.actual);
|
||||
} else if (operation == Operation.SUB) {
|
||||
promote = AnalyzerCaster.promoteNumeric(lhs.actual, rhs.actual, true);
|
||||
} else if (operation == Operation.LSH) {
|
||||
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
|
||||
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
|
||||
shift = true;
|
||||
} else if (operation == Operation.RSH) {
|
||||
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
|
||||
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
|
||||
shift = true;
|
||||
} else if (operation == Operation.USH) {
|
||||
promote = AnalyzerCaster.promoteNumeric(lhs.actual, false);
|
||||
shiftDistance = AnalyzerCaster.promoteNumeric(rhs.actual, false);
|
||||
shift = true;
|
||||
} else if (operation == Operation.BWAND) {
|
||||
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
|
||||
} else if (operation == Operation.XOR) {
|
||||
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
|
||||
} else if (operation == Operation.BWOR) {
|
||||
promote = AnalyzerCaster.promoteXor(lhs.actual, rhs.actual);
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
if (promote == null || (shift && shiftDistance == null)) {
|
||||
throw createError(new ClassCastException("Cannot apply compound assignment " +
|
||||
"[" + operation.symbol + "=] to types [" + lhs.actual + "] and [" + rhs.actual + "]."));
|
||||
}
|
||||
|
||||
cat = operation == Operation.ADD && promote.sort == Sort.STRING;
|
||||
|
||||
if (cat) {
|
||||
if (rhs instanceof EBinary && ((EBinary)rhs).operation == Operation.ADD && rhs.actual.sort == Sort.STRING) {
|
||||
((EBinary)rhs).cat = true;
|
||||
}
|
||||
|
||||
rhs.expected = rhs.actual;
|
||||
} else if (shift) {
|
||||
if (promote.sort == Sort.DEF) {
|
||||
// shifts are promoted independently, but for the def type, we need object.
|
||||
rhs.expected = promote;
|
||||
} else if (shiftDistance.sort == Sort.LONG) {
|
||||
rhs.expected = Definition.INT_TYPE;
|
||||
rhs.explicit = true;
|
||||
} else {
|
||||
rhs.expected = shiftDistance;
|
||||
}
|
||||
} else {
|
||||
rhs.expected = promote;
|
||||
}
|
||||
|
||||
rhs = rhs.cast(locals);
|
||||
|
||||
there = AnalyzerCaster.getLegalCast(location, lhs.actual, promote, false, false);
|
||||
back = AnalyzerCaster.getLegalCast(location, promote, lhs.actual, true, false);
|
||||
|
||||
this.statement = true;
|
||||
this.actual = read ? lhs.actual : Definition.VOID_TYPE;
|
||||
}
|
||||
|
||||
private void analyzeSimple(Locals locals) {
|
||||
AStoreable lhs = (AStoreable)this.lhs;
|
||||
|
||||
// If the lhs node is a def optimized node we update the actual type to remove the need for a cast.
|
||||
if (lhs.isDefOptimized()) {
|
||||
rhs.analyze(locals);
|
||||
rhs.expected = rhs.actual;
|
||||
lhs.updateActual(rhs.actual);
|
||||
// Otherwise, we must adapt the rhs type to the lhs type with a cast.
|
||||
} else {
|
||||
rhs.expected = lhs.actual;
|
||||
rhs.analyze(locals);
|
||||
}
|
||||
|
||||
rhs = rhs.cast(locals);
|
||||
|
||||
this.statement = true;
|
||||
this.actual = read ? lhs.actual : Definition.VOID_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles writing byte code for variable/method chains for all given possibilities
|
||||
* including String concatenation, compound assignment, regular assignment, and simple
|
||||
* reads. Includes proper duplication for chained assignments and assignments that are
|
||||
* also read from.
|
||||
*/
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
// For the case where the assignment represents a String concatenation
|
||||
// we must, depending on the Java version, write a StringBuilder or
|
||||
// track types going onto the stack. This must be done before the
|
||||
// lhs is read because we need the StringBuilder to be placed on the
|
||||
// stack ahead of any potential concatenation arguments.
|
||||
int catElementStackSize = 0;
|
||||
|
||||
if (cat) {
|
||||
catElementStackSize = writer.writeNewStrings();
|
||||
}
|
||||
|
||||
// Cast the lhs to a storeable to perform the necessary operations to store the rhs.
|
||||
AStoreable lhs = (AStoreable)this.lhs;
|
||||
lhs.setup(writer, globals); // call the setup method on the lhs to prepare for a load/store operation
|
||||
|
||||
if (cat) {
|
||||
// Handle the case where we are doing a compound assignment
|
||||
// representing a String concatenation.
|
||||
|
||||
writer.writeDup(lhs.accessElementCount(), catElementStackSize); // dup the top element and insert it
|
||||
// before concat helper on stack
|
||||
lhs.load(writer, globals); // read the current lhs's value
|
||||
writer.writeAppendStrings(lhs.actual); // append the lhs's value using the StringBuilder
|
||||
|
||||
rhs.write(writer, globals); // write the bytecode for the rhs
|
||||
|
||||
if (!(rhs instanceof EBinary) || ((EBinary)rhs).cat) {
|
||||
writer.writeAppendStrings(rhs.actual); // append the rhs's value unless it's also a concatenation
|
||||
}
|
||||
|
||||
writer.writeToStrings(); // put the value for string concat onto the stack
|
||||
writer.writeCast(back); // if necessary, cast the String to the lhs actual type
|
||||
|
||||
if (lhs.read) {
|
||||
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // if this lhs is also read
|
||||
// from dup the value onto the stack
|
||||
}
|
||||
|
||||
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
|
||||
} else if (operation != null) {
|
||||
// Handle the case where we are doing a compound assignment that
|
||||
// does not represent a String concatenation.
|
||||
|
||||
writer.writeDup(lhs.accessElementCount(), 0); // if necessary, dup the previous lhs's value
|
||||
// to be both loaded from and stored to
|
||||
lhs.load(writer, globals); // load the current lhs's value
|
||||
|
||||
if (lhs.read && post) {
|
||||
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also
|
||||
// read from and is a post increment
|
||||
}
|
||||
|
||||
writer.writeCast(there); // if necessary cast the current lhs's value
|
||||
// to the promotion type between the lhs and rhs types
|
||||
rhs.write(writer, globals); // write the bytecode for the rhs
|
||||
|
||||
// XXX: fix these types, but first we need def compound assignment tests.
|
||||
// its tricky here as there are possibly explicit casts, too.
|
||||
// write the operation instruction for compound assignment
|
||||
if (promote.sort == Sort.DEF) {
|
||||
writer.writeDynamicBinaryInstruction(location, promote,
|
||||
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
|
||||
} else {
|
||||
writer.writeBinaryInstruction(location, promote, operation);
|
||||
}
|
||||
|
||||
writer.writeCast(back); // if necessary cast the promotion type value back to the lhs's type
|
||||
|
||||
if (lhs.read && !post) {
|
||||
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also
|
||||
// read from and is not a post increment
|
||||
}
|
||||
|
||||
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
|
||||
} else {
|
||||
// Handle the case for a simple write.
|
||||
|
||||
rhs.write(writer, globals); // write the bytecode for the rhs rhs
|
||||
|
||||
if (lhs.read) {
|
||||
writer.writeDup(lhs.actual.sort.size, lhs.accessElementCount()); // dup the value if the lhs is also read from
|
||||
}
|
||||
|
||||
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,18 +22,17 @@ package org.elasticsearch.painless.node;
|
|||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.WriterConstants;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a binary math expression.
|
||||
|
@ -41,13 +40,13 @@ import org.elasticsearch.painless.Locals;
|
|||
public final class EBinary extends AExpression {
|
||||
|
||||
final Operation operation;
|
||||
AExpression left;
|
||||
AExpression right;
|
||||
Type promote; // promoted type
|
||||
Type shiftDistance; // for shifts, the RHS is promoted independently
|
||||
private AExpression left;
|
||||
private AExpression right;
|
||||
|
||||
private Type promote = null; // promoted type
|
||||
private Type shiftDistance = null; // for shifts, the rhs is promoted independently
|
||||
boolean cat = false;
|
||||
boolean originallyExplicit = false; // record whether there was originally an explicit cast
|
||||
private boolean originallyExplicit = false; // record whether there was originally an explicit cast
|
||||
|
||||
public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
@ -56,7 +55,7 @@ public final class EBinary extends AExpression {
|
|||
this.left = Objects.requireNonNull(left);
|
||||
this.right = Objects.requireNonNull(right);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
left.extractVariables(variables);
|
||||
|
@ -66,6 +65,7 @@ public final class EBinary extends AExpression {
|
|||
@Override
|
||||
void analyze(Locals locals) {
|
||||
originallyExplicit = explicit;
|
||||
|
||||
if (operation == Operation.MUL) {
|
||||
analyzeMul(locals);
|
||||
} else if (operation == Operation.DIV) {
|
||||
|
@ -153,9 +153,11 @@ public final class EBinary extends AExpression {
|
|||
}
|
||||
|
||||
actual = promote;
|
||||
|
||||
if (promote.sort == Sort.DEF) {
|
||||
left.expected = left.actual;
|
||||
right.expected = right.actual;
|
||||
|
||||
if (expected != null) {
|
||||
actual = expected;
|
||||
}
|
||||
|
@ -182,8 +184,8 @@ public final class EBinary extends AExpression {
|
|||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
} catch (ArithmeticException e) {
|
||||
throw createError(e);
|
||||
} catch (ArithmeticException exception) {
|
||||
throw createError(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -204,6 +206,7 @@ public final class EBinary extends AExpression {
|
|||
if (promote.sort == Sort.DEF) {
|
||||
left.expected = left.actual;
|
||||
right.expected = right.actual;
|
||||
|
||||
if (expected != null) {
|
||||
actual = expected;
|
||||
}
|
||||
|
@ -230,8 +233,8 @@ public final class EBinary extends AExpression {
|
|||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
} catch (ArithmeticException e) {
|
||||
throw createError(e);
|
||||
} catch (ArithmeticException exception) {
|
||||
throw createError(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -266,6 +269,7 @@ public final class EBinary extends AExpression {
|
|||
} else if (sort == Sort.DEF) {
|
||||
left.expected = left.actual;
|
||||
right.expected = right.actual;
|
||||
|
||||
if (expected != null) {
|
||||
actual = expected;
|
||||
}
|
||||
|
@ -311,6 +315,7 @@ public final class EBinary extends AExpression {
|
|||
if (promote.sort == Sort.DEF) {
|
||||
left.expected = left.actual;
|
||||
right.expected = right.actual;
|
||||
|
||||
if (expected != null) {
|
||||
actual = expected;
|
||||
}
|
||||
|
@ -349,7 +354,6 @@ public final class EBinary extends AExpression {
|
|||
left = left.cast(variables);
|
||||
right = right.cast(variables);
|
||||
|
||||
// It'd be nice to be able to do constant folding here but we can't because constants aren't flowing through EChain
|
||||
promote = Definition.BOOLEAN_TYPE;
|
||||
actual = Definition.BOOLEAN_TYPE;
|
||||
}
|
||||
|
@ -372,11 +376,13 @@ public final class EBinary extends AExpression {
|
|||
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
|
||||
left.expected = left.actual;
|
||||
right.expected = right.actual;
|
||||
|
||||
if (expected != null) {
|
||||
actual = expected;
|
||||
}
|
||||
} else {
|
||||
left.expected = lhspromote;
|
||||
|
||||
if (rhspromote.sort == Sort.LONG) {
|
||||
right.expected = Definition.INT_TYPE;
|
||||
right.explicit = true;
|
||||
|
@ -419,12 +425,14 @@ public final class EBinary extends AExpression {
|
|||
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
|
||||
left.expected = left.actual;
|
||||
right.expected = right.actual;
|
||||
|
||||
if (expected != null) {
|
||||
actual = expected;
|
||||
}
|
||||
} else {
|
||||
left.expected = lhspromote;
|
||||
if (rhspromote.sort == Sort.LONG) {
|
||||
|
||||
if (rhspromote.sort == Sort.LONG) {
|
||||
right.expected = Definition.INT_TYPE;
|
||||
right.explicit = true;
|
||||
} else {
|
||||
|
@ -466,12 +474,14 @@ public final class EBinary extends AExpression {
|
|||
if (lhspromote.sort == Sort.DEF || rhspromote.sort == Sort.DEF) {
|
||||
left.expected = left.actual;
|
||||
right.expected = right.actual;
|
||||
|
||||
if (expected != null) {
|
||||
actual = expected;
|
||||
}
|
||||
} else {
|
||||
left.expected = lhspromote;
|
||||
if (rhspromote.sort == Sort.LONG) {
|
||||
|
||||
if (rhspromote.sort == Sort.LONG) {
|
||||
right.expected = Definition.INT_TYPE;
|
||||
right.explicit = true;
|
||||
} else {
|
||||
|
@ -511,6 +521,7 @@ public final class EBinary extends AExpression {
|
|||
if (promote.sort == Sort.DEF) {
|
||||
left.expected = left.actual;
|
||||
right.expected = right.actual;
|
||||
|
||||
if (expected != null) {
|
||||
actual = expected;
|
||||
}
|
||||
|
@ -628,25 +639,31 @@ public final class EBinary extends AExpression {
|
|||
|
||||
left.write(writer, globals);
|
||||
|
||||
if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) {
|
||||
if (!(left instanceof EBinary) || !((EBinary)left).cat) {
|
||||
writer.writeAppendStrings(left.actual);
|
||||
}
|
||||
|
||||
right.write(writer, globals);
|
||||
|
||||
if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) {
|
||||
if (!(right instanceof EBinary) || !((EBinary)right).cat) {
|
||||
writer.writeAppendStrings(right.actual);
|
||||
}
|
||||
|
||||
if (!cat) {
|
||||
writer.writeToStrings();
|
||||
}
|
||||
} else if (operation == Operation.FIND) {
|
||||
writeBuildMatcher(writer, globals);
|
||||
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND);
|
||||
} else if (operation == Operation.MATCH) {
|
||||
writeBuildMatcher(writer, globals);
|
||||
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES);
|
||||
} else if (operation == Operation.FIND || operation == Operation.MATCH) {
|
||||
right.write(writer, globals);
|
||||
left.write(writer, globals);
|
||||
writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER);
|
||||
|
||||
if (operation == Operation.FIND) {
|
||||
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND);
|
||||
} else if (operation == Operation.MATCH) {
|
||||
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES);
|
||||
} else {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
} else {
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
@ -663,13 +680,5 @@ public final class EBinary extends AExpression {
|
|||
writer.writeBinaryInstruction(location, actual, operation);
|
||||
}
|
||||
}
|
||||
|
||||
writer.writeBranch(tru, fals);
|
||||
}
|
||||
|
||||
private void writeBuildMatcher(MethodWriter writer, Globals globals) {
|
||||
right.write(writer, globals);
|
||||
left.write(writer, globals);
|
||||
writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,15 +30,16 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* Represents a boolean expression.
|
||||
*/
|
||||
public final class EBool extends AExpression {
|
||||
|
||||
final Operation operation;
|
||||
AExpression left;
|
||||
AExpression right;
|
||||
private final Operation operation;
|
||||
private AExpression left;
|
||||
private AExpression right;
|
||||
|
||||
public EBool(Location location, Operation operation, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
@ -47,7 +48,7 @@ public final class EBool extends AExpression {
|
|||
this.left = Objects.requireNonNull(left);
|
||||
this.right = Objects.requireNonNull(right);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
left.extractVariables(variables);
|
||||
|
@ -79,72 +80,38 @@ public final class EBool extends AExpression {
|
|||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
if (tru != null || fals != null) {
|
||||
if (operation == Operation.AND) {
|
||||
Label localfals = fals == null ? new Label() : fals;
|
||||
if (operation == Operation.AND) {
|
||||
Label fals = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
left.fals = localfals;
|
||||
right.tru = tru;
|
||||
right.fals = fals;
|
||||
left.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, fals);
|
||||
right.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, fals);
|
||||
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
writer.push(true);
|
||||
writer.goTo(end);
|
||||
writer.mark(fals);
|
||||
writer.push(false);
|
||||
writer.mark(end);
|
||||
} else if (operation == Operation.OR) {
|
||||
Label tru = new Label();
|
||||
Label fals = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
if (fals == null) {
|
||||
writer.mark(localfals);
|
||||
}
|
||||
} else if (operation == Operation.OR) {
|
||||
Label localtru = tru == null ? new Label() : tru;
|
||||
left.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFNE, tru);
|
||||
right.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, fals);
|
||||
|
||||
left.tru = localtru;
|
||||
right.tru = tru;
|
||||
right.fals = fals;
|
||||
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
||||
if (tru == null) {
|
||||
writer.mark(localtru);
|
||||
}
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
writer.mark(tru);
|
||||
writer.push(true);
|
||||
writer.goTo(end);
|
||||
writer.mark(fals);
|
||||
writer.push(false);
|
||||
writer.mark(end);
|
||||
} else {
|
||||
if (operation == Operation.AND) {
|
||||
Label localfals = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
left.fals = localfals;
|
||||
right.fals = localfals;
|
||||
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
||||
writer.push(true);
|
||||
writer.goTo(end);
|
||||
writer.mark(localfals);
|
||||
writer.push(false);
|
||||
writer.mark(end);
|
||||
} else if (operation == Operation.OR) {
|
||||
Label localtru = new Label();
|
||||
Label localfals = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
left.tru = localtru;
|
||||
right.fals = localfals;
|
||||
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
||||
writer.mark(localtru);
|
||||
writer.push(true);
|
||||
writer.goTo(end);
|
||||
writer.mark(localfals);
|
||||
writer.push(false);
|
||||
writer.mark(end);
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,12 +37,18 @@ public final class EBoolean extends AExpression {
|
|||
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
void extractVariables(Set<String> variables) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("Must read from constant [" + constant + "]."));
|
||||
}
|
||||
|
||||
actual = Definition.BOOLEAN_TYPE;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,20 +35,20 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
|
|||
/**
|
||||
* Represents a user-defined call.
|
||||
*/
|
||||
public class LCallLocal extends ALink {
|
||||
public final class ECallLocal extends AExpression {
|
||||
|
||||
final String name;
|
||||
final List<AExpression> arguments;
|
||||
private final String name;
|
||||
private final List<AExpression> arguments;
|
||||
|
||||
Method method = null;
|
||||
private Method method = null;
|
||||
|
||||
public LCallLocal(Location location, String name, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
||||
super(location);
|
||||
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
|
@ -57,42 +57,29 @@ public class LCallLocal extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before != null) {
|
||||
throw createError(new IllegalArgumentException("Illegal call [" + name + "] against an existing target."));
|
||||
} else if (store) {
|
||||
throw createError(new IllegalArgumentException("Cannot assign a value to a call [" + name + "]."));
|
||||
}
|
||||
|
||||
void analyze(Locals locals) {
|
||||
MethodKey methodKey = new MethodKey(name, arguments.size());
|
||||
method = locals.getMethod(methodKey);
|
||||
|
||||
if (method != null) {
|
||||
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||
AExpression expression = arguments.get(argument);
|
||||
|
||||
expression.expected = method.arguments.get(argument);
|
||||
expression.internal = true;
|
||||
expression.analyze(locals);
|
||||
arguments.set(argument, expression.cast(locals));
|
||||
}
|
||||
|
||||
statement = true;
|
||||
after = method.rtn;
|
||||
|
||||
return this;
|
||||
if (method == null) {
|
||||
throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
|
||||
}
|
||||
|
||||
throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
|
||||
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||
AExpression expression = arguments.get(argument);
|
||||
|
||||
expression.expected = method.arguments.get(argument);
|
||||
expression.internal = true;
|
||||
expression.analyze(locals);
|
||||
arguments.set(argument, expression.cast(locals));
|
||||
}
|
||||
|
||||
statement = true;
|
||||
actual = method.rtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
|
@ -101,9 +88,4 @@ public class LCallLocal extends ALink {
|
|||
|
||||
writer.invokeStatic(CLASS_TYPE, method.method);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
|
@ -23,29 +23,29 @@ import org.elasticsearch.painless.DefBootstrap;
|
|||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Represents a capturing function reference.
|
||||
*/
|
||||
public class ECapturingFunctionRef extends AExpression implements ILambda {
|
||||
public final String variable;
|
||||
public final String call;
|
||||
|
||||
public final class ECapturingFunctionRef extends AExpression implements ILambda {
|
||||
private final String variable;
|
||||
private final String call;
|
||||
|
||||
private FunctionRef ref;
|
||||
Variable captured;
|
||||
String defPointer;
|
||||
private Variable captured;
|
||||
private String defPointer;
|
||||
|
||||
public ECapturingFunctionRef(Location location, String variable, String call) {
|
||||
super(location);
|
||||
|
@ -53,7 +53,7 @@ public class ECapturingFunctionRef extends AExpression implements ILambda {
|
|||
this.variable = Objects.requireNonNull(variable);
|
||||
this.call = Objects.requireNonNull(call);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(variable);
|
||||
|
@ -106,27 +106,27 @@ public class ECapturingFunctionRef extends AExpression implements ILambda {
|
|||
Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
|
||||
Type interfaceType = Type.getMethodType(ref.interfaceMethodType.toMethodDescriptorString());
|
||||
if (ref.needsBridges()) {
|
||||
writer.invokeDynamic(ref.invokedName,
|
||||
invokedType,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
samMethodType,
|
||||
ref.implMethodASM,
|
||||
samMethodType,
|
||||
LambdaMetafactory.FLAG_BRIDGES,
|
||||
1,
|
||||
writer.invokeDynamic(ref.invokedName,
|
||||
invokedType,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
samMethodType,
|
||||
ref.implMethodASM,
|
||||
samMethodType,
|
||||
LambdaMetafactory.FLAG_BRIDGES,
|
||||
1,
|
||||
interfaceType);
|
||||
} else {
|
||||
writer.invokeDynamic(ref.invokedName,
|
||||
invokedType,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
samMethodType,
|
||||
ref.implMethodASM,
|
||||
samMethodType,
|
||||
writer.invokeDynamic(ref.invokedName,
|
||||
invokedType,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
samMethodType,
|
||||
ref.implMethodASM,
|
||||
samMethodType,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getPointer() {
|
||||
return defPointer;
|
||||
|
|
|
@ -30,27 +30,23 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents an implicit cast in most cases, though it will replace
|
||||
* explicit casts in the tree for simplicity. (Internal only.)
|
||||
* Represents a cast that is inserted into the tree replacing other casts. (Internal only.)
|
||||
*/
|
||||
final class ECast extends AExpression {
|
||||
|
||||
final String type;
|
||||
AExpression child;
|
||||
|
||||
Cast cast = null;
|
||||
private AExpression child;
|
||||
private final Cast cast;
|
||||
|
||||
ECast(Location location, AExpression child, Cast cast) {
|
||||
super(location);
|
||||
|
||||
this.type = null;
|
||||
this.child = Objects.requireNonNull(child);
|
||||
this.cast = Objects.requireNonNull(cast);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
child.extractVariables(variables);
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,6 +59,5 @@ final class ECast extends AExpression {
|
|||
child.write(writer, globals);
|
||||
writer.writeDebugInfo(location);
|
||||
writer.writeCast(cast);
|
||||
writer.writeBranch(tru, fals);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,405 +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.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
Type shiftDistance; // for shifts, the RHS is promoted independently
|
||||
Cast there = null;
|
||||
Cast back = null;
|
||||
|
||||
/** Creates a new RHS-only EChain */
|
||||
public EChain(Location location, ALink link) {
|
||||
this(location, Arrays.asList(link), false, false, null, null);
|
||||
}
|
||||
|
||||
public EChain(Location location, List<ALink> links,
|
||||
boolean pre, boolean post, Operation operation, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.links = Objects.requireNonNull(links);
|
||||
this.pre = pre;
|
||||
this.post = post;
|
||||
this.operation = operation;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (ALink link : links) {
|
||||
link.extractVariables(variables);
|
||||
}
|
||||
if (expression != null) {
|
||||
expression.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
analyzeLinks(locals);
|
||||
analyzeIncrDecr();
|
||||
|
||||
if (operation != null) {
|
||||
analyzeCompound(locals);
|
||||
} else if (expression != null) {
|
||||
analyzeWrite(locals);
|
||||
} else {
|
||||
analyzeRead();
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeLinks(Locals variables) {
|
||||
ALink previous = null;
|
||||
int index = 0;
|
||||
|
||||
while (index < links.size()) {
|
||||
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;
|
||||
}
|
||||
|
||||
ALink analyzed = current.analyze(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() {
|
||||
ALink last = links.get(links.size() - 1);
|
||||
|
||||
if (pre && post) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
} else if (pre || post) {
|
||||
if (expression != null) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
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 createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeCompound(Locals variables) {
|
||||
ALink last = links.get(links.size() - 1);
|
||||
|
||||
expression.analyze(variables);
|
||||
boolean shift = false;
|
||||
|
||||
if (operation == Operation.MUL) {
|
||||
promote = AnalyzerCaster.promoteNumeric(last.after, expression.actual, true);
|
||||
} else if (operation == Operation.DIV) {
|
||||
promote = AnalyzerCaster.promoteNumeric(last.after, expression.actual, true);
|
||||
} else if (operation == Operation.REM) {
|
||||
promote = AnalyzerCaster.promoteNumeric(last.after, expression.actual, true);
|
||||
} else if (operation == Operation.ADD) {
|
||||
promote = AnalyzerCaster.promoteAdd(last.after, expression.actual);
|
||||
} else if (operation == Operation.SUB) {
|
||||
promote = AnalyzerCaster.promoteNumeric(last.after, expression.actual, true);
|
||||
} else if (operation == Operation.LSH) {
|
||||
promote = AnalyzerCaster.promoteNumeric(last.after, false);
|
||||
shiftDistance = AnalyzerCaster.promoteNumeric(expression.actual, false);
|
||||
shift = true;
|
||||
} else if (operation == Operation.RSH) {
|
||||
promote = AnalyzerCaster.promoteNumeric(last.after, false);
|
||||
shiftDistance = AnalyzerCaster.promoteNumeric(expression.actual, false);
|
||||
shift = true;
|
||||
} else if (operation == Operation.USH) {
|
||||
promote = AnalyzerCaster.promoteNumeric(last.after, false);
|
||||
shiftDistance = AnalyzerCaster.promoteNumeric(expression.actual, false);
|
||||
shift = true;
|
||||
} else if (operation == Operation.BWAND) {
|
||||
promote = AnalyzerCaster.promoteXor(last.after, expression.actual);
|
||||
} else if (operation == Operation.XOR) {
|
||||
promote = AnalyzerCaster.promoteXor(last.after, expression.actual);
|
||||
} else if (operation == Operation.BWOR) {
|
||||
promote = AnalyzerCaster.promoteXor(last.after, expression.actual);
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
if (promote == null || (shift && shiftDistance == null)) {
|
||||
throw createError(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 (shift) {
|
||||
if (promote.sort == Sort.DEF) {
|
||||
// shifts are promoted independently, but for the def type, we need object.
|
||||
expression.expected = promote;
|
||||
} else if (shiftDistance.sort == Sort.LONG) {
|
||||
expression.expected = Definition.INT_TYPE;
|
||||
expression.explicit = true;
|
||||
} else {
|
||||
expression.expected = shiftDistance;
|
||||
}
|
||||
} else {
|
||||
expression.expected = promote;
|
||||
}
|
||||
|
||||
expression = expression.cast(variables);
|
||||
|
||||
there = AnalyzerCaster.getLegalCast(location, last.after, promote, false, false);
|
||||
back = AnalyzerCaster.getLegalCast(location, promote, last.after, true, false);
|
||||
|
||||
this.statement = true;
|
||||
this.actual = read ? last.after : Definition.VOID_TYPE;
|
||||
}
|
||||
|
||||
private void analyzeWrite(Locals variables) {
|
||||
ALink last = links.get(links.size() - 1);
|
||||
|
||||
// If the store node is a def node, we remove the cast to def from the expression
|
||||
// and promote the real type to it:
|
||||
if (last instanceof IDefLink) {
|
||||
expression.analyze(variables);
|
||||
last.after = expression.expected = expression.actual;
|
||||
} else {
|
||||
// otherwise we adapt the type of the expression to the store type
|
||||
expression.expected = last.after;
|
||||
expression.analyze(variables);
|
||||
}
|
||||
|
||||
expression = expression.cast(variables);
|
||||
|
||||
this.statement = true;
|
||||
this.actual = read ? last.after : Definition.VOID_TYPE;
|
||||
}
|
||||
|
||||
private void analyzeRead() {
|
||||
ALink last = links.get(links.size() - 1);
|
||||
|
||||
// If the load node is a def node, we adapt its after type to use _this_ expected output type:
|
||||
if (last instanceof IDefLink && this.expected != null) {
|
||||
last.after = this.expected;
|
||||
}
|
||||
|
||||
constant = last.string;
|
||||
statement = last.statement;
|
||||
actual = last.after;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles writing byte code for variable/method chains for all given possibilities
|
||||
* including String concatenation, compound assignment, regular assignment, and simple
|
||||
* reads. Includes proper duplication for chained assignments and assignments that are
|
||||
* also read from.
|
||||
*
|
||||
* Example given 'x[0] += 5;' where x is an array of shorts and x[0] is 1.
|
||||
* Note this example has two links -- x (LVariable) and [0] (LBrace).
|
||||
* The following steps occur:
|
||||
* 1. call link{x}.write(...) -- no op [...]
|
||||
* 2. call link{x}.load(...) -- loads the address of the x array onto the stack [..., address(x)]
|
||||
* 3. call writer.dup(...) -- dup's the address of the x array onto the stack for later use with store [..., address(x), address(x)]
|
||||
* 4. call link{[0]}.write(...) -- load the array index value of the constant int 0 onto the stack [..., address(x), address(x), int(0)]
|
||||
* 5. call link{[0]}.load(...) -- load the short value from x[0] onto the stack [..., address(x), short(1)]
|
||||
* 6. call writer.writeCast(there) -- casts the short on the stack to an int so it can be added with the rhs [..., address(x), int(1)]
|
||||
* 7. call expression.write(...) -- puts the expression's value of the constant int 5 onto the stack [..., address(x), int(1), int(5)]
|
||||
* 8. call writer.writeBinaryInstruction(operation) -- writes the int addition instruction [..., address(x), int(6)]
|
||||
* 9. call writer.writeCast(back) -- convert the value on the stack back into a short [..., address(x), short(6)]
|
||||
* 10. call link{[0]}.store(...) -- store the value on the stack into the 0th index of the array x [...]
|
||||
*/
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
// For the case where the chain represents a String concatenation
|
||||
// we must, depending on the Java version, write a StringBuilder or
|
||||
// track types going onto the stack. This must be done before the
|
||||
// links in the chain are read because we need the StringBuilder to
|
||||
// be placed on the stack ahead of any potential concatenation arguments.
|
||||
int catElementStackSize = 0;
|
||||
if (cat) {
|
||||
catElementStackSize = writer.writeNewStrings();
|
||||
}
|
||||
|
||||
ALink last = links.get(links.size() - 1);
|
||||
|
||||
// Go through all the links in the chain first calling write
|
||||
// and then load, except for the final link which may be a store.
|
||||
// See individual links for more information on what each of the
|
||||
// write, load, and store methods do.
|
||||
for (ALink link : links) {
|
||||
link.write(writer, globals); // call the write method on the link to prepare for a load/store operation
|
||||
|
||||
if (link == last && link.store) {
|
||||
if (cat) {
|
||||
// Handle the case where we are doing a compound assignment
|
||||
// representing a String concatenation.
|
||||
|
||||
writer.writeDup(link.size, catElementStackSize); // dup the top element and insert it before concat helper on stack
|
||||
link.load(writer, globals); // read the current link's value
|
||||
writer.writeAppendStrings(link.after); // append the link's value using the StringBuilder
|
||||
|
||||
expression.write(writer, globals); // write the bytecode for the rhs expression
|
||||
|
||||
if (!(expression instanceof EBinary) ||
|
||||
((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) {
|
||||
writer.writeAppendStrings(expression.actual); // append the expression's value unless it's also a concatenation
|
||||
}
|
||||
|
||||
writer.writeToStrings(); // put the value for string concat onto the stack
|
||||
writer.writeCast(back); // if necessary, cast the String to the lhs actual type
|
||||
|
||||
if (link.load) {
|
||||
writer.writeDup(link.after.sort.size, link.size); // if this link is also read from dup the value onto the stack
|
||||
}
|
||||
|
||||
link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
|
||||
} else if (operation != null) {
|
||||
// Handle the case where we are doing a compound assignment that
|
||||
// does not represent a String concatenation.
|
||||
|
||||
writer.writeDup(link.size, 0); // if necessary, dup the previous link's value to be both loaded from and stored to
|
||||
link.load(writer, globals); // load the current link's value
|
||||
|
||||
if (link.load && post) {
|
||||
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also
|
||||
// read from and is a post increment
|
||||
}
|
||||
|
||||
writer.writeCast(there); // if necessary cast the current link's value
|
||||
// to the promotion type between the lhs and rhs types
|
||||
expression.write(writer, globals); // write the bytecode for the rhs expression
|
||||
// XXX: fix these types, but first we need def compound assignment tests.
|
||||
// its tricky here as there are possibly explicit casts, too.
|
||||
// write the operation instruction for compound assignment
|
||||
if (promote.sort == Sort.DEF) {
|
||||
writer.writeDynamicBinaryInstruction(location, promote,
|
||||
Definition.DEF_TYPE, Definition.DEF_TYPE, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
|
||||
} else {
|
||||
writer.writeBinaryInstruction(location, promote, operation);
|
||||
}
|
||||
|
||||
writer.writeCast(back); // if necessary cast the promotion type value back to the link's type
|
||||
|
||||
if (link.load && !post) {
|
||||
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also
|
||||
// read from and is not a post increment
|
||||
}
|
||||
|
||||
link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
|
||||
} else {
|
||||
// Handle the case for a simple write.
|
||||
|
||||
expression.write(writer, globals); // write the bytecode for the rhs expression
|
||||
|
||||
if (link.load) {
|
||||
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also read from
|
||||
}
|
||||
|
||||
link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
|
||||
}
|
||||
} else {
|
||||
// Handle the case for a simple read.
|
||||
|
||||
link.load(writer, globals); // read the link's value onto the stack
|
||||
}
|
||||
}
|
||||
|
||||
writer.writeBranch(tru, fals); // if this is a branch node, write the bytecode to make an appropiate jump
|
||||
}
|
||||
}
|
|
@ -43,10 +43,11 @@ import static org.elasticsearch.painless.WriterConstants.EQUALS;
|
|||
*/
|
||||
public final class EComp extends AExpression {
|
||||
|
||||
final Operation operation;
|
||||
AExpression left;
|
||||
AExpression right;
|
||||
Type promotedType;
|
||||
private final Operation operation;
|
||||
private AExpression left;
|
||||
private AExpression right;
|
||||
|
||||
private Type promotedType;
|
||||
|
||||
public EComp(Location location, Operation operation, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
@ -55,7 +56,7 @@ public final class EComp extends AExpression {
|
|||
this.left = Objects.requireNonNull(left);
|
||||
this.right = Objects.requireNonNull(right);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
left.extractVariables(variables);
|
||||
|
@ -449,25 +450,21 @@ public final class EComp extends AExpression {
|
|||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
boolean branch = tru != null || fals != null;
|
||||
|
||||
left.write(writer, globals);
|
||||
|
||||
if (!right.isNull) {
|
||||
right.write(writer, globals);
|
||||
}
|
||||
|
||||
Label jump = tru != null ? tru : fals != null ? fals : new Label();
|
||||
Label jump = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
boolean eq = (operation == Operation.EQ || operation == Operation.EQR) && (tru != null || fals == null) ||
|
||||
(operation == Operation.NE || operation == Operation.NER) && fals != null;
|
||||
boolean ne = (operation == Operation.NE || operation == Operation.NER) && (tru != null || fals == null) ||
|
||||
(operation == Operation.EQ || operation == Operation.EQR) && fals != null;
|
||||
boolean lt = operation == Operation.LT && (tru != null || fals == null) || operation == Operation.GTE && fals != null;
|
||||
boolean lte = operation == Operation.LTE && (tru != null || fals == null) || operation == Operation.GT && fals != null;
|
||||
boolean gt = operation == Operation.GT && (tru != null || fals == null) || operation == Operation.LTE && fals != null;
|
||||
boolean gte = operation == Operation.GTE && (tru != null || fals == null) || operation == Operation.LT && fals != null;
|
||||
boolean eq = (operation == Operation.EQ || operation == Operation.EQR);
|
||||
boolean ne = (operation == Operation.NE || operation == Operation.NER);
|
||||
boolean lt = operation == Operation.LT;
|
||||
boolean lte = operation == Operation.LTE;
|
||||
boolean gt = operation == Operation.GT;
|
||||
boolean gte = operation == Operation.GTE;
|
||||
|
||||
boolean writejump = true;
|
||||
|
||||
|
@ -478,8 +475,8 @@ public final class EComp extends AExpression {
|
|||
case CHAR:
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
case BOOL:
|
||||
if (eq) writer.ifZCmp(MethodWriter.EQ, jump);
|
||||
else if (ne) writer.ifZCmp(MethodWriter.NE, jump);
|
||||
if (eq) writer.ifCmp(promotedType.type, MethodWriter.EQ, jump);
|
||||
else if (ne) writer.ifCmp(promotedType.type, MethodWriter.NE, jump);
|
||||
else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
@ -503,10 +500,11 @@ public final class EComp extends AExpression {
|
|||
case DEF:
|
||||
org.objectweb.asm.Type booleanType = org.objectweb.asm.Type.getType(boolean.class);
|
||||
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(booleanType, left.actual.type, right.actual.type);
|
||||
|
||||
if (eq) {
|
||||
if (right.isNull) {
|
||||
writer.ifNull(jump);
|
||||
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
|
||||
} else if (!left.isNull && operation == Operation.EQ) {
|
||||
writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
writejump = false;
|
||||
} else {
|
||||
|
@ -515,7 +513,7 @@ public final class EComp extends AExpression {
|
|||
} else if (ne) {
|
||||
if (right.isNull) {
|
||||
writer.ifNonNull(jump);
|
||||
} else if (!left.isNull && (operation == Operation.EQ || operation == Operation.NE)) {
|
||||
} else if (!left.isNull && operation == Operation.NE) {
|
||||
writer.invokeDefCall("eq", descriptor, DefBootstrap.BINARY_OPERATOR, DefBootstrap.OPERATOR_ALLOWS_NULL);
|
||||
writer.ifZCmp(MethodWriter.EQ, jump);
|
||||
} else {
|
||||
|
@ -537,22 +535,13 @@ public final class EComp extends AExpression {
|
|||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
if (branch && !writejump) {
|
||||
writer.ifZCmp(MethodWriter.NE, jump);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
if (eq) {
|
||||
if (right.isNull) {
|
||||
writer.ifNull(jump);
|
||||
} else if (operation == Operation.EQ || operation == Operation.NE) {
|
||||
} else if (operation == Operation.EQ) {
|
||||
writer.invokeStatic(OBJECTS_TYPE, EQUALS);
|
||||
|
||||
if (branch) {
|
||||
writer.ifZCmp(MethodWriter.NE, jump);
|
||||
}
|
||||
|
||||
writejump = false;
|
||||
} else {
|
||||
writer.ifCmp(promotedType.type, MethodWriter.EQ, jump);
|
||||
|
@ -560,7 +549,7 @@ public final class EComp extends AExpression {
|
|||
} else if (ne) {
|
||||
if (right.isNull) {
|
||||
writer.ifNonNull(jump);
|
||||
} else if (operation == Operation.EQ || operation == Operation.NE) {
|
||||
} else if (operation == Operation.NE) {
|
||||
writer.invokeStatic(OBJECTS_TYPE, EQUALS);
|
||||
writer.ifZCmp(MethodWriter.EQ, jump);
|
||||
} else {
|
||||
|
@ -571,7 +560,7 @@ public final class EComp extends AExpression {
|
|||
}
|
||||
}
|
||||
|
||||
if (!branch && writejump) {
|
||||
if (writejump) {
|
||||
writer.push(false);
|
||||
writer.goTo(end);
|
||||
writer.mark(jump);
|
||||
|
|
|
@ -31,15 +31,16 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* Respresents a conditional expression.
|
||||
*/
|
||||
public final class EConditional extends AExpression {
|
||||
|
||||
AExpression condition;
|
||||
AExpression left;
|
||||
AExpression right;
|
||||
private AExpression condition;
|
||||
private AExpression left;
|
||||
private AExpression right;
|
||||
|
||||
public EConditional(Location location, AExpression condition, AExpression left, AExpression right) {
|
||||
super(location);
|
||||
|
@ -48,7 +49,7 @@ public final class EConditional extends AExpression {
|
|||
this.left = Objects.requireNonNull(left);
|
||||
this.right = Objects.requireNonNull(right);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
|
@ -93,17 +94,15 @@ public final class EConditional extends AExpression {
|
|||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
Label localfals = new Label();
|
||||
Label fals = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
condition.fals = localfals;
|
||||
left.tru = right.tru = tru;
|
||||
left.fals = right.fals = fals;
|
||||
|
||||
condition.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, fals);
|
||||
|
||||
left.write(writer, globals);
|
||||
writer.goTo(end);
|
||||
writer.mark(localfals);
|
||||
writer.mark(fals);
|
||||
right.write(writer, globals);
|
||||
writer.mark(end);
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ import org.elasticsearch.painless.Locals;
|
|||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents a constant. Note this replaces any other expression
|
||||
* node with a constant value set during a cast. (Internal only.)
|
||||
* Represents a constant inserted into the tree replacing
|
||||
* other constants during constant folding. (Internal only.)
|
||||
*/
|
||||
final class EConstant extends AExpression {
|
||||
|
||||
|
@ -40,9 +40,11 @@ final class EConstant extends AExpression {
|
|||
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
@ -82,22 +84,9 @@ final class EConstant extends AExpression {
|
|||
case CHAR: writer.push((char)constant); break;
|
||||
case SHORT: writer.push((short)constant); break;
|
||||
case BYTE: writer.push((byte)constant); break;
|
||||
case BOOL:
|
||||
if (tru != null && (boolean)constant) {
|
||||
writer.goTo(tru);
|
||||
} else if (fals != null && !(boolean)constant) {
|
||||
writer.goTo(fals);
|
||||
} else if (tru == null && fals == null) {
|
||||
writer.push((boolean)constant);
|
||||
}
|
||||
|
||||
break;
|
||||
case BOOL: writer.push((boolean)constant); break;
|
||||
default:
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
if (sort != Sort.BOOL) {
|
||||
writer.writeBranch(tru, fals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,19 +33,23 @@ import java.util.Set;
|
|||
*/
|
||||
public final class EDecimal extends AExpression {
|
||||
|
||||
final String value;
|
||||
private final String value;
|
||||
|
||||
public EDecimal(Location location, String value) {
|
||||
super(location);
|
||||
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("Must read from constant [" + value + "]."));
|
||||
}
|
||||
|
||||
if (value.endsWith("f") || value.endsWith("F")) {
|
||||
try {
|
||||
constant = Float.parseFloat(value.substring(0, value.length() - 1));
|
||||
|
|
|
@ -33,8 +33,8 @@ import java.util.Set;
|
|||
*/
|
||||
public final class EExplicit extends AExpression {
|
||||
|
||||
final String type;
|
||||
AExpression child;
|
||||
private final String type;
|
||||
private AExpression child;
|
||||
|
||||
public EExplicit(Location location, String type, AExpression child) {
|
||||
super(location);
|
||||
|
@ -42,7 +42,7 @@ public final class EExplicit extends AExpression {
|
|||
this.type = Objects.requireNonNull(type);
|
||||
this.child = Objects.requireNonNull(child);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
child.extractVariables(variables);
|
||||
|
|
|
@ -20,30 +20,30 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Represents a function reference.
|
||||
*/
|
||||
public class EFunctionRef extends AExpression implements ILambda {
|
||||
public final String type;
|
||||
public final String call;
|
||||
public final class EFunctionRef extends AExpression implements ILambda {
|
||||
private final String type;
|
||||
private final String call;
|
||||
|
||||
private FunctionRef ref;
|
||||
String defPointer;
|
||||
private String defPointer;
|
||||
|
||||
public EFunctionRef(Location location, String type, String call) {
|
||||
super(location);
|
||||
|
@ -51,7 +51,7 @@ public class EFunctionRef extends AExpression implements ILambda {
|
|||
this.type = Objects.requireNonNull(type);
|
||||
this.call = Objects.requireNonNull(call);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -34,19 +34,20 @@ import java.util.Set;
|
|||
* <p>
|
||||
* Unlike java's, this works for primitive types too.
|
||||
*/
|
||||
public class EInstanceof extends AExpression {
|
||||
AExpression expression;
|
||||
final String type;
|
||||
Class<?> resolvedType;
|
||||
Class<?> expressionType;
|
||||
boolean primitiveExpression;
|
||||
public final class EInstanceof extends AExpression {
|
||||
private AExpression expression;
|
||||
private final String type;
|
||||
|
||||
private Class<?> resolvedType;
|
||||
private Class<?> expressionType;
|
||||
private boolean primitiveExpression;
|
||||
|
||||
public EInstanceof(Location location, AExpression expression, String type) {
|
||||
super(location);
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
this.type = Objects.requireNonNull(type);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
expression.extractVariables(variables);
|
||||
|
@ -54,20 +55,29 @@ public class EInstanceof extends AExpression {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
Definition.Type raw = Definition.getType(type);
|
||||
// map to wrapped type for primitive types
|
||||
resolvedType = MethodType.methodType(raw.clazz).wrap().returnType();
|
||||
expression.analyze(locals);
|
||||
actual = Definition.BOOLEAN_TYPE;
|
||||
|
||||
Definition.Type expressionRaw = expression.actual;
|
||||
if (expressionRaw == null) {
|
||||
expressionRaw = Definition.DEF_TYPE;
|
||||
final Type type;
|
||||
|
||||
// ensure the specified type is part of the definition
|
||||
try {
|
||||
type = Definition.getType(this.type);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
|
||||
}
|
||||
// record if the expression returns a primitive
|
||||
primitiveExpression = expressionRaw.clazz.isPrimitive();
|
||||
|
||||
// map to wrapped type for primitive types
|
||||
expressionType = MethodType.methodType(expressionRaw.clazz).wrap().returnType();
|
||||
resolvedType = type.sort.primitive ? type.sort.boxed : type.clazz;
|
||||
|
||||
// analyze and cast the expression
|
||||
expression.analyze(locals);
|
||||
expression.expected = expression.actual;
|
||||
expression = expression.cast(locals);
|
||||
|
||||
// record if the expression returns a primitive
|
||||
primitiveExpression = expression.actual.sort.primitive;
|
||||
// map to wrapped type for primitive types
|
||||
expressionType = expression.actual.sort.primitive ? expression.actual.sort.boxed : type.clazz;
|
||||
|
||||
actual = Definition.BOOLEAN_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
|
@ -29,7 +30,6 @@ import org.elasticsearch.painless.Definition;
|
|||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
import java.util.ArrayList;
|
||||
|
@ -47,7 +47,7 @@ import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE
|
|||
* This can currently only be the direct argument of a call (method/constructor).
|
||||
* When the argument is of a known type, it uses
|
||||
* <a href="http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html">
|
||||
* Java's lambda translation</a>. However, if its a def call, then we don't have
|
||||
* Java's lambda translation</a>. However, if its a def call, then we don't have
|
||||
* enough information, and have to defer this until link time. In that case a placeholder
|
||||
* and all captures are pushed onto the stack and folded into the signature of the parent call.
|
||||
* <p>
|
||||
|
@ -64,24 +64,25 @@ import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE
|
|||
* <br>
|
||||
* {@code sort(list, lambda$0(capture))}
|
||||
*/
|
||||
public class ELambda extends AExpression implements ILambda {
|
||||
final String name;
|
||||
final FunctionReserved reserved;
|
||||
final List<String> paramTypeStrs;
|
||||
final List<String> paramNameStrs;
|
||||
final List<AStatement> statements;
|
||||
public final class ELambda extends AExpression implements ILambda {
|
||||
|
||||
private final String name;
|
||||
private final FunctionReserved reserved;
|
||||
private final List<String> paramTypeStrs;
|
||||
private final List<String> paramNameStrs;
|
||||
private final List<AStatement> statements;
|
||||
|
||||
// desugared synthetic method (lambda body)
|
||||
SFunction desugared;
|
||||
private SFunction desugared;
|
||||
// captured variables
|
||||
List<Variable> captures;
|
||||
private List<Variable> captures;
|
||||
// static parent, static lambda
|
||||
FunctionRef ref;
|
||||
private FunctionRef ref;
|
||||
// dynamic parent, deferred until link time
|
||||
String defPointer;
|
||||
private String defPointer;
|
||||
|
||||
public ELambda(String name, FunctionReserved reserved,
|
||||
Location location, List<String> paramTypes, List<String> paramNames,
|
||||
public ELambda(String name, FunctionReserved reserved,
|
||||
Location location, List<String> paramTypes, List<String> paramNames,
|
||||
List<AStatement> statements) {
|
||||
super(location);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
|
@ -90,7 +91,7 @@ public class ELambda extends AExpression implements ILambda {
|
|||
this.paramNameStrs = Collections.unmodifiableList(paramNames);
|
||||
this.statements = Collections.unmodifiableList(statements);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AStatement statement : statements) {
|
||||
|
@ -100,7 +101,7 @@ public class ELambda extends AExpression implements ILambda {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
final Definition.Type returnType;
|
||||
final Type returnType;
|
||||
final List<String> actualParamTypeStrs;
|
||||
Method interfaceMethod;
|
||||
// inspect the target first, set interface method if we know it.
|
||||
|
@ -114,12 +115,12 @@ public class ELambda extends AExpression implements ILambda {
|
|||
// we know the method statically, infer return type and any unknown/def types
|
||||
interfaceMethod = expected.struct.getFunctionalMethod();
|
||||
if (interfaceMethod == null) {
|
||||
throw createError(new IllegalArgumentException("Cannot pass lambda to [" + expected.name +
|
||||
throw createError(new IllegalArgumentException("Cannot pass lambda to [" + expected.name +
|
||||
"], not a functional interface"));
|
||||
}
|
||||
// check arity before we manipulate parameters
|
||||
if (interfaceMethod.arguments.size() != paramTypeStrs.size())
|
||||
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name +
|
||||
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name +
|
||||
"] in [" + expected.clazz + "]");
|
||||
// for method invocation, its allowed to ignore the return value
|
||||
if (interfaceMethod.rtn == Definition.VOID_TYPE) {
|
||||
|
@ -159,14 +160,14 @@ public class ELambda extends AExpression implements ILambda {
|
|||
}
|
||||
paramTypes.addAll(actualParamTypeStrs);
|
||||
paramNames.addAll(paramNameStrs);
|
||||
|
||||
|
||||
// desugar lambda body into a synthetic method
|
||||
desugared = new SFunction(reserved, location, returnType.name, name,
|
||||
desugared = new SFunction(reserved, location, returnType.name, name,
|
||||
paramTypes, paramNames, statements, true);
|
||||
desugared.generate();
|
||||
desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), returnType, desugared.parameters,
|
||||
desugared.generateSignature();
|
||||
desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), returnType, desugared.parameters,
|
||||
captures.size(), reserved.getMaxLoopCounter()));
|
||||
|
||||
|
||||
// setup method reference to synthetic method
|
||||
if (expected == null) {
|
||||
ref = null;
|
||||
|
@ -195,8 +196,10 @@ public class ELambda extends AExpression implements ILambda {
|
|||
}
|
||||
// convert MethodTypes to asm Type for the constant pool.
|
||||
String invokedType = ref.invokedType.toMethodDescriptorString();
|
||||
Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
|
||||
Type interfaceType = Type.getMethodType(ref.interfaceMethodType.toMethodDescriptorString());
|
||||
org.objectweb.asm.Type samMethodType =
|
||||
org.objectweb.asm.Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
|
||||
org.objectweb.asm.Type interfaceType =
|
||||
org.objectweb.asm.Type.getMethodType(ref.interfaceMethodType.toMethodDescriptorString());
|
||||
if (ref.needsBridges()) {
|
||||
writer.invokeDynamic(ref.invokedName,
|
||||
invokedType,
|
||||
|
@ -235,8 +238,8 @@ public class ELambda extends AExpression implements ILambda {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Type[] getCaptures() {
|
||||
Type[] types = new Type[captures.size()];
|
||||
public org.objectweb.asm.Type[] getCaptures() {
|
||||
org.objectweb.asm.Type[] types = new org.objectweb.asm.Type[captures.size()];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
types[i] = captures.get(i).type.type;
|
||||
}
|
||||
|
|
|
@ -33,11 +33,11 @@ import java.util.Set;
|
|||
/**
|
||||
* Represents a list initialization shortcut.
|
||||
*/
|
||||
public class EListInit extends AExpression {
|
||||
final List<AExpression> values;
|
||||
public final class EListInit extends AExpression {
|
||||
private final List<AExpression> values;
|
||||
|
||||
Method constructor = null;
|
||||
Method method = null;
|
||||
private Method constructor = null;
|
||||
private Method method = null;
|
||||
|
||||
public EListInit(Location location, List<AExpression> values) {
|
||||
super(location);
|
||||
|
@ -54,6 +54,10 @@ public class EListInit extends AExpression {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("Must read from list initializer."));
|
||||
}
|
||||
|
||||
try {
|
||||
actual = Definition.getType("ArrayList");
|
||||
} catch (IllegalArgumentException exception) {
|
||||
|
|
|
@ -33,12 +33,12 @@ import java.util.Set;
|
|||
/**
|
||||
* Represents a map initialization shortcut.
|
||||
*/
|
||||
public class EMapInit extends AExpression {
|
||||
final List<AExpression> keys;
|
||||
final List<AExpression> values;
|
||||
public final class EMapInit extends AExpression {
|
||||
private final List<AExpression> keys;
|
||||
private final List<AExpression> values;
|
||||
|
||||
Method constructor = null;
|
||||
Method method = null;
|
||||
private Method constructor = null;
|
||||
private Method method = null;
|
||||
|
||||
public EMapInit(Location location, List<AExpression> keys, List<AExpression> values) {
|
||||
super(location);
|
||||
|
@ -60,6 +60,10 @@ public class EMapInit extends AExpression {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("Must read from map initializer."));
|
||||
}
|
||||
|
||||
try {
|
||||
actual = Definition.getType("HashMap");
|
||||
} catch (IllegalArgumentException exception) {
|
||||
|
|
|
@ -33,14 +33,14 @@ import java.util.Set;
|
|||
/**
|
||||
* Represents an array instantiation.
|
||||
*/
|
||||
public final class LNewArray extends ALink {
|
||||
public final class ENewArray extends AExpression {
|
||||
|
||||
final String type;
|
||||
final List<AExpression> arguments;
|
||||
final boolean initialize;
|
||||
private final String type;
|
||||
private final List<AExpression> arguments;
|
||||
private final boolean initialize;
|
||||
|
||||
public LNewArray(Location location, String type, List<AExpression> arguments, boolean initialize) {
|
||||
super(location, -1);
|
||||
public ENewArray(Location location, String type, List<AExpression> arguments, boolean initialize) {
|
||||
super(location);
|
||||
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
|
@ -55,13 +55,9 @@ public final class LNewArray extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before != null) {
|
||||
throw createError(new IllegalArgumentException("Cannot create a new array with a target already defined."));
|
||||
} else if (store) {
|
||||
throw createError(new IllegalArgumentException("Cannot assign a value to a new array."));
|
||||
} else if (!load) {
|
||||
throw createError(new IllegalArgumentException("A newly created array must be read."));
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("A newly created array must be read from."));
|
||||
}
|
||||
|
||||
final Type type;
|
||||
|
@ -81,23 +77,16 @@ public final class LNewArray extends ALink {
|
|||
arguments.set(argument, expression.cast(locals));
|
||||
}
|
||||
|
||||
after = Definition.getType(type.struct, initialize ? 1 : arguments.size());
|
||||
|
||||
return this;
|
||||
actual = Definition.getType(type.struct, initialize ? 1 : arguments.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (initialize) {
|
||||
writer.push(arguments.size());
|
||||
writer.newArray(Definition.getType(after.struct, 0).type);
|
||||
writer.newArray(Definition.getType(actual.struct, 0).type);
|
||||
|
||||
for (int index = 0; index < arguments.size(); ++index) {
|
||||
AExpression argument = arguments.get(index);
|
||||
|
@ -105,7 +94,7 @@ public final class LNewArray extends ALink {
|
|||
writer.dup();
|
||||
writer.push(index);
|
||||
argument.write(writer, globals);
|
||||
writer.arrayStore(Definition.getType(after.struct, 0).type);
|
||||
writer.arrayStore(Definition.getType(actual.struct, 0).type);
|
||||
}
|
||||
} else {
|
||||
for (AExpression argument : arguments) {
|
||||
|
@ -113,15 +102,10 @@ public final class LNewArray extends ALink {
|
|||
}
|
||||
|
||||
if (arguments.size() > 1) {
|
||||
writer.visitMultiANewArrayInsn(after.type.getDescriptor(), after.type.getDimensions());
|
||||
writer.visitMultiANewArrayInsn(actual.type.getDescriptor(), actual.type.getDimensions());
|
||||
} else {
|
||||
writer.newArray(Definition.getType(after.struct, 0).type);
|
||||
writer.newArray(Definition.getType(actual.struct, 0).type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
|
@ -35,20 +35,20 @@ import java.util.Set;
|
|||
/**
|
||||
* Represents and object instantiation.
|
||||
*/
|
||||
public final class LNewObj extends ALink {
|
||||
public final class ENewObj extends AExpression {
|
||||
|
||||
final String type;
|
||||
final List<AExpression> arguments;
|
||||
private final String type;
|
||||
private final List<AExpression> arguments;
|
||||
|
||||
Method constructor;
|
||||
private Method constructor;
|
||||
|
||||
public LNewObj(Location location, String type, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
public ENewObj(Location location, String type, List<AExpression> arguments) {
|
||||
super(location);
|
||||
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
|
@ -57,13 +57,7 @@ public final class LNewObj extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before != null) {
|
||||
throw createError(new IllegalArgumentException("Illegal new call with a target already defined."));
|
||||
} else if (store) {
|
||||
throw createError(new IllegalArgumentException("Cannot assign a value to a new call."));
|
||||
}
|
||||
|
||||
void analyze(Locals locals) {
|
||||
final Type type;
|
||||
|
||||
try {
|
||||
|
@ -94,25 +88,19 @@ public final class LNewObj extends ALink {
|
|||
}
|
||||
|
||||
statement = true;
|
||||
after = type;
|
||||
actual = type;
|
||||
} else {
|
||||
throw createError(new IllegalArgumentException("Unknown new call on type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.newInstance(after.type);
|
||||
|
||||
if (load) {
|
||||
writer.newInstance(actual.type);
|
||||
|
||||
if (read) {
|
||||
writer.dup();
|
||||
}
|
||||
|
||||
|
@ -122,9 +110,4 @@ public final class LNewObj extends ALink {
|
|||
|
||||
writer.invokeConstructor(constructor.owner.type, constructor.method);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
|
@ -37,12 +37,18 @@ public final class ENull extends AExpression {
|
|||
public ENull(Location location) {
|
||||
super(location);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
void extractVariables(Set<String> variables) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("Must read from null constant."));
|
||||
}
|
||||
|
||||
isNull = true;
|
||||
|
||||
if (expected != null) {
|
||||
|
|
|
@ -35,8 +35,8 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
*/
|
||||
public final class ENumeric extends AExpression {
|
||||
|
||||
final String value;
|
||||
int radix;
|
||||
private final String value;
|
||||
private int radix;
|
||||
|
||||
public ENumeric(Location location, String value, int radix) {
|
||||
super(location);
|
||||
|
@ -44,12 +44,18 @@ public final class ENumeric extends AExpression {
|
|||
this.value = Objects.requireNonNull(value);
|
||||
this.radix = radix;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
void extractVariables(Set<String> variables) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("Must read from constant [" + value + "]."));
|
||||
}
|
||||
|
||||
if (value.endsWith("d") || value.endsWith("D")) {
|
||||
if (radix != 10) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
|
|
|
@ -35,61 +35,53 @@ import org.elasticsearch.painless.WriterConstants;
|
|||
/**
|
||||
* Represents a regex constant. All regexes are constants.
|
||||
*/
|
||||
public final class LRegex extends ALink {
|
||||
public final class ERegex extends AExpression {
|
||||
|
||||
private final String pattern;
|
||||
private final int flags;
|
||||
private Constant constant;
|
||||
|
||||
public LRegex(Location location, String pattern, String flagsString) {
|
||||
super(location, 1);
|
||||
public ERegex(Location location, String pattern, String flagsString) {
|
||||
super(location);
|
||||
|
||||
this.pattern = pattern;
|
||||
|
||||
int flags = 0;
|
||||
|
||||
for (int c = 0; c < flagsString.length(); c++) {
|
||||
flags |= flagForChar(flagsString.charAt(c));
|
||||
}
|
||||
|
||||
this.flags = flags;
|
||||
try {
|
||||
// Compile the pattern early after parsing so we can throw an error to the user with the location
|
||||
Pattern.compile(pattern, flags);
|
||||
} catch (PatternSyntaxException e) {
|
||||
throw createError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before != null) {
|
||||
throw createError(new IllegalArgumentException("Illegal Regex constant [" + pattern + "]."));
|
||||
} else if (store) {
|
||||
throw createError(new IllegalArgumentException("Cannot write to Regex constant [" + pattern + "]."));
|
||||
} else if (!load) {
|
||||
throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "]."));
|
||||
}
|
||||
|
||||
constant = new Constant(location, Definition.PATTERN_TYPE.type, "regexAt$" + location.getOffset(), this::initializeConstant);
|
||||
after = Definition.PATTERN_TYPE;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
void extractVariables(Set<String> variables) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type);
|
||||
globals.addConstantInitializer(constant);
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "]."));
|
||||
}
|
||||
|
||||
try {
|
||||
Pattern.compile(pattern, flags);
|
||||
} catch (PatternSyntaxException exception) {
|
||||
throw createError(exception);
|
||||
}
|
||||
|
||||
constant = new Constant(location, Definition.PATTERN_TYPE.type, "regexAt$" + location.getOffset(), this::initializeConstant);
|
||||
actual = Definition.PATTERN_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type);
|
||||
globals.addConstantInitializer(constant);
|
||||
}
|
||||
|
||||
private void initializeConstant(MethodWriter writer) {
|
||||
|
@ -100,15 +92,16 @@ public final class LRegex extends ALink {
|
|||
|
||||
private int flagForChar(char c) {
|
||||
switch (c) {
|
||||
case 'c': return Pattern.CANON_EQ;
|
||||
case 'i': return Pattern.CASE_INSENSITIVE;
|
||||
case 'l': return Pattern.LITERAL;
|
||||
case 'm': return Pattern.MULTILINE;
|
||||
case 's': return Pattern.DOTALL;
|
||||
case 'U': return Pattern.UNICODE_CHARACTER_CLASS;
|
||||
case 'u': return Pattern.UNICODE_CASE;
|
||||
case 'x': return Pattern.COMMENTS;
|
||||
default: throw new IllegalArgumentException("Unknown flag [" + c + "]");
|
||||
case 'c': return Pattern.CANON_EQ;
|
||||
case 'i': return Pattern.CASE_INSENSITIVE;
|
||||
case 'l': return Pattern.LITERAL;
|
||||
case 'm': return Pattern.MULTILINE;
|
||||
case 's': return Pattern.DOTALL;
|
||||
case 'U': return Pattern.UNICODE_CHARACTER_CLASS;
|
||||
case 'u': return Pattern.UNICODE_CASE;
|
||||
case 'x': return Pattern.COMMENTS;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown flag [" + c + "]");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,47 +32,32 @@ import org.elasticsearch.painless.Locals;
|
|||
/**
|
||||
* Represents a static type target.
|
||||
*/
|
||||
public final class LStatic extends ALink {
|
||||
public final class EStatic extends AExpression {
|
||||
|
||||
final String type;
|
||||
private final String type;
|
||||
|
||||
public LStatic(Location location, String type) {
|
||||
super(location, 0);
|
||||
public EStatic(Location location, String type) {
|
||||
super(location);
|
||||
|
||||
this.type = Objects.requireNonNull(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before != null) {
|
||||
throw createError(new IllegalArgumentException("Illegal static type [" + type + "] after target already defined."));
|
||||
}
|
||||
void extractVariables(Set<String> variables) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
try {
|
||||
after = Definition.getType(type);
|
||||
statik = true;
|
||||
actual = Definition.getType(type);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw createError(new IllegalArgumentException("Not a type [" + type + "]."));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
|
@ -31,44 +31,30 @@ import java.util.Set;
|
|||
/**
|
||||
* Represents a string constant.
|
||||
*/
|
||||
public final class LString extends ALink {
|
||||
public final class EString extends AExpression {
|
||||
|
||||
public LString(Location location, String string) {
|
||||
super(location, -1);
|
||||
public EString(Location location, String string) {
|
||||
super(location);
|
||||
|
||||
this.string = Objects.requireNonNull(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before != null) {
|
||||
throw createError(new IllegalArgumentException("Illegal String constant [" + string + "]."));
|
||||
} else if (store) {
|
||||
throw createError(new IllegalArgumentException("Cannot write to read-only String constant [" + string + "]."));
|
||||
} else if (!load) {
|
||||
throw createError(new IllegalArgumentException("Must read String constant [" + string + "]."));
|
||||
}
|
||||
|
||||
after = Definition.STRING_TYPE;
|
||||
|
||||
return this;
|
||||
this.constant = Objects.requireNonNull(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
void extractVariables(Set<String> variables) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.push(string);
|
||||
void analyze(Locals locals) {
|
||||
if (!read) {
|
||||
throw createError(new IllegalArgumentException("Must read from constant [" + constant + "]."));
|
||||
}
|
||||
|
||||
actual = Definition.STRING_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
}
|
|
@ -34,16 +34,18 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* Represents a unary math expression.
|
||||
*/
|
||||
public final class EUnary extends AExpression {
|
||||
|
||||
final Operation operation;
|
||||
AExpression child;
|
||||
Type promote;
|
||||
boolean originallyExplicit = false; // record whether there was originally an explicit cast
|
||||
private final Operation operation;
|
||||
private AExpression child;
|
||||
|
||||
private Type promote;
|
||||
private boolean originallyExplicit = false; // record whether there was originally an explicit cast
|
||||
|
||||
public EUnary(Location location, Operation operation, AExpression child) {
|
||||
super(location);
|
||||
|
@ -51,7 +53,7 @@ public final class EUnary extends AExpression {
|
|||
this.operation = Objects.requireNonNull(operation);
|
||||
this.child = Objects.requireNonNull(child);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
child.extractVariables(variables);
|
||||
|
@ -60,6 +62,7 @@ public final class EUnary extends AExpression {
|
|||
@Override
|
||||
void analyze(Locals locals) {
|
||||
originallyExplicit = explicit;
|
||||
|
||||
if (operation == Operation.NOT) {
|
||||
analyzeNot(locals);
|
||||
} else if (operation == Operation.BWNOT) {
|
||||
|
@ -191,33 +194,29 @@ public final class EUnary extends AExpression {
|
|||
writer.writeDebugInfo(location);
|
||||
|
||||
if (operation == Operation.NOT) {
|
||||
if (tru == null && fals == null) {
|
||||
Label localfals = new Label();
|
||||
Label end = new Label();
|
||||
Label fals = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
child.fals = localfals;
|
||||
child.write(writer, globals);
|
||||
child.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, fals);
|
||||
|
||||
writer.push(false);
|
||||
writer.goTo(end);
|
||||
writer.mark(localfals);
|
||||
writer.push(true);
|
||||
writer.mark(end);
|
||||
} else {
|
||||
child.tru = fals;
|
||||
child.fals = tru;
|
||||
child.write(writer, globals);
|
||||
}
|
||||
writer.push(false);
|
||||
writer.goTo(end);
|
||||
writer.mark(fals);
|
||||
writer.push(true);
|
||||
writer.mark(end);
|
||||
} else {
|
||||
Sort sort = promote.sort;
|
||||
child.write(writer, globals);
|
||||
|
||||
// def calls adopt the wanted return value. if there was a narrowing cast,
|
||||
// we need to flag that so that its done at runtime.
|
||||
// Def calls adopt the wanted return value. If there was a narrowing cast,
|
||||
// we need to flag that so that it's done at runtime.
|
||||
int defFlags = 0;
|
||||
|
||||
if (originallyExplicit) {
|
||||
defFlags |= DefBootstrap.OPERATOR_EXPLICIT_CAST;
|
||||
}
|
||||
|
||||
if (operation == Operation.BWNOT) {
|
||||
if (sort == Sort.DEF) {
|
||||
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
|
||||
|
@ -244,12 +243,10 @@ public final class EUnary extends AExpression {
|
|||
if (sort == Sort.DEF) {
|
||||
org.objectweb.asm.Type descriptor = org.objectweb.asm.Type.getMethodType(actual.type, child.actual.type);
|
||||
writer.invokeDefCall("plus", descriptor, DefBootstrap.UNARY_OPERATOR, defFlags);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
writer.writeBranch(tru, fals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,12 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -32,52 +33,66 @@ import java.util.Set;
|
|||
/**
|
||||
* Represents a variable load/store.
|
||||
*/
|
||||
public final class LVariable extends ALink {
|
||||
public final class EVariable extends AStoreable {
|
||||
|
||||
final String name;
|
||||
private final String name;
|
||||
|
||||
Variable variable;
|
||||
private Variable variable = null;
|
||||
|
||||
public LVariable(Location location, String name) {
|
||||
super(location, 0);
|
||||
public EVariable(Location location, String name) {
|
||||
super(location);
|
||||
|
||||
this.name = Objects.requireNonNull(name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before != null) {
|
||||
throw createError(new IllegalArgumentException("Illegal variable [" + name + "] access with target already defined."));
|
||||
}
|
||||
|
||||
void analyze(Locals locals) {
|
||||
variable = locals.getVariable(location, name);
|
||||
|
||||
if (store && variable.readonly) {
|
||||
if (write && variable.readonly) {
|
||||
throw createError(new IllegalArgumentException("Variable [" + variable.name + "] is read-only."));
|
||||
}
|
||||
|
||||
after = variable.type;
|
||||
|
||||
return this;
|
||||
actual = variable.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.visitVarInsn(actual.type.getOpcode(Opcodes.ILOAD), variable.getSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
throw new IllegalArgumentException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), variable.getSlot());
|
||||
writer.visitVarInsn(actual.type.getOpcode(Opcodes.ILOAD), variable.getSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
writer.visitVarInsn(actual.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
}
|
||||
}
|
|
@ -1,27 +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.node;
|
||||
|
||||
/**
|
||||
* A marker interface applied to LDef* nodes allowing changes to {@link ALink#after} from outside,
|
||||
* by default {@code after} is {@code DEF}.
|
||||
*/
|
||||
interface IDefLink {
|
||||
}
|
|
@ -1,95 +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.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an array load/store or defers to possible shortcuts.
|
||||
*/
|
||||
public final class LBrace extends ALink {
|
||||
|
||||
AExpression index;
|
||||
|
||||
public LBrace(Location location, AExpression index) {
|
||||
super(location, 2);
|
||||
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
index.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before == null) {
|
||||
throw createError(new IllegalArgumentException("Illegal array access made without target."));
|
||||
}
|
||||
|
||||
Sort sort = before.sort;
|
||||
|
||||
if (sort == Sort.ARRAY) {
|
||||
index.expected = Definition.INT_TYPE;
|
||||
index.analyze(locals);
|
||||
index = index.cast(locals);
|
||||
|
||||
after = Definition.getType(before.struct, before.dimensions - 1);
|
||||
|
||||
return this;
|
||||
} else if (sort == Sort.DEF) {
|
||||
return new LDefArray(location, index).copy(this).analyze(locals);
|
||||
} else if (Map.class.isAssignableFrom(before.clazz)) {
|
||||
return new LMapShortcut(location, index).copy(this).analyze(locals);
|
||||
} else if (List.class.isAssignableFrom(before.clazz)) {
|
||||
return new LListShortcut(location, index).copy(this).analyze(locals);
|
||||
}
|
||||
|
||||
throw createError(new IllegalArgumentException("Illegal array access on type [" + before.name + "]."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.arrayLoad(after.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.arrayStore(after.type);
|
||||
}
|
||||
}
|
|
@ -1,133 +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.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.Location;
|
||||
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.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a method call or defers to a def call.
|
||||
*/
|
||||
public final class LCallInvoke extends ALink {
|
||||
|
||||
final String name;
|
||||
final List<AExpression> arguments;
|
||||
|
||||
Method method = null;
|
||||
|
||||
boolean box = false; // true for primitive types
|
||||
|
||||
public LCallInvoke(Location location, String name, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before == null) {
|
||||
throw createError(new IllegalArgumentException("Illegal call [" + name + "] made without target."));
|
||||
} else if (before.sort == Sort.ARRAY) {
|
||||
throw createError(new IllegalArgumentException("Illegal call [" + name + "] on array type."));
|
||||
} else if (store) {
|
||||
throw createError(new IllegalArgumentException("Cannot assign a value to a call [" + name + "]."));
|
||||
}
|
||||
|
||||
MethodKey methodKey = new MethodKey(name, arguments.size());
|
||||
Struct struct = before.struct;
|
||||
if (before.clazz.isPrimitive()) {
|
||||
Class<?> wrapper = MethodType.methodType(before.clazz).wrap().returnType();
|
||||
Type boxed = Definition.getType(wrapper.getSimpleName());
|
||||
struct = boxed.struct;
|
||||
box = true;
|
||||
}
|
||||
method = statik ? struct.staticMethods.get(methodKey) : struct.methods.get(methodKey);
|
||||
|
||||
if (method != null) {
|
||||
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||
AExpression expression = arguments.get(argument);
|
||||
|
||||
expression.expected = method.arguments.get(argument);
|
||||
expression.internal = true;
|
||||
expression.analyze(locals);
|
||||
arguments.set(argument, expression.cast(locals));
|
||||
}
|
||||
|
||||
statement = true;
|
||||
after = method.rtn;
|
||||
|
||||
return this;
|
||||
} else if (before.sort == Sort.DEF) {
|
||||
ALink link = new LDefCall(location, name, arguments);
|
||||
link.copy(this);
|
||||
|
||||
return link.analyze(locals);
|
||||
}
|
||||
|
||||
throw createError(new IllegalArgumentException(
|
||||
"Unknown call [" + name + "] with [" + arguments.size() + "] arguments on type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (box) {
|
||||
writer.box(before.type);
|
||||
}
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
method.write(writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
|
@ -1,134 +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.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Field;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Struct;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a field load/store or defers to a possible shortcuts.
|
||||
*/
|
||||
public final class LField extends ALink {
|
||||
|
||||
final String value;
|
||||
|
||||
Field field;
|
||||
|
||||
public LField(Location location, String value) {
|
||||
super(location, 1);
|
||||
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before == null) {
|
||||
throw createError(new IllegalArgumentException("Illegal field [" + value + "] access made without target."));
|
||||
}
|
||||
|
||||
Sort sort = before.sort;
|
||||
|
||||
if (sort == Sort.ARRAY) {
|
||||
return new LArrayLength(location, value).copy(this).analyze(locals);
|
||||
} else if (sort == Sort.DEF) {
|
||||
return new LDefField(location, value).copy(this).analyze(locals);
|
||||
}
|
||||
|
||||
Struct struct = before.struct;
|
||||
field = statik ? struct.staticMembers.get(value) : struct.members.get(value);
|
||||
|
||||
if (field != null) {
|
||||
if (store && java.lang.reflect.Modifier.isFinal(field.modifiers)) {
|
||||
throw createError(new IllegalArgumentException(
|
||||
"Cannot write to read-only field [" + value + "] for type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
after = field.type;
|
||||
|
||||
return this;
|
||||
} else {
|
||||
boolean shortcut =
|
||||
struct.methods.containsKey(new Definition.MethodKey("get" +
|
||||
Character.toUpperCase(value.charAt(0)) + value.substring(1), 0)) ||
|
||||
struct.methods.containsKey(new Definition.MethodKey("is" +
|
||||
Character.toUpperCase(value.charAt(0)) + value.substring(1), 0)) ||
|
||||
struct.methods.containsKey(new Definition.MethodKey("set" +
|
||||
Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
|
||||
|
||||
if (shortcut) {
|
||||
return new LShortcut(location, value).copy(this).analyze(locals);
|
||||
} else {
|
||||
EConstant index = new EConstant(location, value);
|
||||
index.analyze(locals);
|
||||
|
||||
if (Map.class.isAssignableFrom(before.clazz)) {
|
||||
return new LMapShortcut(location, index).copy(this).analyze(locals);
|
||||
}
|
||||
|
||||
if (List.class.isAssignableFrom(before.clazz)) {
|
||||
return new LListShortcut(location, index).copy(this).analyze(locals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw createError(new IllegalArgumentException("Unknown field [" + value + "] for type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
writer.getStatic(field.owner.type, field.javaName, field.type.type);
|
||||
} else {
|
||||
writer.getField(field.owner.type, field.javaName, field.type.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
writer.putStatic(field.owner.type, field.javaName, field.type.type);
|
||||
} else {
|
||||
writer.putField(field.owner.type, field.javaName, field.type.type);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an array load/store and defers to a child subnode.
|
||||
*/
|
||||
public final class PBrace extends AStoreable {
|
||||
|
||||
private AExpression index;
|
||||
|
||||
private AStoreable sub = null;
|
||||
|
||||
public PBrace(Location location, AExpression prefix, AExpression index) {
|
||||
super(location, prefix);
|
||||
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
prefix.extractVariables(variables);
|
||||
index.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
prefix.analyze(locals);
|
||||
prefix.expected = prefix.actual;
|
||||
prefix = prefix.cast(locals);
|
||||
|
||||
Sort sort = prefix.actual.sort;
|
||||
|
||||
if (sort == Sort.ARRAY) {
|
||||
sub = new PSubBrace(location, prefix.actual, index);
|
||||
} else if (sort == Sort.DEF) {
|
||||
sub = new PSubDefArray(location, index);
|
||||
} else if (Map.class.isAssignableFrom(prefix.actual.clazz)) {
|
||||
sub = new PSubMapShortcut(location, prefix.actual.struct, index);
|
||||
} else if (List.class.isAssignableFrom(prefix.actual.clazz)) {
|
||||
sub = new PSubListShortcut(location, prefix.actual.struct, index);
|
||||
} else {
|
||||
throw createError(new IllegalArgumentException("Illegal array access on type [" + prefix.actual.name + "]."));
|
||||
}
|
||||
|
||||
sub.write = write;
|
||||
sub.read = read;
|
||||
sub.expected = expected;
|
||||
sub.explicit = explicit;
|
||||
sub.analyze(locals);
|
||||
actual = sub.actual;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
prefix.write(writer, globals);
|
||||
sub.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return sub.isDefOptimized();
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
sub.updateActual(actual);
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return sub.accessElementCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
prefix.write(writer, globals);
|
||||
sub.setup(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
sub.load(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
sub.store(writer, globals);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.Definition;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Struct;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a method call and defers to a child subnode.
|
||||
*/
|
||||
public final class PCallInvoke extends AExpression {
|
||||
|
||||
private final String name;
|
||||
private final List<AExpression> arguments;
|
||||
|
||||
private AExpression sub = null;
|
||||
|
||||
public PCallInvoke(Location location, AExpression prefix, String name, List<AExpression> arguments) {
|
||||
super(location, prefix);
|
||||
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
prefix.extractVariables(variables);
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
argument.extractVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
prefix.analyze(locals);
|
||||
prefix.expected = prefix.actual;
|
||||
prefix = prefix.cast(locals);
|
||||
|
||||
if (prefix.actual.sort == Sort.ARRAY) {
|
||||
throw createError(new IllegalArgumentException("Illegal call [" + name + "] on array type."));
|
||||
}
|
||||
|
||||
Struct struct = prefix.actual.struct;
|
||||
|
||||
if (prefix.actual.sort.primitive) {
|
||||
struct = Definition.getType(prefix.actual.sort.boxed.getSimpleName()).struct;
|
||||
}
|
||||
|
||||
MethodKey methodKey = new MethodKey(name, arguments.size());
|
||||
Method method = prefix instanceof EStatic ? struct.staticMethods.get(methodKey) : struct.methods.get(methodKey);
|
||||
|
||||
if (method != null) {
|
||||
sub = new PSubCallInvoke(location, method, prefix.actual, arguments);
|
||||
} else if (prefix.actual.sort == Sort.DEF) {
|
||||
sub = new PSubDefCall(location, name, arguments);
|
||||
} else {
|
||||
throw createError(new IllegalArgumentException(
|
||||
"Unknown call [" + name + "] with [" + arguments.size() + "] arguments on type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
sub.expected = expected;
|
||||
sub.explicit = explicit;
|
||||
sub.analyze(locals);
|
||||
actual = sub.actual;
|
||||
|
||||
statement = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
prefix.write(writer, globals);
|
||||
sub.write(writer, globals);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* 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.Definition;
|
||||
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.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a field load/store and defers to a child subnode.
|
||||
*/
|
||||
public final class PField extends AStoreable {
|
||||
|
||||
private final String value;
|
||||
|
||||
private AStoreable sub = null;
|
||||
|
||||
public PField(Location location, AExpression prefix, String value) {
|
||||
super(location, prefix);
|
||||
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
prefix.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
prefix.analyze(locals);
|
||||
prefix.expected = prefix.actual;
|
||||
prefix = prefix.cast(locals);
|
||||
|
||||
Sort sort = prefix.actual.sort;
|
||||
|
||||
if (sort == Sort.ARRAY) {
|
||||
sub = new PSubArrayLength(location,prefix.actual.name, value);
|
||||
} else if (sort == Sort.DEF) {
|
||||
sub = new PSubDefField(location, value);
|
||||
} else {
|
||||
Struct struct = prefix.actual.struct;
|
||||
Field field = prefix instanceof EStatic ? struct.staticMembers.get(value) : struct.members.get(value);
|
||||
|
||||
if (field != null) {
|
||||
sub = new PSubField(location, field);
|
||||
} else {
|
||||
Method getter = struct.methods.get(
|
||||
new Definition.MethodKey("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));
|
||||
|
||||
if (getter == null) {
|
||||
getter = struct.methods.get(
|
||||
new Definition.MethodKey("is" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));
|
||||
}
|
||||
|
||||
Method setter = struct.methods.get(
|
||||
new Definition.MethodKey("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
|
||||
|
||||
if (getter != null || setter != null) {
|
||||
sub = new PSubShortcut(location, value, prefix.actual.name, getter, setter);
|
||||
} else {
|
||||
EConstant index = new EConstant(location, value);
|
||||
index.analyze(locals);
|
||||
|
||||
if (Map.class.isAssignableFrom(prefix.actual.clazz)) {
|
||||
sub = new PSubMapShortcut(location, struct, index);
|
||||
}
|
||||
|
||||
if (List.class.isAssignableFrom(prefix.actual.clazz)) {
|
||||
sub = new PSubListShortcut(location, struct, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sub == null) {
|
||||
throw createError(new IllegalArgumentException("Unknown field [" + value + "] for type [" + prefix.actual.name + "]."));
|
||||
}
|
||||
|
||||
sub.write = write;
|
||||
sub.read = read;
|
||||
sub.expected = expected;
|
||||
sub.explicit = explicit;
|
||||
sub.analyze(locals);
|
||||
actual = sub.actual;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
prefix.write(writer, globals);
|
||||
sub.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return sub.isDefOptimized();
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
sub.updateActual(actual);
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return sub.accessElementCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
prefix.write(writer, globals);
|
||||
sub.setup(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
sub.load(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
sub.store(writer, globals);
|
||||
}
|
||||
}
|
|
@ -20,9 +20,10 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -31,49 +32,69 @@ import java.util.Set;
|
|||
/**
|
||||
* Represents an array length field load.
|
||||
*/
|
||||
public final class LArrayLength extends ALink {
|
||||
final class PSubArrayLength extends AStoreable {
|
||||
|
||||
final String value;
|
||||
private final String type;
|
||||
private final String value;
|
||||
|
||||
LArrayLength(Location location, String value) {
|
||||
super(location, -1);
|
||||
PSubArrayLength(Location location, String type, String value) {
|
||||
super(location);
|
||||
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if ("length".equals(value)) {
|
||||
if (!load) {
|
||||
throw createError(new IllegalArgumentException("Must read array field [length]."));
|
||||
} else if (store) {
|
||||
throw createError(new IllegalArgumentException("Cannot write to read-only array field [length]."));
|
||||
if (write) {
|
||||
throw createError(new IllegalArgumentException("Cannot write to read-only field [length] for an array."));
|
||||
}
|
||||
|
||||
after = Definition.INT_TYPE;
|
||||
actual = Definition.INT_TYPE;
|
||||
} else {
|
||||
throw createError(new IllegalArgumentException("Illegal field access [" + value + "]."));
|
||||
throw createError(new IllegalArgumentException("Field [" + value + "] does not exist for type [" + type + "]."));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.arrayLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
}
|
|
@ -20,64 +20,81 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents an array load/store or shortcut on a def type. (Internal only.)
|
||||
* Represents an array load/store.
|
||||
*/
|
||||
final class LDefArray extends ALink implements IDefLink {
|
||||
final class PSubBrace extends AStoreable {
|
||||
|
||||
AExpression index;
|
||||
private final Type type;
|
||||
private AExpression index;
|
||||
|
||||
LDefArray(Location location, AExpression index) {
|
||||
super(location, 2);
|
||||
PSubBrace(Location location, Type type, AExpression index) {
|
||||
super(location);
|
||||
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
index.extractVariables(variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
void analyze(Locals locals) {
|
||||
index.expected = Definition.INT_TYPE;
|
||||
index.analyze(locals);
|
||||
index.expected = index.actual;
|
||||
index = index.cast(locals);
|
||||
|
||||
after = Definition.DEF_TYPE;
|
||||
|
||||
return this;
|
||||
actual = Definition.getType(type.struct, type.dimensions - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
if (!write) {
|
||||
setup(writer, globals);
|
||||
load(writer, globals);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type, index.actual.type);
|
||||
writer.invokeDefCall("arrayLoad", methodType, DefBootstrap.ARRAY_LOAD);
|
||||
writer.arrayLoad(actual.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
|
||||
writer.invokeDefCall("arrayStore", methodType, DefBootstrap.ARRAY_STORE);
|
||||
writer.arrayStore(actual.type);
|
||||
}
|
||||
}
|
|
@ -19,67 +19,66 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a cast made in a variable/method chain.
|
||||
* Represents a method call.
|
||||
*/
|
||||
public final class LCast extends ALink {
|
||||
final class PSubCallInvoke extends AExpression {
|
||||
|
||||
final String type;
|
||||
private final Method method;
|
||||
private final Type box;
|
||||
private final List<AExpression> arguments;
|
||||
|
||||
Cast cast = null;
|
||||
public PSubCallInvoke(Location location, Method method, Type box, List<AExpression> arguments) {
|
||||
super(location);
|
||||
|
||||
public LCast(Location location, String type) {
|
||||
super(location, -1);
|
||||
|
||||
this.type = type;
|
||||
this.method = Objects.requireNonNull(method);
|
||||
this.box = box;
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
if (before == null) {
|
||||
throw createError(new IllegalStateException("Illegal cast without a target."));
|
||||
} else if (store) {
|
||||
throw createError(new IllegalArgumentException("Cannot assign a value to a cast."));
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||
AExpression expression = arguments.get(argument);
|
||||
|
||||
expression.expected = method.arguments.get(argument);
|
||||
expression.internal = true;
|
||||
expression.analyze(locals);
|
||||
arguments.set(argument, expression.cast(locals));
|
||||
}
|
||||
|
||||
try {
|
||||
after = Definition.getType(type);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw createError(new IllegalArgumentException("Not a type [" + type + "]."));
|
||||
}
|
||||
|
||||
cast = AnalyzerCaster.getLegalCast(location, before, after, true, false);
|
||||
|
||||
return cast != null ? this : null;
|
||||
statement = true;
|
||||
actual = method.rtn;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.writeCast(cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
if (box.sort.primitive) {
|
||||
writer.box(box.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
method.write(writer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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.DefBootstrap;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents an array load/store or shortcut on a def type. (Internal only.)
|
||||
*/
|
||||
final class PSubDefArray extends AStoreable {
|
||||
|
||||
private AExpression index;
|
||||
|
||||
PSubDefArray(Location location, AExpression index) {
|
||||
super(location);
|
||||
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
index.analyze(locals);
|
||||
index.expected = index.actual;
|
||||
index = index.cast(locals);
|
||||
|
||||
actual = expected == null || explicit ? Definition.DEF_TYPE : expected;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
org.objectweb.asm.Type methodType =
|
||||
org.objectweb.asm.Type.getMethodType(actual.type, Definition.DEF_TYPE.type, index.actual.type);
|
||||
writer.invokeDefCall("arrayLoad", methodType, DefBootstrap.ARRAY_LOAD);
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
org.objectweb.asm.Type methodType =
|
||||
org.objectweb.asm.Type.getMethodType(actual.type, Definition.DEF_TYPE.type, index.actual.type);
|
||||
writer.invokeDefCall("arrayLoad", methodType, DefBootstrap.ARRAY_LOAD);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
org.objectweb.asm.Type methodType =
|
||||
org.objectweb.asm.Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, actual.type);
|
||||
writer.invokeDefCall("arrayStore", methodType, DefBootstrap.ARRAY_STORE);
|
||||
}
|
||||
}
|
|
@ -19,11 +19,11 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
|
@ -35,31 +35,31 @@ import java.util.Set;
|
|||
/**
|
||||
* Represents a method call made on a def type. (Internal only.)
|
||||
*/
|
||||
final class LDefCall extends ALink implements IDefLink {
|
||||
final class PSubDefCall extends AExpression {
|
||||
|
||||
final String name;
|
||||
final List<AExpression> arguments;
|
||||
StringBuilder recipe;
|
||||
List<String> pointers = new ArrayList<>();
|
||||
private final String name;
|
||||
private final List<AExpression> arguments;
|
||||
|
||||
LDefCall(Location location, String name, List<AExpression> arguments) {
|
||||
super(location, -1);
|
||||
private StringBuilder recipe = null;
|
||||
private List<String> pointers = new ArrayList<>();
|
||||
|
||||
PSubDefCall(Location location, String name, List<AExpression> arguments) {
|
||||
super(location);
|
||||
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.arguments = Objects.requireNonNull(arguments);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.extractVariables(variables);
|
||||
}
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
void analyze(Locals locals) {
|
||||
recipe = new StringBuilder();
|
||||
int totalCaptures = 0;
|
||||
|
||||
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||
AExpression expression = arguments.get(argument);
|
||||
|
||||
|
@ -79,19 +79,11 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
arguments.set(argument, expression.cast(locals));
|
||||
}
|
||||
|
||||
statement = true;
|
||||
after = Definition.DEF_TYPE;
|
||||
|
||||
return this;
|
||||
actual = expected == null || explicit ? Definition.DEF_TYPE : expected;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
List<Type> parameterTypes = new ArrayList<>();
|
||||
|
@ -102,26 +94,24 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
// append each argument
|
||||
for (AExpression argument : arguments) {
|
||||
parameterTypes.add(argument.actual.type);
|
||||
|
||||
if (argument instanceof ILambda) {
|
||||
ILambda lambda = (ILambda) argument;
|
||||
|
||||
for (Type capture : lambda.getCaptures()) {
|
||||
parameterTypes.add(capture);
|
||||
}
|
||||
}
|
||||
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
// create method type from return value and arguments
|
||||
Type methodType = Type.getMethodType(after.type, parameterTypes.toArray(new Type[0]));
|
||||
Type methodType = Type.getMethodType(actual.type, parameterTypes.toArray(new Type[0]));
|
||||
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(recipe.toString());
|
||||
args.addAll(pointers);
|
||||
writer.invokeDefCall(name, methodType, DefBootstrap.METHOD_CALL, args.toArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
|
@ -19,43 +19,66 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents a field load/store or shortcut on a def type. (Internal only.)
|
||||
*/
|
||||
final class LDefField extends ALink implements IDefLink {
|
||||
final class PSubDefField extends AStoreable {
|
||||
|
||||
final String value;
|
||||
private final String value;
|
||||
|
||||
LDefField(Location location, String value) {
|
||||
super(location, 1);
|
||||
PSubDefField(Location location, String value) {
|
||||
super(location);
|
||||
|
||||
this.value = Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
after = Definition.DEF_TYPE;
|
||||
|
||||
return this;
|
||||
void analyze(Locals locals) {
|
||||
actual = expected == null || explicit ? Definition.DEF_TYPE : expected;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
org.objectweb.asm.Type methodType =
|
||||
org.objectweb.asm.Type.getMethodType(actual.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDefCall(value, methodType, DefBootstrap.LOAD);
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
@ -63,7 +86,8 @@ final class LDefField extends ALink implements IDefLink {
|
|||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
Type methodType = Type.getMethodType(after.type, Definition.DEF_TYPE.type);
|
||||
org.objectweb.asm.Type methodType =
|
||||
org.objectweb.asm.Type.getMethodType(actual.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDefCall(value, methodType, DefBootstrap.LOAD);
|
||||
}
|
||||
|
||||
|
@ -71,7 +95,8 @@ final class LDefField extends ALink implements IDefLink {
|
|||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
Type methodType = Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
|
||||
org.objectweb.asm.Type methodType =
|
||||
org.objectweb.asm.Type.getMethodType(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, actual.type);
|
||||
writer.invokeDefCall(value, methodType, DefBootstrap.STORE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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.Definition.Field;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a field load/store.
|
||||
*/
|
||||
final class PSubField extends AStoreable {
|
||||
|
||||
private final Field field;
|
||||
|
||||
public PSubField(Location location, Field field) {
|
||||
super(location);
|
||||
|
||||
this.field = Objects.requireNonNull(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if (write && Modifier.isFinal(field.modifiers)) {
|
||||
throw createError(new IllegalArgumentException(
|
||||
"Cannot write to read-only field [" + field.name + "] for type [" + field.type.name + "]."));
|
||||
}
|
||||
|
||||
actual = field.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
writer.getStatic(field.owner.type, field.javaName, field.type.type);
|
||||
} else {
|
||||
writer.getField(field.owner.type, field.javaName, field.type.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
throw new IllegalArgumentException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
writer.getStatic(field.owner.type, field.javaName, field.type.type);
|
||||
} else {
|
||||
writer.getField(field.owner.type, field.javaName, field.type.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
writer.putStatic(field.owner.type, field.javaName, field.type.type);
|
||||
} else {
|
||||
writer.putField(field.owner.type, field.javaName, field.type.type);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,49 +20,53 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
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.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents a list load/store shortcut. (Internal only.)
|
||||
*/
|
||||
final class LListShortcut extends ALink {
|
||||
final class PSubListShortcut extends AStoreable {
|
||||
|
||||
AExpression index;
|
||||
Method getter;
|
||||
Method setter;
|
||||
private final Struct struct;
|
||||
private AExpression index;
|
||||
|
||||
LListShortcut(Location location, AExpression index) {
|
||||
super(location, 2);
|
||||
private Method getter;
|
||||
private Method setter;
|
||||
|
||||
PSubListShortcut(Location location, Struct struct, AExpression index) {
|
||||
super(location);
|
||||
|
||||
this.struct = Objects.requireNonNull(struct);
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
index.extractVariables(variables);
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
getter = before.struct.methods.get(new Definition.MethodKey("get", 1));
|
||||
setter = before.struct.methods.get(new Definition.MethodKey("set", 2));
|
||||
void analyze(Locals locals) {
|
||||
getter = struct.methods.get(new Definition.MethodKey("get", 1));
|
||||
setter = struct.methods.get(new Definition.MethodKey("set", 2));
|
||||
|
||||
if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
|
||||
getter.arguments.get(0).sort != Sort.INT)) {
|
||||
throw createError(new IllegalArgumentException("Illegal list get shortcut for type [" + before.name + "]."));
|
||||
throw createError(new IllegalArgumentException("Illegal list get shortcut for type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
if (setter != null && (setter.arguments.size() != 2 || setter.arguments.get(0).sort != Sort.INT)) {
|
||||
throw createError(new IllegalArgumentException("Illegal list set shortcut for type [" + before.name + "]."));
|
||||
throw createError(new IllegalArgumentException("Illegal list set shortcut for type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
if (getter != null && setter != null && (!getter.arguments.get(0).equals(setter.arguments.get(0))
|
||||
|
@ -70,22 +74,48 @@ final class LListShortcut extends ALink {
|
|||
throw createError(new IllegalArgumentException("Shortcut argument types must match."));
|
||||
}
|
||||
|
||||
if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
|
||||
if ((read || write) && (!read || getter != null) && (!write || setter != null)) {
|
||||
index.expected = Definition.INT_TYPE;
|
||||
index.analyze(locals);
|
||||
index = index.cast(locals);
|
||||
|
||||
after = setter != null ? setter.arguments.get(1) : getter.rtn;
|
||||
actual = setter != null ? setter.arguments.get(1) : getter.rtn;
|
||||
} else {
|
||||
throw createError(new IllegalArgumentException("Illegal list shortcut for type [" + before.name + "]."));
|
||||
throw createError(new IllegalArgumentException("Illegal list shortcut for type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
getter.write(writer);
|
||||
|
||||
if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
|
||||
writer.checkCast(getter.rtn.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
throw new IllegalArgumentException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
|
@ -20,6 +20,8 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Struct;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
|
@ -34,34 +36,37 @@ import org.elasticsearch.painless.MethodWriter;
|
|||
/**
|
||||
* Represents a map load/store shortcut. (Internal only.)
|
||||
*/
|
||||
final class LMapShortcut extends ALink {
|
||||
final class PSubMapShortcut extends AStoreable {
|
||||
|
||||
AExpression index;
|
||||
Method getter;
|
||||
Method setter;
|
||||
private final Struct struct;
|
||||
private AExpression index;
|
||||
|
||||
LMapShortcut(Location location, AExpression index) {
|
||||
super(location, 2);
|
||||
private Method getter;
|
||||
private Method setter;
|
||||
|
||||
PSubMapShortcut(Location location, Struct struct, AExpression index) {
|
||||
super(location);
|
||||
|
||||
this.struct = Objects.requireNonNull(struct);
|
||||
this.index = Objects.requireNonNull(index);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
index.extractVariables(variables);
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
getter = before.struct.methods.get(new Definition.MethodKey("get", 1));
|
||||
setter = before.struct.methods.get(new Definition.MethodKey("put", 2));
|
||||
void analyze(Locals locals) {
|
||||
getter = struct.methods.get(new Definition.MethodKey("get", 1));
|
||||
setter = struct.methods.get(new Definition.MethodKey("put", 2));
|
||||
|
||||
if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1)) {
|
||||
throw createError(new IllegalArgumentException("Illegal map get shortcut for type [" + before.name + "]."));
|
||||
throw createError(new IllegalArgumentException("Illegal map get shortcut for type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
if (setter != null && setter.arguments.size() != 2) {
|
||||
throw createError(new IllegalArgumentException("Illegal map set shortcut for type [" + before.name + "]."));
|
||||
throw createError(new IllegalArgumentException("Illegal map set shortcut for type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
if (getter != null && setter != null &&
|
||||
|
@ -69,22 +74,48 @@ final class LMapShortcut extends ALink {
|
|||
throw createError(new IllegalArgumentException("Shortcut argument types must match."));
|
||||
}
|
||||
|
||||
if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
|
||||
if ((read || write) && (!read || getter != null) && (!write || setter != null)) {
|
||||
index.expected = setter != null ? setter.arguments.get(0) : getter.arguments.get(0);
|
||||
index.analyze(locals);
|
||||
index = index.cast(locals);
|
||||
|
||||
after = setter != null ? setter.arguments.get(1) : getter.rtn;
|
||||
actual = setter != null ? setter.arguments.get(1) : getter.rtn;
|
||||
} else {
|
||||
throw createError(new IllegalArgumentException("Illegal map shortcut for type [" + before.name + "]."));
|
||||
throw createError(new IllegalArgumentException("Illegal map shortcut for type [" + struct.name + "]."));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
getter.write(writer);
|
||||
|
||||
if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
|
||||
writer.checkCast(getter.rtn.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
throw new IllegalArgumentException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
|
@ -19,75 +19,91 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Struct;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a field load/store shortcut. (Internal only.)
|
||||
*/
|
||||
final class LShortcut extends ALink {
|
||||
final class PSubShortcut extends AStoreable {
|
||||
|
||||
final String value;
|
||||
private final String value;
|
||||
private final String type;
|
||||
private final Method getter;
|
||||
private final Method setter;
|
||||
|
||||
Method getter = null;
|
||||
Method setter = null;
|
||||
PSubShortcut(Location location, String value, String type, Method getter, Method setter) {
|
||||
super(location);
|
||||
|
||||
LShortcut(Location location, String value) {
|
||||
super(location, 1);
|
||||
|
||||
this.value = Objects.requireNonNull(value);
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
|
||||
@Override
|
||||
ALink analyze(Locals locals) {
|
||||
Struct struct = before.struct;
|
||||
|
||||
getter = struct.methods.get(new Definition.MethodKey("get" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));
|
||||
|
||||
if (getter == null) {
|
||||
getter = struct.methods.get(new Definition.MethodKey("is" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 0));
|
||||
}
|
||||
|
||||
setter = struct.methods.get(new Definition.MethodKey("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
if (getter != null && (getter.rtn.sort == Sort.VOID || !getter.arguments.isEmpty())) {
|
||||
throw createError(new IllegalArgumentException(
|
||||
"Illegal get shortcut on field [" + value + "] for type [" + struct.name + "]."));
|
||||
"Illegal get shortcut on field [" + value + "] for type [" + type + "]."));
|
||||
}
|
||||
|
||||
if (setter != null && (setter.rtn.sort != Sort.VOID || setter.arguments.size() != 1)) {
|
||||
throw createError(new IllegalArgumentException(
|
||||
"Illegal set shortcut on field [" + value + "] for type [" + struct.name + "]."));
|
||||
"Illegal set shortcut on field [" + value + "] for type [" + type + "]."));
|
||||
}
|
||||
|
||||
if (getter != null && setter != null && setter.arguments.get(0) != getter.rtn) {
|
||||
throw createError(new IllegalArgumentException("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;
|
||||
if ((getter != null || setter != null) && (!read || getter != null) && (!write || setter != null)) {
|
||||
actual = setter != null ? setter.arguments.get(0) : getter.rtn;
|
||||
} else {
|
||||
throw createError(new IllegalArgumentException("Illegal shortcut on field [" + value + "] for type [" + struct.name + "]."));
|
||||
throw createError(new IllegalArgumentException("Illegal shortcut on field [" + value + "] for type [" + type + "]."));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
getter.write(writer);
|
||||
|
||||
if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) {
|
||||
writer.checkCast(getter.rtn.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int accessElementCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDefOptimized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateActual(Type actual) {
|
||||
throw new IllegalArgumentException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
@Override
|
||||
void setup(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
|
@ -33,14 +33,14 @@ import java.util.Set;
|
|||
*/
|
||||
public final class SBlock extends AStatement {
|
||||
|
||||
final List<AStatement> statements;
|
||||
private final List<AStatement> statements;
|
||||
|
||||
public SBlock(Location location, List<AStatement> statements) {
|
||||
super(location);
|
||||
|
||||
this.statements = Collections.unmodifiableList(statements);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (AStatement statement : statements) {
|
||||
|
|
|
@ -34,9 +34,11 @@ public final class SBreak extends AStatement {
|
|||
public SBreak(Location location) {
|
||||
super(location);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
void extractVariables(Set<String> variables) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -20,33 +20,32 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents a catch block as part of a try-catch block.
|
||||
*/
|
||||
public final class SCatch extends AStatement {
|
||||
|
||||
final String type;
|
||||
final String name;
|
||||
final SBlock block;
|
||||
private final String type;
|
||||
private final String name;
|
||||
private final SBlock block;
|
||||
|
||||
Variable variable;
|
||||
private Variable variable = null;
|
||||
|
||||
Label begin;
|
||||
Label end;
|
||||
Label exception;
|
||||
Label begin = null;
|
||||
Label end = null;
|
||||
Label exception = null;
|
||||
|
||||
public SCatch(Location location, String type, String name, SBlock block) {
|
||||
super(location);
|
||||
|
@ -55,10 +54,11 @@ public final class SCatch extends AStatement {
|
|||
this.name = Objects.requireNonNull(name);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(name);
|
||||
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
|
|
|
@ -34,9 +34,11 @@ public final class SContinue extends AStatement {
|
|||
public SContinue(Location location) {
|
||||
super(location);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {}
|
||||
void extractVariables(Set<String> variables) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
|
|
|
@ -33,14 +33,14 @@ import java.util.Set;
|
|||
*/
|
||||
public final class SDeclBlock extends AStatement {
|
||||
|
||||
final List<SDeclaration> declarations;
|
||||
private final List<SDeclaration> declarations;
|
||||
|
||||
public SDeclBlock(Location location, List<SDeclaration> declarations) {
|
||||
super(location);
|
||||
|
||||
this.declarations = Collections.unmodifiableList(declarations);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
for (SDeclaration declaration : declarations) {
|
||||
|
|
|
@ -20,28 +20,27 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents a single variable declaration.
|
||||
*/
|
||||
public final class SDeclaration extends AStatement {
|
||||
|
||||
final String type;
|
||||
final String name;
|
||||
AExpression expression;
|
||||
private final String type;
|
||||
private final String name;
|
||||
private AExpression expression;
|
||||
|
||||
Variable variable;
|
||||
private Variable variable = null;
|
||||
|
||||
public SDeclaration(Location location, String type, String name, AExpression expression) {
|
||||
super(location);
|
||||
|
@ -50,10 +49,11 @@ public final class SDeclaration extends AStatement {
|
|||
this.name = Objects.requireNonNull(name);
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(name);
|
||||
|
||||
if (expression != null) {
|
||||
expression.extractVariables(variables);
|
||||
}
|
||||
|
|
|
@ -21,22 +21,24 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents a do-while loop.
|
||||
*/
|
||||
public final class SDo extends AStatement {
|
||||
|
||||
final SBlock block;
|
||||
AExpression condition;
|
||||
private final SBlock block;
|
||||
private AExpression condition;
|
||||
|
||||
private boolean continuous = false;
|
||||
|
||||
public SDo(Location location, SBlock block, AExpression condition) {
|
||||
super(location);
|
||||
|
@ -44,10 +46,11 @@ public final class SDo extends AStatement {
|
|||
this.condition = Objects.requireNonNull(condition);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
|
@ -75,7 +78,7 @@ public final class SDo extends AStatement {
|
|||
condition = condition.cast(locals);
|
||||
|
||||
if (condition.constant != null) {
|
||||
final boolean continuous = (boolean)condition.constant;
|
||||
continuous = (boolean)condition.constant;
|
||||
|
||||
if (!continuous) {
|
||||
throw createError(new IllegalArgumentException("Extraneous do while loop."));
|
||||
|
@ -110,8 +113,10 @@ public final class SDo extends AStatement {
|
|||
|
||||
writer.mark(begin);
|
||||
|
||||
condition.fals = end;
|
||||
condition.write(writer, globals);
|
||||
if (!continuous) {
|
||||
condition.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, end);
|
||||
}
|
||||
|
||||
if (loopCounter != null) {
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), Math.max(1, block.statementCount), location);
|
||||
|
|
|
@ -19,51 +19,29 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
|
||||
|
||||
/**
|
||||
* Represents a for-each loop shortcut for iterables. Defers to other S-nodes for non-iterable types.
|
||||
* Represents a for-each loop and defers to subnodes depending on type.
|
||||
*/
|
||||
public class SEach extends AStatement {
|
||||
|
||||
final String type;
|
||||
final String name;
|
||||
AExpression expression;
|
||||
final SBlock block;
|
||||
private final String type;
|
||||
private final String name;
|
||||
private AExpression expression;
|
||||
private final SBlock block;
|
||||
|
||||
// Members for all cases.
|
||||
Variable variable = null;
|
||||
Cast cast = null;
|
||||
|
||||
// Members for the array case.
|
||||
Variable array = null;
|
||||
Variable index = null;
|
||||
Type indexed = null;
|
||||
|
||||
// Members for the iterable case.
|
||||
Variable iterator = null;
|
||||
Method method = null;
|
||||
private AStatement sub = null;
|
||||
|
||||
public SEach(Location location, String type, String name, AExpression expression, SBlock block) {
|
||||
super(location);
|
||||
|
@ -73,11 +51,13 @@ public class SEach extends AStatement {
|
|||
this.expression = Objects.requireNonNull(expression);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
variables.add(name);
|
||||
|
||||
expression.extractVariables(variables);
|
||||
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
|
@ -98,17 +78,18 @@ public class SEach extends AStatement {
|
|||
}
|
||||
|
||||
locals = Locals.newLocalScope(locals);
|
||||
|
||||
variable = locals.addVariable(location, type, name, true);
|
||||
Variable variable = locals.addVariable(location, type, name, true);
|
||||
|
||||
if (expression.actual.sort == Sort.ARRAY) {
|
||||
analyzeArray(locals, type);
|
||||
sub = new SSubEachArray(location, variable, expression, block);
|
||||
} else if (expression.actual.sort == Sort.DEF || Iterable.class.isAssignableFrom(expression.actual.clazz)) {
|
||||
analyzeIterable(locals, type);
|
||||
sub = new SSubEachIterable(location, variable, expression, block);
|
||||
} else {
|
||||
throw createError(new IllegalArgumentException("Illegal for each type [" + expression.actual.name + "]."));
|
||||
}
|
||||
|
||||
sub.analyze(locals);
|
||||
|
||||
if (block == null) {
|
||||
throw createError(new IllegalArgumentException("Extraneous for each loop."));
|
||||
}
|
||||
|
@ -125,110 +106,12 @@ public class SEach extends AStatement {
|
|||
statementCount = 1;
|
||||
|
||||
if (locals.hasVariable(Locals.LOOP)) {
|
||||
loopCounter = locals.getVariable(location, Locals.LOOP);
|
||||
sub.loopCounter = locals.getVariable(location, Locals.LOOP);
|
||||
}
|
||||
}
|
||||
|
||||
void analyzeArray(Locals variables, Type type) {
|
||||
// We must store the array and index as variables for securing slots on the stack, and
|
||||
// also add the location offset to make the names unique in case of nested for each loops.
|
||||
array = variables.addVariable(location, expression.actual, "#array" + location.getOffset(), true);
|
||||
index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true);
|
||||
indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1);
|
||||
cast = AnalyzerCaster.getLegalCast(location, indexed, type, true, true);
|
||||
}
|
||||
|
||||
void analyzeIterable(Locals variables, Type type) {
|
||||
// We must store the iterator as a variable for securing a slot on the stack, and
|
||||
// also add the location offset to make the name unique in case of nested for each loops.
|
||||
iterator = variables.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true);
|
||||
|
||||
if (expression.actual.sort == Sort.DEF) {
|
||||
method = null;
|
||||
} else {
|
||||
method = expression.actual.struct.methods.get(new MethodKey("iterator", 0));
|
||||
|
||||
if (method == null) {
|
||||
throw createError(new IllegalArgumentException(
|
||||
"Unable to create iterator for the type [" + expression.actual.name + "]."));
|
||||
}
|
||||
}
|
||||
|
||||
cast = AnalyzerCaster.getLegalCast(location, Definition.DEF_TYPE, type, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
if (array != null) {
|
||||
writeArray(writer, globals);
|
||||
} else if (iterator != null) {
|
||||
writeIterable(writer, globals);
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
||||
void writeArray(MethodWriter writer, Globals globals) {
|
||||
expression.write(writer, globals);
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.getSlot());
|
||||
writer.push(-1);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.getSlot());
|
||||
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
writer.mark(begin);
|
||||
|
||||
writer.visitIincInsn(index.getSlot(), 1);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
|
||||
writer.arrayLength();
|
||||
writer.ifICmp(MethodWriter.GE, end);
|
||||
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
|
||||
writer.arrayLoad(indexed.type);
|
||||
writer.writeCast(cast);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
|
||||
block.write(writer, globals);
|
||||
|
||||
writer.goTo(begin);
|
||||
writer.mark(end);
|
||||
}
|
||||
|
||||
void writeIterable(MethodWriter writer, Globals globals) {
|
||||
expression.write(writer, globals);
|
||||
|
||||
if (method == null) {
|
||||
Type itr = Definition.getType("Iterator");
|
||||
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR);
|
||||
} else {
|
||||
method.write(writer);
|
||||
}
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.getSlot());
|
||||
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
writer.mark(begin);
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
|
||||
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT);
|
||||
writer.ifZCmp(MethodWriter.EQ, end);
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
|
||||
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
|
||||
writer.writeCast(cast);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
|
||||
block.write(writer, globals);
|
||||
|
||||
writer.goTo(begin);
|
||||
writer.mark(end);
|
||||
sub.write(writer, globals);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,30 +19,29 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents the top-level node for an expression as a statement.
|
||||
*/
|
||||
public final class SExpression extends AStatement {
|
||||
|
||||
AExpression expression;
|
||||
private AExpression expression;
|
||||
|
||||
public SExpression(Location location, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
expression.extractVariables(variables);
|
||||
|
|
|
@ -21,23 +21,25 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents a for loop.
|
||||
*/
|
||||
public final class SFor extends AStatement {
|
||||
|
||||
ANode initializer;
|
||||
AExpression condition;
|
||||
AExpression afterthought;
|
||||
final SBlock block;
|
||||
private ANode initializer;
|
||||
private AExpression condition;
|
||||
private AExpression afterthought;
|
||||
private final SBlock block;
|
||||
|
||||
private boolean continuous = false;
|
||||
|
||||
public SFor(Location location, ANode initializer, AExpression condition, AExpression afterthought, SBlock block) {
|
||||
super(location);
|
||||
|
@ -47,18 +49,21 @@ public final class SFor extends AStatement {
|
|||
this.afterthought = afterthought;
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
if (initializer != null) {
|
||||
initializer.extractVariables(variables);
|
||||
}
|
||||
|
||||
if (condition != null) {
|
||||
condition.extractVariables(variables);
|
||||
}
|
||||
|
||||
if (afterthought != null) {
|
||||
afterthought.extractVariables(variables);
|
||||
}
|
||||
|
||||
if (block != null) {
|
||||
block.extractVariables(variables);
|
||||
}
|
||||
|
@ -68,11 +73,9 @@ public final class SFor extends AStatement {
|
|||
void analyze(Locals locals) {
|
||||
locals = Locals.newLocalScope(locals);
|
||||
|
||||
boolean continuous = false;
|
||||
|
||||
if (initializer != null) {
|
||||
if (initializer instanceof AStatement) {
|
||||
((AStatement)initializer).analyze(locals);
|
||||
initializer.analyze(locals);
|
||||
} else if (initializer instanceof AExpression) {
|
||||
AExpression initializer = (AExpression)this.initializer;
|
||||
|
||||
|
@ -150,7 +153,7 @@ public final class SFor extends AStatement {
|
|||
Label end = new Label();
|
||||
|
||||
if (initializer instanceof SDeclBlock) {
|
||||
((SDeclBlock)initializer).write(writer, globals);
|
||||
initializer.write(writer, globals);
|
||||
} else if (initializer instanceof AExpression) {
|
||||
AExpression initializer = (AExpression)this.initializer;
|
||||
|
||||
|
@ -160,9 +163,9 @@ public final class SFor extends AStatement {
|
|||
|
||||
writer.mark(start);
|
||||
|
||||
if (condition != null) {
|
||||
condition.fals = end;
|
||||
if (condition != null && !continuous) {
|
||||
condition.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, end);
|
||||
}
|
||||
|
||||
boolean allEscape = false;
|
||||
|
@ -179,6 +182,7 @@ public final class SFor extends AStatement {
|
|||
if (loopCounter != null) {
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location);
|
||||
}
|
||||
|
||||
block.write(writer, globals);
|
||||
} else {
|
||||
if (loopCounter != null) {
|
||||
|
|
|
@ -23,16 +23,17 @@ import org.elasticsearch.painless.CompilerSettings;
|
|||
import org.elasticsearch.painless.Constant;
|
||||
import org.elasticsearch.painless.Def;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Parameter;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.WriterConstants;
|
||||
import org.elasticsearch.painless.node.SSource.Reserved;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
@ -50,24 +51,49 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
|
|||
/**
|
||||
* Represents a user-defined function.
|
||||
*/
|
||||
public class SFunction extends AStatement {
|
||||
public final class SFunction extends AStatement {
|
||||
public static final class FunctionReserved implements Reserved {
|
||||
public static final String THIS = "#this";
|
||||
public static final String LOOP = "#loop";
|
||||
|
||||
private int maxLoopCounter = 0;
|
||||
|
||||
public void markReserved(String name) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
public boolean isReserved(String name) {
|
||||
return name.equals(THIS) || name.equals(LOOP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLoopCounter(int max) {
|
||||
maxLoopCounter = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLoopCounter() {
|
||||
return maxLoopCounter;
|
||||
}
|
||||
}
|
||||
|
||||
final FunctionReserved reserved;
|
||||
final String rtnTypeStr;
|
||||
private final String rtnTypeStr;
|
||||
public final String name;
|
||||
final List<String> paramTypeStrs;
|
||||
final List<String> paramNameStrs;
|
||||
final List<AStatement> statements;
|
||||
private final List<String> paramTypeStrs;
|
||||
private final List<String> paramNameStrs;
|
||||
private final List<AStatement> statements;
|
||||
public final boolean synthetic;
|
||||
|
||||
Type rtnType = null;
|
||||
List<Parameter> parameters = new ArrayList<>();
|
||||
Method method = null;
|
||||
|
||||
Variable loop = null;
|
||||
private Variable loop = null;
|
||||
|
||||
public SFunction(FunctionReserved reserved, Location location,
|
||||
String rtnType, String name, List<String> paramTypes,
|
||||
List<String> paramNames, List<AStatement> statements, boolean synthetic) {
|
||||
public SFunction(FunctionReserved reserved, Location location, String rtnType, String name,
|
||||
List<String> paramTypes, List<String> paramNames, List<AStatement> statements,
|
||||
boolean synthetic) {
|
||||
super(location);
|
||||
|
||||
this.reserved = Objects.requireNonNull(reserved);
|
||||
|
@ -78,14 +104,14 @@ public class SFunction extends AStatement {
|
|||
this.statements = Collections.unmodifiableList(statements);
|
||||
this.synthetic = synthetic;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
// we should never be extracting from a function, as functions are top-level!
|
||||
throw new IllegalStateException("Illegal tree structure");
|
||||
}
|
||||
|
||||
void generate() {
|
||||
void generateSignature() {
|
||||
try {
|
||||
rtnType = Definition.getType(rtnTypeStr);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
|
@ -150,7 +176,7 @@ public class SFunction extends AStatement {
|
|||
loop = locals.getVariable(null, FunctionReserved.LOOP);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Writes the function to given ClassVisitor. */
|
||||
void write (ClassVisitor writer, CompilerSettings settings, Globals globals) {
|
||||
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
|
||||
|
@ -185,7 +211,7 @@ public class SFunction extends AStatement {
|
|||
}
|
||||
|
||||
String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size());
|
||||
globals.addConstantInitializer(new Constant(location, WriterConstants.METHOD_HANDLE_TYPE,
|
||||
globals.addConstantInitializer(new Constant(location, WriterConstants.METHOD_HANDLE_TYPE,
|
||||
staticHandleFieldName, this::initializeConstant));
|
||||
}
|
||||
|
||||
|
@ -197,43 +223,4 @@ public class SFunction extends AStatement {
|
|||
false);
|
||||
writer.push(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 interface Reserved {
|
||||
void markReserved(String name);
|
||||
boolean isReserved(String name);
|
||||
|
||||
void setMaxLoopCounter(int max);
|
||||
int getMaxLoopCounter();
|
||||
}
|
||||
|
||||
public static final class FunctionReserved implements Reserved {
|
||||
public static final String THIS = "#this";
|
||||
public static final String LOOP = "#loop";
|
||||
|
||||
private int maxLoopCounter = 0;
|
||||
|
||||
public void markReserved(String name) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
public boolean isReserved(String name) {
|
||||
return name.equals(THIS) || name.equals(LOOP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLoopCounter(int max) {
|
||||
maxLoopCounter = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLoopCounter() {
|
||||
return maxLoopCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,15 +21,15 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents an if block.
|
||||
*/
|
||||
|
@ -44,10 +44,11 @@ public final class SIf extends AStatement {
|
|||
this.condition = Objects.requireNonNull(condition);
|
||||
this.ifblock = ifblock;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
|
||||
if (ifblock != null) {
|
||||
ifblock.extractVariables(variables);
|
||||
}
|
||||
|
@ -84,8 +85,8 @@ public final class SIf extends AStatement {
|
|||
|
||||
Label fals = new Label();
|
||||
|
||||
condition.fals = fals;
|
||||
condition.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, fals);
|
||||
|
||||
ifblock.continu = continu;
|
||||
ifblock.brake = brake;
|
||||
|
|
|
@ -21,23 +21,23 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents an if/else block.
|
||||
*/
|
||||
public final class SIfElse extends AStatement {
|
||||
|
||||
AExpression condition;
|
||||
final SBlock ifblock;
|
||||
final SBlock elseblock;
|
||||
private AExpression condition;
|
||||
private final SBlock ifblock;
|
||||
private final SBlock elseblock;
|
||||
|
||||
public SIfElse(Location location, AExpression condition, SBlock ifblock, SBlock elseblock) {
|
||||
super(location);
|
||||
|
@ -46,13 +46,15 @@ public final class SIfElse extends AStatement {
|
|||
this.ifblock = ifblock;
|
||||
this.elseblock = elseblock;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
|
||||
if (ifblock != null) {
|
||||
ifblock.extractVariables(variables);
|
||||
}
|
||||
|
||||
if (elseblock != null) {
|
||||
elseblock.extractVariables(variables);
|
||||
}
|
||||
|
@ -104,11 +106,11 @@ public final class SIfElse extends AStatement {
|
|||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label fals = new Label();
|
||||
Label end = new Label();
|
||||
Label fals = elseblock != null ? new Label() : end;
|
||||
|
||||
condition.fals = fals;
|
||||
condition.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, fals);
|
||||
|
||||
ifblock.continu = continu;
|
||||
ifblock.brake = brake;
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -32,14 +32,14 @@ import java.util.Set;
|
|||
*/
|
||||
public final class SReturn extends AStatement {
|
||||
|
||||
AExpression expression;
|
||||
private AExpression expression;
|
||||
|
||||
public SReturn(Location location, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
expression.extractVariables(variables);
|
||||
|
|
|
@ -27,11 +27,10 @@ import org.elasticsearch.painless.Executable;
|
|||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.node.SFunction.Reserved;
|
||||
import org.elasticsearch.painless.WriterConstants;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.SimpleChecksAdapter;
|
||||
import org.elasticsearch.painless.WriterConstants;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
@ -60,20 +59,72 @@ import static org.elasticsearch.painless.WriterConstants.MAP_TYPE;
|
|||
*/
|
||||
public final class SSource extends AStatement {
|
||||
|
||||
final String name;
|
||||
final String source;
|
||||
final Printer debugStream;
|
||||
final CompilerSettings settings;
|
||||
final MainMethodReserved reserved;
|
||||
final List<SFunction> functions;
|
||||
final Globals globals;
|
||||
final List<AStatement> statements;
|
||||
/**
|
||||
* 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 interface Reserved {
|
||||
void markReserved(String name);
|
||||
boolean isReserved(String name);
|
||||
|
||||
void setMaxLoopCounter(int max);
|
||||
int getMaxLoopCounter();
|
||||
}
|
||||
|
||||
public static final class MainMethodReserved implements Reserved {
|
||||
private boolean score = false;
|
||||
private boolean ctx = false;
|
||||
private int maxLoopCounter = 0;
|
||||
|
||||
@Override
|
||||
public void markReserved(String name) {
|
||||
if (Locals.SCORE.equals(name)) {
|
||||
score = true;
|
||||
} else if (Locals.CTX.equals(name)) {
|
||||
ctx = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReserved(String name) {
|
||||
return Locals.KEYWORDS.contains(name);
|
||||
}
|
||||
|
||||
public boolean usesScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public boolean usesCtx() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLoopCounter(int max) {
|
||||
maxLoopCounter = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLoopCounter() {
|
||||
return maxLoopCounter;
|
||||
}
|
||||
}
|
||||
|
||||
private final CompilerSettings settings;
|
||||
private final String name;
|
||||
private final String source;
|
||||
private final Printer debugStream;
|
||||
private final MainMethodReserved reserved;
|
||||
private final List<SFunction> functions;
|
||||
private final Globals globals;
|
||||
private final List<AStatement> statements;
|
||||
|
||||
private Locals mainMethod;
|
||||
private byte[] bytes;
|
||||
|
||||
public SSource(CompilerSettings settings, String name, String source, Printer debugStream,
|
||||
MainMethodReserved reserved, Location location,
|
||||
public SSource(CompilerSettings settings, String name, String source, Printer debugStream,
|
||||
MainMethodReserved reserved, Location location,
|
||||
List<SFunction> functions, Globals globals, List<AStatement> statements) {
|
||||
super(location);
|
||||
this.settings = Objects.requireNonNull(settings);
|
||||
|
@ -88,18 +139,18 @@ public final class SSource extends AStatement {
|
|||
this.statements = Collections.unmodifiableList(statements);
|
||||
this.globals = globals;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
// we should never be extracting from a function, as functions are top-level!
|
||||
throw new IllegalStateException("Illegal tree structure");
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
public void analyze() {
|
||||
Map<MethodKey, Method> methods = new HashMap<>();
|
||||
|
||||
for (SFunction function : functions) {
|
||||
function.generate();
|
||||
function.generateSignature();
|
||||
|
||||
MethodKey key = new MethodKey(function.name, function.parameters.size());
|
||||
|
||||
|
@ -114,7 +165,7 @@ public final class SSource extends AStatement {
|
|||
@Override
|
||||
void analyze(Locals program) {
|
||||
for (SFunction function : functions) {
|
||||
Locals functionLocals = Locals.newFunctionScope(program, function.rtnType, function.parameters,
|
||||
Locals functionLocals = Locals.newFunctionScope(program, function.rtnType, function.parameters,
|
||||
function.reserved.getMaxLoopCounter());
|
||||
function.analyze(functionLocals);
|
||||
}
|
||||
|
@ -154,7 +205,7 @@ public final class SSource extends AStatement {
|
|||
|
||||
ClassWriter writer = new ClassWriter(classFrames);
|
||||
ClassVisitor visitor = writer;
|
||||
|
||||
|
||||
// if picky is enabled, turn on some checks. instead of VerifyError at the end, you get a helpful stacktrace.
|
||||
if (settings.isPicky()) {
|
||||
visitor = new SimpleChecksAdapter(visitor);
|
||||
|
@ -180,12 +231,12 @@ public final class SSource extends AStatement {
|
|||
execute.visitCode();
|
||||
write(execute, globals);
|
||||
execute.endMethod();
|
||||
|
||||
|
||||
// Write all functions:
|
||||
for (SFunction function : functions) {
|
||||
function.write(visitor, settings, globals);
|
||||
}
|
||||
|
||||
|
||||
// Write all synthetic functions. Note that this process may add more :)
|
||||
while (!globals.getSyntheticMethods().isEmpty()) {
|
||||
List<SFunction> current = new ArrayList<>(globals.getSyntheticMethods().values());
|
||||
|
@ -210,7 +261,7 @@ public final class SSource extends AStatement {
|
|||
}
|
||||
|
||||
// Initialize the constants in a static initializer
|
||||
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
|
||||
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
|
||||
WriterConstants.CLINIT, visitor, globals.getStatements(), settings);
|
||||
clinit.visitCode();
|
||||
for (Constant constant : inits) {
|
||||
|
@ -220,13 +271,13 @@ public final class SSource extends AStatement {
|
|||
clinit.returnValue();
|
||||
clinit.endMethod();
|
||||
}
|
||||
|
||||
|
||||
// End writing the class and store the generated bytes.
|
||||
|
||||
visitor.visitEnd();
|
||||
bytes = writer.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
if (reserved.usesScore()) {
|
||||
|
@ -281,43 +332,4 @@ public final class SSource extends AStatement {
|
|||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
public static final class MainMethodReserved implements Reserved {
|
||||
private boolean score = false;
|
||||
private boolean ctx = false;
|
||||
private int maxLoopCounter = 0;
|
||||
|
||||
@Override
|
||||
public void markReserved(String name) {
|
||||
if (Locals.SCORE.equals(name)) {
|
||||
score = true;
|
||||
} else if (Locals.CTX.equals(name)) {
|
||||
ctx = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReserved(String name) {
|
||||
return Locals.KEYWORDS.contains(name);
|
||||
}
|
||||
|
||||
public boolean usesScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public boolean usesCtx() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLoopCounter(int max) {
|
||||
maxLoopCounter = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLoopCounter() {
|
||||
return maxLoopCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a for-each loop for arrays.
|
||||
*/
|
||||
final class SSubEachArray extends AStatement {
|
||||
private final Variable variable;
|
||||
private AExpression expression;
|
||||
private final SBlock block;
|
||||
|
||||
private Cast cast = null;
|
||||
private Variable array = null;
|
||||
private Variable index = null;
|
||||
private Type indexed = null;
|
||||
|
||||
public SSubEachArray(Location location, Variable variable, AExpression expression, SBlock block) {
|
||||
super(location);
|
||||
|
||||
this.variable = Objects.requireNonNull(variable);
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
// We must store the array and index as variables for securing slots on the stack, and
|
||||
// also add the location offset to make the names unique in case of nested for each loops.
|
||||
array = locals.addVariable(location, expression.actual, "#array" + location.getOffset(), true);
|
||||
index = locals.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true);
|
||||
indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1);
|
||||
cast = AnalyzerCaster.getLegalCast(location, indexed, variable.type, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
expression.write(writer, globals);
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.getSlot());
|
||||
writer.push(-1);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.getSlot());
|
||||
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
writer.mark(begin);
|
||||
|
||||
writer.visitIincInsn(index.getSlot(), 1);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
|
||||
writer.arrayLength();
|
||||
writer.ifICmp(MethodWriter.GE, end);
|
||||
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
|
||||
writer.arrayLoad(indexed.type);
|
||||
writer.writeCast(cast);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
|
||||
if (loopCounter != null) {
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location);
|
||||
}
|
||||
|
||||
block.write(writer, globals);
|
||||
|
||||
writer.goTo(begin);
|
||||
writer.mark(end);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
|
||||
import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
|
||||
|
||||
/**
|
||||
* Represents a for-each loop for iterables.
|
||||
*/
|
||||
final class SSubEachIterable extends AStatement {
|
||||
|
||||
private AExpression expression;
|
||||
private final SBlock block;
|
||||
private final Variable variable;
|
||||
|
||||
private Cast cast = null;
|
||||
private Variable iterator = null;
|
||||
private Method method = null;
|
||||
|
||||
public SSubEachIterable(Location location, Variable variable, AExpression expression, SBlock block) {
|
||||
super(location);
|
||||
|
||||
this.variable = Objects.requireNonNull(variable);
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
// We must store the iterator as a variable for securing a slot on the stack, and
|
||||
// also add the location offset to make the name unique in case of nested for each loops.
|
||||
iterator = locals.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true);
|
||||
|
||||
if (expression.actual.sort == Sort.DEF) {
|
||||
method = null;
|
||||
} else {
|
||||
method = expression.actual.struct.methods.get(new MethodKey("iterator", 0));
|
||||
|
||||
if (method == null) {
|
||||
throw createError(new IllegalArgumentException(
|
||||
"Unable to create iterator for the type [" + expression.actual.name + "]."));
|
||||
}
|
||||
}
|
||||
|
||||
cast = AnalyzerCaster.getLegalCast(location, Definition.DEF_TYPE, variable.type, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
expression.write(writer, globals);
|
||||
|
||||
if (method == null) {
|
||||
Type itr = Definition.getType("Iterator");
|
||||
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR);
|
||||
} else {
|
||||
method.write(writer);
|
||||
}
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.getSlot());
|
||||
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
writer.mark(begin);
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
|
||||
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT);
|
||||
writer.ifZCmp(MethodWriter.EQ, end);
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
|
||||
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
|
||||
writer.writeCast(cast);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
|
||||
if (loopCounter != null) {
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location);
|
||||
}
|
||||
|
||||
block.write(writer, globals);
|
||||
|
||||
writer.goTo(begin);
|
||||
writer.mark(end);
|
||||
}
|
||||
}
|
|
@ -21,8 +21,8 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -33,14 +33,14 @@ import java.util.Set;
|
|||
*/
|
||||
public final class SThrow extends AStatement {
|
||||
|
||||
AExpression expression;
|
||||
private AExpression expression;
|
||||
|
||||
public SThrow(Location location, AExpression expression) {
|
||||
super(location);
|
||||
|
||||
this.expression = Objects.requireNonNull(expression);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
expression.extractVariables(variables);
|
||||
|
|
|
@ -21,9 +21,9 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -34,8 +34,8 @@ import java.util.Set;
|
|||
*/
|
||||
public final class STry extends AStatement {
|
||||
|
||||
final SBlock block;
|
||||
final List<SCatch> catches;
|
||||
private final SBlock block;
|
||||
private final List<SCatch> catches;
|
||||
|
||||
public STry(Location location, SBlock block, List<SCatch> catches) {
|
||||
super(location);
|
||||
|
@ -43,7 +43,7 @@ public final class STry extends AStatement {
|
|||
this.block = block;
|
||||
this.catches = Collections.unmodifiableList(catches);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
if (block != null) {
|
||||
|
|
|
@ -21,22 +21,24 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
/**
|
||||
* Represents a while loop.
|
||||
*/
|
||||
public final class SWhile extends AStatement {
|
||||
|
||||
AExpression condition;
|
||||
final SBlock block;
|
||||
private AExpression condition;
|
||||
private final SBlock block;
|
||||
|
||||
private boolean continuous = false;
|
||||
|
||||
public SWhile(Location location, AExpression condition, SBlock block) {
|
||||
super(location);
|
||||
|
@ -44,7 +46,7 @@ public final class SWhile extends AStatement {
|
|||
this.condition = Objects.requireNonNull(condition);
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void extractVariables(Set<String> variables) {
|
||||
condition.extractVariables(variables);
|
||||
|
@ -61,8 +63,6 @@ public final class SWhile extends AStatement {
|
|||
condition.analyze(locals);
|
||||
condition = condition.cast(locals);
|
||||
|
||||
boolean continuous = false;
|
||||
|
||||
if (condition.constant != null) {
|
||||
continuous = (boolean)condition.constant;
|
||||
|
||||
|
@ -109,8 +109,10 @@ public final class SWhile extends AStatement {
|
|||
|
||||
writer.mark(begin);
|
||||
|
||||
condition.fals = end;
|
||||
condition.write(writer, globals);
|
||||
if (!continuous) {
|
||||
condition.write(writer, globals);
|
||||
writer.ifZCmp(Opcodes.IFEQ, end);
|
||||
}
|
||||
|
||||
if (block != null) {
|
||||
if (loopCounter != null) {
|
||||
|
|
|
@ -22,52 +22,58 @@
|
|||
* <p>
|
||||
* The following are the types of nodes:
|
||||
* A* (abstract) - These are the abstract nodes that are the superclasses for the other types.
|
||||
* I* (interface) -- These are marker interfaces to denote a property of the node.
|
||||
* S* (statement) - These are nodes that represent a statement in Painless. These are the highest level nodes.
|
||||
* E* (expression) - These are nodes that represent an expression in Painless. These are the middle level nodes.
|
||||
* L* (link) - These are nodes that represent a piece of a variable/method chain. The are the lowest level nodes.
|
||||
* I* (interface) - These are marker interfaces to denote a property of the node.
|
||||
* S* (statement) - These are nodes that represent a statement in Painless.
|
||||
* E* (expression) - These are nodes that represent an expression in Painless.
|
||||
* P* (postfix) - These are nodes that represent a postfix of a variable chain.
|
||||
* E/P* (storeable) - These are nodes that are allowed to store a value to memory.
|
||||
* *Sub* (sub) - These are partial nodes with a parent (S/E/P)* node used to split up logic into smaller pieces.
|
||||
* <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.AExpression} - The superclass for all E* (expression) and P* (postfix) nodes.
|
||||
* {@link org.elasticsearch.painless.node.ANode} - The superclass for all nodes.
|
||||
* {@link org.elasticsearch.painless.node.AStatement} - The superclass for all S* (statement) nodes.
|
||||
* {@link org.elasticsearch.painless.node.AStoreable} - The super class for an expression that can store a value in local memory.
|
||||
* {@link org.elasticsearch.painless.node.EAssignment} - Represents an assignment with the lhs and rhs as child 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.ECallLocal} - Represents a user-defined call.
|
||||
* {@link org.elasticsearch.painless.node.ECapturingFunctionRef} - Represents a function reference (capturing).
|
||||
* {@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.ECast} - Represents a cast inserted into the tree replacing others. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.EComp} - Represents a comparison expression.
|
||||
* {@link org.elasticsearch.painless.node.EConditional} - Represents a conditional expression.
|
||||
* {@link org.elasticsearch.painless.node.EConstant} - Represents a constant. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.EConstant} - Represents a constant inserted into the tree replacing others. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.EDecimal} - Represents a decimal constant.
|
||||
* {@link org.elasticsearch.painless.node.EExplicit} - Represents an explicit cast.
|
||||
* {@link org.elasticsearch.painless.node.EFunctionRef} - Represents a function reference (non-capturing).
|
||||
* {@link org.elasticsearch.painless.node.EInstanceof} - Represents an instanceof check.
|
||||
* {@link org.elasticsearch.painless.node.ELambda} - Represents a lambda function.
|
||||
* {@link org.elasticsearch.painless.node.EListInit} - Represents a list initialization shortcut.
|
||||
* {@link org.elasticsearch.painless.node.EMapInit} - Represents a map initialization shortcut.
|
||||
* {@link org.elasticsearch.painless.node.ENewArray} - Represents an array instantiation.
|
||||
* {@link org.elasticsearch.painless.node.ENewObj} - Represents and object instantiation.
|
||||
* {@link org.elasticsearch.painless.node.ENull} - Represents a null constant.
|
||||
* {@link org.elasticsearch.painless.node.ENumeric} - Represents a non-decimal numeric constant.
|
||||
* {@link org.elasticsearch.painless.node.ERegex} - Represents a regular expression constant.
|
||||
* {@link org.elasticsearch.painless.node.EStatic} - Represents a static type target.
|
||||
* {@link org.elasticsearch.painless.node.EString} - Represents a string constant.
|
||||
* {@link org.elasticsearch.painless.node.EUnary} - Represents a unary math expression.
|
||||
* {@link org.elasticsearch.painless.node.IDefLink} - A marker interface for all LDef* (link) nodes.
|
||||
* {@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.LCallInvoke} - Represents a method call or defers to a def call.
|
||||
* {@link org.elasticsearch.painless.node.LCallLocal} - Represents a user-defined 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} - Represents and object instantiation.
|
||||
* {@link org.elasticsearch.painless.node.LShortcut} - Represents a field load/store shortcut. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.LStatic} - Represents a static type target.
|
||||
* {@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.EVariable} - Represents a variable load/store.
|
||||
* {@link org.elasticsearch.painless.node.ILambda} - Represents a marker to signify this node is a lambda function.
|
||||
* {@link org.elasticsearch.painless.node.PBrace} - Represents an array load/store and defers to a child subnode.
|
||||
* {@link org.elasticsearch.painless.node.PCallInvoke} - Represents a method call and defers to a child subnode.
|
||||
* {@link org.elasticsearch.painless.node.PField} - Represents a field load/store and defers to a child subnode.
|
||||
* {@link org.elasticsearch.painless.node.PSubArrayLength} - Represents an array length field load.
|
||||
* {@link org.elasticsearch.painless.node.PSubBrace} - Represents an array load/store.
|
||||
* {@link org.elasticsearch.painless.node.PSubCallInvoke} - Represents a method call.
|
||||
* {@link org.elasticsearch.painless.node.PSubDefArray} - Represents an array load/store or shortcut on a def type. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.PSubDefCall} - Represents a method call made on a def type. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.PSubDefField} - Represents a field load/store or shortcut on a def type. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.PSubField} - Represents a field load/store.
|
||||
* {@link org.elasticsearch.painless.node.PSubListShortcut} - Represents a list load/store shortcut. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.PSubMapShortcut} - Represents a map load/store shortcut. (Internal only.)
|
||||
* {@link org.elasticsearch.painless.node.PSubShortcut} - Represents a field load/store shortcut. (Internal only.)
|
||||
* {@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.SCatch} - Represents a catch block as part of a try-catch block.
|
||||
|
@ -75,7 +81,7 @@
|
|||
* {@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.SEach} - Represents a for each loop shortcut for iterables.
|
||||
* {@link org.elasticsearch.painless.node.SEach} - Represents a for-each loop and defers to subnodes depending on type.
|
||||
* {@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.SFunction} - Represents a user-defined function.
|
||||
|
@ -83,6 +89,8 @@
|
|||
* {@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.SSubEachArray} - Represents a for-each loop for arrays.
|
||||
* {@link org.elasticsearch.painless.node.SSubEachIterable} - Represents a for-each loop for iterables.
|
||||
* {@link org.elasticsearch.painless.node.SThrow} - Represents a throw statement.
|
||||
* {@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.
|
||||
|
@ -92,14 +100,14 @@
|
|||
* <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
|
||||
* are build bottom-up; however, this helps enforce tree structure correctness and fits naturally with a
|
||||
* standard recursive-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.
|
||||
* <p>
|
||||
* Generally, expression nodes have member data that evaluate static types. The typical order for an expression node
|
||||
* Generally, expression nodes have member data that evaluate static and def types. The typical order for an expression node
|
||||
* during the analysis phase looks like the following:
|
||||
* {@code
|
||||
* For known expected types:
|
||||
|
@ -127,16 +135,24 @@
|
|||
* }
|
||||
* 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.
|
||||
* Postfix nodes represent postfixes in a variable/method chain including braces, calls, or fields.
|
||||
* Postfix nodes will always have a prefix node that is the prior piece of the variable/method chain.
|
||||
* Analysis of a postfix node will cause a chain of analysis calls to happen where the prefix will
|
||||
* be analyzed first and continue until the prefix is not a postfix node. Writing out a series of
|
||||
* loads from a postfix node works in the same fashion. Stores work somewhat differently as
|
||||
* described by later documentation.
|
||||
* <p>
|
||||
* Storebable nodes have three methods for writing -- setup, load, and store. These methods
|
||||
* are used in conjuction with a parent node aware of the storeable node (lhs) that has a node
|
||||
* representing a value to store (rhs). The setup method is always once called before a store
|
||||
* to give storeable nodes a chance to write any prefixes they may have and any values such as
|
||||
* array indices before the store happens. Load is called on a storeable node that must also
|
||||
* be read from, and store is called to write a value to memory.
|
||||
* <p>
|
||||
* Sub nodes are partial nodes that require a parent to work correctly. These nodes can really
|
||||
* represent anything the parent node would like to split up into logical pieces and don't really
|
||||
* have any distinct set of rules. The currently existing subnodes all have ANode as a super class
|
||||
* somewhere in their class heirachy so the parent node can defer some analysis and writing to
|
||||
* the sub node.
|
||||
*/
|
||||
package org.elasticsearch.painless.node;
|
||||
|
|
|
@ -67,7 +67,7 @@ public class BasicAPITests extends ScriptTestCase {
|
|||
ctx.put("_source", _source);
|
||||
params.put("ctx", ctx);
|
||||
|
||||
assertEquals("testvalue", exec("ctx._source['load'].5 = ctx._source['load'].remove('load5')", params));
|
||||
assertEquals("testvalue", exec("ctx._source['load'].5 = ctx._source['load'].remove('load5')", params, true));
|
||||
}
|
||||
|
||||
/** Test loads and stores with a list */
|
||||
|
@ -118,7 +118,7 @@ public class BasicAPITests extends ScriptTestCase {
|
|||
assertEquals("{}", exec("Map map = new HashMap(); return map.toString();"));
|
||||
assertEquals("{}", exec("def map = new HashMap(); return map.toString();"));
|
||||
}
|
||||
|
||||
|
||||
public void testPrimitivesHaveMethods() {
|
||||
assertEquals(5, exec("int x = 5; return x.intValue();"));
|
||||
assertEquals("5", exec("int x = 5; return x.toString();"));
|
||||
|
|
|
@ -68,14 +68,14 @@ public class BasicExpressionTests extends ScriptTestCase {
|
|||
"((Map)y).put(2, 3);\n" +
|
||||
"return x.get(2);\n"));
|
||||
}
|
||||
|
||||
|
||||
public void testIllegalDefCast() {
|
||||
Exception exception = expectScriptThrows(ClassCastException.class, () -> {
|
||||
Exception exception = expectScriptThrows(ClassCastException.class, () -> {
|
||||
exec("def x = 1.0; int y = x; return y;");
|
||||
});
|
||||
assertTrue(exception.getMessage().contains("cannot be cast"));
|
||||
|
||||
exception = expectScriptThrows(ClassCastException.class, () -> {
|
||||
exception = expectScriptThrows(ClassCastException.class, () -> {
|
||||
exec("def x = (short)1; byte y = x; return y;");
|
||||
});
|
||||
assertTrue(exception.getMessage().contains("cannot be cast"));
|
||||
|
@ -112,11 +112,11 @@ public class BasicExpressionTests extends ScriptTestCase {
|
|||
*/
|
||||
public void testBoxing() {
|
||||
// return
|
||||
assertEquals(4, exec("return params.get(\"x\");", Collections.singletonMap("x", 4)));
|
||||
assertEquals(4, exec("return params.get(\"x\");", Collections.singletonMap("x", 4), true));
|
||||
// assignment
|
||||
assertEquals(4, exec("int y = params.get(\"x\"); return y;", Collections.singletonMap("x", 4)));
|
||||
assertEquals(4, exec("int y = params.get(\"x\"); return y;", Collections.singletonMap("x", 4), true));
|
||||
// comparison
|
||||
assertEquals(true, exec("return 5 > params.get(\"x\");", Collections.singletonMap("x", 4)));
|
||||
assertEquals(true, exec("return 5 > params.get(\"x\");", Collections.singletonMap("x", 4), true));
|
||||
}
|
||||
|
||||
public void testBool() {
|
||||
|
|
|
@ -251,25 +251,25 @@ public class BasicStatementTests extends ScriptTestCase {
|
|||
assertEquals(10, exec("def i = 1; if (i == 1) {i = 2; return 10}"));
|
||||
assertEquals(10, exec("def i = 1; if (i == 1) {i = 2; return 10} else {return 12}"));
|
||||
}
|
||||
|
||||
|
||||
public void testArrayLoopWithoutCounter() {
|
||||
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
|
||||
"for (int i = 0; i < array.length; i++) { sum += array[i] } return sum",
|
||||
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
|
||||
"for (int i = 0; i < array.length; i++) { sum += array[i] } return sum",
|
||||
Collections.emptyMap(),
|
||||
Collections.singletonMap(CompilerSettings.MAX_LOOP_COUNTER, "0"),
|
||||
null
|
||||
null, true
|
||||
));
|
||||
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
|
||||
"int i = 0; while (i < array.length) { sum += array[i++] } return sum",
|
||||
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
|
||||
"int i = 0; while (i < array.length) { sum += array[i++] } return sum",
|
||||
Collections.emptyMap(),
|
||||
Collections.singletonMap(CompilerSettings.MAX_LOOP_COUNTER, "0"),
|
||||
null
|
||||
null, true
|
||||
));
|
||||
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
|
||||
"int i = 0; do { sum += array[i++] } while (i < array.length); return sum",
|
||||
assertEquals(6L, exec("long sum = 0; long[] array = new long[] { 1, 2, 3 };" +
|
||||
"int i = 0; do { sum += array[i++] } while (i < array.length); return sum",
|
||||
Collections.emptyMap(),
|
||||
Collections.singletonMap(CompilerSettings.MAX_LOOP_COUNTER, "0"),
|
||||
null
|
||||
null, true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ package org.elasticsearch.painless;
|
|||
public class EqualsTests extends ScriptTestCase {
|
||||
public void testTypesEquals() {
|
||||
assertEquals(true, exec("return false === false;"));
|
||||
assertEquals(false, exec("boolean x = false; boolean y = true; return x === y;"));
|
||||
assertEquals(true, exec("boolean x = false; boolean y = false; return x === y;"));
|
||||
assertEquals(false, exec("return (byte)3 === (byte)4;"));
|
||||
assertEquals(true, exec("byte x = 3; byte y = 3; return x === y;"));
|
||||
|
@ -40,6 +41,7 @@ public class EqualsTests extends ScriptTestCase {
|
|||
assertEquals(true, exec("double x = 3; double y = 3; return x === y;"));
|
||||
|
||||
assertEquals(true, exec("return false == false;"));
|
||||
assertEquals(false, exec("boolean x = false; boolean y = true; return x == y;"));
|
||||
assertEquals(true, exec("boolean x = false; boolean y = false; return x == y;"));
|
||||
assertEquals(false, exec("return (byte)3 == (byte)4;"));
|
||||
assertEquals(true, exec("byte x = 3; byte y = 3; return x == y;"));
|
||||
|
@ -59,6 +61,7 @@ public class EqualsTests extends ScriptTestCase {
|
|||
|
||||
public void testTypesNotEquals() {
|
||||
assertEquals(false, exec("return true !== true;"));
|
||||
assertEquals(true, exec("boolean x = true; boolean y = false; return x !== y;"));
|
||||
assertEquals(false, exec("boolean x = false; boolean y = false; return x !== y;"));
|
||||
assertEquals(true, exec("return (byte)3 !== (byte)4;"));
|
||||
assertEquals(false, exec("byte x = 3; byte y = 3; return x !== y;"));
|
||||
|
@ -76,6 +79,7 @@ public class EqualsTests extends ScriptTestCase {
|
|||
assertEquals(false, exec("double x = 3; double y = 3; return x !== y;"));
|
||||
|
||||
assertEquals(false, exec("return true != true;"));
|
||||
assertEquals(true, exec("boolean x = true; boolean y = false; return x != y;"));
|
||||
assertEquals(false, exec("boolean x = false; boolean y = false; return x != y;"));
|
||||
assertEquals(true, exec("return (byte)3 != (byte)4;"));
|
||||
assertEquals(false, exec("byte x = 3; byte y = 3; return x != y;"));
|
||||
|
|
|
@ -64,7 +64,7 @@ public class NoSemiColonTests extends ScriptTestCase {
|
|||
assertEquals(10, exec("10"));
|
||||
assertEquals(10, exec("5 + 5"));
|
||||
assertEquals(10, exec("5 + 5"));
|
||||
assertEquals(10, exec("params.param == 'yes' ? 10 : 5", Collections.singletonMap("param", "yes")));
|
||||
assertEquals(10, exec("params.param == 'yes' ? 10 : 5", Collections.singletonMap("param", "yes"), true));
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
public class PostfixTests extends ScriptTestCase {
|
||||
public void testConstantPostfixes() {
|
||||
assertEquals("2", exec("2.toString()"));
|
||||
assertEquals(4, exec("[1, 2, 3, 4, 5][3]"));
|
||||
assertEquals("4", exec("[1, 2, 3, 4, 5][3].toString()"));
|
||||
assertEquals(3, exec("new int[] {1, 2, 3, 4, 5}[2]"));
|
||||
assertEquals("4", exec("(2 + 2).toString()"));
|
||||
}
|
||||
|
||||
public void testConditionalPostfixes() {
|
||||
assertEquals("5", exec("boolean b = false; (b ? 4 : 5).toString()"));
|
||||
assertEquals(3, exec(
|
||||
"Map x = new HashMap(); x['test'] = 3;" +
|
||||
"Map y = new HashMap(); y['test'] = 4;" +
|
||||
"boolean b = true;" +
|
||||
"return (int)(b ? x : y).get('test')")
|
||||
);
|
||||
}
|
||||
|
||||
public void testAssignmentPostfixes() {
|
||||
assertEquals(true, exec("int x; '3' == (x = 3).toString()"));
|
||||
assertEquals(-1, exec("int x; (x = 3).compareTo(4)"));
|
||||
assertEquals(3L, exec("long[] x; (x = new long[1])[0] = 3; return x[0]"));
|
||||
assertEquals(2, exec("int x; ((x)) = 2; return x;"));
|
||||
}
|
||||
|
||||
public void testDefConditionalPostfixes() {
|
||||
assertEquals("5", exec("def b = false; (b ? 4 : 5).toString()"));
|
||||
assertEquals(3, exec(
|
||||
"def x = new HashMap(); x['test'] = 3;" +
|
||||
"def y = new HashMap(); y['test'] = 4;" +
|
||||
"boolean b = true;" +
|
||||
"return (b ? x : y).get('test')")
|
||||
);
|
||||
}
|
||||
|
||||
public void testDefAssignmentPostfixes() {
|
||||
assertEquals(true, exec("def x; '3' == (x = 3).toString()"));
|
||||
assertEquals(-1, exec("def x; (x = 3).compareTo(4)"));
|
||||
assertEquals(3L, exec("def x; (x = new long[1])[0] = 3; return x[0]"));
|
||||
assertEquals(2, exec("def x; ((x)) = 2; return x;"));
|
||||
}
|
||||
}
|
|
@ -114,8 +114,8 @@ public class RegexTests extends ScriptTestCase {
|
|||
}
|
||||
|
||||
public void testFindOnInput() {
|
||||
assertEquals(true, exec("return params.s =~ /foo/", singletonMap("s", "fooasdfdf")));
|
||||
assertEquals(false, exec("return params.s =~ /foo/", singletonMap("s", "11f2ooasdfdf")));
|
||||
assertEquals(true, exec("return params.s =~ /foo/", singletonMap("s", "fooasdfdf"), true));
|
||||
assertEquals(false, exec("return params.s =~ /foo/", singletonMap("s", "11f2ooasdfdf"), true));
|
||||
}
|
||||
|
||||
public void testGroup() {
|
||||
|
@ -183,7 +183,7 @@ public class RegexTests extends ScriptTestCase {
|
|||
public void testReplaceAllMatchesCharSequence() {
|
||||
CharSequence charSequence = CharBuffer.wrap("the quick brown fox");
|
||||
assertEquals("thE qUIck brOwn fOx",
|
||||
exec("params.a.replaceAll(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence)));
|
||||
exec("params.a.replaceAll(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence), true));
|
||||
}
|
||||
|
||||
public void testReplaceAllNoMatchString() {
|
||||
|
@ -193,7 +193,7 @@ public class RegexTests extends ScriptTestCase {
|
|||
public void testReplaceAllNoMatchCharSequence() {
|
||||
CharSequence charSequence = CharBuffer.wrap("i am cat");
|
||||
assertEquals("i am cat",
|
||||
exec("params.a.replaceAll(/dolphin/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence)));
|
||||
exec("params.a.replaceAll(/dolphin/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence), true));
|
||||
}
|
||||
|
||||
public void testReplaceAllQuoteReplacement() {
|
||||
|
@ -211,7 +211,7 @@ public class RegexTests extends ScriptTestCase {
|
|||
public void testReplaceFirstMatchesCharSequence() {
|
||||
CharSequence charSequence = CharBuffer.wrap("the quick brown fox");
|
||||
assertEquals("thE quick brown fox",
|
||||
exec("params.a.replaceFirst(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence)));
|
||||
exec("params.a.replaceFirst(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence), true));
|
||||
}
|
||||
|
||||
public void testReplaceFirstNoMatchString() {
|
||||
|
@ -221,7 +221,7 @@ public class RegexTests extends ScriptTestCase {
|
|||
public void testReplaceFirstNoMatchCharSequence() {
|
||||
CharSequence charSequence = CharBuffer.wrap("i am cat");
|
||||
assertEquals("i am cat",
|
||||
exec("params.a.replaceFirst(/dolphin/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence)));
|
||||
exec("params.a.replaceFirst(/dolphin/, m -> m.group().toUpperCase(Locale.ROOT))", singletonMap("a", charSequence), true));
|
||||
}
|
||||
|
||||
public void testReplaceFirstQuoteReplacement() {
|
||||
|
@ -255,7 +255,7 @@ public class RegexTests extends ScriptTestCase {
|
|||
|
||||
public void testBogusRegexFlag() {
|
||||
IllegalArgumentException e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("/asdf/b", emptyMap(), emptyMap(), null); // Not picky so we get a non-assertion error
|
||||
exec("/asdf/b", false); // Not picky so we get a non-assertion error
|
||||
});
|
||||
assertEquals("unexpected token ['b'] was expecting one of [{<EOF>, ';'}].", e.getMessage());
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class ReservedWordTests extends ScriptTestCase {
|
|||
|
||||
/** 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>())));
|
||||
assertEquals(5, exec("ctx.foo = 5; return ctx.foo;", Collections.singletonMap("ctx", new HashMap<String,Object>()), true));
|
||||
}
|
||||
|
||||
/** check that we can't declare a variable of _value, its really reserved! */
|
||||
|
|
|
@ -53,7 +53,8 @@ public class ScoreTests extends ScriptTestCase {
|
|||
public float score() throws IOException {
|
||||
return 2.5f;
|
||||
}
|
||||
}));
|
||||
},
|
||||
true));
|
||||
}
|
||||
|
||||
public void testScoreNotUsed() {
|
||||
|
@ -63,7 +64,8 @@ public class ScoreTests extends ScriptTestCase {
|
|||
public float score() throws IOException {
|
||||
throw new AssertionError("score() should not be called");
|
||||
}
|
||||
}));
|
||||
},
|
||||
true));
|
||||
}
|
||||
|
||||
public void testScoreCached() {
|
||||
|
@ -77,6 +79,7 @@ public class ScoreTests extends ScriptTestCase {
|
|||
}
|
||||
throw new AssertionError("score() should not be called twice");
|
||||
}
|
||||
}));
|
||||
},
|
||||
true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,12 +46,12 @@ public class ScriptEngineTests extends ScriptTestCase {
|
|||
obj1.put("l", Arrays.asList("2", "1"));
|
||||
vars.put("obj1", obj1);
|
||||
|
||||
Object value = exec("return params['obj1'];", vars);
|
||||
Object value = exec("return params['obj1'];", vars, true);
|
||||
obj1 = (Map<String, Object>)value;
|
||||
assertEquals("value1", obj1.get("prop1"));
|
||||
assertEquals("value2", ((Map<String, Object>) obj1.get("obj2")).get("prop2"));
|
||||
|
||||
value = exec("return params.obj1.l.0;", vars);
|
||||
value = exec("return params.obj1.l.0;", vars, true);
|
||||
assertEquals("2", value);
|
||||
}
|
||||
|
||||
|
@ -65,15 +65,15 @@ public class ScriptEngineTests extends ScriptTestCase {
|
|||
obj1.put("obj2", obj2);
|
||||
vars.put("l", Arrays.asList("1", "2", "3", obj1));
|
||||
|
||||
assertEquals(4, exec("return params.l.size();", vars));
|
||||
assertEquals("1", exec("return params.l.0;", vars));
|
||||
assertEquals(4, exec("return params.l.size();", vars, true));
|
||||
assertEquals("1", exec("return params.l.0;", vars, true));
|
||||
|
||||
Object value = exec("return params.l.3;", vars);
|
||||
Object value = exec("return params.l.3;", vars, true);
|
||||
obj1 = (Map<String, Object>)value;
|
||||
assertEquals("value1", obj1.get("prop1"));
|
||||
assertEquals("value2", ((Map<String, Object>)obj1.get("obj2")).get("prop2"));
|
||||
|
||||
assertEquals("value1", exec("return params.l.3.prop1;", vars));
|
||||
assertEquals("value1", exec("return params.l.3.prop1;", vars, true));
|
||||
}
|
||||
|
||||
public void testChangingVarsCrossExecution1() {
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.painless;
|
|||
import org.apache.lucene.search.Scorer;
|
||||
import org.elasticsearch.common.lucene.ScorerAware;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.painless.antlr.Walker;
|
||||
import org.elasticsearch.script.CompiledScript;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
|
@ -49,19 +50,30 @@ public abstract class ScriptTestCase extends ESTestCase {
|
|||
|
||||
/** Compiles and returns the result of {@code script} */
|
||||
public Object exec(String script) {
|
||||
return exec(script, null);
|
||||
return exec(script, null, true);
|
||||
}
|
||||
|
||||
/** Compiles and returns the result of {@code script} with access to {@code picky} */
|
||||
public Object exec(String script, boolean picky) {
|
||||
return exec(script, null, picky);
|
||||
}
|
||||
|
||||
/** Compiles and returns the result of {@code script} with access to {@code vars} */
|
||||
public Object exec(String script, Map<String, Object> vars) {
|
||||
public Object exec(String script, Map<String, Object> vars, boolean picky) {
|
||||
Map<String,String> compilerSettings = new HashMap<>();
|
||||
compilerSettings.put(CompilerSettings.PICKY, "true");
|
||||
compilerSettings.put(CompilerSettings.INITIAL_CALL_SITE_DEPTH, random().nextBoolean() ? "0" : "10");
|
||||
return exec(script, vars, compilerSettings, null);
|
||||
return exec(script, vars, compilerSettings, null, picky);
|
||||
}
|
||||
|
||||
/** Compiles and returns the result of {@code script} with access to {@code vars} and compile-time parameters */
|
||||
public Object exec(String script, Map<String, Object> vars, Map<String,String> compileParams, Scorer scorer) {
|
||||
public Object exec(String script, Map<String, Object> vars, Map<String,String> compileParams, Scorer scorer, boolean picky) {
|
||||
// test for ambiguity errors before running the actual script if picky is true
|
||||
if (picky) {
|
||||
CompilerSettings pickySettings = new CompilerSettings();
|
||||
pickySettings.setPicky(true);
|
||||
Walker.buildPainlessTree(getTestName(), script, pickySettings, null);
|
||||
}
|
||||
// test actual script execution
|
||||
Object object = scriptEngine.compile(null, script, compileParams);
|
||||
CompiledScript compiled = new CompiledScript(ScriptService.ScriptType.INLINE, getTestName(), "painless", object);
|
||||
ExecutableScript executableScript = scriptEngine.executable(compiled, vars);
|
||||
|
@ -79,7 +91,7 @@ public abstract class ScriptTestCase extends ESTestCase {
|
|||
final String asm = Debugger.toString(script);
|
||||
assertTrue("bytecode not found, got: \n" + asm , asm.contains(bytecode));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses the {@link Debugger} to get the bytecode output for a script and compare
|
||||
* it against an expected bytecode pattern as a regular expression (please try to avoid!)
|
||||
|
@ -88,7 +100,7 @@ public abstract class ScriptTestCase extends ESTestCase {
|
|||
final String asm = Debugger.toString(script);
|
||||
assertTrue("bytecode not found, got: \n" + asm , asm.matches(pattern));
|
||||
}
|
||||
|
||||
|
||||
/** Checks a specific exception class is thrown (boxed inside ScriptException) and returns it. */
|
||||
public static <T extends Throwable> T expectScriptThrows(Class<T> expectedType, ThrowingRunnable runnable) {
|
||||
try {
|
||||
|
@ -104,7 +116,7 @@ public abstract class ScriptTestCase extends ESTestCase {
|
|||
assertion.initCause(e);
|
||||
throw assertion;
|
||||
}
|
||||
AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected "
|
||||
AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected "
|
||||
+ expectedType.getSimpleName());
|
||||
assertion.initCause(e);
|
||||
throw assertion;
|
||||
|
|
|
@ -31,27 +31,27 @@ public class TryCatchTests extends ScriptTestCase {
|
|||
});
|
||||
assertEquals("test", exception.getMessage());
|
||||
}
|
||||
|
||||
|
||||
/** catches the exact exception */
|
||||
public void testCatch() {
|
||||
assertEquals(1, exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
|
||||
"catch (RuntimeException e) { return 1; } return 2;",
|
||||
Collections.singletonMap("param", "true")));
|
||||
assertEquals(1, exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
|
||||
"catch (RuntimeException e) { return 1; } return 2;",
|
||||
Collections.singletonMap("param", "true"), true));
|
||||
}
|
||||
|
||||
|
||||
/** catches superclass of the exception */
|
||||
public void testCatchSuperclass() {
|
||||
assertEquals(1, exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
|
||||
"catch (Exception e) { return 1; } return 2;",
|
||||
Collections.singletonMap("param", "true")));
|
||||
assertEquals(1, exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
|
||||
"catch (Exception e) { return 1; } return 2;",
|
||||
Collections.singletonMap("param", "true"), true));
|
||||
}
|
||||
|
||||
|
||||
/** tries to catch a different type of exception */
|
||||
public void testNoCatch() {
|
||||
RuntimeException exception = expectScriptThrows(RuntimeException.class, () -> {
|
||||
exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
|
||||
"catch (ArithmeticException e) { return 1; } return 2;",
|
||||
Collections.singletonMap("param", "true"));
|
||||
exec("try { if (params.param == 'true') throw new RuntimeException('test'); } " +
|
||||
"catch (ArithmeticException e) { return 1; } return 2;",
|
||||
Collections.singletonMap("param", "true"), true);
|
||||
});
|
||||
assertEquals("test", exception.getMessage());
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
|
|||
|
||||
public void testBogusParameter() {
|
||||
IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
|
||||
exec("return 5;", null, Collections.singletonMap("bogusParameterKey", "bogusParameterValue"), null);
|
||||
exec("return 5;", null, Collections.singletonMap("bogusParameterKey", "bogusParameterValue"), null, true);
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("Unrecognized compile-time parameter"));
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
|
|||
"The maximum number of statements that can be executed in a loop has been reached."));
|
||||
|
||||
RuntimeException parseException = expectScriptThrows(RuntimeException.class, () -> {
|
||||
exec("try { int x; } catch (PainlessError error) {}");
|
||||
exec("try { int x; } catch (PainlessError error) {}", false);
|
||||
fail("should have hit ParseException");
|
||||
});
|
||||
assertTrue(parseException.getMessage().contains("unexpected token ['PainlessError']"));
|
||||
|
@ -208,9 +208,9 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
|
|||
public void testRCurlyNotDelim() {
|
||||
IllegalArgumentException e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
// We don't want PICKY here so we get the normal error message
|
||||
exec("def i = 1} return 1", emptyMap(), emptyMap(), null);
|
||||
exec("def i = 1} return 1", emptyMap(), emptyMap(), null, false);
|
||||
});
|
||||
assertEquals("invalid sequence of tokens near ['}'].", e.getMessage());
|
||||
assertEquals("unexpected token ['}'] was expecting one of [<EOF>].", e.getMessage());
|
||||
}
|
||||
|
||||
public void testBadBoxingCast() {
|
||||
|
|
|
@ -1,94 +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.antlr;
|
||||
|
||||
import org.antlr.v4.runtime.ANTLRInputStream;
|
||||
import org.antlr.v4.runtime.BaseErrorListener;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.DiagnosticErrorListener;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.antlr.v4.runtime.atn.PredictionMode;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.SourceContext;
|
||||
import org.elasticsearch.painless.ScriptTestCase;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
public class ParserTests extends ScriptTestCase {
|
||||
private static class TestException extends RuntimeException {
|
||||
TestException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private SourceContext buildAntlrTree(String source) {
|
||||
ANTLRInputStream stream = new ANTLRInputStream(source);
|
||||
PainlessLexer lexer = new EnhancedPainlessLexer(stream, "testing");
|
||||
PainlessParser parser = new PainlessParser(new CommonTokenStream(lexer));
|
||||
ParserErrorStrategy strategy = new ParserErrorStrategy("testing");
|
||||
|
||||
lexer.removeErrorListeners();
|
||||
parser.removeErrorListeners();
|
||||
|
||||
// Diagnostic listener invokes syntaxError on other listeners for ambiguity issues,
|
||||
parser.addErrorListener(new DiagnosticErrorListener(true));
|
||||
// a second listener to fail the test when the above happens.
|
||||
parser.addErrorListener(new BaseErrorListener() {
|
||||
@Override
|
||||
public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol, final int line,
|
||||
final int charPositionInLine, final String msg, final RecognitionException e) {
|
||||
throw new TestException("line: " + line + ", offset: " + charPositionInLine +
|
||||
", symbol:" + offendingSymbol + " " + msg);
|
||||
}
|
||||
});
|
||||
|
||||
// Enable exact ambiguity detection (costly). we enable exact since its the default for
|
||||
// DiagnosticErrorListener, life is too short to think about what 'inexact ambiguity' might mean.
|
||||
parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
|
||||
parser.setErrorHandler(strategy);
|
||||
|
||||
return parser.source();
|
||||
}
|
||||
|
||||
public void testIllegalSecondary() {
|
||||
//TODO: Need way more corner case tests.
|
||||
Exception exception = expectThrows(TestException.class, () -> buildAntlrTree("(x = 5).y"));
|
||||
assertTrue(exception.getMessage().contains("no viable alternative"));
|
||||
exception = expectThrows(TestException.class, () -> buildAntlrTree("((x = 5).y = 2).z;"));
|
||||
assertTrue(exception.getMessage().contains("no viable alternative"));
|
||||
exception = expectThrows(TestException.class, () -> buildAntlrTree("(2 + 2).z"));
|
||||
assertTrue(exception.getMessage().contains("no viable alternative"));
|
||||
exception = expectThrows(RuntimeException.class, () -> buildAntlrTree("((Map)x.-x)"));
|
||||
assertTrue(exception.getMessage().contains("unexpected character"));
|
||||
}
|
||||
|
||||
public void testLambdaSyntax() {
|
||||
buildAntlrTree("call(p -> {p.doSomething();});");
|
||||
buildAntlrTree("call(int p -> {p.doSomething();});");
|
||||
buildAntlrTree("call((p, u, v) -> {p.doSomething(); blah = 1;});");
|
||||
buildAntlrTree("call(1, (p, u, v) -> {p.doSomething(); blah = 1;}, 3);");
|
||||
buildAntlrTree("call((p, u, v) -> {p.doSomething(); blah = 1;});");
|
||||
buildAntlrTree("call(x, y, z, (int p, int u, int v) -> {p.doSomething(); blah = 1;});");
|
||||
buildAntlrTree("call(x, y, z, (long p, List u, String v) -> {p.doSomething(); blah = 1;});");
|
||||
buildAntlrTree("call(x, y, z, (int p, u, int v) -> {p.doSomething(); blah = 1;});");
|
||||
buildAntlrTree("call(x, (int p, u, int v) -> {p.doSomething(); blah = 1;}, z," +
|
||||
" (int p, u, int v) -> {p.doSomething(); blah = 1;}, 'test');");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue