Merge pull request #18951 from rmuir/refactor_variables
Refactor painless variables handling
This commit is contained in:
commit
b0efca6473
|
@ -109,7 +109,7 @@ final class Compiler {
|
|||
java.lang.reflect.Constructor<? extends Executable> constructor =
|
||||
clazz.getConstructor(String.class, String.class, BitSet.class);
|
||||
|
||||
return constructor.newInstance(name, source, root.getExpressions());
|
||||
return constructor.newInstance(name, source, root.getStatements());
|
||||
} 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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.painless;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A constant initializer to be added to the class file.
|
||||
*/
|
||||
public class Constant {
|
||||
public final Location location;
|
||||
public final String name;
|
||||
public final org.objectweb.asm.Type type;
|
||||
public final Consumer<MethodWriter> initializer;
|
||||
|
||||
/**
|
||||
* Create a new constant.
|
||||
*
|
||||
* @param location the location in the script that is creating it
|
||||
* @param type the type of the constant
|
||||
* @param name the name of the constant
|
||||
* @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the
|
||||
* value of the constant on the stack. Generating the load instruction is managed by the caller.
|
||||
*/
|
||||
public Constant(Location location, org.objectweb.asm.Type type, String name, Consumer<MethodWriter> initializer) {
|
||||
this.location = location;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.initializer = initializer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.painless.node.SFunction;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Program-wide globals (initializers, synthetic methods, etc)
|
||||
*/
|
||||
public class Globals {
|
||||
private final Map<String,SFunction> syntheticMethods = new HashMap<>();
|
||||
private final Map<String,Constant> constantInitializers = new HashMap<>();
|
||||
private final BitSet statements;
|
||||
|
||||
/** Create a new Globals from the set of statement boundaries */
|
||||
public Globals(BitSet statements) {
|
||||
this.statements = statements;
|
||||
}
|
||||
|
||||
/** Adds a new synthetic method to be written. It must be analyzed! */
|
||||
public void addSyntheticMethod(SFunction function) {
|
||||
if (!function.synthetic) {
|
||||
throw new IllegalStateException("method: " + function.name + " is not synthetic");
|
||||
}
|
||||
if (syntheticMethods.put(function.name, function) != null) {
|
||||
throw new IllegalStateException("synthetic method: " + function.name + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds a new constant initializer to be written */
|
||||
public void addConstantInitializer(Constant constant) {
|
||||
if (constantInitializers.put(constant.name, constant) != null) {
|
||||
throw new IllegalStateException("constant initializer: " + constant.name + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the current synthetic methods */
|
||||
public Map<String,SFunction> getSyntheticMethods() {
|
||||
return syntheticMethods;
|
||||
}
|
||||
|
||||
/** Returns the current initializers */
|
||||
public Map<String,Constant> getConstantInitializers() {
|
||||
return constantInitializers;
|
||||
}
|
||||
|
||||
/** Returns the set of statement boundaries */
|
||||
public BitSet getStatements() {
|
||||
return statements;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Extension of locals for lambdas */
|
||||
// Note: this isn't functional yet, it throws UOE
|
||||
// TODO: implement slot renumbering for captures.
|
||||
class LambdaLocals extends Locals {
|
||||
private List<Variable> captures;
|
||||
|
||||
LambdaLocals(Locals parent, List<Parameter> parameters, List<Variable> captures) {
|
||||
super(parent);
|
||||
for (Parameter parameter : parameters) {
|
||||
defineVariable(parameter.location, parameter.type, parameter.name, false);
|
||||
}
|
||||
this.captures = Objects.requireNonNull(captures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Variable getVariable(Location location, String name) {
|
||||
Variable variable = lookupVariable(location, name);
|
||||
if (variable != null) {
|
||||
return variable;
|
||||
}
|
||||
if (getParent() != null) {
|
||||
variable = getParent().getVariable(location, name);
|
||||
if (variable != null) {
|
||||
assert captures != null; // unused right now
|
||||
// make it read-only, and record that it was used.
|
||||
throw new UnsupportedOperationException("lambda capture is not supported");
|
||||
}
|
||||
}
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined."));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getReturnType() {
|
||||
return Definition.DEF_TYPE;
|
||||
}
|
||||
}
|
|
@ -23,141 +23,273 @@ import org.elasticsearch.painless.Definition.Method;
|
|||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Tracks user defined methods and variables across compilation phases.
|
||||
*/
|
||||
public final class Locals {
|
||||
public class Locals {
|
||||
|
||||
/** Reserved word: params map parameter */
|
||||
public static final String PARAMS = "params";
|
||||
/** Reserved word: Lucene scorer parameter */
|
||||
public static final String SCORER = "#scorer";
|
||||
/** Reserved word: _value variable for aggregations */
|
||||
public static final String VALUE = "_value";
|
||||
/** Reserved word: _score variable for search scripts */
|
||||
public static final String SCORE = "_score";
|
||||
/** Reserved word: ctx map for executable scripts */
|
||||
public static final String CTX = "ctx";
|
||||
/** Reserved word: loop counter */
|
||||
public static final String LOOP = "#loop";
|
||||
/** Reserved word: unused */
|
||||
public static final String THIS = "#this";
|
||||
/** Reserved word: unused */
|
||||
public static final String DOC = "doc";
|
||||
|
||||
/** Map of always reserved keywords */
|
||||
public static final Set<String> KEYWORDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
THIS,PARAMS,SCORER,DOC,VALUE,SCORE,CTX,LOOP
|
||||
)));
|
||||
|
||||
/** Creates a new local variable scope (e.g. loop) inside the current scope */
|
||||
public static Locals newLocalScope(Locals currentScope) {
|
||||
return new Locals(currentScope);
|
||||
}
|
||||
|
||||
/** Creates a new lambda scope inside the current scope */
|
||||
public static Locals newLambdaScope(Locals currentScope, List<Parameter> parameters, List<Variable> captures) {
|
||||
return new LambdaLocals(currentScope, parameters, captures);
|
||||
}
|
||||
|
||||
/** Creates a new function scope inside the current scope */
|
||||
public static Locals newFunctionScope(Locals programScope, Type returnType, List<Parameter> parameters, int maxLoopCounter) {
|
||||
Locals locals = new Locals(programScope, returnType);
|
||||
for (Parameter parameter : parameters) {
|
||||
locals.defineVariable(parameter.location, parameter.type, parameter.name, false);
|
||||
}
|
||||
// Loop counter to catch infinite loops. Internal use only.
|
||||
if (maxLoopCounter > 0) {
|
||||
locals.defineVariable(null, Definition.INT_TYPE, LOOP, true);
|
||||
}
|
||||
return locals;
|
||||
}
|
||||
|
||||
/** Creates a new main method scope */
|
||||
public static Locals newMainMethodScope(Locals programScope, boolean usesScore, boolean usesCtx, int maxLoopCounter) {
|
||||
Locals locals = new Locals(programScope, Definition.OBJECT_TYPE);
|
||||
// This reference. Internal use only.
|
||||
locals.defineVariable(null, Definition.getType("Object"), THIS, true);
|
||||
|
||||
// Input map of variables passed to the script.
|
||||
locals.defineVariable(null, Definition.getType("Map"), PARAMS, true);
|
||||
|
||||
// Scorer parameter passed to the script. Internal use only.
|
||||
locals.defineVariable(null, Definition.DEF_TYPE, SCORER, true);
|
||||
|
||||
// Doc parameter passed to the script. TODO: Currently working as a Map, we can do better?
|
||||
locals.defineVariable(null, Definition.getType("Map"), DOC, true);
|
||||
|
||||
// Aggregation _value parameter passed to the script.
|
||||
locals.defineVariable(null, Definition.DEF_TYPE, VALUE, true);
|
||||
|
||||
// Shortcut variables.
|
||||
|
||||
// Document's score as a read-only double.
|
||||
if (usesScore) {
|
||||
locals.defineVariable(null, Definition.DOUBLE_TYPE, SCORE, true);
|
||||
}
|
||||
|
||||
// The ctx map set by executable scripts as a read-only map.
|
||||
if (usesCtx) {
|
||||
locals.defineVariable(null, Definition.getType("Map"), CTX, true);
|
||||
}
|
||||
|
||||
// Loop counter to catch infinite loops. Internal use only.
|
||||
if (maxLoopCounter > 0) {
|
||||
locals.defineVariable(null, Definition.INT_TYPE, LOOP, true);
|
||||
}
|
||||
return locals;
|
||||
}
|
||||
|
||||
/** Creates a new program scope: the list of methods. It is the parent for all methods */
|
||||
public static Locals newProgramScope(Collection<Method> methods) {
|
||||
Locals locals = new Locals(null, null);
|
||||
for (Method method : methods) {
|
||||
locals.addMethod(method);
|
||||
}
|
||||
return locals;
|
||||
}
|
||||
|
||||
/** Checks if a variable exists or not, in this scope or any parents. */
|
||||
public final boolean hasVariable(String name) {
|
||||
Variable variable = lookupVariable(null, name);
|
||||
if (variable != null) {
|
||||
return true;
|
||||
}
|
||||
if (parent != null) {
|
||||
return parent.hasVariable(name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Accesses a variable. This will throw IAE if the variable does not exist */
|
||||
public Variable getVariable(Location location, String name) {
|
||||
Variable variable = lookupVariable(location, name);
|
||||
if (variable != null) {
|
||||
return variable;
|
||||
}
|
||||
if (parent != null) {
|
||||
return parent.getVariable(location, name);
|
||||
}
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined."));
|
||||
}
|
||||
|
||||
/** Looks up a method. Returns null if the method does not exist. */
|
||||
public final Method getMethod(MethodKey key) {
|
||||
Method method = lookupMethod(key);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
if (parent != null) {
|
||||
return parent.getMethod(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Creates a new variable. Throws IAE if the variable has already been defined (even in a parent) or reserved. */
|
||||
public final Variable addVariable(Location location, Type type, String name, boolean readonly) {
|
||||
if (hasVariable(name)) {
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined."));
|
||||
}
|
||||
if (KEYWORDS.contains(name)) {
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is reserved."));
|
||||
}
|
||||
return defineVariable(location, type, name, readonly);
|
||||
}
|
||||
|
||||
/** Return type of this scope (e.g. int, if inside a function that returns int) */
|
||||
public Type getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
/** Returns the top-level program scope. */
|
||||
public Locals getProgramScope() {
|
||||
Locals locals = this;
|
||||
while (locals.getParent() != null) {
|
||||
locals = locals.getParent();
|
||||
}
|
||||
return locals;
|
||||
}
|
||||
|
||||
///// private impl
|
||||
|
||||
// parent scope
|
||||
private final Locals parent;
|
||||
// return type of this scope
|
||||
private final Type returnType;
|
||||
// next slot number to assign
|
||||
int nextSlotNumber;
|
||||
// variable name -> variable
|
||||
Map<String,Variable> variables;
|
||||
// method name+arity -> methods
|
||||
Map<MethodKey,Method> methods;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Create a new Locals
|
||||
*/
|
||||
public interface Reserved {
|
||||
void markReserved(String name);
|
||||
boolean isReserved(String name);
|
||||
|
||||
void setMaxLoopCounter(int max);
|
||||
int getMaxLoopCounter();
|
||||
Locals(Locals parent) {
|
||||
this(parent, parent.getReturnType());
|
||||
}
|
||||
|
||||
public static final class ExecuteReserved implements Reserved {
|
||||
public static final String THIS = "#this";
|
||||
public static final String PARAMS = "params";
|
||||
public static final String SCORER = "#scorer";
|
||||
public static final String DOC = "doc";
|
||||
public static final String VALUE = "_value";
|
||||
public static final String SCORE = "_score";
|
||||
public static final String CTX = "ctx";
|
||||
public static final String LOOP = "#loop";
|
||||
|
||||
private boolean score = false;
|
||||
private boolean ctx = false;
|
||||
private int maxLoopCounter = 0;
|
||||
|
||||
@Override
|
||||
public void markReserved(String name) {
|
||||
if (SCORE.equals(name)) {
|
||||
score = true;
|
||||
} else if (CTX.equals(name)) {
|
||||
ctx = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReserved(String name) {
|
||||
return name.equals(THIS) || name.equals(PARAMS) || name.equals(SCORER) || name.equals(DOC) ||
|
||||
name.equals(VALUE) || name.equals(SCORE) || name.equals(CTX) || name.equals(LOOP);
|
||||
}
|
||||
|
||||
public boolean usesScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public boolean usesCtx() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLoopCounter(int max) {
|
||||
maxLoopCounter = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLoopCounter() {
|
||||
return maxLoopCounter;
|
||||
|
||||
/**
|
||||
* Create a new Locals with specified return type
|
||||
*/
|
||||
Locals(Locals parent, Type returnType) {
|
||||
this.parent = parent;
|
||||
this.returnType = returnType;
|
||||
if (parent == null) {
|
||||
this.nextSlotNumber = 0;
|
||||
} else {
|
||||
this.nextSlotNumber = parent.getNextSlot();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class FunctionReserved implements Reserved {
|
||||
public static final String THIS = "#this";
|
||||
public static final String LOOP = "#loop";
|
||||
/** Returns the parent scope */
|
||||
Locals getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
private int maxLoopCounter = 0;
|
||||
|
||||
public void markReserved(String name) {
|
||||
// Do nothing.
|
||||
/** Looks up a variable at this scope only. Returns null if the variable does not exist. */
|
||||
Variable lookupVariable(Location location, String name) {
|
||||
if (variables == null) {
|
||||
return null;
|
||||
}
|
||||
return variables.get(name);
|
||||
}
|
||||
|
||||
public boolean isReserved(String name) {
|
||||
return name.equals(THIS) || name.equals(LOOP);
|
||||
/** Looks up a method at this scope only. Returns null if the method does not exist. */
|
||||
Method lookupMethod(MethodKey key) {
|
||||
if (methods == null) {
|
||||
return null;
|
||||
}
|
||||
return methods.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLoopCounter(int max) {
|
||||
maxLoopCounter = max;
|
||||
|
||||
/** Defines a variable at this scope internally. */
|
||||
Variable defineVariable(Location location, Type type, String name, boolean readonly) {
|
||||
if (variables == null) {
|
||||
variables = new HashMap<>();
|
||||
}
|
||||
Variable variable = new Variable(location, name, type, readonly);
|
||||
variable.slot = getNextSlot();
|
||||
variables.put(name, variable); // TODO: check result
|
||||
nextSlotNumber += type.type.getSize();
|
||||
return variable;
|
||||
}
|
||||
|
||||
// TODO: make private, thats bogus
|
||||
public void addMethod(Method method) {
|
||||
if (methods == null) {
|
||||
methods = new HashMap<>();
|
||||
}
|
||||
methods.put(new MethodKey(method.name, method.arguments.size()), method);
|
||||
// TODO: check result
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLoopCounter() {
|
||||
return maxLoopCounter;
|
||||
}
|
||||
|
||||
int getNextSlot() {
|
||||
return nextSlotNumber;
|
||||
}
|
||||
|
||||
public static final class Variable {
|
||||
public final Location location;
|
||||
public final String name;
|
||||
public final Type type;
|
||||
public final int slot;
|
||||
int slot = -1;
|
||||
public final boolean readonly;
|
||||
|
||||
public boolean read = false;
|
||||
|
||||
private Variable(Location location, String name, Type type, int slot, boolean readonly) {
|
||||
|
||||
public Variable(Location location, String name, Type type, boolean readonly) {
|
||||
this.location = location;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.slot = slot;
|
||||
this.readonly = readonly;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Constant {
|
||||
public final Location location;
|
||||
public final String name;
|
||||
public final org.objectweb.asm.Type type;
|
||||
public final Consumer<MethodWriter> initializer;
|
||||
|
||||
private Constant(Location location, String name, org.objectweb.asm.Type type, Consumer<MethodWriter> initializer) {
|
||||
this.location = location;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.initializer = initializer;
|
||||
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Parameter {
|
||||
|
||||
public static class Parameter {
|
||||
public final Location location;
|
||||
public final String name;
|
||||
public final Type type;
|
||||
|
@ -168,197 +300,4 @@ public final class Locals {
|
|||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
private final Reserved reserved;
|
||||
private final Map<MethodKey, Method> methods;
|
||||
private final Map<String, Constant> constants;
|
||||
private final Type rtnType;
|
||||
|
||||
// TODO: this datastructure runs in linear time for nearly all operations. use linkedhashset instead?
|
||||
private final Deque<Integer> scopes = new ArrayDeque<>();
|
||||
private final Deque<Variable> variables = new ArrayDeque<>();
|
||||
|
||||
public Locals(ExecuteReserved reserved, Map<MethodKey, Method> methods) {
|
||||
this.reserved = reserved;
|
||||
this.methods = Collections.unmodifiableMap(methods);
|
||||
this.constants = new HashMap<>();
|
||||
this.rtnType = Definition.OBJECT_TYPE;
|
||||
|
||||
incrementScope();
|
||||
|
||||
// Method variables.
|
||||
|
||||
// This reference. Internal use only.
|
||||
addVariable(null, Definition.getType("Object"), ExecuteReserved.THIS, true, true);
|
||||
|
||||
// Input map of variables passed to the script.
|
||||
addVariable(null, Definition.getType("Map"), ExecuteReserved.PARAMS, true, true);
|
||||
|
||||
// Scorer parameter passed to the script. Internal use only.
|
||||
addVariable(null, Definition.DEF_TYPE, ExecuteReserved.SCORER, true, true);
|
||||
|
||||
// Doc parameter passed to the script. TODO: Currently working as a Map, we can do better?
|
||||
addVariable(null, Definition.getType("Map"), ExecuteReserved.DOC, true, true);
|
||||
|
||||
// Aggregation _value parameter passed to the script.
|
||||
addVariable(null, Definition.DEF_TYPE, ExecuteReserved.VALUE, true, true);
|
||||
|
||||
// Shortcut variables.
|
||||
|
||||
// Document's score as a read-only double.
|
||||
if (reserved.usesScore()) {
|
||||
addVariable(null, Definition.DOUBLE_TYPE, ExecuteReserved.SCORE, true, true);
|
||||
}
|
||||
|
||||
// The ctx map set by executable scripts as a read-only map.
|
||||
if (reserved.usesCtx()) {
|
||||
addVariable(null, Definition.getType("Map"), ExecuteReserved.CTX, true, true);
|
||||
}
|
||||
|
||||
// Loop counter to catch infinite loops. Internal use only.
|
||||
if (reserved.getMaxLoopCounter() > 0) {
|
||||
addVariable(null, Definition.INT_TYPE, ExecuteReserved.LOOP, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public Locals(FunctionReserved reserved, Locals locals, Type rtnType, List<Parameter> parameters) {
|
||||
this.reserved = reserved;
|
||||
this.methods = locals.methods;
|
||||
this.constants = locals.constants;
|
||||
this.rtnType = rtnType;
|
||||
|
||||
incrementScope();
|
||||
|
||||
for (Parameter parameter : parameters) {
|
||||
addVariable(parameter.location, parameter.type, parameter.name, false, false);
|
||||
}
|
||||
|
||||
// Loop counter to catch infinite loops. Internal use only.
|
||||
if (reserved.getMaxLoopCounter() > 0) {
|
||||
addVariable(null, Definition.INT_TYPE, ExecuteReserved.LOOP, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxLoopCounter() {
|
||||
return reserved.getMaxLoopCounter();
|
||||
}
|
||||
|
||||
public Method getMethod(MethodKey key) {
|
||||
return methods.get(key);
|
||||
}
|
||||
|
||||
public Type getReturnType() {
|
||||
return rtnType;
|
||||
}
|
||||
|
||||
public void incrementScope() {
|
||||
scopes.push(0);
|
||||
}
|
||||
|
||||
public void decrementScope() {
|
||||
int remove = scopes.pop();
|
||||
|
||||
while (remove > 0) {
|
||||
Variable variable = variables.pop();
|
||||
|
||||
// This checks whether or not a variable is used when exiting a local scope.
|
||||
if (variable.read) {
|
||||
throw variable.location.createError(new IllegalArgumentException("Variable [" + variable.name + "] is never used."));
|
||||
}
|
||||
|
||||
--remove;
|
||||
}
|
||||
}
|
||||
|
||||
public Variable getVariable(Location location, String name) {
|
||||
Iterator<Variable> itr = variables.iterator();
|
||||
|
||||
while (itr.hasNext()) {
|
||||
Variable variable = itr.next();
|
||||
|
||||
if (variable.name.equals(name)) {
|
||||
return variable;
|
||||
}
|
||||
}
|
||||
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined."));
|
||||
}
|
||||
|
||||
public boolean isVariable(String name) {
|
||||
Iterator<Variable> itr = variables.iterator();
|
||||
|
||||
while (itr.hasNext()) {
|
||||
Variable variable = itr.next();
|
||||
|
||||
if (variable.name.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Variable addVariable(Location location, Type type, String name, boolean readonly, boolean reserved) {
|
||||
if (!reserved && this.reserved.isReserved(name)) {
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is reserved."));
|
||||
}
|
||||
|
||||
if (isVariable(name)) {
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined."));
|
||||
}
|
||||
|
||||
Variable previous = variables.peekFirst();
|
||||
int slot = 0;
|
||||
|
||||
if (previous != null) {
|
||||
slot = previous.slot + previous.type.type.getSize();
|
||||
}
|
||||
|
||||
Variable variable = new Variable(location, name, type, slot, readonly);
|
||||
variables.push(variable);
|
||||
|
||||
int update = scopes.pop() + 1;
|
||||
scopes.push(update);
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new constant.
|
||||
*
|
||||
* @param location the location in the script that is creating it
|
||||
* @param type the type of the constant
|
||||
* @param name the name of the constant
|
||||
* @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the
|
||||
* value of the constant on the stack. Generating the load instruction is managed by the caller.
|
||||
* @return the constant
|
||||
*/
|
||||
public Constant addConstant(Location location, org.objectweb.asm.Type type, String name, Consumer<MethodWriter> initializer) {
|
||||
if (constants.containsKey(name)) {
|
||||
throw location.createError(new IllegalArgumentException("Constant [" + name + "] is already defined."));
|
||||
}
|
||||
|
||||
Constant constant = new Constant(location, name, type, initializer);
|
||||
constants.put(name, constant);
|
||||
return constant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new constant.
|
||||
*
|
||||
* @param location the location in the script that is creating it
|
||||
* @param type the type of the constant
|
||||
* @param name the name of the constant
|
||||
* @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the
|
||||
* value of the constant on the stack. Generating the load instruction is managed by the caller.
|
||||
* @return the constant
|
||||
*/
|
||||
public Constant addConstant(Location location, Type type, String name, Consumer<MethodWriter> initializer) {
|
||||
return addConstant(location, type.type, name, initializer);
|
||||
}
|
||||
|
||||
public Collection<Constant> getConstants() {
|
||||
return constants.values();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,11 +29,12 @@ import org.antlr.v4.runtime.Recognizer;
|
|||
import org.antlr.v4.runtime.atn.PredictionMode;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.elasticsearch.painless.CompilerSettings;
|
||||
import org.elasticsearch.painless.Locals.ExecuteReserved;
|
||||
import org.elasticsearch.painless.Locals.FunctionReserved;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.node.SFunction.Reserved;
|
||||
import org.elasticsearch.painless.node.SSource.MainMethodReserved;
|
||||
import org.elasticsearch.painless.node.SFunction.FunctionReserved;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.Locals.Reserved;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.ArgumentContext;
|
||||
import org.elasticsearch.painless.antlr.PainlessParser.ArgumentsContext;
|
||||
|
@ -114,6 +115,7 @@ import org.elasticsearch.painless.node.EConditional;
|
|||
import org.elasticsearch.painless.node.EDecimal;
|
||||
import org.elasticsearch.painless.node.EExplicit;
|
||||
import org.elasticsearch.painless.node.EFunctionRef;
|
||||
import org.elasticsearch.painless.node.ELambda;
|
||||
import org.elasticsearch.painless.node.ENull;
|
||||
import org.elasticsearch.painless.node.ENumeric;
|
||||
import org.elasticsearch.painless.node.EUnary;
|
||||
|
@ -151,6 +153,7 @@ import org.objectweb.asm.util.Printer;
|
|||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -170,7 +173,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
private final String sourceText;
|
||||
|
||||
private final Deque<Reserved> reserved = new ArrayDeque<>();
|
||||
private final List<SFunction> synthetic = new ArrayList<>();
|
||||
private final Globals globals;
|
||||
private int syntheticCounter = 0;
|
||||
|
||||
private Walker(String sourceName, String sourceText, CompilerSettings settings, Printer debugStream) {
|
||||
|
@ -178,6 +181,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
this.settings = settings;
|
||||
this.sourceName = Location.computeSourceName(sourceName, sourceText);
|
||||
this.sourceText = sourceText;
|
||||
this.globals = new Globals(new BitSet(sourceText.length()));
|
||||
this.source = (SSource)visit(buildAntlrTree(sourceText));
|
||||
}
|
||||
|
||||
|
@ -223,7 +227,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
|
||||
@Override
|
||||
public Object visitSource(SourceContext ctx) {
|
||||
reserved.push(new ExecuteReserved());
|
||||
reserved.push(new MainMethodReserved());
|
||||
|
||||
List<SFunction> functions = new ArrayList<>();
|
||||
|
||||
|
@ -236,11 +240,9 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
for (StatementContext statement : ctx.statement()) {
|
||||
statements.add((AStatement)visit(statement));
|
||||
}
|
||||
|
||||
functions.addAll(synthetic);
|
||||
|
||||
return new SSource(sourceName, sourceText, debugStream,
|
||||
(ExecuteReserved)reserved.pop(), location(ctx), functions, statements);
|
||||
return new SSource(sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(),
|
||||
location(ctx), functions, globals, statements);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -265,7 +267,8 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
statements.add((AStatement)visit(statement));
|
||||
}
|
||||
|
||||
return new SFunction((FunctionReserved)reserved.pop(), location(ctx), rtnType, name, paramTypes, paramNames, statements, false);
|
||||
return new SFunction((FunctionReserved)reserved.pop(), location(ctx), rtnType, name,
|
||||
paramTypes, paramNames, statements, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -977,10 +980,8 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
}
|
||||
|
||||
String name = nextLambda();
|
||||
synthetic.add(new SFunction((FunctionReserved)reserved.pop(), location(ctx), "def", name,
|
||||
paramTypes, paramNames, statements, true));
|
||||
return new EFunctionRef(location(ctx), "this", name);
|
||||
// TODO: use a real node for captures and shit
|
||||
return new ELambda(name, (FunctionReserved)reserved.pop(), location(ctx),
|
||||
paramTypes, paramNames, statements);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1021,8 +1022,8 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
|
|||
new EChain(location,
|
||||
new LVariable(location, "size"))))));
|
||||
String name = nextLambda();
|
||||
synthetic.add(new SFunction(new FunctionReserved(), location, arrayType, name,
|
||||
Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true));
|
||||
globals.addSyntheticMethod(new SFunction(new FunctionReserved(), location, arrayType, name,
|
||||
Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true));
|
||||
return new EFunctionRef(location(ctx), "this", name);
|
||||
}
|
||||
return new EFunctionRef(location(ctx), ctx.decltype().getText(), ctx.NEW().getText());
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
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;
|
||||
|
@ -111,7 +112,7 @@ public abstract class AExpression extends ANode {
|
|||
/**
|
||||
* Writes ASM based on the data collected during the analysis phase.
|
||||
*/
|
||||
abstract void write(MethodWriter writer);
|
||||
abstract void write(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Inserts {@link ECast} nodes into the tree for implicit casts. Also replaces
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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;
|
||||
|
@ -91,17 +92,17 @@ public abstract class ALink extends ANode {
|
|||
/**
|
||||
* Write values before a load/store occurs such as an array index.
|
||||
*/
|
||||
abstract void write(MethodWriter writer);
|
||||
abstract void write(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Write a load for the specific link type.
|
||||
*/
|
||||
abstract void load(MethodWriter writer);
|
||||
abstract void load(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Write a store for the specific link type.
|
||||
*/
|
||||
abstract void store(MethodWriter writer);
|
||||
abstract void store(MethodWriter writer, Globals globals);
|
||||
|
||||
/**
|
||||
* Used to copy link data from one to another during analysis in the case of replacement.
|
||||
|
|
|
@ -19,7 +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;
|
||||
|
@ -88,7 +90,7 @@ public abstract class AStatement extends ANode {
|
|||
* Set to the loop counter variable slot as a shortcut if loop statements
|
||||
* are being counted.
|
||||
*/
|
||||
int loopCounterSlot = -1;
|
||||
Variable loopCounter = null;
|
||||
|
||||
/**
|
||||
* Set to the approximate number of statements in a loop block to prevent
|
||||
|
@ -120,5 +122,5 @@ public abstract class AStatement extends ANode {
|
|||
/**
|
||||
* Writes ASM based on the data collected during the analysis phase.
|
||||
*/
|
||||
abstract void write(MethodWriter writer);
|
||||
abstract void write(MethodWriter writer, Globals globals);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Location;
|
||||
|
@ -604,7 +605,7 @@ public final class EBinary extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (promote.sort == Sort.STRING && operation == Operation.ADD) {
|
||||
|
@ -612,13 +613,13 @@ public final class EBinary extends AExpression {
|
|||
writer.writeNewStrings();
|
||||
}
|
||||
|
||||
left.write(writer);
|
||||
left.write(writer, globals);
|
||||
|
||||
if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) {
|
||||
writer.writeAppendStrings(left.actual);
|
||||
}
|
||||
|
||||
right.write(writer);
|
||||
right.write(writer, globals);
|
||||
|
||||
if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) {
|
||||
writer.writeAppendStrings(right.actual);
|
||||
|
@ -628,14 +629,14 @@ public final class EBinary extends AExpression {
|
|||
writer.writeToStrings();
|
||||
}
|
||||
} else if (operation == Operation.FIND) {
|
||||
writeBuildMatcher(writer);
|
||||
writeBuildMatcher(writer, globals);
|
||||
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND);
|
||||
} else if (operation == Operation.MATCH) {
|
||||
writeBuildMatcher(writer);
|
||||
writeBuildMatcher(writer, globals);
|
||||
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES);
|
||||
} else {
|
||||
left.write(writer);
|
||||
right.write(writer);
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
||||
if (promote.sort == Sort.DEF || (shiftDistance != null && shiftDistance.sort == Sort.DEF)) {
|
||||
writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, false);
|
||||
|
@ -647,9 +648,9 @@ public final class EBinary extends AExpression {
|
|||
writer.writeBranch(tru, fals);
|
||||
}
|
||||
|
||||
private void writeBuildMatcher(MethodWriter writer) {
|
||||
right.write(writer);
|
||||
left.write(writer);
|
||||
private void writeBuildMatcher(MethodWriter writer, Globals globals) {
|
||||
right.write(writer, globals);
|
||||
left.write(writer, globals);
|
||||
writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Operation;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
|
@ -67,7 +68,7 @@ public final class EBool extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
if (tru != null || fals != null) {
|
||||
if (operation == Operation.AND) {
|
||||
Label localfals = fals == null ? new Label() : fals;
|
||||
|
@ -76,8 +77,8 @@ public final class EBool extends AExpression {
|
|||
right.tru = tru;
|
||||
right.fals = fals;
|
||||
|
||||
left.write(writer);
|
||||
right.write(writer);
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
||||
if (fals == null) {
|
||||
writer.mark(localfals);
|
||||
|
@ -89,8 +90,8 @@ public final class EBool extends AExpression {
|
|||
right.tru = tru;
|
||||
right.fals = fals;
|
||||
|
||||
left.write(writer);
|
||||
right.write(writer);
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
||||
if (tru == null) {
|
||||
writer.mark(localtru);
|
||||
|
@ -106,8 +107,8 @@ public final class EBool extends AExpression {
|
|||
left.fals = localfals;
|
||||
right.fals = localfals;
|
||||
|
||||
left.write(writer);
|
||||
right.write(writer);
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
||||
writer.push(true);
|
||||
writer.goTo(end);
|
||||
|
@ -122,8 +123,8 @@ public final class EBool extends AExpression {
|
|||
left.tru = localtru;
|
||||
right.fals = localfals;
|
||||
|
||||
left.write(writer);
|
||||
right.write(writer);
|
||||
left.write(writer, globals);
|
||||
right.write(writer, globals);
|
||||
|
||||
writer.mark(localtru);
|
||||
writer.push(true);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.MethodWriter;
|
||||
|
@ -41,7 +42,7 @@ public final class EBoolean extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter adapter) {
|
||||
void write(MethodWriter adapter, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.painless.node;
|
|||
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;
|
||||
|
@ -37,7 +38,7 @@ import java.lang.invoke.LambdaMetafactory;
|
|||
/**
|
||||
* Represents a capturing function reference.
|
||||
*/
|
||||
public class ECapturingFunctionRef extends AExpression {
|
||||
public class ECapturingFunctionRef extends AExpression implements ILambda {
|
||||
public final String type;
|
||||
public final String call;
|
||||
|
||||
|
@ -79,21 +80,21 @@ public class ECapturingFunctionRef extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
if (defPointer != null) {
|
||||
// dynamic interface: push captured parameter on stack
|
||||
// TODO: don't do this: its just to cutover :)
|
||||
writer.push((String)null);
|
||||
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot);
|
||||
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
|
||||
} else if (ref == null) {
|
||||
// typed interface, dynamic implementation
|
||||
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot);
|
||||
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
|
||||
String descriptor = Type.getMethodType(expected.type, captured.type.type).getDescriptor();
|
||||
writer.invokeDynamic(call, descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.REFERENCE, expected.name);
|
||||
} else {
|
||||
// typed interface, typed implementation
|
||||
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot);
|
||||
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
|
||||
// convert MethodTypes to asm Type for the constant pool.
|
||||
String invokedType = ref.invokedType.toMethodDescriptorString();
|
||||
Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
|
||||
|
@ -119,4 +120,14 @@ public class ECapturingFunctionRef extends AExpression {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPointer() {
|
||||
return defPointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type[] getCaptures() {
|
||||
return new Type[] { captured.type.type };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Definition.Cast;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
|
@ -50,8 +51,8 @@ final class ECast extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
child.write(writer);
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
child.write(writer, globals);
|
||||
writer.writeDebugInfo(location);
|
||||
writer.writeCast(cast);
|
||||
writer.writeBranch(tru, fals);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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;
|
||||
|
@ -286,7 +287,7 @@ public final class EChain extends AExpression {
|
|||
* 10. call link{[0]}.store(...) -- store the value on the stack into the 0th index of the array x [...]
|
||||
*/
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
// For the case where the chain represents a String concatenation
|
||||
|
@ -306,7 +307,7 @@ public final class EChain extends AExpression {
|
|||
// See individual links for more information on what each of the
|
||||
// write, load, and store methods do.
|
||||
for (ALink link : links) {
|
||||
link.write(writer); // call the write method on the link to prepare for a load/store operation
|
||||
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) {
|
||||
|
@ -314,10 +315,10 @@ public final class EChain extends AExpression {
|
|||
// representing a String concatenation.
|
||||
|
||||
writer.writeDup(link.size, catElementStackSize); // dup the top element and insert it before concat helper on stack
|
||||
link.load(writer); // read the current link's value
|
||||
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); // write the bytecode for the rhs expression
|
||||
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) {
|
||||
|
@ -331,13 +332,13 @@ public final class EChain extends AExpression {
|
|||
writer.writeDup(link.after.sort.size, link.size); // if this link is also read from dup the value onto the stack
|
||||
}
|
||||
|
||||
link.store(writer); // store the link's value from the stack in its respective variable/field/array
|
||||
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); // load the current link's value
|
||||
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
|
||||
|
@ -346,7 +347,7 @@ public final class EChain extends AExpression {
|
|||
|
||||
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
|
||||
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
|
||||
|
@ -364,22 +365,22 @@ public final class EChain extends AExpression {
|
|||
// read from and is not a post increment
|
||||
}
|
||||
|
||||
link.store(writer); // store the link's value from the stack in its respective variable/field/array
|
||||
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); // write the bytecode for the rhs expression
|
||||
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); // store the link's value from the stack in its respective variable/field/array
|
||||
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); // read the link's value onto the stack
|
||||
link.load(writer, globals); // read the link's value onto the stack
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Location;
|
||||
|
@ -436,15 +437,15 @@ public final class EComp extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
boolean branch = tru != null || fals != null;
|
||||
|
||||
left.write(writer);
|
||||
left.write(writer, globals);
|
||||
|
||||
if (!right.isNull) {
|
||||
right.write(writer);
|
||||
right.write(writer, globals);
|
||||
}
|
||||
|
||||
Label jump = tru != null ? tru : fals != null ? fals : new Label();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Type;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
|
@ -78,7 +79,7 @@ public final class EConditional extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
Label localfals = new Label();
|
||||
|
@ -88,11 +89,11 @@ public final class EConditional extends AExpression {
|
|||
left.tru = right.tru = tru;
|
||||
left.fals = right.fals = fals;
|
||||
|
||||
condition.write(writer);
|
||||
left.write(writer);
|
||||
condition.write(writer, globals);
|
||||
left.write(writer, globals);
|
||||
writer.goTo(end);
|
||||
writer.mark(localfals);
|
||||
right.write(writer);
|
||||
right.write(writer, globals);
|
||||
writer.mark(end);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Definition.Sort;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
|
@ -63,7 +64,7 @@ final class EConstant extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
Sort sort = actual.sort;
|
||||
|
||||
switch (sort) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.MethodWriter;
|
||||
|
@ -57,7 +58,7 @@ public final class EDecimal extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.MethodWriter;
|
||||
|
@ -54,7 +55,7 @@ public final class EExplicit extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ 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;
|
||||
|
@ -35,7 +36,7 @@ import java.lang.invoke.LambdaMetafactory;
|
|||
/**
|
||||
* Represents a function reference.
|
||||
*/
|
||||
public class EFunctionRef extends AExpression {
|
||||
public class EFunctionRef extends AExpression implements ILambda {
|
||||
public final String type;
|
||||
public final String call;
|
||||
|
||||
|
@ -83,7 +84,7 @@ public class EFunctionRef extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
if (ref != null) {
|
||||
writer.writeDebugInfo(location);
|
||||
// convert MethodTypes to asm Type for the constant pool.
|
||||
|
@ -114,4 +115,14 @@ public class EFunctionRef extends AExpression {
|
|||
writer.push((String)null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPointer() {
|
||||
return defPointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type[] getCaptures() {
|
||||
return new Type[0]; // no captures
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,23 +20,33 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.FunctionReserved;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.node.SFunction.FunctionReserved;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ELambda extends AExpression {
|
||||
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;
|
||||
// desugared synthetic method (lambda body)
|
||||
SFunction desugared;
|
||||
// method ref (impl detail)
|
||||
ILambda impl;
|
||||
|
||||
public ELambda(FunctionReserved reserved, Location location,
|
||||
List<String> paramTypes, List<String> paramNames, List<AStatement> statements) {
|
||||
public ELambda(String name, FunctionReserved reserved,
|
||||
Location location, List<String> paramTypes, List<String> paramNames,
|
||||
List<AStatement> statements) {
|
||||
super(location);
|
||||
|
||||
this.name = name;
|
||||
this.reserved = reserved;
|
||||
this.paramTypeStrs = Collections.unmodifiableList(paramTypes);
|
||||
this.paramNameStrs = Collections.unmodifiableList(paramNames);
|
||||
|
@ -45,11 +55,40 @@ public class ELambda extends AExpression {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
throw createError(new UnsupportedOperationException("Lambda functions are not supported."));
|
||||
// desugar lambda body into a synthetic method
|
||||
desugared = new SFunction(reserved, location, "def", name,
|
||||
paramTypeStrs, paramNameStrs, statements, true);
|
||||
desugared.generate();
|
||||
List<Variable> captures = new ArrayList<>();
|
||||
desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), desugared.parameters, captures));
|
||||
|
||||
// setup reference
|
||||
EFunctionRef ref = new EFunctionRef(location, "this", name);
|
||||
ref.expected = expected;
|
||||
// hack, create a new scope, with our method, so the ref can see it (impl detail)
|
||||
locals = Locals.newLocalScope(locals);
|
||||
locals.addMethod(desugared.method);
|
||||
ref.analyze(locals);
|
||||
actual = ref.actual;
|
||||
impl = ref;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
AExpression expr = (AExpression) impl;
|
||||
expr.write(writer, globals);
|
||||
// add synthetic method to the queue to be written
|
||||
globals.addSyntheticMethod(desugared);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPointer() {
|
||||
return impl.getPointer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type[] getCaptures() {
|
||||
return impl.getCaptures();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.objectweb.asm.Opcodes;
|
||||
|
@ -50,7 +51,7 @@ public final class ENull extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.visitInsn(Opcodes.ACONST_NULL);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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;
|
||||
|
@ -96,7 +97,7 @@ public final class ENumeric extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.Definition.Type;
|
||||
|
@ -177,7 +178,7 @@ public final class EUnary extends AExpression {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (operation == Operation.NOT) {
|
||||
|
@ -186,7 +187,7 @@ public final class EUnary extends AExpression {
|
|||
Label end = new Label();
|
||||
|
||||
child.fals = localfals;
|
||||
child.write(writer);
|
||||
child.write(writer, globals);
|
||||
|
||||
writer.push(false);
|
||||
writer.goTo(end);
|
||||
|
@ -196,11 +197,11 @@ public final class EUnary extends AExpression {
|
|||
} else {
|
||||
child.tru = fals;
|
||||
child.fals = tru;
|
||||
child.write(writer);
|
||||
child.write(writer, globals);
|
||||
}
|
||||
} else {
|
||||
Sort sort = promote.sort;
|
||||
child.write(writer);
|
||||
child.write(writer, globals);
|
||||
|
||||
if (operation == Operation.BWNOT) {
|
||||
if (sort == Sort.DEF) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Interface for lambda/method reference nodes. They need special handling by LDefCall.
|
||||
* <p>
|
||||
* This is because they know nothing about the target interface, and can only push
|
||||
* all their captures onto the stack and defer everything until link-time.
|
||||
*/
|
||||
interface ILambda {
|
||||
|
||||
/** Returns reference to resolve at link-time */
|
||||
String getPointer();
|
||||
|
||||
/** Returns the types of captured parameters. Can be empty */
|
||||
org.objectweb.asm.Type[] getCaptures();
|
||||
|
||||
/** Returns the number of captured parameters */
|
||||
default int getCaptureCount() {
|
||||
return getCaptures().length;
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@
|
|||
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.MethodWriter;
|
||||
|
@ -55,18 +56,18 @@ public final class LArrayLength extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.arrayLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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;
|
||||
|
@ -69,18 +70,18 @@ public final class LBrace extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
index.write(writer);
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.arrayLoad(after.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.arrayStore(after.type);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ 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.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
|
@ -86,16 +87,16 @@ public final class LCallInvoke extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer);
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(method.modifiers)) {
|
||||
|
@ -108,7 +109,7 @@ public final class LCallInvoke extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
@ -77,23 +78,23 @@ public class LCallLocal extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer);
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
writer.invokeStatic(CLASS_TYPE, method.method);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.Cast;
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
|
@ -61,18 +62,18 @@ public final class LCast extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.writeCast(cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.Locals;
|
||||
|
@ -53,12 +54,12 @@ final class LDefArray extends ALink implements IDefLink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
index.write(writer);
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type);
|
||||
|
@ -66,7 +67,7 @@ final class LDefArray extends ALink implements IDefLink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
|
||||
|
|
|
@ -20,10 +20,12 @@
|
|||
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.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -64,13 +66,11 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
expression.internal = true;
|
||||
expression.analyze(locals);
|
||||
|
||||
if (expression instanceof EFunctionRef) {
|
||||
pointers.add(((EFunctionRef)expression).defPointer);
|
||||
if (expression instanceof ILambda) {
|
||||
ILambda lambda = (ILambda) expression;
|
||||
pointers.add(lambda.getPointer());
|
||||
recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference
|
||||
} else if (expression instanceof ECapturingFunctionRef) {
|
||||
pointers.add(((ECapturingFunctionRef)expression).defPointer);
|
||||
recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference
|
||||
totalCaptures++;
|
||||
totalCaptures += lambda.getCaptureCount();
|
||||
}
|
||||
|
||||
expression.expected = expression.actual;
|
||||
|
@ -84,12 +84,12 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
StringBuilder signature = new StringBuilder();
|
||||
|
@ -100,11 +100,13 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
|
||||
for (AExpression argument : arguments) {
|
||||
signature.append(argument.actual.type.getDescriptor());
|
||||
if (argument instanceof ECapturingFunctionRef) {
|
||||
ECapturingFunctionRef capturingRef = (ECapturingFunctionRef) argument;
|
||||
signature.append(capturingRef.captured.type.type.getDescriptor());
|
||||
if (argument instanceof ILambda) {
|
||||
ILambda lambda = (ILambda) argument;
|
||||
for (Type capture : lambda.getCaptures()) {
|
||||
signature.append(capture.getDescriptor());
|
||||
}
|
||||
}
|
||||
argument.write(writer);
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
signature.append(')');
|
||||
|
@ -119,7 +121,7 @@ final class LDefCall extends ALink implements IDefLink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.Locals;
|
||||
|
@ -50,12 +51,12 @@ final class LDefField extends ALink implements IDefLink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type);
|
||||
|
@ -63,7 +64,7 @@ final class LDefField extends ALink implements IDefLink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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;
|
||||
|
@ -100,12 +101,12 @@ public final class LField extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
|
@ -116,7 +117,7 @@ public final class LField extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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;
|
||||
|
@ -74,12 +75,12 @@ final class LListShortcut extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
index.write(writer);
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
|
||||
|
@ -94,7 +95,7 @@ final class LListShortcut extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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;
|
||||
|
@ -73,12 +74,12 @@ final class LMapShortcut extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
index.write(writer);
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
index.write(writer, globals);
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
|
||||
|
@ -93,7 +94,7 @@ final class LMapShortcut extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.Locals;
|
||||
|
@ -74,16 +75,16 @@ public final class LNewArray extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer);
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
if (arguments.size() > 1) {
|
||||
|
@ -94,7 +95,7 @@ public final class LNewArray extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.Struct;
|
||||
|
@ -93,12 +94,12 @@ public final class LNewObj extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.newInstance(after.type);
|
||||
|
||||
|
@ -107,14 +108,14 @@ public final class LNewObj extends ALink {
|
|||
}
|
||||
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer);
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
writer.invokeConstructor(constructor.owner.type, constructor.method);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Constant;
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Constant;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
@ -64,25 +65,26 @@ public final class LRegex extends ALink {
|
|||
throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "]."));
|
||||
}
|
||||
|
||||
constant = locals.addConstant(location, Definition.PATTERN_TYPE, "regexAt$" + location.getOffset(), this::initializeConstant);
|
||||
constant = new Constant(location, Definition.PATTERN_TYPE.type, "regexAt$" + location.getOffset(), this::initializeConstant);
|
||||
after = Definition.PATTERN_TYPE;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type);
|
||||
globals.addConstantInitializer(constant);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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;
|
||||
|
@ -79,12 +80,12 @@ final class LShortcut extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
|
||||
|
@ -99,7 +100,7 @@ final class LShortcut extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Definition;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
|
@ -54,17 +55,17 @@ public final class LStatic extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.MethodWriter;
|
||||
|
@ -51,17 +52,17 @@ public final class LString extends ALink {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.push(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
|
|||
|
||||
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.Opcodes;
|
||||
|
@ -32,7 +33,7 @@ public final class LVariable extends ALink {
|
|||
|
||||
final String name;
|
||||
|
||||
int slot;
|
||||
Variable variable;
|
||||
|
||||
public LVariable(Location location, String name) {
|
||||
super(location, 0);
|
||||
|
@ -46,30 +47,29 @@ public final class LVariable extends ALink {
|
|||
throw createError(new IllegalArgumentException("Illegal variable [" + name + "] access with target already defined."));
|
||||
}
|
||||
|
||||
Variable variable = locals.getVariable(location, name);
|
||||
variable = locals.getVariable(location, name);
|
||||
|
||||
if (store && variable.readonly) {
|
||||
throw createError(new IllegalArgumentException("Variable [" + variable.name + "] is read-only."));
|
||||
}
|
||||
|
||||
slot = variable.slot;
|
||||
after = variable.type;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
void load(MethodWriter writer) {
|
||||
writer.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), slot);
|
||||
void load(MethodWriter writer, Globals globals) {
|
||||
writer.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), variable.getSlot());
|
||||
}
|
||||
|
||||
@Override
|
||||
void store(MethodWriter writer) {
|
||||
writer.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), slot);
|
||||
void store(MethodWriter writer, Globals globals) {
|
||||
writer.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
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;
|
||||
|
@ -70,11 +71,11 @@ public final class SBlock extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
for (AStatement statement : statements) {
|
||||
statement.continu = continu;
|
||||
statement.brake = brake;
|
||||
statement.write(writer);
|
||||
statement.write(writer, globals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
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;
|
||||
|
@ -45,7 +46,7 @@ public final class SBreak extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.goTo(brake);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.Locals;
|
||||
|
@ -65,7 +66,7 @@ public final class SCatch extends AStatement {
|
|||
throw createError(new ClassCastException("Not an exception type [" + this.type + "]."));
|
||||
}
|
||||
|
||||
variable = locals.addVariable(location, type, name, true, false);
|
||||
variable = locals.addVariable(location, type, name, true);
|
||||
|
||||
if (block != null) {
|
||||
block.lastSource = lastSource;
|
||||
|
@ -84,18 +85,18 @@ public final class SCatch extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label jump = new Label();
|
||||
|
||||
writer.mark(jump);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
|
||||
if (block != null) {
|
||||
block.continu = continu;
|
||||
block.brake = brake;
|
||||
block.write(writer);
|
||||
block.write(writer, globals);
|
||||
}
|
||||
|
||||
writer.visitTryCatchBlock(begin, end, jump, variable.type.type.getInternalName());
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
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;
|
||||
|
@ -48,7 +49,7 @@ public final class SContinue extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.goTo(continu);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
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;
|
||||
|
@ -49,9 +50,9 @@ public final class SDeclBlock extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
for (AStatement declaration : declarations) {
|
||||
declaration.write(writer);
|
||||
declaration.write(writer, globals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.Locals;
|
||||
|
@ -62,11 +63,11 @@ public final class SDeclaration extends AStatement {
|
|||
expression = expression.cast(locals);
|
||||
}
|
||||
|
||||
variable = locals.addVariable(location, type, name, false, false);
|
||||
variable = locals.addVariable(location, type, name, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
if (expression == null) {
|
||||
|
@ -83,9 +84,9 @@ public final class SDeclaration extends AStatement {
|
|||
default: writer.visitInsn(Opcodes.ACONST_NULL);
|
||||
}
|
||||
} else {
|
||||
expression.write(writer);
|
||||
expression.write(writer, globals);
|
||||
}
|
||||
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.objectweb.asm.Label;
|
||||
|
@ -42,7 +43,7 @@ public final class SDo extends AStatement {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
locals.incrementScope();
|
||||
locals = Locals.newLocalScope(locals);
|
||||
|
||||
if (block == null) {
|
||||
throw createError(new IllegalArgumentException("Extraneous do while loop."));
|
||||
|
@ -76,15 +77,13 @@ public final class SDo extends AStatement {
|
|||
|
||||
statementCount = 1;
|
||||
|
||||
if (locals.getMaxLoopCounter() > 0) {
|
||||
loopCounterSlot = locals.getVariable(location, "#loop").slot;
|
||||
if (locals.hasVariable(Locals.LOOP)) {
|
||||
loopCounter = locals.getVariable(location, Locals.LOOP);
|
||||
}
|
||||
|
||||
locals.decrementScope();
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label start = new Label();
|
||||
|
@ -95,14 +94,14 @@ public final class SDo extends AStatement {
|
|||
|
||||
block.continu = begin;
|
||||
block.brake = end;
|
||||
block.write(writer);
|
||||
block.write(writer, globals);
|
||||
|
||||
writer.mark(begin);
|
||||
|
||||
condition.fals = end;
|
||||
condition.write(writer);
|
||||
condition.write(writer, globals);
|
||||
|
||||
writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), location);
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), Math.max(1, block.statementCount), location);
|
||||
|
||||
writer.goTo(start);
|
||||
writer.mark(end);
|
||||
|
|
|
@ -22,6 +22,7 @@ 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;
|
||||
|
@ -85,9 +86,9 @@ public class SEach extends AStatement {
|
|||
throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
|
||||
}
|
||||
|
||||
locals.incrementScope();
|
||||
locals = Locals.newLocalScope(locals);
|
||||
|
||||
variable = locals.addVariable(location, type, name, true, false);
|
||||
variable = locals.addVariable(location, type, name, true);
|
||||
|
||||
if (expression.actual.sort == Sort.ARRAY) {
|
||||
analyzeArray(locals, type);
|
||||
|
@ -112,18 +113,16 @@ public class SEach extends AStatement {
|
|||
|
||||
statementCount = 1;
|
||||
|
||||
if (locals.getMaxLoopCounter() > 0) {
|
||||
loopCounterSlot = locals.getVariable(location, "#loop").slot;
|
||||
if (locals.hasVariable(Locals.LOOP)) {
|
||||
loopCounter = locals.getVariable(location, Locals.LOOP);
|
||||
}
|
||||
|
||||
locals.decrementScope();
|
||||
}
|
||||
|
||||
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, false);
|
||||
index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true, false);
|
||||
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);
|
||||
}
|
||||
|
@ -131,7 +130,7 @@ public class SEach extends AStatement {
|
|||
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, false);
|
||||
iterator = variables.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true);
|
||||
|
||||
if (expression.actual.sort == Sort.DEF) {
|
||||
method = null;
|
||||
|
@ -148,49 +147,49 @@ public class SEach extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
if (array != null) {
|
||||
writeArray(writer);
|
||||
writeArray(writer, globals);
|
||||
} else if (iterator != null) {
|
||||
writeIterable(writer);
|
||||
writeIterable(writer, globals);
|
||||
} else {
|
||||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
||||
void writeArray(MethodWriter writer) {
|
||||
expression.write(writer);
|
||||
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.slot);
|
||||
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.slot);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.getSlot());
|
||||
|
||||
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.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.slot);
|
||||
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot);
|
||||
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.slot);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
|
||||
block.write(writer);
|
||||
block.write(writer, globals);
|
||||
|
||||
writer.goTo(begin);
|
||||
writer.mark(end);
|
||||
}
|
||||
|
||||
void writeIterable(MethodWriter writer) {
|
||||
expression.write(writer);
|
||||
void writeIterable(MethodWriter writer, Globals globals) {
|
||||
expression.write(writer, globals);
|
||||
|
||||
if (method == null) {
|
||||
Type itr = Definition.getType("Iterator");
|
||||
|
@ -202,23 +201,23 @@ public class SEach extends AStatement {
|
|||
writer.invokeVirtual(method.owner.type, method.method);
|
||||
}
|
||||
|
||||
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.slot);
|
||||
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.slot);
|
||||
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.slot);
|
||||
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.slot);
|
||||
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
|
||||
|
||||
block.write(writer);
|
||||
block.write(writer, globals);
|
||||
|
||||
writer.goTo(begin);
|
||||
writer.mark(end);
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.painless.node;
|
|||
import org.elasticsearch.painless.Definition.Type;
|
||||
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;
|
||||
|
||||
|
@ -63,9 +64,9 @@ public final class SExpression extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
expression.write(writer);
|
||||
expression.write(writer, globals);
|
||||
|
||||
if (methodEscape) {
|
||||
writer.returnValue();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.objectweb.asm.Label;
|
||||
|
@ -46,7 +47,7 @@ public final class SFor extends AStatement {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
locals.incrementScope();
|
||||
locals = Locals.newLocalScope(locals);
|
||||
|
||||
boolean continuous = false;
|
||||
|
||||
|
@ -116,15 +117,13 @@ public final class SFor extends AStatement {
|
|||
|
||||
statementCount = 1;
|
||||
|
||||
if (locals.getMaxLoopCounter() > 0) {
|
||||
loopCounterSlot = locals.getVariable(location, "#loop").slot;
|
||||
if (locals.hasVariable(Locals.LOOP)) {
|
||||
loopCounter = locals.getVariable(location, Locals.LOOP);
|
||||
}
|
||||
|
||||
locals.decrementScope();
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label start = new Label();
|
||||
|
@ -132,11 +131,11 @@ public final class SFor extends AStatement {
|
|||
Label end = new Label();
|
||||
|
||||
if (initializer instanceof SDeclBlock) {
|
||||
((SDeclBlock)initializer).write(writer);
|
||||
((SDeclBlock)initializer).write(writer, globals);
|
||||
} else if (initializer instanceof AExpression) {
|
||||
AExpression initializer = (AExpression)this.initializer;
|
||||
|
||||
initializer.write(writer);
|
||||
initializer.write(writer, globals);
|
||||
writer.writePop(initializer.expected.sort.size);
|
||||
}
|
||||
|
||||
|
@ -144,7 +143,7 @@ public final class SFor extends AStatement {
|
|||
|
||||
if (condition != null) {
|
||||
condition.fals = end;
|
||||
condition.write(writer);
|
||||
condition.write(writer, globals);
|
||||
}
|
||||
|
||||
boolean allEscape = false;
|
||||
|
@ -158,15 +157,15 @@ public final class SFor extends AStatement {
|
|||
++statementCount;
|
||||
}
|
||||
|
||||
writer.writeLoopCounter(loopCounterSlot, statementCount, location);
|
||||
block.write(writer);
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location);
|
||||
block.write(writer, globals);
|
||||
} else {
|
||||
writer.writeLoopCounter(loopCounterSlot, 1, location);
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), 1, location);
|
||||
}
|
||||
|
||||
if (afterthought != null) {
|
||||
writer.mark(begin);
|
||||
afterthought.write(writer);
|
||||
afterthought.write(writer, globals);
|
||||
}
|
||||
|
||||
if (afterthought != null || !allEscape) {
|
||||
|
|
|
@ -19,27 +19,26 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
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.Locals;
|
||||
import org.elasticsearch.painless.Locals.Parameter;
|
||||
import org.elasticsearch.painless.Locals.FunctionReserved;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.WriterConstants;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -51,17 +50,17 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
|
|||
public class SFunction extends AStatement {
|
||||
final FunctionReserved reserved;
|
||||
final String rtnTypeStr;
|
||||
final String name;
|
||||
public final String name;
|
||||
final List<String> paramTypeStrs;
|
||||
final List<String> paramNameStrs;
|
||||
final List<AStatement> statements;
|
||||
final boolean synthetic;
|
||||
public final boolean synthetic;
|
||||
|
||||
Type rtnType = null;
|
||||
List<Parameter> parameters = new ArrayList<>();
|
||||
Method method = null;
|
||||
|
||||
Locals locals = null;
|
||||
Variable loop = null;
|
||||
|
||||
public SFunction(FunctionReserved reserved, Location location,
|
||||
String rtnType, String name, List<String> paramTypes,
|
||||
|
@ -115,10 +114,7 @@ public class SFunction extends AStatement {
|
|||
throw createError(new IllegalArgumentException("Cannot generate an empty function [" + name + "]."));
|
||||
}
|
||||
|
||||
this.locals = new Locals(reserved, locals, rtnType, parameters);
|
||||
locals = this.locals;
|
||||
|
||||
locals.incrementScope();
|
||||
locals = Locals.newLocalScope(locals);
|
||||
|
||||
AStatement last = statements.get(statements.size() - 1);
|
||||
|
||||
|
@ -141,37 +137,33 @@ public class SFunction extends AStatement {
|
|||
throw createError(new IllegalArgumentException("Not all paths provide a return value for method [" + name + "]."));
|
||||
}
|
||||
|
||||
locals.decrementScope();
|
||||
|
||||
String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size());
|
||||
locals.addConstant(location, WriterConstants.METHOD_HANDLE_TYPE, staticHandleFieldName, this::initializeConstant);
|
||||
if (reserved.getMaxLoopCounter() > 0) {
|
||||
loop = locals.getVariable(null, FunctionReserved.LOOP);
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes the function to given ClassVisitor. */
|
||||
void write (ClassVisitor writer, BitSet statements) {
|
||||
void write (ClassVisitor writer, Globals globals) {
|
||||
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
|
||||
if (synthetic) {
|
||||
access |= Opcodes.ACC_SYNTHETIC;
|
||||
}
|
||||
final MethodWriter function = new MethodWriter(access, method.method, writer, statements);
|
||||
write(function);
|
||||
final MethodWriter function = new MethodWriter(access, method.method, writer, globals.getStatements());
|
||||
write(function, globals);
|
||||
function.endMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter function) {
|
||||
void write(MethodWriter function, Globals globals) {
|
||||
if (reserved.getMaxLoopCounter() > 0) {
|
||||
// if there is infinite loop protection, we do this once:
|
||||
// int #loop = settings.getMaxLoopCounter()
|
||||
|
||||
Variable loop = locals.getVariable(null, FunctionReserved.LOOP);
|
||||
|
||||
function.push(reserved.getMaxLoopCounter());
|
||||
function.visitVarInsn(Opcodes.ISTORE, loop.slot);
|
||||
function.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
|
||||
}
|
||||
|
||||
for (AStatement statement : statements) {
|
||||
statement.write(function);
|
||||
statement.write(function, globals);
|
||||
}
|
||||
|
||||
if (!methodEscape) {
|
||||
|
@ -181,6 +173,10 @@ public class SFunction extends AStatement {
|
|||
throw createError(new IllegalStateException("Illegal tree structure."));
|
||||
}
|
||||
}
|
||||
|
||||
String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size());
|
||||
globals.addConstantInitializer(new Constant(location, WriterConstants.METHOD_HANDLE_TYPE,
|
||||
staticHandleFieldName, this::initializeConstant));
|
||||
}
|
||||
|
||||
private void initializeConstant(MethodWriter writer) {
|
||||
|
@ -191,4 +187,43 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.objectweb.asm.Label;
|
||||
|
@ -58,9 +59,7 @@ public final class SIf extends AStatement {
|
|||
ifblock.inLoop = inLoop;
|
||||
ifblock.lastLoop = lastLoop;
|
||||
|
||||
locals.incrementScope();
|
||||
ifblock.analyze(locals);
|
||||
locals.decrementScope();
|
||||
ifblock.analyze(Locals.newLocalScope(locals));
|
||||
|
||||
anyContinue = ifblock.anyContinue;
|
||||
anyBreak = ifblock.anyBreak;
|
||||
|
@ -68,17 +67,17 @@ public final class SIf extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label fals = new Label();
|
||||
|
||||
condition.fals = fals;
|
||||
condition.write(writer);
|
||||
condition.write(writer, globals);
|
||||
|
||||
ifblock.continu = continu;
|
||||
ifblock.brake = brake;
|
||||
ifblock.write(writer);
|
||||
ifblock.write(writer, globals);
|
||||
|
||||
writer.mark(fals);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.objectweb.asm.Label;
|
||||
|
@ -60,9 +61,7 @@ public final class SIfElse extends AStatement {
|
|||
ifblock.inLoop = inLoop;
|
||||
ifblock.lastLoop = lastLoop;
|
||||
|
||||
locals.incrementScope();
|
||||
ifblock.analyze(locals);
|
||||
locals.decrementScope();
|
||||
ifblock.analyze(Locals.newLocalScope(locals));
|
||||
|
||||
anyContinue = ifblock.anyContinue;
|
||||
anyBreak = ifblock.anyBreak;
|
||||
|
@ -76,9 +75,7 @@ public final class SIfElse extends AStatement {
|
|||
elseblock.inLoop = inLoop;
|
||||
elseblock.lastLoop = lastLoop;
|
||||
|
||||
locals.incrementScope();
|
||||
elseblock.analyze(locals);
|
||||
locals.decrementScope();
|
||||
elseblock.analyze(Locals.newLocalScope(locals));
|
||||
|
||||
methodEscape = ifblock.methodEscape && elseblock.methodEscape;
|
||||
loopEscape = ifblock.loopEscape && elseblock.loopEscape;
|
||||
|
@ -89,18 +86,18 @@ public final class SIfElse extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label end = new Label();
|
||||
Label fals = elseblock != null ? new Label() : end;
|
||||
|
||||
condition.fals = fals;
|
||||
condition.write(writer);
|
||||
condition.write(writer, globals);
|
||||
|
||||
ifblock.continu = continu;
|
||||
ifblock.brake = brake;
|
||||
ifblock.write(writer);
|
||||
ifblock.write(writer, globals);
|
||||
|
||||
if (!ifblock.allEscape) {
|
||||
writer.goTo(end);
|
||||
|
@ -110,7 +107,7 @@ public final class SIfElse extends AStatement {
|
|||
|
||||
elseblock.continu = continu;
|
||||
elseblock.brake = brake;
|
||||
elseblock.write(writer);
|
||||
elseblock.write(writer, globals);
|
||||
|
||||
writer.mark(end);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
||||
|
@ -51,9 +52,9 @@ public final class SReturn extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
expression.write(writer);
|
||||
expression.write(writer, globals);
|
||||
writer.returnValue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,14 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.Constant;
|
||||
import org.elasticsearch.painless.Definition.Method;
|
||||
import org.elasticsearch.painless.Definition.MethodKey;
|
||||
import org.elasticsearch.painless.Executable;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.Constant;
|
||||
import org.elasticsearch.painless.Locals.ExecuteReserved;
|
||||
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;
|
||||
|
@ -35,7 +36,9 @@ import org.objectweb.asm.Opcodes;
|
|||
import org.objectweb.asm.util.Printer;
|
||||
import org.objectweb.asm.util.TraceClassVisitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -56,24 +59,28 @@ public final class SSource extends AStatement {
|
|||
final String name;
|
||||
final String source;
|
||||
final Printer debugStream;
|
||||
final ExecuteReserved reserved;
|
||||
final MainMethodReserved reserved;
|
||||
final List<SFunction> functions;
|
||||
final Globals globals;
|
||||
final List<AStatement> statements;
|
||||
|
||||
private Locals locals;
|
||||
private BitSet expressions;
|
||||
private Locals mainMethod;
|
||||
private byte[] bytes;
|
||||
|
||||
public SSource(String name, String source, Printer debugStream, ExecuteReserved reserved, Location location,
|
||||
List<SFunction> functions, List<AStatement> statements) {
|
||||
public SSource(String name, String source, Printer debugStream, MainMethodReserved reserved, Location location,
|
||||
List<SFunction> functions, Globals globals, List<AStatement> statements) {
|
||||
super(location);
|
||||
|
||||
this.name = name;
|
||||
this.source = source;
|
||||
this.debugStream = debugStream;
|
||||
this.reserved = reserved;
|
||||
// process any synthetic functions generated by walker (because right now, thats still easy)
|
||||
functions.addAll(globals.getSyntheticMethods().values());
|
||||
globals.getSyntheticMethods().clear();
|
||||
this.functions = Collections.unmodifiableList(functions);
|
||||
this.statements = Collections.unmodifiableList(statements);
|
||||
this.globals = globals;
|
||||
}
|
||||
|
||||
public void analyze() {
|
||||
|
@ -89,21 +96,22 @@ public final class SSource extends AStatement {
|
|||
}
|
||||
}
|
||||
|
||||
locals = new Locals(reserved, methods);
|
||||
analyze(locals);
|
||||
analyze(Locals.newProgramScope(methods.values()));
|
||||
}
|
||||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
void analyze(Locals program) {
|
||||
for (SFunction function : functions) {
|
||||
function.analyze(locals);
|
||||
Locals functionLocals = Locals.newFunctionScope(program, function.rtnType, function.parameters,
|
||||
function.reserved.getMaxLoopCounter());
|
||||
function.analyze(functionLocals);
|
||||
}
|
||||
|
||||
if (statements == null || statements.isEmpty()) {
|
||||
throw createError(new IllegalArgumentException("Cannot generate an empty script."));
|
||||
}
|
||||
|
||||
locals.incrementScope();
|
||||
mainMethod = Locals.newMainMethodScope(program, reserved.usesScore(), reserved.usesCtx(), reserved.getMaxLoopCounter());
|
||||
|
||||
AStatement last = statements.get(statements.size() - 1);
|
||||
|
||||
|
@ -116,13 +124,11 @@ public final class SSource extends AStatement {
|
|||
|
||||
statement.lastSource = statement == last;
|
||||
|
||||
statement.analyze(locals);
|
||||
statement.analyze(mainMethod);
|
||||
|
||||
methodEscape = statement.methodEscape;
|
||||
allEscape = statement.allEscape;
|
||||
}
|
||||
|
||||
locals.decrementScope();
|
||||
}
|
||||
|
||||
public void write() {
|
||||
|
@ -144,10 +150,8 @@ public final class SSource extends AStatement {
|
|||
visitor.visit(classVersion, classAccess, className, null, classBase, classInterfaces);
|
||||
visitor.visitSource(Location.computeSourceName(name, source), null);
|
||||
|
||||
expressions = new BitSet(source.length());
|
||||
|
||||
// Write the constructor:
|
||||
MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, expressions);
|
||||
MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements());
|
||||
constructor.visitCode();
|
||||
constructor.loadThis();
|
||||
constructor.loadArgs();
|
||||
|
@ -156,20 +160,31 @@ public final class SSource extends AStatement {
|
|||
constructor.endMethod();
|
||||
|
||||
// Write the execute method:
|
||||
MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, expressions);
|
||||
MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, globals.getStatements());
|
||||
execute.visitCode();
|
||||
write(execute);
|
||||
write(execute, globals);
|
||||
execute.endMethod();
|
||||
|
||||
// Write all functions:
|
||||
for (SFunction function : functions) {
|
||||
function.write(visitor, expressions);
|
||||
function.write(visitor, globals);
|
||||
}
|
||||
|
||||
// Write all synthetic functions. Note that this process may add more :)
|
||||
while (!globals.getSyntheticMethods().isEmpty()) {
|
||||
List<SFunction> current = new ArrayList<>(globals.getSyntheticMethods().values());
|
||||
globals.getSyntheticMethods().clear();
|
||||
for (SFunction function : current) {
|
||||
function.write(visitor, globals);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the constants
|
||||
if (false == locals.getConstants().isEmpty()) {
|
||||
if (false == globals.getConstantInitializers().isEmpty()) {
|
||||
Collection<Constant> inits = globals.getConstantInitializers().values();
|
||||
|
||||
// Fields
|
||||
for (Constant constant : locals.getConstants()) {
|
||||
for (Constant constant : inits) {
|
||||
visitor.visitField(
|
||||
Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
|
||||
constant.name,
|
||||
|
@ -180,8 +195,8 @@ public final class SSource extends AStatement {
|
|||
|
||||
// Initialize the constants in a static initializer
|
||||
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
|
||||
WriterConstants.CLINIT, visitor, expressions);
|
||||
for (Constant constant : locals.getConstants()) {
|
||||
WriterConstants.CLINIT, visitor, globals.getStatements());
|
||||
for (Constant constant : inits) {
|
||||
constant.initializer.accept(clinit);
|
||||
clinit.putStatic(CLASS_TYPE, constant.name, constant.type);
|
||||
}
|
||||
|
@ -196,44 +211,44 @@ public final class SSource extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
if (reserved.usesScore()) {
|
||||
// if the _score value is used, we do this once:
|
||||
// final double _score = scorer.score();
|
||||
Variable scorer = locals.getVariable(null, ExecuteReserved.SCORER);
|
||||
Variable score = locals.getVariable(null, ExecuteReserved.SCORE);
|
||||
Variable scorer = mainMethod.getVariable(null, Locals.SCORER);
|
||||
Variable score = mainMethod.getVariable(null, Locals.SCORE);
|
||||
|
||||
writer.visitVarInsn(Opcodes.ALOAD, scorer.slot);
|
||||
writer.visitVarInsn(Opcodes.ALOAD, scorer.getSlot());
|
||||
writer.invokeVirtual(WriterConstants.SCORER_TYPE, WriterConstants.SCORER_SCORE);
|
||||
writer.visitInsn(Opcodes.F2D);
|
||||
writer.visitVarInsn(Opcodes.DSTORE, score.slot);
|
||||
writer.visitVarInsn(Opcodes.DSTORE, score.getSlot());
|
||||
}
|
||||
|
||||
if (reserved.usesCtx()) {
|
||||
// if the _ctx value is used, we do this once:
|
||||
// final Map<String,Object> ctx = input.get("ctx");
|
||||
|
||||
Variable input = locals.getVariable(null, ExecuteReserved.PARAMS);
|
||||
Variable ctx = locals.getVariable(null, ExecuteReserved.CTX);
|
||||
Variable input = mainMethod.getVariable(null, Locals.PARAMS);
|
||||
Variable ctx = mainMethod.getVariable(null, Locals.CTX);
|
||||
|
||||
writer.visitVarInsn(Opcodes.ALOAD, input.slot);
|
||||
writer.push(ExecuteReserved.CTX);
|
||||
writer.visitVarInsn(Opcodes.ALOAD, input.getSlot());
|
||||
writer.push(Locals.CTX);
|
||||
writer.invokeInterface(MAP_TYPE, MAP_GET);
|
||||
writer.visitVarInsn(Opcodes.ASTORE, ctx.slot);
|
||||
writer.visitVarInsn(Opcodes.ASTORE, ctx.getSlot());
|
||||
}
|
||||
|
||||
if (reserved.getMaxLoopCounter() > 0) {
|
||||
// if there is infinite loop protection, we do this once:
|
||||
// int #loop = settings.getMaxLoopCounter()
|
||||
|
||||
Variable loop = locals.getVariable(null, ExecuteReserved.LOOP);
|
||||
Variable loop = mainMethod.getVariable(null, Locals.LOOP);
|
||||
|
||||
writer.push(reserved.getMaxLoopCounter());
|
||||
writer.visitVarInsn(Opcodes.ISTORE, loop.slot);
|
||||
writer.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
|
||||
}
|
||||
|
||||
for (AStatement statement : statements) {
|
||||
statement.write(writer);
|
||||
statement.write(writer, globals);
|
||||
}
|
||||
|
||||
if (!methodEscape) {
|
||||
|
@ -242,11 +257,50 @@ public final class SSource extends AStatement {
|
|||
}
|
||||
}
|
||||
|
||||
public BitSet getExpressions() {
|
||||
return expressions;
|
||||
public BitSet getStatements() {
|
||||
return globals.getStatements();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.MethodWriter;
|
||||
|
@ -50,9 +51,9 @@ public final class SThrow extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
expression.write(writer);
|
||||
expression.write(writer, globals);
|
||||
writer.throwException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
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;
|
||||
|
@ -52,9 +53,7 @@ public final class STry extends AStatement {
|
|||
block.inLoop = inLoop;
|
||||
block.lastLoop = lastLoop;
|
||||
|
||||
locals.incrementScope();
|
||||
block.analyze(locals);
|
||||
locals.decrementScope();
|
||||
block.analyze(Locals.newLocalScope(locals));
|
||||
|
||||
methodEscape = block.methodEscape;
|
||||
loopEscape = block.loopEscape;
|
||||
|
@ -69,9 +68,7 @@ public final class STry extends AStatement {
|
|||
catc.inLoop = inLoop;
|
||||
catc.lastLoop = lastLoop;
|
||||
|
||||
locals.incrementScope();
|
||||
catc.analyze(locals);
|
||||
locals.decrementScope();
|
||||
catc.analyze(Locals.newLocalScope(locals));
|
||||
|
||||
methodEscape &= catc.methodEscape;
|
||||
loopEscape &= catc.loopEscape;
|
||||
|
@ -86,7 +83,7 @@ public final class STry extends AStatement {
|
|||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label begin = new Label();
|
||||
|
@ -97,7 +94,7 @@ public final class STry extends AStatement {
|
|||
|
||||
block.continu = continu;
|
||||
block.brake = brake;
|
||||
block.write(writer);
|
||||
block.write(writer, globals);
|
||||
|
||||
if (!block.allEscape) {
|
||||
writer.goTo(exception);
|
||||
|
@ -109,7 +106,7 @@ public final class STry extends AStatement {
|
|||
catc.begin = begin;
|
||||
catc.end = end;
|
||||
catc.exception = catches.size() > 1 ? exception : null;
|
||||
catc.write(writer);
|
||||
catc.write(writer, globals);
|
||||
}
|
||||
|
||||
if (!block.allEscape || catches.size() > 1) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
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.objectweb.asm.Label;
|
||||
|
@ -42,7 +43,7 @@ public final class SWhile extends AStatement {
|
|||
|
||||
@Override
|
||||
void analyze(Locals locals) {
|
||||
locals.incrementScope();
|
||||
locals = Locals.newLocalScope(locals);
|
||||
|
||||
condition.expected = Definition.BOOLEAN_TYPE;
|
||||
condition.analyze(locals);
|
||||
|
@ -82,15 +83,13 @@ public final class SWhile extends AStatement {
|
|||
|
||||
statementCount = 1;
|
||||
|
||||
if (locals.getMaxLoopCounter() > 0) {
|
||||
loopCounterSlot = locals.getVariable(location, "#loop").slot;
|
||||
if (locals.hasVariable(Locals.LOOP)) {
|
||||
loopCounter = locals.getVariable(location, Locals.LOOP);
|
||||
}
|
||||
|
||||
locals.decrementScope();
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer) {
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeStatementOffset(location);
|
||||
|
||||
Label begin = new Label();
|
||||
|
@ -99,16 +98,16 @@ public final class SWhile extends AStatement {
|
|||
writer.mark(begin);
|
||||
|
||||
condition.fals = end;
|
||||
condition.write(writer);
|
||||
condition.write(writer, globals);
|
||||
|
||||
if (block != null) {
|
||||
writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), location);
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), Math.max(1, block.statementCount), location);
|
||||
|
||||
block.continu = begin;
|
||||
block.brake = end;
|
||||
block.write(writer);
|
||||
block.write(writer, globals);
|
||||
} else {
|
||||
writer.writeLoopCounter(loopCounterSlot, 1, location);
|
||||
writer.writeLoopCounter(loopCounter.getSlot(), 1, location);
|
||||
}
|
||||
|
||||
if (block == null || !block.allEscape) {
|
||||
|
|
|
@ -30,7 +30,7 @@ public class ReservedWordTests extends ScriptTestCase {
|
|||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("int _score = 5; return _score;");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("Variable [_score] is reserved"));
|
||||
assertTrue(expected.getMessage().contains("Variable [_score] is already defined"));
|
||||
}
|
||||
|
||||
/** check that we can't write to _score, its read-only! */
|
||||
|
@ -46,7 +46,7 @@ public class ReservedWordTests extends ScriptTestCase {
|
|||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("int doc = 5; return doc;");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("Variable [doc] is reserved"));
|
||||
assertTrue(expected.getMessage().contains("Variable [doc] is already defined"));
|
||||
}
|
||||
|
||||
/** check that we can't write to doc, its read-only! */
|
||||
|
@ -62,7 +62,7 @@ public class ReservedWordTests extends ScriptTestCase {
|
|||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("int ctx = 5; return ctx;");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("Variable [ctx] is reserved"));
|
||||
assertTrue(expected.getMessage().contains("Variable [ctx] is already defined"));
|
||||
}
|
||||
|
||||
/** check that we can't write to ctx, its read-only! */
|
||||
|
@ -83,7 +83,7 @@ public class ReservedWordTests extends ScriptTestCase {
|
|||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
exec("int _value = 5; return _value;");
|
||||
});
|
||||
assertTrue(expected.getMessage().contains("Variable [_value] is reserved"));
|
||||
assertTrue(expected.getMessage().contains("Variable [_value] is already defined"));
|
||||
}
|
||||
|
||||
/** check that we can't write to _value, its read-only! */
|
||||
|
|
Loading…
Reference in New Issue