From e8bc0c0e1fe79e9a0a31db589527d79830078ae6 Mon Sep 17 00:00:00 2001 From: Octavia Togami Date: Fri, 21 Feb 2020 00:17:49 -0800 Subject: [PATCH] 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) --- .../internal/expression/ExecutionData.java | 24 +++-- .../internal/expression/Expression.java | 99 ++----------------- .../internal/expression/ExpressionHelper.java | 11 +-- .../expression/ExpressionValidator.java | 6 +- .../internal/expression/Functions.java | 88 +++++++++-------- .../expression/invoke/CompilingVisitor.java | 17 ++-- .../expression/invoke/ExpressionCompiler.java | 4 +- .../expression/invoke/ExpressionHandles.java | 46 +++++++-- .../internal/expression/ExpressionTest.java | 6 ++ 9 files changed, 131 insertions(+), 170 deletions(-) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExecutionData.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExecutionData.java index 68fef9a56..0bd238ddf 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExecutionData.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExecutionData.java @@ -19,9 +19,7 @@ package com.sk89q.worldedit.internal.expression; -import com.google.common.collect.SetMultimap; - -import java.lang.invoke.MethodHandle; +import java.time.Instant; 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, * 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 SetMultimap functions; + private final Functions functions; + private final Instant deadline; - public ExecutionData(SlotTable slots, SetMultimap functions) { + public ExecutionData(SlotTable slots, Functions functions, Instant deadline) { this.slots = slots; this.functions = functions; + this.deadline = deadline; } public SlotTable getSlots() { return requireNonNull(slots, "Cannot use variables in a constant"); } - public SetMultimap getFunctions() { + public Functions getFunctions() { 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."); + } + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Expression.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Expression.java index ef43748f8..37f0ec10e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Expression.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Expression.java @@ -19,32 +19,20 @@ package com.sk89q.worldedit.internal.expression; -import com.google.common.base.Throwables; 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.antlr.ExpressionLexer; import com.sk89q.worldedit.antlr.ExpressionParser; 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.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTreeWalker; -import java.lang.invoke.MethodHandle; +import java.time.Instant; import java.util.List; 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. @@ -74,19 +62,10 @@ import java.util.concurrent.TimeoutException; */ public class Expression { - private static final ThreadLocal> 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 List providedSlots; private final ExpressionParser.AllStatementsContext root; - private final SetMultimap functions = Functions.getFunctionMap(); - private ExpressionEnvironment environment; + private final Functions functions = Functions.create(); private final CompiledExpression compiledExpression; public static Expression compile(String expression, String... variableNames) throws ExpressionException { @@ -138,52 +117,9 @@ public class Expression { slot.setValue(values[i]); } + Instant deadline = Instant.now().plusMillis(timeout); // evaluation exceptions are thrown out of this method - if (timeout < 0) { - return evaluateRoot(); - } - return evaluateRootTimed(timeout); - } - - private double evaluateRootTimed(int timeout) throws EvaluationException { - CountDownLatch startLatch = new CountDownLatch(1); - Request request = Request.request(); - Future 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(); - } + return compiledExpression.execute(new ExecutionData(slots, functions, deadline)); } public void optimize() { @@ -199,35 +135,12 @@ public class Expression { return slots; } - public static Expression getInstance() { - return instance.get().peek(); - } - - private void pushInstance() { - Stack threadLocalExprStack = instance.get(); - if (threadLocalExprStack == null) { - instance.set(threadLocalExprStack = new Stack<>()); - } - - threadLocalExprStack.push(this); - } - - private void popInstance() { - Stack threadLocalExprStack = instance.get(); - - threadLocalExprStack.pop(); - - if (threadLocalExprStack.isEmpty()) { - instance.set(null); - } - } - public ExpressionEnvironment getEnvironment() { - return environment; + return functions.getEnvironment(); } public void setEnvironment(ExpressionEnvironment environment) { - this.environment = environment; + functions.setEnvironment(environment); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java index dc9e58775..984b50b9a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionHelper.java @@ -19,7 +19,6 @@ package com.sk89q.worldedit.internal.expression; -import com.google.common.collect.SetMultimap; import com.sk89q.worldedit.antlr.ExpressionParser; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; @@ -60,16 +59,10 @@ public class ExpressionHelper { check(iterations <= 256, ctx, "Loop exceeded 256 iterations"); } - public static void checkTimeout() { - if (Thread.interrupted()) { - throw new ExpressionTimeoutException("Calculations exceeded time limit."); - } - } - - public static MethodHandle resolveFunction(SetMultimap functions, + public static MethodHandle resolveFunction(Functions functions, ExpressionParser.FunctionCallContext ctx) { String fnName = ctx.name.getText(); - Set matchingFns = functions.get(fnName); + Set matchingFns = functions.getMap().get(fnName); check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'"); for (MethodHandle function : matchingFns) { MethodType type = function.type(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionValidator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionValidator.java index c823df306..0361a4a73 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionValidator.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/ExpressionValidator.java @@ -19,11 +19,9 @@ package com.sk89q.worldedit.internal.expression; -import com.google.common.collect.SetMultimap; import com.sk89q.worldedit.antlr.ExpressionBaseListener; import com.sk89q.worldedit.antlr.ExpressionParser; -import java.lang.invoke.MethodHandle; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -34,10 +32,10 @@ import static com.sk89q.worldedit.internal.expression.ExpressionHelper.resolveFu class ExpressionValidator extends ExpressionBaseListener { private final Set variableNames = new HashSet<>(); - private final SetMultimap functions; + private final Functions functions; ExpressionValidator(Collection variableNames, - SetMultimap functions) { + Functions functions) { this.variableNames.addAll(variableNames); this.functions = functions; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java index 4d16a01dc..1cbf2baee 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java @@ -44,25 +44,10 @@ import static java.lang.invoke.MethodType.methodType; /** * Contains all functions that can be used in expressions. */ -final class Functions { +public final class Functions { - static SetMultimap getFunctionMap() { - SetMultimap map = HashMultimap.create(); - 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) - ); + static Functions create() { + return new Functions(); } private static final MethodHandle DOUBLE_VALUE; @@ -149,15 +134,6 @@ final class Functions { map.put("ridgedmulti", lookup.findStatic(Functions.class, "ridgedmulti", methodType(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( @@ -174,6 +150,20 @@ final class Functions { methodType(double.class, double.class, double.class, double.class, double.class, double.class, double.class), Functions.class) .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) { @@ -201,6 +191,35 @@ final class Functions { private static final Int2ObjectMap globalMegaBuffer = new Int2ObjectOpenHashMap<>(); private final Int2ObjectMap megaBuffer = new Int2ObjectOpenHashMap<>(); + private final SetMultimap map; + private ExpressionEnvironment environment; + + private Functions() { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + SetMultimap 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 getMap() { + return map; + } + + public ExpressionEnvironment getEnvironment() { + return environment; + } + + public void setEnvironment(ExpressionEnvironment environment) { + this.environment = environment; + } private static double[] getSubBuffer(Int2ObjectMap megabuf, int key) { return megabuf.computeIfAbsent(key, k -> new double[1024]); @@ -332,9 +351,7 @@ final class Functions { return ret; } - private static double query(double x, double y, double z, LocalSlot type, LocalSlot data) { - final ExpressionEnvironment environment = Expression.getInstance().getEnvironment(); - + private double query(double x, double y, double z, LocalSlot type, LocalSlot data) { // Read values from world final double typeId = environment.getBlockType(x, y, z); final double dataValue = environment.getBlockData(x, y, z); @@ -342,9 +359,7 @@ final class Functions { return queryInternal(type, data, typeId, dataValue); } - private static double queryAbs(double x, double y, double z, LocalSlot type, LocalSlot data) { - final ExpressionEnvironment environment = Expression.getInstance().getEnvironment(); - + private double queryAbs(double x, double y, double z, LocalSlot type, LocalSlot data) { // Read values from world final double typeId = environment.getBlockTypeAbs(x, y, z); final double dataValue = environment.getBlockDataAbs(x, y, z); @@ -352,9 +367,7 @@ final class Functions { return queryInternal(type, data, typeId, dataValue); } - private static double queryRel(double x, double y, double z, LocalSlot type, LocalSlot data) { - final ExpressionEnvironment environment = Expression.getInstance().getEnvironment(); - + private double queryRel(double x, double y, double z, LocalSlot type, LocalSlot data) { // Read values from world final double typeId = environment.getBlockTypeRel(x, y, z); final double dataValue = environment.getBlockDataRel(x, y, z); @@ -362,7 +375,4 @@ final class Functions { return queryInternal(type, data, typeId, dataValue); } - private Functions() { - } - } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/CompilingVisitor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/CompilingVisitor.java index e0bf4f701..2bf2f5b33 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/CompilingVisitor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/CompilingVisitor.java @@ -19,13 +19,13 @@ package com.sk89q.worldedit.internal.expression.invoke; -import com.google.common.collect.SetMultimap; import com.sk89q.worldedit.antlr.ExpressionBaseVisitor; import com.sk89q.worldedit.antlr.ExpressionParser; import com.sk89q.worldedit.internal.expression.BreakException; import com.sk89q.worldedit.internal.expression.EvaluationException; import com.sk89q.worldedit.internal.expression.ExecutionData; import com.sk89q.worldedit.internal.expression.ExpressionHelper; +import com.sk89q.worldedit.internal.expression.Functions; import com.sk89q.worldedit.internal.expression.LocalSlot; import it.unimi.dsi.fastutil.doubles.Double2ObjectLinkedOpenHashMap; 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.NEW_LS_CONSTANT; 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; /** @@ -86,9 +87,9 @@ class CompilingVisitor extends ExpressionBaseVisitor { * (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. */ - private final SetMultimap functions; + private final Functions functions; - CompilingVisitor(SetMultimap functions) { + CompilingVisitor(Functions functions) { this.functions = functions; } @@ -136,7 +137,6 @@ class CompilingVisitor extends ExpressionBaseVisitor { private MethodHandle evaluateBoolean(ParserRuleContext boolExpression) { MethodHandle value = evaluateForNamedValue(boolExpression, "a boolean"); - value = value.asType(value.type().unwrap()); // Pass `value` into converter, returns (ExecutionData)boolean; return MethodHandles.collectArguments( DOUBLE_TO_BOOL, 0, value @@ -340,13 +340,13 @@ class CompilingVisitor extends ExpressionBaseVisitor { // Inject left as primary condition, on failure take right with data parameter // logic = (Double,ExecutionData)Double MethodHandle logic = MethodHandles.guardWithTest( - // data arg dropped implicitly + // data arg dropped implicitly -- (Double)boolean; DOUBLE_TO_BOOL, - // drop data arg + // drop data arg -- (Double,ExecutionData)Double; MethodHandles.dropArguments( MethodHandles.identity(Double.class), 1, ExecutionData.class ), - // drop left arg, call right + // drop left arg, call right -- (Double,ExecutionData)Double; MethodHandles.dropArguments( right, 0, Double.class ) @@ -367,9 +367,8 @@ class CompilingVisitor extends ExpressionBaseVisitor { // Map two data args to two double args, then evaluate op MethodHandle doubleData = MethodHandles.filterArguments( 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); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/ExpressionCompiler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/ExpressionCompiler.java index 2811a3760..7d2fb6799 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/ExpressionCompiler.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/ExpressionCompiler.java @@ -19,9 +19,9 @@ package com.sk89q.worldedit.internal.expression.invoke; -import com.google.common.collect.SetMultimap; import com.sk89q.worldedit.antlr.ExpressionParser; import com.sk89q.worldedit.internal.expression.CompiledExpression; +import com.sk89q.worldedit.internal.expression.Functions; import java.lang.invoke.LambdaConversionException; import java.lang.invoke.LambdaMetafactory; @@ -66,7 +66,7 @@ public class ExpressionCompiler { } public CompiledExpression compileExpression(ExpressionParser.AllStatementsContext root, - SetMultimap functions) { + Functions functions) { MethodHandle invokable = root.accept(new CompilingVisitor(functions)); return (CompiledExpression) safeInvoke( HANDLE_TO_CE_CONVERTER, h -> h.invoke(invokable) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/ExpressionHandles.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/ExpressionHandles.java index 9846dde65..0d118d6be 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/ExpressionHandles.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/invoke/ExpressionHandles.java @@ -38,10 +38,10 @@ import java.lang.invoke.MethodType; import java.util.Objects; import java.util.function.DoubleBinaryOperator; 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.checkIterations; -import static com.sk89q.worldedit.internal.expression.ExpressionHelper.checkTimeout; import static com.sk89q.worldedit.internal.expression.ExpressionHelper.getErrorPosition; import static java.lang.invoke.MethodHandles.collectArguments; 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 SWITCH_IMPL; + // (Object)boolean; static final MethodHandle IS_NULL; + // (Double)boolean; static final MethodHandle DOUBLE_TO_BOOL; + // (double, double)Double; static final MethodHandle CALL_BINARY_OP; static final MethodHandle NEW_LS_CONSTANT; @@ -95,10 +98,11 @@ class ExpressionHandles { IS_NULL = lookup.findStatic(Objects.class, "isNull", methodType(boolean.class, Object.class)); - DOUBLE_TO_BOOL = lookup.findStatic(ExpressionHandles.class, "doubleToBool", - methodType(boolean.class, double.class)); + DOUBLE_TO_BOOL = boxDoubles(lookup.findStatic(ExpressionHandles.class, "doubleToBool", + methodType(boolean.class, double.class))); 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, methodType(void.class, double.class)); } 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 interface Invokable { Object invoke(MethodHandle handle) throws Throwable; @@ -237,7 +269,7 @@ class ExpressionHandles { } while ((boolean) standardInvoke(condition, data)) { checkIterations(iterations, body.ctx); - checkTimeout(); + data.checkDeadline(); iterations++; try { result = (Double) standardInvoke(body.handle, data); @@ -264,7 +296,7 @@ class ExpressionHandles { int iterations = 0; do { checkIterations(iterations, body.ctx); - checkTimeout(); + data.checkDeadline(); iterations++; try { result = (Double) standardInvoke(body.handle, data); @@ -297,7 +329,7 @@ class ExpressionHandles { LocalSlot.Variable variable = initVariable(data, counterToken); for (double i = first; i <= last; i++) { checkIterations(iterations, body.ctx); - checkTimeout(); + data.checkDeadline(); iterations++; variable.setValue(i); try { diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java index 2ffa12c1d..2cea279ce 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java @@ -47,6 +47,12 @@ class ExpressionTest extends BaseExpressionTest { // check variables 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