commit
3dfe2bbcb6
|
@ -32,6 +32,10 @@ LBRACE: '[';
|
|||
RBRACE: ']';
|
||||
LP: '(';
|
||||
RP: ')';
|
||||
// We switch modes after a dot to ensure there are not conflicts
|
||||
// between shortcuts and decimal values. Without the mode switch
|
||||
// shortcuts such as id.0.0 will fail because 0.0 will be interpreted
|
||||
// as a decimal value instead of two individual list-style shortcuts.
|
||||
DOT: '.' -> mode(AFTER_DOT);
|
||||
COMMA: ',';
|
||||
SEMICOLON: ';';
|
||||
|
|
|
@ -33,6 +33,7 @@ statement
|
|||
| WHILE LP expression RP ( trailer | empty ) # while
|
||||
| DO block WHILE LP expression RP delimiter # do
|
||||
| FOR LP initializer? SEMICOLON expression? SEMICOLON afterthought? RP ( trailer | empty ) # for
|
||||
| FOR LP decltype ID COLON expression RP trailer # each
|
||||
| declaration delimiter # decl
|
||||
| CONTINUE delimiter # continue
|
||||
| BREAK delimiter # break
|
||||
|
|
|
@ -104,17 +104,16 @@ final class Compiler {
|
|||
SSource root = Walker.buildPainlessTree(name, source, reserved, settings);
|
||||
Variables variables = Analyzer.analyze(reserved, root);
|
||||
BitSet expressions = new BitSet(source.length());
|
||||
|
||||
byte[] bytes = Writer.write(settings, name, source, variables, root, expressions);
|
||||
|
||||
try {
|
||||
Class<? extends Executable> clazz = loader.define(CLASS_NAME, bytes);
|
||||
java.lang.reflect.Constructor<? extends Executable> constructor =
|
||||
java.lang.reflect.Constructor<? extends Executable> constructor =
|
||||
clazz.getConstructor(String.class, String.class, BitSet.class);
|
||||
|
||||
return constructor.newInstance(name, source, expressions);
|
||||
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
|
||||
throw new IllegalStateException(
|
||||
"An internal error occurred attempting to define the script [" + name + "].", exception);
|
||||
throw new IllegalStateException("An internal error occurred attempting to define the script [" + name + "].", exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.lang.invoke.MethodHandles;
|
|||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
@ -103,6 +104,8 @@ public final class Def {
|
|||
private static final MethodHandle LIST_GET;
|
||||
/** pointer to List.set(int,Object) */
|
||||
private static final MethodHandle LIST_SET;
|
||||
/** pointer to Iterable.iterator() */
|
||||
private static final MethodHandle ITERATOR;
|
||||
/** factory for arraylength MethodHandle (intrinsic) from Java 9 */
|
||||
private static final MethodHandle JAVA9_ARRAY_LENGTH_MH_FACTORY;
|
||||
|
||||
|
@ -114,6 +117,7 @@ public final class Def {
|
|||
MAP_PUT = lookup.findVirtual(Map.class , "put", MethodType.methodType(Object.class, Object.class, Object.class));
|
||||
LIST_GET = lookup.findVirtual(List.class, "get", MethodType.methodType(Object.class, int.class));
|
||||
LIST_SET = lookup.findVirtual(List.class, "set", MethodType.methodType(Object.class, int.class, Object.class));
|
||||
ITERATOR = lookup.findVirtual(Iterable.class, "iterator", MethodType.methodType(Iterator.class));
|
||||
} catch (final ReflectiveOperationException roe) {
|
||||
throw new AssertionError(roe);
|
||||
}
|
||||
|
@ -374,6 +378,118 @@ public final class Def {
|
|||
throw new IllegalArgumentException("Attempting to address a non-array type " +
|
||||
"[" + receiverClass.getCanonicalName() + "] as an array.");
|
||||
}
|
||||
|
||||
/** Helper class for isolating MethodHandles and methods to get iterators over arrays
|
||||
* (to emulate "enhanced for loop" using MethodHandles). These cause boxing, and are not as efficient
|
||||
* as they could be, but works.
|
||||
*/
|
||||
@SuppressWarnings("unused") // iterator() methods are are actually used, javac just does not know :)
|
||||
private static final class ArrayIteratorHelper {
|
||||
private static final Lookup PRIV_LOOKUP = MethodHandles.lookup();
|
||||
|
||||
private static final Map<Class<?>,MethodHandle> ARRAY_TYPE_MH_MAPPING = Collections.unmodifiableMap(
|
||||
Stream.of(boolean[].class, byte[].class, short[].class, int[].class, long[].class,
|
||||
char[].class, float[].class, double[].class, Object[].class)
|
||||
.collect(Collectors.toMap(Function.identity(), type -> {
|
||||
try {
|
||||
return PRIV_LOOKUP.findStatic(PRIV_LOOKUP.lookupClass(), "iterator", MethodType.methodType(Iterator.class, type));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
||||
private static final MethodHandle OBJECT_ARRAY_MH = ARRAY_TYPE_MH_MAPPING.get(Object[].class);
|
||||
|
||||
static Iterator<Boolean> iterator(final boolean[] array) {
|
||||
return new Iterator<Boolean>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Boolean next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
static Iterator<Byte> iterator(final byte[] array) {
|
||||
return new Iterator<Byte>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Byte next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
static Iterator<Short> iterator(final short[] array) {
|
||||
return new Iterator<Short>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Short next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
static Iterator<Integer> iterator(final int[] array) {
|
||||
return new Iterator<Integer>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Integer next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
static Iterator<Long> iterator(final long[] array) {
|
||||
return new Iterator<Long>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Long next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
static Iterator<Character> iterator(final char[] array) {
|
||||
return new Iterator<Character>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Character next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
static Iterator<Float> iterator(final float[] array) {
|
||||
return new Iterator<Float>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Float next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
static Iterator<Double> iterator(final double[] array) {
|
||||
return new Iterator<Double>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Double next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
static Iterator<Object> iterator(final Object[] array) {
|
||||
return new Iterator<Object>() {
|
||||
int index = 0;
|
||||
@Override public boolean hasNext() { return index < array.length; }
|
||||
@Override public Object next() { return array[index++]; }
|
||||
};
|
||||
}
|
||||
|
||||
static MethodHandle newIterator(Class<?> arrayType) {
|
||||
if (!arrayType.isArray()) {
|
||||
throw new IllegalArgumentException("type must be an array");
|
||||
}
|
||||
return (ARRAY_TYPE_MH_MAPPING.containsKey(arrayType)) ?
|
||||
ARRAY_TYPE_MH_MAPPING.get(arrayType) :
|
||||
OBJECT_ARRAY_MH.asType(OBJECT_ARRAY_MH.type().changeParameterType(0, arrayType));
|
||||
}
|
||||
|
||||
private ArrayIteratorHelper() {}
|
||||
}
|
||||
/**
|
||||
* Returns a method handle to do iteration (for enhanced for loop)
|
||||
* @param receiverClass Class of the array to load the value from
|
||||
* @return a MethodHandle that accepts the receiver as first argument, returns iterator
|
||||
*/
|
||||
static MethodHandle lookupIterator(Class<?> receiverClass) {
|
||||
if (Iterable.class.isAssignableFrom(receiverClass)) {
|
||||
return ITERATOR;
|
||||
} else if (receiverClass.isArray()) {
|
||||
return ArrayIteratorHelper.newIterator(receiverClass);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot iterate over [" + receiverClass.getCanonicalName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Below methods are not cached, instead invoked directly because they are performant.
|
||||
// We also check for Long values first when possible since the type is more
|
||||
|
|
|
@ -56,6 +56,8 @@ public final class DefBootstrap {
|
|||
public static final int ARRAY_LOAD = 3;
|
||||
/** static bootstrap parameter indicating a dynamic array store, e.g. foo[bar] = baz */
|
||||
public static final int ARRAY_STORE = 4;
|
||||
/** static bootstrap parameter indicating a dynamic iteration, e.g. for (x : y) */
|
||||
public static final int ITERATOR = 5;
|
||||
|
||||
/**
|
||||
* CallSite that implements the polymorphic inlining cache (PIC).
|
||||
|
@ -103,6 +105,8 @@ public final class DefBootstrap {
|
|||
return Def.lookupArrayLoad(clazz);
|
||||
case ARRAY_STORE:
|
||||
return Def.lookupArrayStore(clazz);
|
||||
case ITERATOR:
|
||||
return Def.lookupIterator(clazz);
|
||||
default: throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,13 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
|
|||
throw convertToScriptException(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds stack trace and other useful information to exceptiosn thrown
|
||||
* from a Painless script.
|
||||
* @param t The throwable to build an exception around.
|
||||
* @return The generated ScriptException.
|
||||
*/
|
||||
private ScriptException convertToScriptException(Throwable t) {
|
||||
// create a script stack: this is just the script portion
|
||||
List<String> scriptStack = new ArrayList<>();
|
||||
|
@ -169,7 +175,7 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
|
|||
}
|
||||
throw new ScriptException("runtime error", t, scriptStack, name, PainlessScriptEngineService.NAME);
|
||||
}
|
||||
|
||||
|
||||
/** returns true for methods that are part of the runtime */
|
||||
private static boolean shouldFilter(StackTraceElement element) {
|
||||
return element.getClassName().startsWith("org.elasticsearch.painless.") ||
|
||||
|
|
|
@ -138,15 +138,15 @@ public final class Variables {
|
|||
|
||||
public void decrementScope() {
|
||||
int remove = scopes.pop();
|
||||
|
||||
|
||||
while (remove > 0) {
|
||||
Variable variable = variables.pop();
|
||||
|
||||
|
||||
// TODO: is this working? the code reads backwards...
|
||||
if (variable.read) {
|
||||
throw variable.location.createError(new IllegalArgumentException("Variable [" + variable.name + "] never used."));
|
||||
}
|
||||
|
||||
|
||||
--remove;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.lang.invoke.CallSite;
|
|||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.BitSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -56,6 +57,10 @@ public final class WriterConstants {
|
|||
public final static Type MAP_TYPE = Type.getType(Map.class);
|
||||
public final static Method MAP_GET = getAsmMethod(Object.class, "get", Object.class);
|
||||
|
||||
public final static Type ITERATOR_TYPE = Type.getType(Iterator.class);
|
||||
public final static Method ITERATOR_HASNEXT = getAsmMethod(boolean.class, "hasNext");
|
||||
public final static Method ITERATOR_NEXT = getAsmMethod(Object.class, "next");
|
||||
|
||||
public final static Type UTILITY_TYPE = Type.getType(Utility.class);
|
||||
public final static Method STRING_TO_CHAR = getAsmMethod(char.class, "StringTochar", String.class);
|
||||
public final static Method CHAR_TO_STRING = getAsmMethod(String.class, "charToString", char.class);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -46,6 +46,13 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
|
|||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitFor(PainlessParser.ForContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>The default implementation returns the result of calling
|
||||
* {@link #visitChildren} on {@code ctx}.</p>
|
||||
*/
|
||||
@Override public T visitEach(PainlessParser.EachContext ctx) { return visitChildren(ctx); }
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
|
|
@ -44,6 +44,13 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
|
|||
* @return the visitor result
|
||||
*/
|
||||
T visitFor(PainlessParser.ForContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code each}
|
||||
* labeled alternative in {@link PainlessParser#statement}.
|
||||
* @param ctx the parse tree
|
||||
* @return the visitor result
|
||||
*/
|
||||
T visitEach(PainlessParser.EachContext ctx);
|
||||
/**
|
||||
* Visit a parse tree produced by the {@code decl}
|
||||
* labeled alternative in {@link PainlessParser#statement}.
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.elasticsearch.painless.antlr.PainlessParser.DeclvarContext;
|
|||
import org.elasticsearch.painless.antlr.PainlessParser.DelimiterContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.DoContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.DynamicContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.EachContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.EmptyContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.ExprContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.ExpressionContext;
|
||||
|
@ -118,6 +119,7 @@ import org.elasticsearch.painless.node.SContinue;
|
|||
import org.elasticsearch.painless.node.SDeclBlock;
|
||||
import org.elasticsearch.painless.node.SDeclaration;
|
||||
import org.elasticsearch.painless.node.SDo;
|
||||
import org.elasticsearch.painless.node.SEach;
|
||||
import org.elasticsearch.painless.node.SExpression;
|
||||
import org.elasticsearch.painless.node.SFor;
|
||||
import org.elasticsearch.painless.node.SIf;
|
||||
|
@ -261,16 +263,28 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
if (ctx.trailer() != null) {
|
||||
SBlock block = (SBlock)visit(ctx.trailer());
|
||||
|
||||
return new SFor(location(ctx),
|
||||
settings.getMaxLoopCounter(), initializer, expression, afterthought, block);
|
||||
return new SFor(location(ctx), settings.getMaxLoopCounter(), initializer, expression, afterthought, block);
|
||||
} else if (ctx.empty() != null) {
|
||||
return new SFor(location(ctx),
|
||||
settings.getMaxLoopCounter(), initializer, expression, afterthought, null);
|
||||
return new SFor(location(ctx), settings.getMaxLoopCounter(), initializer, expression, afterthought, null);
|
||||
} else {
|
||||
throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitEach(EachContext ctx) {
|
||||
if (settings.getMaxLoopCounter() > 0) {
|
||||
reserved.usesLoop();
|
||||
}
|
||||
|
||||
String type = ctx.decltype().getText();
|
||||
String name = ctx.ID().getText();
|
||||
AExpression expression = (AExpression)visitExpression(ctx.expression());
|
||||
SBlock block = (SBlock)visit(ctx.trailer());
|
||||
|
||||
return new SEach(location(ctx), settings.getMaxLoopCounter(), type, name, expression, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitDecl(DeclContext ctx) {
|
||||
return visit(ctx.declaration());
|
||||
|
|
|
@ -123,9 +123,19 @@ public abstract class AExpression extends ANode {
|
|||
|
||||
if (cast == null) {
|
||||
if (constant == null || this instanceof EConstant) {
|
||||
// For the case where a cast is not required and a constant is not set
|
||||
// or the node is already an EConstant no changes are required to the tree.
|
||||
|
||||
return this;
|
||||
} else {
|
||||
final EConstant econstant = new EConstant(location, constant);
|
||||
// For the case where a cast is not required but a
|
||||
// constant is set, an EConstant replaces this node
|
||||
// with the constant copied from this node. Note that
|
||||
// for constants output data does not need to be copied
|
||||
// from this node because the output data for the EConstant
|
||||
// will already be the same.
|
||||
|
||||
EConstant econstant = new EConstant(location, constant);
|
||||
econstant.analyze(variables);
|
||||
|
||||
if (!expected.equals(econstant.actual)) {
|
||||
|
@ -136,7 +146,12 @@ public abstract class AExpression extends ANode {
|
|||
}
|
||||
} else {
|
||||
if (constant == null) {
|
||||
final ECast ecast = new ECast(location, this, cast);
|
||||
// For the case where a cast is required and a constant is not set.
|
||||
// Modify the tree to add an ECast between this node and its parent.
|
||||
// The output data from this node is copied to the ECast for
|
||||
// further reads done by the parent.
|
||||
|
||||
ECast ecast = new ECast(location, this, cast);
|
||||
ecast.statement = statement;
|
||||
ecast.actual = expected;
|
||||
ecast.isNull = isNull;
|
||||
|
@ -144,9 +159,17 @@ public abstract class AExpression extends ANode {
|
|||
return ecast;
|
||||
} else {
|
||||
if (expected.sort.constant) {
|
||||
// For the case where a cast is required, a constant is set,
|
||||
// and the constant can be immediately cast to the expected type.
|
||||
// An EConstant replaces this node with the constant cast appropriately
|
||||
// from the constant value defined by this node. Note that
|
||||
// for constants output data does not need to be copied
|
||||
// from this node because the output data for the EConstant
|
||||
// will already be the same.
|
||||
|
||||
constant = AnalyzerCaster.constCast(location, constant, cast);
|
||||
|
||||
final EConstant econstant = new EConstant(location, constant);
|
||||
EConstant econstant = new EConstant(location, constant);
|
||||
econstant.analyze(variables);
|
||||
|
||||
if (!expected.equals(econstant.actual)) {
|
||||
|
@ -155,19 +178,36 @@ public abstract class AExpression extends ANode {
|
|||
|
||||
return econstant;
|
||||
} else if (this instanceof EConstant) {
|
||||
final ECast ecast = new ECast(location, this, cast);
|
||||
// For the case where a cast is required, a constant is set,
|
||||
// the constant cannot be immediately cast to the expected type,
|
||||
// and this node is already an EConstant. Modify the tree to add
|
||||
// an ECast between this node and its parent. Note that
|
||||
// for constants output data does not need to be copied
|
||||
// from this node because the output data for the EConstant
|
||||
// will already be the same.
|
||||
|
||||
ECast ecast = new ECast(location, this, cast);
|
||||
ecast.actual = expected;
|
||||
|
||||
return ecast;
|
||||
} else {
|
||||
final EConstant econstant = new EConstant(location, constant);
|
||||
// For the case where a cast is required, a constant is set,
|
||||
// the constant cannot be immediately cast to the expected type,
|
||||
// and this node is not an EConstant. Replace this node with
|
||||
// an Econstant node copying the constant from this node.
|
||||
// Modify the tree to add an ECast between the EConstant node
|
||||
// and its parent. Note that for constants output data does not
|
||||
// need to be copied from this node because the output data for
|
||||
// the EConstant will already be the same.
|
||||
|
||||
EConstant econstant = new EConstant(location, constant);
|
||||
econstant.analyze(variables);
|
||||
|
||||
if (!actual.equals(econstant.actual)) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
final ECast ecast = new ECast(location, econstant, cast);
|
||||
ECast ecast = new ECast(location, econstant, cast);
|
||||
ecast.actual = expected;
|
||||
|
||||
return ecast;
|
||||
|
|
|
@ -480,6 +480,7 @@ public final class EBinary extends AExpression {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (actual.sort == Sort.STRING && operation == Operation.ADD) {
|
||||
if (!cat) {
|
||||
writer.writeNewStrings();
|
||||
|
|
|
@ -247,74 +247,115 @@ public final class EChain extends AExpression {
|
|||
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) {
|
||||
// can cause class cast exception among other things at runtime
|
||||
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.
|
||||
if (cat) {
|
||||
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);
|
||||
link.write(writer); // call the write method on the link to prepare for a load/store operation
|
||||
|
||||
if (link == last && link.store) {
|
||||
if (cat) {
|
||||
writer.writeDup(link.size, 1);
|
||||
link.load(writer);
|
||||
writer.writeAppendStrings(link.after);
|
||||
// Handle the case where we are doing a compound assignment
|
||||
// representing a String concatenation.
|
||||
|
||||
expression.write(writer);
|
||||
writer.writeDup(link.size, 1); // dup the StringBuilder
|
||||
link.load(writer); // read the current link's value
|
||||
writer.writeAppendStrings(link.after); // append the link's value using the StringBuilder
|
||||
|
||||
expression.write(writer); // 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);
|
||||
writer.writeAppendStrings(expression.actual); // append the expression's value unless it's also a concatenation
|
||||
}
|
||||
|
||||
writer.writeToStrings();
|
||||
writer.writeCast(back);
|
||||
writer.writeToStrings(); // put the value of the StringBuilder on 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);
|
||||
writer.writeDup(link.after.sort.size, link.size); // if this link is also read from dup the value onto the stack
|
||||
}
|
||||
|
||||
link.store(writer);
|
||||
link.store(writer); // store the link's value from the stack in its respective variable/field/array
|
||||
} else if (operation != null) {
|
||||
writer.writeDup(link.size, 0);
|
||||
link.load(writer);
|
||||
// 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); // load the current link's value
|
||||
|
||||
if (link.load && post) {
|
||||
writer.writeDup(link.after.sort.size, link.size);
|
||||
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);
|
||||
expression.write(writer);
|
||||
writer.writeBinaryInstruction(location, promote, operation);
|
||||
writer.writeCast(there); // if necessary cast the current link's value
|
||||
// to the promotion type between the lhs and rhs types
|
||||
expression.write(writer); // write the bytecode for the rhs expression
|
||||
writer.writeBinaryInstruction(location, promote, operation); // write the operation instruction for compound assignment
|
||||
|
||||
writer.writeCast(back);
|
||||
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);
|
||||
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);
|
||||
link.store(writer); // store the link's value from the stack in its respective variable/field/array
|
||||
} else {
|
||||
expression.write(writer);
|
||||
// Handle the case for a simple write.
|
||||
|
||||
expression.write(writer); // write the bytecode for the rhs expression
|
||||
|
||||
if (link.load) {
|
||||
writer.writeDup(link.after.sort.size, link.size);
|
||||
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also read from
|
||||
}
|
||||
|
||||
link.store(writer);
|
||||
link.store(writer); // store the link's value from the stack in its respective variable/field/array
|
||||
}
|
||||
} else {
|
||||
link.load(writer);
|
||||
// Handle the case for a simple read.
|
||||
|
||||
link.load(writer); // read the link's value onto the stack
|
||||
}
|
||||
}
|
||||
|
||||
writer.writeBranch(tru, fals);
|
||||
writer.writeBranch(tru, fals); // if this is a branch node, write the bytecode to make an appropiate jump
|
||||
}
|
||||
}
|
||||
|
|
|
@ -401,6 +401,7 @@ public final class EComp extends AExpression {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
boolean branch = tru != null || fals != null;
|
||||
org.objectweb.asm.Type rtype = right.actual.type;
|
||||
Sort rsort = right.actual.sort;
|
||||
|
|
|
@ -80,6 +80,7 @@ public final class EConditional extends AExpression {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
Label localfals = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
|
|
|
@ -167,6 +167,7 @@ public final class EUnary extends AExpression {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (operation == Operation.NOT) {
|
||||
if (tru == null && fals == null) {
|
||||
Label localfals = new Label();
|
||||
|
|
|
@ -47,7 +47,7 @@ public final class LBrace extends ALink {
|
|||
throw createError(new IllegalArgumentException("Illegal array access made without target."));
|
||||
}
|
||||
|
||||
final Sort sort = before.sort;
|
||||
Sort sort = before.sort;
|
||||
|
||||
if (sort == Sort.ARRAY) {
|
||||
index.expected = Definition.INT_TYPE;
|
||||
|
@ -84,5 +84,4 @@ public final class LBrace extends ALink {
|
|||
writer.writeDebugInfo(location);
|
||||
writer.arrayStore(after.type);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ public final class LCall extends ALink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer);
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ final class LDefArray extends ALink implements IDefLink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type);
|
||||
writer.invokeDynamic("arrayLoad", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ARRAY_LOAD);
|
||||
}
|
||||
|
@ -67,6 +68,7 @@ final class LDefArray extends ALink implements IDefLink {
|
|||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
|
||||
writer.invokeDynamic("arrayStore", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ARRAY_STORE);
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
StringBuilder signature = new StringBuilder();
|
||||
|
||||
signature.append('(');
|
||||
|
|
|
@ -57,6 +57,7 @@ final class LDefField extends ALink implements IDefLink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.LOAD);
|
||||
}
|
||||
|
@ -64,6 +65,7 @@ final class LDefField extends ALink implements IDefLink {
|
|||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
|
||||
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.STORE);
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ public final class LField extends ALink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
writer.getStatic(field.owner.type, field.javaName, field.type.type);
|
||||
} else {
|
||||
|
@ -117,6 +118,7 @@ public final class LField extends ALink {
|
|||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
writer.putStatic(field.owner.type, field.javaName, field.type.type);
|
||||
} else {
|
||||
|
|
|
@ -81,6 +81,7 @@ final class LListShortcut extends ALink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
|
||||
writer.invokeInterface(getter.owner.type, getter.method);
|
||||
} else {
|
||||
|
@ -95,6 +96,7 @@ final class LListShortcut extends ALink {
|
|||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
|
||||
writer.invokeInterface(setter.owner.type, setter.method);
|
||||
} else {
|
||||
|
|
|
@ -80,6 +80,7 @@ final class LMapShortcut extends ALink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
|
||||
writer.invokeInterface(getter.owner.type, getter.method);
|
||||
} else {
|
||||
|
@ -94,6 +95,7 @@ final class LMapShortcut extends ALink {
|
|||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
|
||||
writer.invokeInterface(setter.owner.type, setter.method);
|
||||
} else {
|
||||
|
|
|
@ -81,6 +81,7 @@ public final class LNewArray extends ALink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer);
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ final class LShortcut extends ALink {
|
|||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
|
||||
writer.invokeInterface(getter.owner.type, getter.method);
|
||||
} else {
|
||||
|
@ -100,6 +101,7 @@ final class LShortcut extends ALink {
|
|||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
|
||||
writer.invokeInterface(setter.owner.type, setter.method);
|
||||
} else {
|
||||
|
|
|
@ -45,9 +45,11 @@ public final class SBlock extends AStatement {
|
|||
throw createError(new IllegalArgumentException("A block must contain at least one statement."));
|
||||
}
|
||||
|
||||
final AStatement last = statements.get(statements.size() - 1);
|
||||
AStatement last = statements.get(statements.size() - 1);
|
||||
|
||||
for (AStatement statement : statements) {
|
||||
// Note that we do not need to check after the last statement because
|
||||
// there is no statement that can be unreachable after the last.
|
||||
if (allEscape) {
|
||||
throw createError(new IllegalArgumentException("Unreachable statement."));
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ public final class SCatch extends AStatement {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label jump = new Label();
|
||||
|
||||
writer.mark(jump);
|
||||
|
|
|
@ -50,7 +50,7 @@ public final class SDeclBlock extends AStatement {
|
|||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
for (SDeclaration declaration : declarations) {
|
||||
for (AStatement declaration : declarations) {
|
||||
declaration.write(writer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ public final class SDeclaration extends AStatement {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
if (expression == null) {
|
||||
switch (variable.type.sort) {
|
||||
case VOID: throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
|
|
|
@ -88,6 +88,7 @@ public final class SDo extends AStatement {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label start = new Label();
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* 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.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Variables;
|
||||
import org.elasticsearch.painless.Variables.Variable;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
|
||||
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.
|
||||
*/
|
||||
public class SEach extends AStatement {
|
||||
|
||||
final int maxLoopCounter;
|
||||
final String type;
|
||||
final String name;
|
||||
AExpression expression;
|
||||
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;
|
||||
|
||||
public SEach(Location location, int maxLoopCounter, String type, String name, AExpression expression, SBlock block) {
|
||||
super(location);
|
||||
|
||||
this.maxLoopCounter = maxLoopCounter;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.expression = expression;
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Variables variables) {
|
||||
expression.analyze(variables);
|
||||
expression.expected = expression.actual;
|
||||
expression = expression.cast(variables);
|
||||
|
||||
final Type type;
|
||||
|
||||
try {
|
||||
type = Definition.getType(this.type);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
|
||||
}
|
||||
|
||||
variables.incrementScope();
|
||||
|
||||
variable = variables.addVariable(location, type, name, true, false);
|
||||
|
||||
if (expression.actual.sort == Sort.ARRAY) {
|
||||
analyzeArray(variables, type);
|
||||
} else if (expression.actual.sort == Sort.DEF || Iterable.class.isAssignableFrom(expression.actual.clazz)) {
|
||||
analyzeIterable(variables, type);
|
||||
} else {
|
||||
throw location.createError(new IllegalArgumentException("Illegal for each type [" + expression.actual.name + "]."));
|
||||
}
|
||||
|
||||
if (block == null) {
|
||||
throw location.createError(new IllegalArgumentException("Extraneous for each loop."));
|
||||
}
|
||||
|
||||
block.beginLoop = true;
|
||||
block.inLoop = true;
|
||||
block.analyze(variables);
|
||||
block.statementCount = Math.max(1, block.statementCount);
|
||||
|
||||
if (block.loopEscape && !block.anyContinue) {
|
||||
throw createError(new IllegalArgumentException("Extraneous for loop."));
|
||||
}
|
||||
|
||||
statementCount = 1;
|
||||
|
||||
if (maxLoopCounter > 0) {
|
||||
loopCounterSlot = variables.getVariable(location, "#loop").slot;
|
||||
}
|
||||
|
||||
variables.decrementScope();
|
||||
}
|
||||
|
||||
void analyzeArray(Variables 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, false);
|
||||
index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true, false);
|
||||
indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1);
|
||||
cast = AnalyzerCaster.getLegalCast(location, indexed, type, true, true);
|
||||
}
|
||||
|
||||
void analyzeIterable(Variables 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, false);
|
||||
|
||||
if (expression.actual.sort == Sort.DEF) {
|
||||
method = null;
|
||||
} else {
|
||||
method = expression.actual.struct.methods.get(new MethodKey("iterator", 0));
|
||||
|
||||
if (method == null) {
|
||||
throw location.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) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
if (array != null) {
|
||||
writeArray(writer);
|
||||
} else if (iterator != null) {
|
||||
writeIterable(writer);
|
||||
} else {
|
||||
throw location.createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
||||
void writeArray(MethodWriter writer) {
|
||||
expression.write(writer);
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.slot);
|
||||
writer.push(-1);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.slot);
|
||||
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
writer.mark(begin);
|
||||
|
||||
writer.visitIincInsn(index.slot, 1);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot);
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.slot);
|
||||
writer.arrayLength();
|
||||
writer.ifICmp(MethodWriter.GE, end);
|
||||
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.slot);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot);
|
||||
writer.arrayLoad(indexed.type);
|
||||
writer.writeCast(cast);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot);
|
||||
|
||||
block.write(writer);
|
||||
|
||||
writer.goTo(begin);
|
||||
writer.mark(end);
|
||||
}
|
||||
|
||||
void writeIterable(MethodWriter writer) {
|
||||
expression.write(writer);
|
||||
|
||||
if (method == null) {
|
||||
Type itr = Definition.getType("Iterator");
|
||||
String desc = org.objectweb.asm.Type.getMethodDescriptor(itr.type, Definition.DEF_TYPE.type);
|
||||
writer.invokeDynamic("iterator", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ITERATOR);
|
||||
} else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) {
|
||||
writer.invokeInterface(method.owner.type, method.method);
|
||||
} else {
|
||||
writer.invokeVirtual(method.owner.type, method.method);
|
||||
}
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.slot);
|
||||
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
writer.mark(begin);
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.slot);
|
||||
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT);
|
||||
writer.ifZCmp(MethodWriter.EQ, end);
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.slot);
|
||||
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
|
||||
writer.writeCast(cast);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot);
|
||||
|
||||
block.write(writer);
|
||||
|
||||
writer.goTo(begin);
|
||||
writer.mark(end);
|
||||
}
|
||||
}
|
|
@ -40,11 +40,11 @@ public final class SFor extends AStatement {
|
|||
ANode initializer, AExpression condition, AExpression afterthought, SBlock block) {
|
||||
super(location);
|
||||
|
||||
this.maxLoopCounter = maxLoopCounter;
|
||||
this.initializer = initializer;
|
||||
this.condition = condition;
|
||||
this.afterthought = afterthought;
|
||||
this.block = block;
|
||||
this.maxLoopCounter = maxLoopCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,8 +54,8 @@ public final class SFor extends AStatement {
|
|||
boolean continuous = false;
|
||||
|
||||
if (initializer != null) {
|
||||
if (initializer instanceof SDeclBlock) {
|
||||
((SDeclBlock)initializer).analyze(variables);
|
||||
if (initializer instanceof AStatement) {
|
||||
((AStatement)initializer).analyze(variables);
|
||||
} else if (initializer instanceof AExpression) {
|
||||
AExpression initializer = (AExpression)this.initializer;
|
||||
|
||||
|
@ -129,6 +129,7 @@ public final class SFor extends AStatement {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label start = new Label();
|
||||
Label begin = afterthought == null ? start : new Label();
|
||||
Label end = new Label();
|
||||
|
|
|
@ -70,6 +70,7 @@ public final class SIf extends AStatement {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label fals = new Label();
|
||||
|
||||
condition.fals = fals;
|
||||
|
|
|
@ -91,6 +91,7 @@ public final class SIfElse extends AStatement {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label end = new Label();
|
||||
Label fals = elseblock != null ? new Label() : end;
|
||||
|
||||
|
|
|
@ -48,15 +48,17 @@ public final class SSource extends AStatement {
|
|||
|
||||
variables.incrementScope();
|
||||
|
||||
final AStatement last = statements.get(statements.size() - 1);
|
||||
AStatement last = statements.get(statements.size() - 1);
|
||||
|
||||
for (AStatement statement : statements) {
|
||||
// TODO: why are we checking only statements 0..n-1 (this effectively checks only the previous statement)
|
||||
// Note that we do not need to check after the last statement because
|
||||
// there is no statement that can be unreachable after the last.
|
||||
if (allEscape) {
|
||||
throw createError(new IllegalArgumentException("Unreachable statement."));
|
||||
}
|
||||
|
||||
statement.lastSource = statement == last;
|
||||
|
||||
statement.analyze(variables);
|
||||
|
||||
methodEscape = statement.methodEscape;
|
||||
|
|
|
@ -35,11 +35,11 @@ public final class STry extends AStatement {
|
|||
final SBlock block;
|
||||
final List<SCatch> catches;
|
||||
|
||||
public STry(Location location, SBlock block, List<SCatch> traps) {
|
||||
public STry(Location location, SBlock block, List<SCatch> catches) {
|
||||
super(location);
|
||||
|
||||
this.block = block;
|
||||
this.catches = Collections.unmodifiableList(traps);
|
||||
this.catches = Collections.unmodifiableList(catches);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -88,6 +88,7 @@ public final class STry extends AStatement {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
Label exception = new Label();
|
||||
|
|
|
@ -94,6 +94,7 @@ public final class SWhile extends AStatement {
|
|||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label begin = new Label();
|
||||
Label end = new Label();
|
||||
|
||||
|
|
|
@ -70,6 +70,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.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.SIf} - Represents an if block.
|
||||
|
@ -90,7 +91,7 @@
|
|||
* <p>
|
||||
* Generally, statement nodes have member data that evaluate legal control-flow during the analysis phase.
|
||||
* The typical order for statement nodes is for each node to call analyze on it's children during the analysis phase
|
||||
* and write on it's children during the writing phase. No modifications are made to the structure of statement nodes.
|
||||
* 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
|
||||
* during the analysis phase looks like the following:
|
||||
|
|
|
@ -125,6 +125,44 @@ public class BasicStatementTests extends ScriptTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testIterableForEachStatement() {
|
||||
assertEquals(6, exec("List l = new ArrayList(); l.add(1); l.add(2); l.add(3); int total = 0;" +
|
||||
" for (int x : l) total += x; return total"));
|
||||
assertEquals("123", exec("List l = new ArrayList(); l.add('1'); l.add('2'); l.add('3'); String cat = '';" +
|
||||
" for (String x : l) cat += x; return cat"));
|
||||
assertEquals("1236", exec("Map m = new HashMap(); m.put('1', 1); m.put('2', 2); m.put('3', 3);" +
|
||||
" String cat = ''; int total = 0;" +
|
||||
" for (Map.Entry e : m.entrySet()) { cat += e.getKey(); total += e.getValue(); } return cat + total"));
|
||||
}
|
||||
|
||||
public void testIterableForEachStatementDef() {
|
||||
assertEquals(6, exec("def l = new ArrayList(); l.add(1); l.add(2); l.add(3); int total = 0;" +
|
||||
" for (int x : l) total += x; return total"));
|
||||
assertEquals("123", exec("def l = new ArrayList(); l.add('1'); l.add('2'); l.add('3'); String cat = '';" +
|
||||
" for (String x : l) cat += x; return cat"));
|
||||
assertEquals("1236", exec("def m = new HashMap(); m.put('1', 1); m.put('2', 2); m.put('3', 3);" +
|
||||
" String cat = ''; int total = 0;" +
|
||||
" for (Map.Entry e : m.entrySet()) { cat += e.getKey(); total += e.getValue(); } return cat + total"));
|
||||
}
|
||||
|
||||
public void testArrayForEachStatement() {
|
||||
assertEquals(6, exec("int[] a = new int[3]; a[0] = 1; a[1] = 2; a[2] = 3; int total = 0;" +
|
||||
" for (int x : a) total += x; return total"));
|
||||
assertEquals("123", exec("String[] a = new String[3]; a[0] = '1'; a[1] = '2'; a[2] = '3'; def total = '';" +
|
||||
" for (String x : a) total += x; return total"));
|
||||
assertEquals(6, exec("int[][] i = new int[3][1]; i[0][0] = 1; i[1][0] = 2; i[2][0] = 3; int total = 0;" +
|
||||
" for (int[] j : i) total += j[0]; return total"));
|
||||
}
|
||||
|
||||
public void testArrayForEachStatementDef() {
|
||||
assertEquals(6, exec("def a = new int[3]; a[0] = 1; a[1] = 2; a[2] = 3; int total = 0;" +
|
||||
" for (int x : a) total += x; return total"));
|
||||
assertEquals("123", exec("def a = new String[3]; a[0] = '1'; a[1] = '2'; a[2] = '3'; def total = '';" +
|
||||
" for (String x : a) total += x; return total"));
|
||||
assertEquals(6, exec("def i = new int[3][1]; i[0][0] = 1; i[1][0] = 2; i[2][0] = 3; int total = 0;" +
|
||||
" for (int[] j : i) total += j[0]; return total"));
|
||||
}
|
||||
|
||||
public void testDeclarationStatement() {
|
||||
assertEquals((byte)2, exec("byte a = 2; return a;"));
|
||||
assertEquals((short)2, exec("short a = 2; return a;"));
|
||||
|
|
Loading…
Reference in New Issue