geforkt von Mirrors/FastAsyncWorldEdit
Expression Goodie Bag (#553)
* Remove async expression eval. Implement timeout inline * Remove static state from expr functions * Remove now-unused TL stack * Rework some expr handles (cherry picked from commit 6bc1d4647cc6892ae4dca9fc0e2d239777903c38)
Dieser Commit ist enthalten in:
Ursprung
88a5806b0f
Commit
e8bc0c0e1f
@ -19,9 +19,7 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.internal.expression;
|
package com.sk89q.worldedit.internal.expression;
|
||||||
|
|
||||||
import com.google.common.collect.SetMultimap;
|
import java.time.Instant;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@ -31,21 +29,33 @@ public class ExecutionData {
|
|||||||
* Special execution context for evaluating constant values. As long as no variables are used,
|
* Special execution context for evaluating constant values. As long as no variables are used,
|
||||||
* it can be considered constant.
|
* it can be considered constant.
|
||||||
*/
|
*/
|
||||||
public static final ExecutionData CONSTANT_EVALUATOR = new ExecutionData(null, null);
|
public static final ExecutionData CONSTANT_EVALUATOR = new ExecutionData(null, null, Instant.MAX);
|
||||||
|
|
||||||
private final SlotTable slots;
|
private final SlotTable slots;
|
||||||
private final SetMultimap<String, MethodHandle> functions;
|
private final Functions functions;
|
||||||
|
private final Instant deadline;
|
||||||
|
|
||||||
public ExecutionData(SlotTable slots, SetMultimap<String, MethodHandle> functions) {
|
public ExecutionData(SlotTable slots, Functions functions, Instant deadline) {
|
||||||
this.slots = slots;
|
this.slots = slots;
|
||||||
this.functions = functions;
|
this.functions = functions;
|
||||||
|
this.deadline = deadline;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SlotTable getSlots() {
|
public SlotTable getSlots() {
|
||||||
return requireNonNull(slots, "Cannot use variables in a constant");
|
return requireNonNull(slots, "Cannot use variables in a constant");
|
||||||
}
|
}
|
||||||
|
|
||||||
public SetMultimap<String, MethodHandle> getFunctions() {
|
public Functions getFunctions() {
|
||||||
return requireNonNull(functions, "Cannot use functions in a constant");
|
return requireNonNull(functions, "Cannot use functions in a constant");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Instant getDeadline() {
|
||||||
|
return deadline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkDeadline() {
|
||||||
|
if (Instant.now().isAfter(deadline)) {
|
||||||
|
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,32 +19,20 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.internal.expression;
|
package com.sk89q.worldedit.internal.expression;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.SetMultimap;
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
import com.sk89q.worldedit.antlr.ExpressionLexer;
|
import com.sk89q.worldedit.antlr.ExpressionLexer;
|
||||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||||
import com.sk89q.worldedit.internal.expression.invoke.ExpressionCompiler;
|
import com.sk89q.worldedit.internal.expression.invoke.ExpressionCompiler;
|
||||||
import com.sk89q.worldedit.session.request.Request;
|
|
||||||
import org.antlr.v4.runtime.CharStream;
|
import org.antlr.v4.runtime.CharStream;
|
||||||
import org.antlr.v4.runtime.CharStreams;
|
import org.antlr.v4.runtime.CharStreams;
|
||||||
import org.antlr.v4.runtime.CommonTokenStream;
|
import org.antlr.v4.runtime.CommonTokenStream;
|
||||||
import org.antlr.v4.runtime.misc.ParseCancellationException;
|
import org.antlr.v4.runtime.misc.ParseCancellationException;
|
||||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Stack;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles and evaluates expressions.
|
* Compiles and evaluates expressions.
|
||||||
@ -74,19 +62,10 @@ import java.util.concurrent.TimeoutException;
|
|||||||
*/
|
*/
|
||||||
public class Expression {
|
public class Expression {
|
||||||
|
|
||||||
private static final ThreadLocal<Stack<Expression>> instance = new ThreadLocal<>();
|
|
||||||
private static final ExecutorService evalThread = Executors.newFixedThreadPool(
|
|
||||||
Runtime.getRuntime().availableProcessors(),
|
|
||||||
new ThreadFactoryBuilder()
|
|
||||||
.setDaemon(true)
|
|
||||||
.setNameFormat("worldedit-expression-eval-%d")
|
|
||||||
.build());
|
|
||||||
|
|
||||||
private final SlotTable slots = new SlotTable();
|
private final SlotTable slots = new SlotTable();
|
||||||
private final List<String> providedSlots;
|
private final List<String> providedSlots;
|
||||||
private final ExpressionParser.AllStatementsContext root;
|
private final ExpressionParser.AllStatementsContext root;
|
||||||
private final SetMultimap<String, MethodHandle> functions = Functions.getFunctionMap();
|
private final Functions functions = Functions.create();
|
||||||
private ExpressionEnvironment environment;
|
|
||||||
private final CompiledExpression compiledExpression;
|
private final CompiledExpression compiledExpression;
|
||||||
|
|
||||||
public static Expression compile(String expression, String... variableNames) throws ExpressionException {
|
public static Expression compile(String expression, String... variableNames) throws ExpressionException {
|
||||||
@ -138,52 +117,9 @@ public class Expression {
|
|||||||
slot.setValue(values[i]);
|
slot.setValue(values[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Instant deadline = Instant.now().plusMillis(timeout);
|
||||||
// evaluation exceptions are thrown out of this method
|
// evaluation exceptions are thrown out of this method
|
||||||
if (timeout < 0) {
|
return compiledExpression.execute(new ExecutionData(slots, functions, deadline));
|
||||||
return evaluateRoot();
|
|
||||||
}
|
|
||||||
return evaluateRootTimed(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double evaluateRootTimed(int timeout) throws EvaluationException {
|
|
||||||
CountDownLatch startLatch = new CountDownLatch(1);
|
|
||||||
Request request = Request.request();
|
|
||||||
Future<Double> result = evalThread.submit(() -> {
|
|
||||||
Request local = Request.request();
|
|
||||||
local.setSession(request.getSession());
|
|
||||||
local.setWorld(request.getWorld());
|
|
||||||
local.setEditSession(request.getEditSession());
|
|
||||||
try {
|
|
||||||
startLatch.countDown();
|
|
||||||
return Expression.this.evaluateRoot();
|
|
||||||
} finally {
|
|
||||||
Request.reset();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
startLatch.await();
|
|
||||||
return result.get(timeout, TimeUnit.MILLISECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
result.cancel(true);
|
|
||||||
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
Throwable cause = e.getCause();
|
|
||||||
Throwables.throwIfInstanceOf(cause, EvaluationException.class);
|
|
||||||
Throwables.throwIfUnchecked(cause);
|
|
||||||
throw new RuntimeException(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Double evaluateRoot() throws EvaluationException {
|
|
||||||
pushInstance();
|
|
||||||
try {
|
|
||||||
return compiledExpression.execute(new ExecutionData(slots, functions));
|
|
||||||
} finally {
|
|
||||||
popInstance();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void optimize() {
|
public void optimize() {
|
||||||
@ -199,35 +135,12 @@ public class Expression {
|
|||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Expression getInstance() {
|
|
||||||
return instance.get().peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pushInstance() {
|
|
||||||
Stack<Expression> threadLocalExprStack = instance.get();
|
|
||||||
if (threadLocalExprStack == null) {
|
|
||||||
instance.set(threadLocalExprStack = new Stack<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
threadLocalExprStack.push(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void popInstance() {
|
|
||||||
Stack<Expression> threadLocalExprStack = instance.get();
|
|
||||||
|
|
||||||
threadLocalExprStack.pop();
|
|
||||||
|
|
||||||
if (threadLocalExprStack.isEmpty()) {
|
|
||||||
instance.set(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExpressionEnvironment getEnvironment() {
|
public ExpressionEnvironment getEnvironment() {
|
||||||
return environment;
|
return functions.getEnvironment();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEnvironment(ExpressionEnvironment environment) {
|
public void setEnvironment(ExpressionEnvironment environment) {
|
||||||
this.environment = environment;
|
functions.setEnvironment(environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.internal.expression;
|
package com.sk89q.worldedit.internal.expression;
|
||||||
|
|
||||||
import com.google.common.collect.SetMultimap;
|
|
||||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||||
import org.antlr.v4.runtime.ParserRuleContext;
|
import org.antlr.v4.runtime.ParserRuleContext;
|
||||||
import org.antlr.v4.runtime.Token;
|
import org.antlr.v4.runtime.Token;
|
||||||
@ -60,16 +59,10 @@ public class ExpressionHelper {
|
|||||||
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
|
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkTimeout() {
|
public static MethodHandle resolveFunction(Functions functions,
|
||||||
if (Thread.interrupted()) {
|
|
||||||
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodHandle resolveFunction(SetMultimap<String, MethodHandle> functions,
|
|
||||||
ExpressionParser.FunctionCallContext ctx) {
|
ExpressionParser.FunctionCallContext ctx) {
|
||||||
String fnName = ctx.name.getText();
|
String fnName = ctx.name.getText();
|
||||||
Set<MethodHandle> matchingFns = functions.get(fnName);
|
Set<MethodHandle> matchingFns = functions.getMap().get(fnName);
|
||||||
check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'");
|
check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'");
|
||||||
for (MethodHandle function : matchingFns) {
|
for (MethodHandle function : matchingFns) {
|
||||||
MethodType type = function.type();
|
MethodType type = function.type();
|
||||||
|
@ -19,11 +19,9 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.internal.expression;
|
package com.sk89q.worldedit.internal.expression;
|
||||||
|
|
||||||
import com.google.common.collect.SetMultimap;
|
|
||||||
import com.sk89q.worldedit.antlr.ExpressionBaseListener;
|
import com.sk89q.worldedit.antlr.ExpressionBaseListener;
|
||||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -34,10 +32,10 @@ import static com.sk89q.worldedit.internal.expression.ExpressionHelper.resolveFu
|
|||||||
class ExpressionValidator extends ExpressionBaseListener {
|
class ExpressionValidator extends ExpressionBaseListener {
|
||||||
|
|
||||||
private final Set<String> variableNames = new HashSet<>();
|
private final Set<String> variableNames = new HashSet<>();
|
||||||
private final SetMultimap<String, MethodHandle> functions;
|
private final Functions functions;
|
||||||
|
|
||||||
ExpressionValidator(Collection<String> variableNames,
|
ExpressionValidator(Collection<String> variableNames,
|
||||||
SetMultimap<String, MethodHandle> functions) {
|
Functions functions) {
|
||||||
this.variableNames.addAll(variableNames);
|
this.variableNames.addAll(variableNames);
|
||||||
this.functions = functions;
|
this.functions = functions;
|
||||||
}
|
}
|
||||||
|
@ -44,25 +44,10 @@ import static java.lang.invoke.MethodType.methodType;
|
|||||||
/**
|
/**
|
||||||
* Contains all functions that can be used in expressions.
|
* Contains all functions that can be used in expressions.
|
||||||
*/
|
*/
|
||||||
final class Functions {
|
public final class Functions {
|
||||||
|
|
||||||
static SetMultimap<String, MethodHandle> getFunctionMap() {
|
static Functions create() {
|
||||||
SetMultimap<String, MethodHandle> map = HashMultimap.create();
|
return new Functions();
|
||||||
Functions functions = new Functions();
|
|
||||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
|
||||||
|
|
||||||
try {
|
|
||||||
addMathHandles(map, lookup);
|
|
||||||
addStaticFunctionHandles(map, lookup);
|
|
||||||
functions.addInstanceFunctionHandles(map, lookup);
|
|
||||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up all the functions
|
|
||||||
return ImmutableSetMultimap.copyOf(
|
|
||||||
Multimaps.transformValues(map, Functions::clean)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final MethodHandle DOUBLE_VALUE;
|
private static final MethodHandle DOUBLE_VALUE;
|
||||||
@ -149,15 +134,6 @@ final class Functions {
|
|||||||
map.put("ridgedmulti", lookup.findStatic(Functions.class, "ridgedmulti",
|
map.put("ridgedmulti", lookup.findStatic(Functions.class, "ridgedmulti",
|
||||||
methodType(double.class, double.class, double.class, double.class, double.class,
|
methodType(double.class, double.class, double.class, double.class, double.class,
|
||||||
double.class, double.class)));
|
double.class, double.class)));
|
||||||
map.put("query", lookup.findStatic(Functions.class, "query",
|
|
||||||
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
|
||||||
LocalSlot.class)));
|
|
||||||
map.put("queryAbs", lookup.findStatic(Functions.class, "queryAbs",
|
|
||||||
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
|
||||||
LocalSlot.class)));
|
|
||||||
map.put("queryRel", lookup.findStatic(Functions.class, "queryRel",
|
|
||||||
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
|
||||||
LocalSlot.class)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInstanceFunctionHandles(
|
private void addInstanceFunctionHandles(
|
||||||
@ -174,6 +150,20 @@ final class Functions {
|
|||||||
methodType(double.class, double.class, double.class, double.class, double.class,
|
methodType(double.class, double.class, double.class, double.class, double.class,
|
||||||
double.class, double.class), Functions.class)
|
double.class, double.class), Functions.class)
|
||||||
.bindTo(this));
|
.bindTo(this));
|
||||||
|
|
||||||
|
// rely on expression field
|
||||||
|
map.put("query", lookup.findSpecial(Functions.class, "query",
|
||||||
|
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
||||||
|
LocalSlot.class), Functions.class)
|
||||||
|
.bindTo(this));
|
||||||
|
map.put("queryAbs", lookup.findSpecial(Functions.class, "queryAbs",
|
||||||
|
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
||||||
|
LocalSlot.class), Functions.class)
|
||||||
|
.bindTo(this));
|
||||||
|
map.put("queryRel", lookup.findSpecial(Functions.class, "queryRel",
|
||||||
|
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
||||||
|
LocalSlot.class), Functions.class)
|
||||||
|
.bindTo(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double rotate(Variable x, Variable y, double angle) {
|
private static double rotate(Variable x, Variable y, double angle) {
|
||||||
@ -201,6 +191,35 @@ final class Functions {
|
|||||||
|
|
||||||
private static final Int2ObjectMap<double[]> globalMegaBuffer = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<double[]> globalMegaBuffer = new Int2ObjectOpenHashMap<>();
|
||||||
private final Int2ObjectMap<double[]> megaBuffer = new Int2ObjectOpenHashMap<>();
|
private final Int2ObjectMap<double[]> megaBuffer = new Int2ObjectOpenHashMap<>();
|
||||||
|
private final SetMultimap<String, MethodHandle> map;
|
||||||
|
private ExpressionEnvironment environment;
|
||||||
|
|
||||||
|
private Functions() {
|
||||||
|
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||||
|
SetMultimap<String, MethodHandle> map = HashMultimap.create();
|
||||||
|
try {
|
||||||
|
addMathHandles(map, lookup);
|
||||||
|
addStaticFunctionHandles(map, lookup);
|
||||||
|
addInstanceFunctionHandles(map, lookup);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
this.map = ImmutableSetMultimap.copyOf(
|
||||||
|
Multimaps.transformValues(map, Functions::clean)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SetMultimap<String, MethodHandle> getMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExpressionEnvironment getEnvironment() {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnvironment(ExpressionEnvironment environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
private static double[] getSubBuffer(Int2ObjectMap<double[]> megabuf, int key) {
|
private static double[] getSubBuffer(Int2ObjectMap<double[]> megabuf, int key) {
|
||||||
return megabuf.computeIfAbsent(key, k -> new double[1024]);
|
return megabuf.computeIfAbsent(key, k -> new double[1024]);
|
||||||
@ -332,9 +351,7 @@ final class Functions {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double query(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
private double query(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
||||||
final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
|
|
||||||
|
|
||||||
// Read values from world
|
// Read values from world
|
||||||
final double typeId = environment.getBlockType(x, y, z);
|
final double typeId = environment.getBlockType(x, y, z);
|
||||||
final double dataValue = environment.getBlockData(x, y, z);
|
final double dataValue = environment.getBlockData(x, y, z);
|
||||||
@ -342,9 +359,7 @@ final class Functions {
|
|||||||
return queryInternal(type, data, typeId, dataValue);
|
return queryInternal(type, data, typeId, dataValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double queryAbs(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
private double queryAbs(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
||||||
final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
|
|
||||||
|
|
||||||
// Read values from world
|
// Read values from world
|
||||||
final double typeId = environment.getBlockTypeAbs(x, y, z);
|
final double typeId = environment.getBlockTypeAbs(x, y, z);
|
||||||
final double dataValue = environment.getBlockDataAbs(x, y, z);
|
final double dataValue = environment.getBlockDataAbs(x, y, z);
|
||||||
@ -352,9 +367,7 @@ final class Functions {
|
|||||||
return queryInternal(type, data, typeId, dataValue);
|
return queryInternal(type, data, typeId, dataValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double queryRel(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
private double queryRel(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
||||||
final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
|
|
||||||
|
|
||||||
// Read values from world
|
// Read values from world
|
||||||
final double typeId = environment.getBlockTypeRel(x, y, z);
|
final double typeId = environment.getBlockTypeRel(x, y, z);
|
||||||
final double dataValue = environment.getBlockDataRel(x, y, z);
|
final double dataValue = environment.getBlockDataRel(x, y, z);
|
||||||
@ -362,7 +375,4 @@ final class Functions {
|
|||||||
return queryInternal(type, data, typeId, dataValue);
|
return queryInternal(type, data, typeId, dataValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Functions() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,13 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.internal.expression.invoke;
|
package com.sk89q.worldedit.internal.expression.invoke;
|
||||||
|
|
||||||
import com.google.common.collect.SetMultimap;
|
|
||||||
import com.sk89q.worldedit.antlr.ExpressionBaseVisitor;
|
import com.sk89q.worldedit.antlr.ExpressionBaseVisitor;
|
||||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||||
import com.sk89q.worldedit.internal.expression.BreakException;
|
import com.sk89q.worldedit.internal.expression.BreakException;
|
||||||
import com.sk89q.worldedit.internal.expression.EvaluationException;
|
import com.sk89q.worldedit.internal.expression.EvaluationException;
|
||||||
import com.sk89q.worldedit.internal.expression.ExecutionData;
|
import com.sk89q.worldedit.internal.expression.ExecutionData;
|
||||||
import com.sk89q.worldedit.internal.expression.ExpressionHelper;
|
import com.sk89q.worldedit.internal.expression.ExpressionHelper;
|
||||||
|
import com.sk89q.worldedit.internal.expression.Functions;
|
||||||
import com.sk89q.worldedit.internal.expression.LocalSlot;
|
import com.sk89q.worldedit.internal.expression.LocalSlot;
|
||||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectLinkedOpenHashMap;
|
import it.unimi.dsi.fastutil.doubles.Double2ObjectLinkedOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
|
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
|
||||||
@ -73,6 +73,7 @@ import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.D
|
|||||||
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.IS_NULL;
|
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.IS_NULL;
|
||||||
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.NEW_LS_CONSTANT;
|
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.NEW_LS_CONSTANT;
|
||||||
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.NULL_DOUBLE;
|
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.NULL_DOUBLE;
|
||||||
|
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.unboxDoubles;
|
||||||
import static java.lang.invoke.MethodType.methodType;
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,9 +87,9 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
|
|||||||
* (ExecutionData)Double, with a few as (ExecutionData,Double)Double where it needs an existing
|
* (ExecutionData)Double, with a few as (ExecutionData,Double)Double where it needs an existing
|
||||||
* value passed in. EVERY handle returned from an overriden method must be of the first type.
|
* value passed in. EVERY handle returned from an overriden method must be of the first type.
|
||||||
*/
|
*/
|
||||||
private final SetMultimap<String, MethodHandle> functions;
|
private final Functions functions;
|
||||||
|
|
||||||
CompilingVisitor(SetMultimap<String, MethodHandle> functions) {
|
CompilingVisitor(Functions functions) {
|
||||||
this.functions = functions;
|
this.functions = functions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +137,6 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
|
|||||||
|
|
||||||
private MethodHandle evaluateBoolean(ParserRuleContext boolExpression) {
|
private MethodHandle evaluateBoolean(ParserRuleContext boolExpression) {
|
||||||
MethodHandle value = evaluateForNamedValue(boolExpression, "a boolean");
|
MethodHandle value = evaluateForNamedValue(boolExpression, "a boolean");
|
||||||
value = value.asType(value.type().unwrap());
|
|
||||||
// Pass `value` into converter, returns (ExecutionData)boolean;
|
// Pass `value` into converter, returns (ExecutionData)boolean;
|
||||||
return MethodHandles.collectArguments(
|
return MethodHandles.collectArguments(
|
||||||
DOUBLE_TO_BOOL, 0, value
|
DOUBLE_TO_BOOL, 0, value
|
||||||
@ -340,13 +340,13 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
|
|||||||
// Inject left as primary condition, on failure take right with data parameter
|
// Inject left as primary condition, on failure take right with data parameter
|
||||||
// logic = (Double,ExecutionData)Double
|
// logic = (Double,ExecutionData)Double
|
||||||
MethodHandle logic = MethodHandles.guardWithTest(
|
MethodHandle logic = MethodHandles.guardWithTest(
|
||||||
// data arg dropped implicitly
|
// data arg dropped implicitly -- (Double)boolean;
|
||||||
DOUBLE_TO_BOOL,
|
DOUBLE_TO_BOOL,
|
||||||
// drop data arg
|
// drop data arg -- (Double,ExecutionData)Double;
|
||||||
MethodHandles.dropArguments(
|
MethodHandles.dropArguments(
|
||||||
MethodHandles.identity(Double.class), 1, ExecutionData.class
|
MethodHandles.identity(Double.class), 1, ExecutionData.class
|
||||||
),
|
),
|
||||||
// drop left arg, call right
|
// drop left arg, call right -- (Double,ExecutionData)Double;
|
||||||
MethodHandles.dropArguments(
|
MethodHandles.dropArguments(
|
||||||
right, 0, Double.class
|
right, 0, Double.class
|
||||||
)
|
)
|
||||||
@ -367,9 +367,8 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
|
|||||||
// Map two data args to two double args, then evaluate op
|
// Map two data args to two double args, then evaluate op
|
||||||
MethodHandle doubleData = MethodHandles.filterArguments(
|
MethodHandle doubleData = MethodHandles.filterArguments(
|
||||||
CALL_BINARY_OP.bindTo(op), 0,
|
CALL_BINARY_OP.bindTo(op), 0,
|
||||||
mhLeft.asType(mhLeft.type().unwrap()), mhRight.asType(mhRight.type().unwrap())
|
unboxDoubles(mhLeft), unboxDoubles(mhRight)
|
||||||
);
|
);
|
||||||
doubleData = doubleData.asType(doubleData.type().wrap());
|
|
||||||
return ExpressionHandles.dedupData(doubleData);
|
return ExpressionHandles.dedupData(doubleData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.internal.expression.invoke;
|
package com.sk89q.worldedit.internal.expression.invoke;
|
||||||
|
|
||||||
import com.google.common.collect.SetMultimap;
|
|
||||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||||
import com.sk89q.worldedit.internal.expression.CompiledExpression;
|
import com.sk89q.worldedit.internal.expression.CompiledExpression;
|
||||||
|
import com.sk89q.worldedit.internal.expression.Functions;
|
||||||
|
|
||||||
import java.lang.invoke.LambdaConversionException;
|
import java.lang.invoke.LambdaConversionException;
|
||||||
import java.lang.invoke.LambdaMetafactory;
|
import java.lang.invoke.LambdaMetafactory;
|
||||||
@ -66,7 +66,7 @@ public class ExpressionCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CompiledExpression compileExpression(ExpressionParser.AllStatementsContext root,
|
public CompiledExpression compileExpression(ExpressionParser.AllStatementsContext root,
|
||||||
SetMultimap<String, MethodHandle> functions) {
|
Functions functions) {
|
||||||
MethodHandle invokable = root.accept(new CompilingVisitor(functions));
|
MethodHandle invokable = root.accept(new CompilingVisitor(functions));
|
||||||
return (CompiledExpression) safeInvoke(
|
return (CompiledExpression) safeInvoke(
|
||||||
HANDLE_TO_CE_CONVERTER, h -> h.invoke(invokable)
|
HANDLE_TO_CE_CONVERTER, h -> h.invoke(invokable)
|
||||||
|
@ -38,10 +38,10 @@ import java.lang.invoke.MethodType;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.DoubleBinaryOperator;
|
import java.util.function.DoubleBinaryOperator;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.check;
|
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.check;
|
||||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.checkIterations;
|
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.checkIterations;
|
||||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.checkTimeout;
|
|
||||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.getErrorPosition;
|
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.getErrorPosition;
|
||||||
import static java.lang.invoke.MethodHandles.collectArguments;
|
import static java.lang.invoke.MethodHandles.collectArguments;
|
||||||
import static java.lang.invoke.MethodHandles.constant;
|
import static java.lang.invoke.MethodHandles.constant;
|
||||||
@ -63,8 +63,11 @@ class ExpressionHandles {
|
|||||||
private static final MethodHandle SIMPLE_FOR_LOOP_IMPL;
|
private static final MethodHandle SIMPLE_FOR_LOOP_IMPL;
|
||||||
private static final MethodHandle SWITCH_IMPL;
|
private static final MethodHandle SWITCH_IMPL;
|
||||||
|
|
||||||
|
// (Object)boolean;
|
||||||
static final MethodHandle IS_NULL;
|
static final MethodHandle IS_NULL;
|
||||||
|
// (Double)boolean;
|
||||||
static final MethodHandle DOUBLE_TO_BOOL;
|
static final MethodHandle DOUBLE_TO_BOOL;
|
||||||
|
// (double, double)Double;
|
||||||
static final MethodHandle CALL_BINARY_OP;
|
static final MethodHandle CALL_BINARY_OP;
|
||||||
static final MethodHandle NEW_LS_CONSTANT;
|
static final MethodHandle NEW_LS_CONSTANT;
|
||||||
|
|
||||||
@ -95,10 +98,11 @@ class ExpressionHandles {
|
|||||||
|
|
||||||
IS_NULL = lookup.findStatic(Objects.class, "isNull",
|
IS_NULL = lookup.findStatic(Objects.class, "isNull",
|
||||||
methodType(boolean.class, Object.class));
|
methodType(boolean.class, Object.class));
|
||||||
DOUBLE_TO_BOOL = lookup.findStatic(ExpressionHandles.class, "doubleToBool",
|
DOUBLE_TO_BOOL = boxDoubles(lookup.findStatic(ExpressionHandles.class, "doubleToBool",
|
||||||
methodType(boolean.class, double.class));
|
methodType(boolean.class, double.class)));
|
||||||
CALL_BINARY_OP = lookup.findVirtual(DoubleBinaryOperator.class, "applyAsDouble",
|
CALL_BINARY_OP = lookup.findVirtual(DoubleBinaryOperator.class, "applyAsDouble",
|
||||||
methodType(double.class, double.class, double.class));
|
methodType(double.class, double.class, double.class))
|
||||||
|
.asType(methodType(Double.class, DoubleBinaryOperator.class, double.class, double.class));
|
||||||
NEW_LS_CONSTANT = lookup.findConstructor(LocalSlot.Constant.class,
|
NEW_LS_CONSTANT = lookup.findConstructor(LocalSlot.Constant.class,
|
||||||
methodType(void.class, double.class));
|
methodType(void.class, double.class));
|
||||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
@ -106,6 +110,34 @@ class ExpressionHandles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MethodHandle boxDoubles(MethodHandle handle) {
|
||||||
|
MethodType type = handle.type();
|
||||||
|
type = methodType(
|
||||||
|
boxIfPrimitiveDouble(type.returnType()),
|
||||||
|
type.parameterList().stream().map(ExpressionHandles::boxIfPrimitiveDouble)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
return handle.asType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> boxIfPrimitiveDouble(Class<?> clazz) {
|
||||||
|
return clazz == double.class ? Double.class : clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MethodHandle unboxDoubles(MethodHandle handle) {
|
||||||
|
MethodType type = handle.type();
|
||||||
|
type = methodType(
|
||||||
|
unboxIfDouble(type.returnType()),
|
||||||
|
type.parameterList().stream().map(ExpressionHandles::unboxIfDouble)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
return handle.asType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> unboxIfDouble(Class<?> clazz) {
|
||||||
|
return clazz == Double.class ? double.class : clazz;
|
||||||
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface Invokable {
|
interface Invokable {
|
||||||
Object invoke(MethodHandle handle) throws Throwable;
|
Object invoke(MethodHandle handle) throws Throwable;
|
||||||
@ -237,7 +269,7 @@ class ExpressionHandles {
|
|||||||
}
|
}
|
||||||
while ((boolean) standardInvoke(condition, data)) {
|
while ((boolean) standardInvoke(condition, data)) {
|
||||||
checkIterations(iterations, body.ctx);
|
checkIterations(iterations, body.ctx);
|
||||||
checkTimeout();
|
data.checkDeadline();
|
||||||
iterations++;
|
iterations++;
|
||||||
try {
|
try {
|
||||||
result = (Double) standardInvoke(body.handle, data);
|
result = (Double) standardInvoke(body.handle, data);
|
||||||
@ -264,7 +296,7 @@ class ExpressionHandles {
|
|||||||
int iterations = 0;
|
int iterations = 0;
|
||||||
do {
|
do {
|
||||||
checkIterations(iterations, body.ctx);
|
checkIterations(iterations, body.ctx);
|
||||||
checkTimeout();
|
data.checkDeadline();
|
||||||
iterations++;
|
iterations++;
|
||||||
try {
|
try {
|
||||||
result = (Double) standardInvoke(body.handle, data);
|
result = (Double) standardInvoke(body.handle, data);
|
||||||
@ -297,7 +329,7 @@ class ExpressionHandles {
|
|||||||
LocalSlot.Variable variable = initVariable(data, counterToken);
|
LocalSlot.Variable variable = initVariable(data, counterToken);
|
||||||
for (double i = first; i <= last; i++) {
|
for (double i = first; i <= last; i++) {
|
||||||
checkIterations(iterations, body.ctx);
|
checkIterations(iterations, body.ctx);
|
||||||
checkTimeout();
|
data.checkDeadline();
|
||||||
iterations++;
|
iterations++;
|
||||||
variable.setValue(i);
|
variable.setValue(i);
|
||||||
try {
|
try {
|
||||||
|
@ -47,6 +47,12 @@ class ExpressionTest extends BaseExpressionTest {
|
|||||||
|
|
||||||
// check variables
|
// check variables
|
||||||
assertEquals(8, compile("foo+bar", "foo", "bar").evaluate(5D, 3D), 0);
|
assertEquals(8, compile("foo+bar", "foo", "bar").evaluate(5D, 3D), 0);
|
||||||
|
|
||||||
|
// check conditionals
|
||||||
|
assertEquals(5, simpleEval("0 || 5"), 0);
|
||||||
|
assertEquals(2, simpleEval("2 || 5"), 0);
|
||||||
|
assertEquals(5, simpleEval("2 && 5"), 0);
|
||||||
|
assertEquals(0, simpleEval("5 && 0"), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren