Upstream and some refactoring

Note: Maybe this doesn't compile, ij is broken af smh, let's give it a try...
This commit is contained in:
NotMyFault 2020-01-04 18:34:30 +01:00
parent 0bf6cfad8d
commit b292416496
29 changed files with 1556 additions and 805 deletions

View File

@ -28,6 +28,7 @@ import com.sk89q.worldedit.bukkit.adapter.IBukkitAdapter;
import com.sk89q.worldedit.bukkit.adapter.SimpleBukkitAdapter;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.PlayerProxy;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
@ -43,6 +44,7 @@ import com.sk89q.worldedit.world.item.ItemType;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@ -108,6 +110,16 @@ public enum BukkitAdapter {
return getAdapter().adapt(world);
}
/**
* Create a WorldEdit Actor from a Bukkit CommandSender
*
* @param sender The Bukkit CommandSender
* @return The WorldEdit Actor
*/
public static Actor adapt(CommandSender sender) {
return WorldEditPlugin.getInstance().wrapCommandSender(sender);
}
/**
* Create a WorldEdit Player from a Bukkit Player.
*
@ -118,6 +130,21 @@ public enum BukkitAdapter {
return WorldEditPlugin.getInstance().wrapPlayer(player);
}
/**
* Create a Bukkit CommandSender from a WorldEdit Actor.
*
* @param actor The WorldEdit actor
* @return The Bukkit command sender
*/
public static CommandSender adapt(Actor actor) {
if (actor instanceof com.sk89q.worldedit.entity.Player) {
return adapt((com.sk89q.worldedit.entity.Player) actor);
} else if (actor instanceof BukkitBlockCommandSender) {
return ((BukkitBlockCommandSender) actor).getSender();
}
return ((BukkitCommandSender) actor).getSender();
}
/**
* Create a Bukkit Player from a WorldEdit Player.
*

View File

@ -34,7 +34,6 @@ import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.command.BlockCommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
@ -127,7 +126,10 @@ public class BukkitBlockCommandSender extends AbstractNonPlayerActor implements
@Override
public void setPermission(String permission, boolean value) {
}
public BlockCommandSender getSender() {
return this.sender;
}
@Override

View File

@ -101,6 +101,10 @@ public class BukkitCommandSender extends AbstractNonPlayerActor {
return WorldEdit.getInstance().getConfiguration().defaultLocale;
}
public CommandSender getSender() {
return this.sender;
}
@Override
public SessionKey getSessionKey() {
return new SessionKey() {

View File

@ -21,4 +21,5 @@
* This package contains the old command system. It is no longer in use. Please switch
* to Piston, Intake, ACF, or similar systems.
*/
@Deprecated
package com.sk89q.minecraft.util.commands;

View File

@ -131,7 +131,7 @@ public class HistoryCommands {
}
}
if (timesRedone > 0) {
actor.printInfo(TranslatableComponent.of("worldedit.redo.undone", TextComponent.of(timesRedone)));
actor.printInfo(TranslatableComponent.of("worldedit.redo.redone", TextComponent.of(timesRedone)));
} else {
actor.printError(TranslatableComponent.of("worldedit.redo.none"));
}

View File

@ -24,8 +24,8 @@ import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
@ -34,6 +34,7 @@ import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
import com.sk89q.worldedit.util.formatting.text.format.TextColor;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.registry.LegacyMapper;
import java.util.OptionalInt;
@ -63,10 +64,15 @@ public class QueryTool implements BlockTool {
final OptionalInt internalId = BlockStateIdAccess.getBlockStateId(block.toImmutableState());
if (internalId.isPresent()) {
builder.append(TextComponent.of(" (" + internalId.getAsInt() + ") ", TextColor.DARK_GRAY)
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.internalid.hover"))));
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.internalid.hover"))));
}
final int[] legacy = LegacyMapper.getInstance().getLegacyFromBlock(block.toImmutableState());
if (legacy != null) {
builder.append(TextComponent.of(" (" + legacy[0] + ":" + legacy[1] + ") ", TextColor.DARK_GRAY)
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.legacy.hover"))));
}
builder.append(TextComponent.of(" (" + world.getBlockLightLevel(blockPoint) + "/"
+ world.getBlockLightLevel(blockPoint.add(0, 1, 0)) + ")", TextColor.WHITE)
+ world.getBlockLightLevel(blockPoint.add(0, 1, 0)) + ")", TextColor.WHITE)
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.light.hover"))));
player.print(builder.build());

View File

@ -25,9 +25,12 @@ package com.sk89q.worldedit.internal.expression;
*/
public class BreakException extends RuntimeException {
public static final BreakException BREAK = new BreakException(false);
public static final BreakException CONTINUE = new BreakException(true);
public final boolean doContinue;
public BreakException(boolean doContinue) {
private BreakException(boolean doContinue) {
super(doContinue ? "'continue' encountered outside a loop" : "'break' encountered outside a loop",
null, true, false);

View File

@ -0,0 +1,29 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression;
/**
* Represents a "compiled" expression.
*/
public interface CompiledExpression {
Double execute(ExecutionData executionData);
}

View File

@ -1,625 +0,0 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression;
import com.google.common.base.Throwables;
import com.google.common.collect.SetMultimap;
import com.sk89q.worldedit.antlr.ExpressionBaseVisitor;
import com.sk89q.worldedit.antlr.ExpressionParser;
import it.unimi.dsi.fastutil.doubles.Double2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
import it.unimi.dsi.fastutil.doubles.Double2ObjectMaps;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.RuleNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.List;
import java.util.Optional;
import java.util.function.DoubleBinaryOperator;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.sk89q.worldedit.antlr.ExpressionLexer.ASSIGN;
import static com.sk89q.worldedit.antlr.ExpressionLexer.DIVIDE;
import static com.sk89q.worldedit.antlr.ExpressionLexer.DIVIDE_ASSIGN;
import static com.sk89q.worldedit.antlr.ExpressionLexer.EQUAL;
import static com.sk89q.worldedit.antlr.ExpressionLexer.EXCLAMATION_MARK;
import static com.sk89q.worldedit.antlr.ExpressionLexer.GREATER_THAN;
import static com.sk89q.worldedit.antlr.ExpressionLexer.GREATER_THAN_OR_EQUAL;
import static com.sk89q.worldedit.antlr.ExpressionLexer.INCREMENT;
import static com.sk89q.worldedit.antlr.ExpressionLexer.LEFT_SHIFT;
import static com.sk89q.worldedit.antlr.ExpressionLexer.LESS_THAN;
import static com.sk89q.worldedit.antlr.ExpressionLexer.LESS_THAN_OR_EQUAL;
import static com.sk89q.worldedit.antlr.ExpressionLexer.MINUS;
import static com.sk89q.worldedit.antlr.ExpressionLexer.MINUS_ASSIGN;
import static com.sk89q.worldedit.antlr.ExpressionLexer.MODULO;
import static com.sk89q.worldedit.antlr.ExpressionLexer.MODULO_ASSIGN;
import static com.sk89q.worldedit.antlr.ExpressionLexer.NEAR;
import static com.sk89q.worldedit.antlr.ExpressionLexer.NOT_EQUAL;
import static com.sk89q.worldedit.antlr.ExpressionLexer.PLUS;
import static com.sk89q.worldedit.antlr.ExpressionLexer.PLUS_ASSIGN;
import static com.sk89q.worldedit.antlr.ExpressionLexer.POWER_ASSIGN;
import static com.sk89q.worldedit.antlr.ExpressionLexer.RIGHT_SHIFT;
import static com.sk89q.worldedit.antlr.ExpressionLexer.TIMES;
import static com.sk89q.worldedit.antlr.ExpressionLexer.TIMES_ASSIGN;
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.WRAPPED_CONSTANT;
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.evalException;
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.getArgumentHandleName;
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.resolveFunction;
class EvaluatingVisitor extends ExpressionBaseVisitor<Double> {
private final SlotTable slots;
private final SetMultimap<String, MethodHandle> functions;
EvaluatingVisitor(SlotTable slots,
SetMultimap<String, MethodHandle> functions) {
this.slots = slots;
this.functions = functions;
}
private LocalSlot.Variable initVariable(String name, ParserRuleContext ctx) {
return slots.initVariable(name)
.orElseThrow(() -> evalException(ctx, "Cannot overwrite non-variable '" + name + "'"));
}
private Supplier<EvaluationException> varNotInitException(String name, ParserRuleContext ctx) {
return () -> evalException(ctx, "'" + name + "' is not initialized yet");
}
private LocalSlot.Variable getVariable(String name, ParserRuleContext ctx) {
LocalSlot slot = slots.getSlot(name)
.orElseThrow(varNotInitException(name, ctx));
check(slot instanceof LocalSlot.Variable, ctx, "'" + name + "' is not a variable");
return (LocalSlot.Variable) slot;
}
private double getSlotValue(String name, ParserRuleContext ctx) {
return slots.getSlotValue(name)
.orElseThrow(varNotInitException(name, ctx));
}
private Token extractToken(ParserRuleContext ctx) {
List<TerminalNode> children = ctx.children.stream()
.filter(TerminalNode.class::isInstance)
.map(TerminalNode.class::cast)
.collect(Collectors.toList());
check(children.size() == 1, ctx, "Expected exactly one token, got " + children.size());
return children.get(0).getSymbol();
}
private Double evaluate(ParserRuleContext ctx) {
return ctx.accept(this);
}
private double evaluateForValue(ParserRuleContext ctx) {
Double result = evaluate(ctx);
check(result != null, ctx, "Invalid expression for a value");
return result;
}
private boolean evaluateBoolean(ParserRuleContext boolExpression) {
Double bool = evaluate(boolExpression);
check(bool != null, boolExpression, "Invalid expression for boolean");
return doubleToBool(bool);
}
private boolean doubleToBool(double bool) {
return bool > 0;
}
private double boolToDouble(boolean bool) {
return bool ? 1 : 0;
}
private Double evaluateConditional(ParserRuleContext condition,
ParserRuleContext trueBranch,
ParserRuleContext falseBranch) {
ParserRuleContext ctx = evaluateBoolean(condition) ? trueBranch : falseBranch;
return ctx == null ? null : evaluate(ctx);
}
@Override
public Double visitIfStatement(ExpressionParser.IfStatementContext ctx) {
return evaluateConditional(ctx.condition, ctx.trueBranch, ctx.falseBranch);
}
@Override
public Double visitTernaryExpr(ExpressionParser.TernaryExprContext ctx) {
return evaluateConditional(ctx.condition, ctx.trueBranch, ctx.falseBranch);
}
@Override
public Double visitWhileStatement(ExpressionParser.WhileStatementContext ctx) {
Double result = defaultResult();
int iterations = 0;
while (evaluateBoolean(ctx.condition)) {
checkIterations(iterations, ctx.body);
checkTimeout();
iterations++;
try {
result = evaluate(ctx.body);
} catch (BreakException ex) {
if (!ex.doContinue) {
break;
}
}
}
return result;
}
@Override
public Double visitDoStatement(ExpressionParser.DoStatementContext ctx) {
Double result = defaultResult();
int iterations = 0;
do {
checkIterations(iterations, ctx.body);
checkTimeout();
iterations++;
try {
result = evaluate(ctx.body);
} catch (BreakException ex) {
if (!ex.doContinue) {
break;
}
}
} while (evaluateBoolean(ctx.condition));
return result;
}
@Override
public Double visitForStatement(ExpressionParser.ForStatementContext ctx) {
Double result = defaultResult();
int iterations = 0;
evaluate(ctx.init);
while (evaluateBoolean(ctx.condition)) {
checkIterations(iterations, ctx.body);
checkTimeout();
iterations++;
try {
result = evaluate(ctx.body);
} catch (BreakException ex) {
if (!ex.doContinue) {
break;
}
}
evaluate(ctx.update);
}
return result;
}
@Override
public Double visitSimpleForStatement(ExpressionParser.SimpleForStatementContext ctx) {
Double result = defaultResult();
int iterations = 0;
double first = evaluateForValue(ctx.first);
double last = evaluateForValue(ctx.last);
String counter = ctx.counter.getText();
LocalSlot.Variable variable = initVariable(counter, ctx);
for (double i = first; i <= last; i++) {
checkIterations(iterations, ctx.body);
checkTimeout();
iterations++;
variable.setValue(i);
try {
result = evaluate(ctx.body);
} catch (BreakException ex) {
if (!ex.doContinue) {
break;
}
}
}
return result;
}
@Override
public Double visitBreakStatement(ExpressionParser.BreakStatementContext ctx) {
throw new BreakException(false);
}
@Override
public Double visitContinueStatement(ExpressionParser.ContinueStatementContext ctx) {
throw new BreakException(true);
}
@Override
public Double visitReturnStatement(ExpressionParser.ReturnStatementContext ctx) {
if (ctx.value != null) {
return evaluate(ctx.value);
}
return null;
}
@Override
public Double visitSwitchStatement(ExpressionParser.SwitchStatementContext ctx) {
Double2ObjectMap<ParserRuleContext> cases = new Double2ObjectLinkedOpenHashMap<>(ctx.labels.size());
ParserRuleContext defaultCase = null;
for (int i = 0; i < ctx.labels.size(); i++) {
ExpressionParser.SwitchLabelContext label = ctx.labels.get(i);
ExpressionParser.StatementsContext body = ctx.bodies.get(i);
if (label instanceof ExpressionParser.CaseContext) {
ExpressionParser.CaseContext caseContext = (ExpressionParser.CaseContext) label;
double key = evaluateForValue(caseContext.constant);
check(!cases.containsKey(key), body, "Duplicate cases detected.");
cases.put(key, body);
} else {
check(defaultCase == null, body, "Duplicate default cases detected.");
defaultCase = body;
}
}
double value = evaluateForValue(ctx.target);
boolean matched = false;
Double evaluated = null;
boolean falling = false;
for (Double2ObjectMap.Entry<ParserRuleContext> entry : Double2ObjectMaps.fastIterable(cases)) {
if (falling || entry.getDoubleKey() == value) {
matched = true;
try {
evaluated = evaluate(entry.getValue());
falling = true;
} catch (BreakException brk) {
check(!brk.doContinue, entry.getValue(), "Cannot continue in a switch");
falling = false;
break;
}
}
}
// This if is like the one in the loop, default's "case" is `!matched` & present
if ((falling || !matched) && defaultCase != null) {
try {
evaluated = evaluate(defaultCase);
} catch (BreakException brk) {
check(!brk.doContinue, defaultCase, "Cannot continue in a switch");
}
}
return evaluated;
}
@Override
public Double visitExpressionStatement(ExpressionParser.ExpressionStatementContext ctx) {
return evaluate(ctx.expression());
}
@Override
public Double visitPostCrementExpr(ExpressionParser.PostCrementExprContext ctx) {
String target = ctx.target.getText();
LocalSlot.Variable variable = getVariable(target, ctx);
double value = variable.getValue();
if (ctx.op.getType() == INCREMENT) {
value++;
} else {
value--;
}
variable.setValue(value);
return value;
}
@Override
public Double visitPreCrementExpr(ExpressionParser.PreCrementExprContext ctx) {
String target = ctx.target.getText();
LocalSlot.Variable variable = getVariable(target, ctx);
double value = variable.getValue();
double result = value;
if (ctx.op.getType() == INCREMENT) {
value++;
} else {
value--;
}
variable.setValue(value);
return result;
}
@Override
public Double visitPlusMinusExpr(ExpressionParser.PlusMinusExprContext ctx) {
double value = evaluateForValue(ctx.expr);
switch (ctx.op.getType()) {
case PLUS:
return +value;
case MINUS:
return -value;
}
throw evalException(ctx, "Invalid text for plus/minus expr: " + ctx.op.getText());
}
@Override
public Double visitNotExpr(ExpressionParser.NotExprContext ctx) {
return boolToDouble(!evaluateBoolean(ctx.expr));
}
@Override
public Double visitComplementExpr(ExpressionParser.ComplementExprContext ctx) {
return (double) ~(long) evaluateForValue(ctx.expr);
}
@Override
public Double visitConditionalAndExpr(ExpressionParser.ConditionalAndExprContext ctx) {
if (!evaluateBoolean(ctx.left)) {
return boolToDouble(false);
}
return evaluateForValue(ctx.right);
}
@Override
public Double visitConditionalOrExpr(ExpressionParser.ConditionalOrExprContext ctx) {
double left = evaluateForValue(ctx.left);
if (doubleToBool(left)) {
return left;
}
return evaluateForValue(ctx.right);
}
private double evaluateBinary(ParserRuleContext left,
ParserRuleContext right,
DoubleBinaryOperator op) {
return op.applyAsDouble(evaluateForValue(left), evaluateForValue(right));
}
@Override
public Double visitPowerExpr(ExpressionParser.PowerExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, Math::pow);
}
@Override
public Double visitMultiplicativeExpr(ExpressionParser.MultiplicativeExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
switch (ctx.op.getType()) {
case TIMES:
return l * r;
case DIVIDE:
return l / r;
case MODULO:
return l % r;
}
throw evalException(ctx, "Invalid text for multiplicative expr: " + ctx.op.getText());
});
}
@Override
public Double visitAddExpr(ExpressionParser.AddExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
switch (ctx.op.getType()) {
case PLUS:
return l + r;
case MINUS:
return l - r;
}
throw evalException(ctx, "Invalid text for additive expr: " + ctx.op.getText());
});
}
@Override
public Double visitShiftExpr(ExpressionParser.ShiftExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
switch (ctx.op.getType()) {
case LEFT_SHIFT:
return (double) ((long) l << (long) r);
case RIGHT_SHIFT:
return (double) ((long) l >> (long) r);
}
throw evalException(ctx, "Invalid text for shift expr: " + ctx.op.getText());
});
}
@Override
public Double visitRelationalExpr(ExpressionParser.RelationalExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
switch (ctx.op.getType()) {
case LESS_THAN:
return boolToDouble(l < r);
case LESS_THAN_OR_EQUAL:
return boolToDouble(l <= r);
case GREATER_THAN:
return boolToDouble(l > r);
case GREATER_THAN_OR_EQUAL:
return boolToDouble(l >= r);
}
throw evalException(ctx, "Invalid text for relational expr: " + ctx.op.getText());
});
}
@Override
public Double visitEqualityExpr(ExpressionParser.EqualityExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
switch (ctx.op.getType()) {
case EQUAL:
return boolToDouble(l == r);
case NOT_EQUAL:
return boolToDouble(l != r);
case NEAR:
return boolToDouble(almostEqual2sComplement(l, r, 450359963L));
case GREATER_THAN_OR_EQUAL:
return boolToDouble(l >= r);
}
throw evalException(ctx, "Invalid text for equality expr: " + ctx.op.getText());
});
}
// Usable AlmostEqual function, based on http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
private static boolean almostEqual2sComplement(double a, double b, long maxUlps) {
// Make sure maxUlps is non-negative and small enough that the
// default NAN won't compare as equal to anything.
//assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); // this is for floats, not doubles
long aLong = Double.doubleToRawLongBits(a);
// Make aLong lexicographically ordered as a twos-complement long
if (aLong < 0) aLong = 0x8000000000000000L - aLong;
long bLong = Double.doubleToRawLongBits(b);
// Make bLong lexicographically ordered as a twos-complement long
if (bLong < 0) bLong = 0x8000000000000000L - bLong;
final long longDiff = Math.abs(aLong - bLong);
return longDiff <= maxUlps;
}
@Override
public Double visitPostfixExpr(ExpressionParser.PostfixExprContext ctx) {
double value = evaluateForValue(ctx.expr);
if (ctx.op.getType() == EXCLAMATION_MARK) {
return factorial(value);
}
throw evalException(ctx,
"Invalid text for post-unary expr: " + ctx.op.getText());
}
private static final double[] factorials = new double[171];
static {
factorials[0] = 1;
for (int i = 1; i < factorials.length; ++i) {
factorials[i] = factorials[i - 1] * i;
}
}
private static double factorial(double x) throws EvaluationException {
final int n = (int) x;
if (n < 0) {
return 0;
}
if (n >= factorials.length) {
return Double.POSITIVE_INFINITY;
}
return factorials[n];
}
@Override
public Double visitAssignment(ExpressionParser.AssignmentContext ctx) {
int type = extractToken(ctx.assignmentOperator()).getType();
String target = ctx.target.getText();
double value;
double arg = evaluateForValue(ctx.expression());
LocalSlot.Variable variable;
if (type == ASSIGN) {
variable = initVariable(target, ctx);
value = arg;
} else {
variable = getVariable(target, ctx);
value = variable.getValue();
switch (type) {
case POWER_ASSIGN:
value = Math.pow(value, arg);
break;
case TIMES_ASSIGN:
value *= arg;
break;
case DIVIDE_ASSIGN:
value /= arg;
break;
case MODULO_ASSIGN:
value %= arg;
break;
case PLUS_ASSIGN:
value += arg;
break;
case MINUS_ASSIGN:
value -= arg;
break;
default:
throw evalException(ctx, "Invalid text for assign expr: " +
ctx.assignmentOperator().getText());
}
}
variable.setValue(value);
return value;
}
@Override
public Double visitFunctionCall(ExpressionParser.FunctionCallContext ctx) {
MethodHandle handle = resolveFunction(functions, ctx);
String fnName = ctx.name.getText();
Object[] arguments = new Object[ctx.args.size()];
for (int i = 0; i < arguments.length; i++) {
ExpressionParser.ExpressionContext arg = ctx.args.get(i);
Object transformed = getArgument(fnName, handle.type(), i, arg);
arguments[i] = transformed;
}
try {
// Some methods return other Numbers
Number number = (Number) handle.invokeWithArguments(arguments);
return number == null ? null : number.doubleValue();
} catch (Throwable throwable) {
Throwables.throwIfUnchecked(throwable);
throw new RuntimeException(throwable);
}
}
private Object getArgument(String fnName, MethodType type, int i, ParserRuleContext arg) {
// Pass variable handle in for modification?
String handleName = getArgumentHandleName(fnName, type, i, arg);
if (handleName == null) {
return evaluateForValue(arg);
}
if (handleName.equals(WRAPPED_CONSTANT)) {
return new LocalSlot.Constant(evaluateForValue(arg));
}
return getVariable(handleName, arg);
}
@Override
public Double visitConstantExpression(ExpressionParser.ConstantExpressionContext ctx) {
try {
return Double.parseDouble(ctx.getText());
} catch (NumberFormatException e) {
// Rare, but might happen, e.g. if too many digits
throw evalException(ctx, "Invalid constant: " + e.getMessage());
}
}
@Override
public Double visitIdExpr(ExpressionParser.IdExprContext ctx) {
String source = ctx.source.getText();
return getSlotValue(source, ctx);
}
@Override
public Double visitChildren(RuleNode node) {
Double result = defaultResult();
int n = node.getChildCount();
for (int i = 0; i < n; i++) {
ParseTree c = node.getChild(i);
if (c instanceof TerminalNode && ((TerminalNode) c).getSymbol().getType() == Token.EOF) {
break;
}
Double childResult = c.accept(this);
if (c instanceof ExpressionParser.ReturnStatementContext) {
return childResult;
}
result = aggregateResult(result, childResult);
}
return result;
}
@Override
protected Double aggregateResult(Double aggregate, Double nextResult) {
return Optional.ofNullable(nextResult).orElse(aggregate);
}
}

View File

@ -19,8 +19,6 @@
package com.sk89q.worldedit.internal.expression;
import com.sk89q.worldedit.internal.expression.ExpressionException;
/**
* Thrown when there's a problem during expression evaluation.
*/

View File

@ -0,0 +1,51 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression;
import com.google.common.collect.SetMultimap;
import java.lang.invoke.MethodHandle;
import static java.util.Objects.requireNonNull;
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);
private final SlotTable slots;
private final SetMultimap<String, MethodHandle> functions;
public ExecutionData(SlotTable slots, SetMultimap<String, MethodHandle> functions) {
this.slots = slots;
this.functions = functions;
}
public SlotTable getSlots() {
return requireNonNull(slots, "Cannot use variables in a constant");
}
public SetMultimap<String, MethodHandle> getFunctions() {
return requireNonNull(functions, "Cannot use functions in a constant");
}
}

View File

@ -26,6 +26,7 @@ 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;
@ -37,13 +38,7 @@ import java.lang.invoke.MethodHandle;
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;
import java.util.concurrent.*;
/**
* Compiles and evaluates expressions.
@ -83,14 +78,11 @@ public class Expression {
private final SlotTable slots = new SlotTable();
private final List<String> providedSlots;
private ExpressionParser.AllStatementsContext root;
private final ExpressionParser.AllStatementsContext root;
private final SetMultimap<String, MethodHandle> functions = Functions.getFunctionMap();
private final CompiledExpression compiledExpression;
private ExpressionEnvironment environment;
public static Expression compile(String expression, String... variableNames) throws ExpressionException {
return new Expression(expression, variableNames);
}
private Expression(String expression, String... variableNames) throws ExpressionException {
slots.putSlot("e", new LocalSlot.Constant(Math.E));
slots.putSlot("pi", new LocalSlot.Constant(Math.PI));
@ -119,6 +111,15 @@ public class Expression {
throw new ParserException(parser.getState(), e);
}
ParseTreeWalker.DEFAULT.walk(new ExpressionValidator(slots.keySet(), functions), root);
this.compiledExpression = new ExpressionCompiler().compileExpression(root, functions);
}
public static Expression compile(String expression, String... variableNames) throws ExpressionException {
return new Expression(expression, variableNames);
}
public static Expression getInstance() {
return instance.get().peek();
}
public double evaluate(double... values) throws EvaluationException {
@ -177,7 +178,7 @@ public class Expression {
private Double evaluateRoot() throws EvaluationException {
pushInstance();
try {
return root.accept(new EvaluatingVisitor(slots, functions));
return compiledExpression.execute(new ExecutionData(slots, functions));
} finally {
popInstance();
}
@ -196,10 +197,6 @@ public class Expression {
return slots;
}
public static Expression getInstance() {
return instance.get().peek();
}
private void pushInstance() {
Stack<Expression> threadLocalExprStack = instance.get();
if (threadLocalExprStack == null) {

View File

@ -33,33 +33,51 @@ import java.util.stream.Collectors;
import static com.sk89q.worldedit.antlr.ExpressionLexer.ID;
class ExpressionHelper {
public class ExpressionHelper {
static void check(boolean condition, ParserRuleContext ctx, String message) {
/**
* The argument should be wrapped in a {@link LocalSlot.Constant} before being passed.
*/
public static final String WRAPPED_CONSTANT = "<wrapped constant>";
private ExpressionHelper() {
}
public static void check(boolean condition, ParserRuleContext ctx, String message) {
if (!condition) {
throw evalException(ctx, message);
}
}
static EvaluationException evalException(ParserRuleContext ctx, String message) {
public static int getErrorPosition(Token token) {
return token.getCharPositionInLine();
}
public static EvaluationException evalException(ParserRuleContext ctx, String message) {
return evalException(ctx.start, message);
}
public static EvaluationException evalException(Token token, String message) {
return new EvaluationException(
ctx.getStart().getCharPositionInLine(),
message
getErrorPosition(token),
message
);
}
static void checkIterations(int iterations, ParserRuleContext ctx) {
public static void checkIterations(int iterations, ParserRuleContext ctx) {
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
}
static void checkTimeout() {
// Special argument handle names
public static void checkTimeout() {
if (Thread.interrupted()) {
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
}
}
static MethodHandle resolveFunction(SetMultimap<String, MethodHandle> functions,
ExpressionParser.FunctionCallContext ctx) {
public static MethodHandle resolveFunction(SetMultimap<String, MethodHandle> functions,
ExpressionParser.FunctionCallContext ctx) {
String fnName = ctx.name.getText();
Set<MethodHandle> matchingFns = functions.get(fnName);
check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'");
@ -79,34 +97,28 @@ class ExpressionHelper {
}
// We matched no function, fail with appropriate message.
String possibleCounts = matchingFns.stream()
.map(mh -> mh.isVarargsCollector()
? (mh.type().parameterCount() - 1) + "+"
: String.valueOf(mh.type().parameterCount()))
.collect(Collectors.joining("/"));
.map(mh -> mh.isVarargsCollector()
? (mh.type().parameterCount() - 1) + "+"
: String.valueOf(mh.type().parameterCount()))
.collect(Collectors.joining("/"));
throw evalException(ctx, "Incorrect number of arguments for function '" + fnName + "', " +
"expected " + possibleCounts + ", " +
"got " + ctx.args.size());
"expected " + possibleCounts + ", " +
"got " + ctx.args.size());
}
// Special argument handle names
/**
* The argument should be wrapped in a {@link LocalSlot.Constant} before being passed.
*/
static final String WRAPPED_CONSTANT = "<wrapped constant>";
/**
* If this argument needs a handle, returns the name of the handle needed. Otherwise, returns
* {@code null}. If {@code arg} isn't a valid handle reference, throws.
*/
static String getArgumentHandleName(String fnName, MethodType type, int i,
ParserRuleContext arg) {
public static String getArgumentHandleName(String fnName, MethodType type, int i,
ParserRuleContext arg) {
// Pass variable handle in for modification?
Class<?> pType = type.parameterType(i);
Optional<String> id = tryResolveId(arg);
if (pType == LocalSlot.Variable.class) {
// MUST be an id
check(id.isPresent(), arg,
"Function '" + fnName + "' requires a variable in parameter " + i);
"Function '" + fnName + "' requires a variable in parameter " + i);
return id.get();
} else if (pType == LocalSlot.class) {
return id.orElse(WRAPPED_CONSTANT);
@ -116,7 +128,7 @@ class ExpressionHelper {
private static Optional<String> tryResolveId(ParserRuleContext arg) {
Optional<ExpressionParser.WrappedExprContext> wrappedExprContext =
tryAs(arg, ExpressionParser.WrappedExprContext.class);
tryAs(arg, ExpressionParser.WrappedExprContext.class);
if (wrappedExprContext.isPresent()) {
return tryResolveId(wrappedExprContext.get().expression());
}
@ -127,8 +139,8 @@ class ExpressionHelper {
}
private static <T extends ParserRuleContext> Optional<T> tryAs(
ParserRuleContext ctx,
Class<T> rule
ParserRuleContext ctx,
Class<T> rule
) {
if (rule.isInstance(ctx)) {
return Optional.of(rule.cast(ctx));
@ -143,7 +155,4 @@ class ExpressionHelper {
return tryAs(ctxs.get(0), rule);
}
private ExpressionHelper() {
}
}
}

View File

@ -23,6 +23,7 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.primitives.Doubles;
import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
import com.sk89q.worldedit.math.Vector3;
@ -36,6 +37,8 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.ThreadLocalRandom;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.invoke.MethodHandles.filterReturnValue;
import static java.lang.invoke.MethodType.methodType;
/**
@ -56,7 +59,35 @@ final class Functions {
throw new IllegalStateException(e);
}
return ImmutableSetMultimap.copyOf(map);
// clean up all the functions
return ImmutableSetMultimap.copyOf(
Multimaps.transformValues(map, Functions::clean)
);
}
private static final MethodHandle DOUBLE_VALUE;
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
DOUBLE_VALUE = lookup.findVirtual(Number.class, "doubleValue",
methodType(double.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
private static MethodHandle clean(MethodHandle handle) {
// box it all first
handle = handle.asType(handle.type().wrap());
if (handle.type().returnType() != Double.class) {
// Ensure that the handle returns a Double, even if originally a Number
checkState(Number.class.isAssignableFrom(handle.type().returnType()),
"Function does not return a number");
handle = handle.asType(handle.type().changeReturnType(Number.class));
handle = filterReturnValue(handle, DOUBLE_VALUE);
}
return handle;
}
private static void addMathHandles(

View File

@ -0,0 +1,653 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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.*;
import it.unimi.dsi.fastutil.doubles.Double2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.RuleNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.List;
import java.util.function.DoubleBinaryOperator;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.sk89q.worldedit.antlr.ExpressionLexer.*;
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.WRAPPED_CONSTANT;
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.*;
import static java.lang.invoke.MethodType.methodType;
/**
* The brains of {@link ExpressionCompiler}.
*/
class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
private static final MethodHandle BREAK_STATEMENT =
ExpressionHandles.dropData(MethodHandles.throwException(Double.class, BreakException.class)
.bindTo(BreakException.BREAK));
private static final MethodHandle CONTINUE_STATEMENT =
ExpressionHandles.dropData(MethodHandles.throwException(Double.class, BreakException.class)
.bindTo(BreakException.CONTINUE));
private static final double[] factorials = new double[171];
/**
* Method handle (ExecutionData)Double, returns null.
*/
private static final MethodHandle DEFAULT_RESULT =
ExpressionHandles.dropData(MethodHandles.constant(Double.class, null));
static {
factorials[0] = 1;
for (int i = 1; i < factorials.length; ++i) {
factorials[i] = factorials[i - 1] * i;
}
}
/*
* General idea is that we don't need to pass around variables, they're all in ExecutionData.
* We do need to pass that around, so most MethodHandles will be of the type
* (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<String, MethodHandle> functions;
CompilingVisitor(SetMultimap<String, MethodHandle> functions) {
this.functions = functions;
}
// Usable AlmostEqual function, based on http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
private static boolean almostEqual2sComplement(double a, double b, long maxUlps) {
// Make sure maxUlps is non-negative and small enough that the
// default NAN won't compare as equal to anything.
//assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); // this is for floats, not doubles
long aLong = Double.doubleToRawLongBits(a);
// Make aLong lexicographically ordered as a twos-complement long
if (aLong < 0) aLong = 0x8000000000000000L - aLong;
long bLong = Double.doubleToRawLongBits(b);
// Make bLong lexicographically ordered as a twos-complement long
if (bLong < 0) bLong = 0x8000000000000000L - bLong;
final long longDiff = Math.abs(aLong - bLong);
return longDiff <= maxUlps;
}
private static double factorial(double x) throws EvaluationException {
final int n = (int) x;
if (n < 0) {
return 0;
}
if (n >= factorials.length) {
return Double.POSITIVE_INFINITY;
}
return factorials[n];
}
private Token extractToken(ParserRuleContext ctx) {
List<TerminalNode> children = ctx.children.stream()
.filter(TerminalNode.class::isInstance)
.map(TerminalNode.class::cast)
.collect(Collectors.toList());
ExpressionHelper.check(children.size() == 1, ctx, "Expected exactly one token, got " + children.size());
return children.get(0).getSymbol();
}
private ExecNode evaluate(ParserRuleContext ctx) {
MethodHandle mh = ctx.accept(this);
if (ctx.parent instanceof ParserRuleContext) {
checkHandle(mh, (ParserRuleContext) ctx.parent);
}
return new ExecNode(ctx, mh);
}
private void checkHandle(MethodHandle mh, ParserRuleContext ctx) {
ExpressionHelper.check(mh.type().equals(ExpressionHandles.COMPILED_EXPRESSION_SIG), ctx,
"Incorrect type returned from handler for " + ctx.getClass());
}
private MethodHandle evaluateForNamedValue(ParserRuleContext ctx, String name) {
MethodHandle guard = MethodHandles.guardWithTest(
// if result is null
IS_NULL.asType(methodType(boolean.class, Double.class)),
// throw appropriate exception, dropping `result` argument
MethodHandles.dropArguments(
ExpressionHandles.throwEvalException(ctx, "Invalid expression for " + name), 0, Double.class
),
// else return the argument we were passed
MethodHandles.identity(Double.class)
);
// now pass `result` into `guard`
MethodHandle result = evaluate(ctx).handle;
return MethodHandles.collectArguments(guard, 0, result);
}
private MethodHandle evaluateForValue(ParserRuleContext ctx) {
return evaluateForNamedValue(ctx, "a value");
}
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
);
}
private MethodHandle evaluateConditional(ParserRuleContext condition,
ParserRuleContext trueBranch,
ParserRuleContext falseBranch) {
// easiest one of the bunch
return MethodHandles.guardWithTest(
evaluateBoolean(condition),
trueBranch == null ? NULL_DOUBLE : evaluate(trueBranch).handle,
falseBranch == null ? NULL_DOUBLE : evaluate(falseBranch).handle
);
}
@Override
public MethodHandle visitIfStatement(ExpressionParser.IfStatementContext ctx) {
return evaluateConditional(ctx.condition, ctx.trueBranch, ctx.falseBranch);
}
@Override
public MethodHandle visitTernaryExpr(ExpressionParser.TernaryExprContext ctx) {
return evaluateConditional(ctx.condition, ctx.trueBranch, ctx.falseBranch);
}
@Override
public MethodHandle visitWhileStatement(ExpressionParser.WhileStatementContext ctx) {
return ExpressionHandles.whileLoop(
evaluateBoolean(ctx.condition),
evaluate(ctx.body)
);
}
@Override
public MethodHandle visitDoStatement(ExpressionParser.DoStatementContext ctx) {
return ExpressionHandles.doWhileLoop(
evaluateBoolean(ctx.condition),
evaluate(ctx.body)
);
}
@Override
public MethodHandle visitForStatement(ExpressionParser.ForStatementContext ctx) {
return ExpressionHandles.forLoop(
evaluate(ctx.init).handle,
evaluateBoolean(ctx.condition),
evaluate(ctx.body),
evaluate(ctx.update).handle
);
}
@Override
public MethodHandle visitSimpleForStatement(ExpressionParser.SimpleForStatementContext ctx) {
return ExpressionHandles.simpleForLoop(
evaluateForValue(ctx.first),
evaluateForValue(ctx.last),
ctx.counter,
evaluate(ctx.body)
);
}
@Override
public MethodHandle visitBreakStatement(ExpressionParser.BreakStatementContext ctx) {
return BREAK_STATEMENT;
}
@Override
public MethodHandle visitContinueStatement(ExpressionParser.ContinueStatementContext ctx) {
return CONTINUE_STATEMENT;
}
@Override
public MethodHandle visitReturnStatement(ExpressionParser.ReturnStatementContext ctx) {
if (ctx.value != null) {
return evaluate(ctx.value).handle;
}
return defaultResult();
}
@Override
public MethodHandle visitSwitchStatement(ExpressionParser.SwitchStatementContext ctx) {
Double2ObjectMap<ExecNode> cases = new Double2ObjectLinkedOpenHashMap<>(ctx.labels.size());
ExecNode defaultCase = null;
for (int i = 0; i < ctx.labels.size(); i++) {
ExpressionParser.SwitchLabelContext label = ctx.labels.get(i);
ExpressionParser.StatementsContext body = ctx.bodies.get(i);
ExecNode node = evaluate(body);
if (label instanceof ExpressionParser.CaseContext) {
ExpressionParser.CaseContext caseContext = (ExpressionParser.CaseContext) label;
double key = (double) ExpressionHandles.constantInvoke(evaluateForValue(caseContext.constant));
ExpressionHelper.check(!cases.containsKey(key), body, "Duplicate cases detected.");
cases.put(key, node);
} else {
ExpressionHelper.check(defaultCase == null, body, "Duplicate default cases detected.");
defaultCase = node;
}
}
return ExpressionHandles.switchStatement(cases, evaluateForValue(ctx.target), defaultCase);
}
@Override
public MethodHandle visitExpressionStatement(ExpressionParser.ExpressionStatementContext ctx) {
return evaluate(ctx.expression()).handle;
}
@Override
public MethodHandle visitPostCrementExpr(ExpressionParser.PostCrementExprContext ctx) {
Token target = ctx.target;
int opType = ctx.op.getType();
return ExpressionHandles.call(data -> {
LocalSlot.Variable variable = ExpressionHandles.getVariable(data, target);
double value = variable.getValue();
if (opType == INCREMENT) {
value++;
} else {
value--;
}
variable.setValue(value);
return value;
});
}
@Override
public MethodHandle visitPreCrementExpr(ExpressionParser.PreCrementExprContext ctx) {
Token target = ctx.target;
int opType = ctx.op.getType();
return ExpressionHandles.call(data -> {
LocalSlot.Variable variable = ExpressionHandles.getVariable(data, target);
double value = variable.getValue();
double result = value;
if (opType == INCREMENT) {
value++;
} else {
value--;
}
variable.setValue(value);
return result;
});
}
@Override
public MethodHandle visitPlusMinusExpr(ExpressionParser.PlusMinusExprContext ctx) {
MethodHandle value = evaluateForValue(ctx.expr);
switch (ctx.op.getType()) {
case PLUS:
return value;
case MINUS:
return ExpressionHandles.call(data ->
-(double) ExpressionHandles.standardInvoke(value, data)
);
}
throw ExpressionHelper.evalException(ctx, "Invalid text for plus/minus expr: " + ctx.op.getText());
}
@Override
public MethodHandle visitNotExpr(ExpressionParser.NotExprContext ctx) {
MethodHandle expr = evaluateBoolean(ctx.expr);
return ExpressionHandles.call(data ->
ExpressionHandles.boolToDouble(!(boolean) ExpressionHandles.standardInvoke(expr, data))
);
}
@Override
public MethodHandle visitComplementExpr(ExpressionParser.ComplementExprContext ctx) {
MethodHandle expr = evaluateForValue(ctx.expr);
// Looks weird. In order:
// - Convert back to double from following long
// - Convert to long from double value
// - Convert from Object to Double to double.
return ExpressionHandles.call(data ->
(double) ~(long) (double) ExpressionHandles.standardInvoke(expr, data)
);
}
@Override
public MethodHandle visitConditionalAndExpr(ExpressionParser.ConditionalAndExprContext ctx) {
MethodHandle left = evaluateBoolean(ctx.left);
MethodHandle right = evaluateForValue(ctx.right);
return MethodHandles.guardWithTest(
left,
right,
ExpressionHandles.dropData(
MethodHandles.constant(Double.class, ExpressionHandles.boolToDouble(false))
)
);
}
@Override
public MethodHandle visitConditionalOrExpr(ExpressionParser.ConditionalOrExprContext ctx) {
MethodHandle left = evaluateForValue(ctx.left);
MethodHandle right = evaluateForValue(ctx.right);
// Inject left as primary condition, on failure take right with data parameter
// logic = (Double,ExecutionData)Double
MethodHandle logic = MethodHandles.guardWithTest(
// data arg dropped implicitly
DOUBLE_TO_BOOL,
// drop data arg
MethodHandles.dropArguments(
MethodHandles.identity(Double.class), 1, ExecutionData.class
),
// drop left arg, call right
MethodHandles.dropArguments(
right, 0, Double.class
)
);
// mixed = (ExecutionData,ExecutionData)Double
MethodHandle mixed = MethodHandles.collectArguments(
logic, 0, left
);
// Deduplicate ExecutionData
return ExpressionHandles.dedupData(mixed);
}
private MethodHandle evaluateBinary(ParserRuleContext left,
ParserRuleContext right,
DoubleBinaryOperator op) {
MethodHandle mhLeft = evaluateForValue(left);
MethodHandle mhRight = evaluateForValue(right);
// 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())
);
doubleData = doubleData.asType(doubleData.type().wrap());
return ExpressionHandles.dedupData(doubleData);
}
private MethodHandle evaluateBinary(ParserRuleContext left,
ParserRuleContext right,
Supplier<DoubleBinaryOperator> op) {
return evaluateBinary(left, right, op.get());
}
@Override
public MethodHandle visitPowerExpr(ExpressionParser.PowerExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, Math::pow);
}
@Override
public MethodHandle visitMultiplicativeExpr(ExpressionParser.MultiplicativeExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, () -> {
switch (ctx.op.getType()) {
case TIMES:
return (l, r) -> l * r;
case DIVIDE:
return (l, r) -> l / r;
case MODULO:
return (l, r) -> l % r;
}
throw ExpressionHelper.evalException(ctx, "Invalid text for multiplicative expr: " + ctx.op.getText());
});
}
@Override
public MethodHandle visitAddExpr(ExpressionParser.AddExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, () -> {
switch (ctx.op.getType()) {
case PLUS:
return Double::sum;
case MINUS:
return (l, r) -> l - r;
}
throw ExpressionHelper.evalException(ctx, "Invalid text for additive expr: " + ctx.op.getText());
});
}
@Override
public MethodHandle visitShiftExpr(ExpressionParser.ShiftExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, () -> {
switch (ctx.op.getType()) {
case LEFT_SHIFT:
return (l, r) -> (double) ((long) l << (long) r);
case RIGHT_SHIFT:
return (l, r) -> (double) ((long) l >> (long) r);
}
throw ExpressionHelper.evalException(ctx, "Invalid text for shift expr: " + ctx.op.getText());
});
}
@Override
public MethodHandle visitRelationalExpr(ExpressionParser.RelationalExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, () -> {
switch (ctx.op.getType()) {
case LESS_THAN:
return (l, r) -> ExpressionHandles.boolToDouble(l < r);
case LESS_THAN_OR_EQUAL:
return (l, r) -> ExpressionHandles.boolToDouble(l <= r);
case GREATER_THAN:
return (l, r) -> ExpressionHandles.boolToDouble(l > r);
case GREATER_THAN_OR_EQUAL:
return (l, r) -> ExpressionHandles.boolToDouble(l >= r);
}
throw ExpressionHelper.evalException(ctx, "Invalid text for relational expr: " + ctx.op.getText());
});
}
@Override
public MethodHandle visitEqualityExpr(ExpressionParser.EqualityExprContext ctx) {
return evaluateBinary(ctx.left, ctx.right, () -> {
switch (ctx.op.getType()) {
case EQUAL:
return (l, r) -> ExpressionHandles.boolToDouble(l == r);
case NOT_EQUAL:
return (l, r) -> ExpressionHandles.boolToDouble(l != r);
case NEAR:
return (l, r) -> ExpressionHandles.boolToDouble(almostEqual2sComplement(l, r, 450359963L));
case GREATER_THAN_OR_EQUAL:
return (l, r) -> ExpressionHandles.boolToDouble(l >= r);
}
throw ExpressionHelper.evalException(ctx, "Invalid text for equality expr: " + ctx.op.getText());
});
}
@Override
public MethodHandle visitPostfixExpr(ExpressionParser.PostfixExprContext ctx) {
MethodHandle value = evaluateForValue(ctx.expr);
if (ctx.op.getType() == EXCLAMATION_MARK) {
return ExpressionHandles.call(data ->
factorial((double) ExpressionHandles.standardInvoke(value, data))
);
}
throw ExpressionHelper.evalException(ctx,
"Invalid text for post-unary expr: " + ctx.op.getText());
}
@Override
public MethodHandle visitAssignment(ExpressionParser.AssignmentContext ctx) {
int type = extractToken(ctx.assignmentOperator()).getType();
Token target = ctx.target;
MethodHandle getArg = evaluateForValue(ctx.expression());
return ExpressionHandles.call(data -> {
double value;
double arg = (double) ExpressionHandles.standardInvoke(getArg, data);
LocalSlot.Variable variable;
if (type == ASSIGN) {
variable = ExpressionHandles.initVariable(data, target);
value = arg;
} else {
variable = ExpressionHandles.getVariable(data, target);
value = variable.getValue();
switch (type) {
case POWER_ASSIGN:
value = Math.pow(value, arg);
break;
case TIMES_ASSIGN:
value *= arg;
break;
case DIVIDE_ASSIGN:
value /= arg;
break;
case MODULO_ASSIGN:
value %= arg;
break;
case PLUS_ASSIGN:
value += arg;
break;
case MINUS_ASSIGN:
value -= arg;
break;
default:
throw ExpressionHelper.evalException(ctx, "Invalid text for assign expr: " +
ctx.assignmentOperator().getText());
}
}
variable.setValue(value);
return value;
});
}
@Override
public MethodHandle visitFunctionCall(ExpressionParser.FunctionCallContext ctx) {
MethodHandle handle = ExpressionHelper.resolveFunction(functions, ctx);
String fnName = ctx.name.getText();
MethodHandle[] arguments = new MethodHandle[ctx.args.size()];
for (int i = 0; i < arguments.length; i++) {
ExpressionParser.ExpressionContext arg = ctx.args.get(i);
MethodHandle transformed = getArgument(fnName, handle.type(), i, arg);
Class<?> ptype = handle.type().parameterType(i);
Class<?> rtype = transformed.type().returnType();
if (ptype != rtype && ptype.isAssignableFrom(rtype)) {
// need to upcast
transformed = transformed.asType(transformed.type().changeReturnType(ptype));
}
arguments[i] = transformed;
}
// Take each of our data accepting arguments, apply them over the source method
MethodHandle manyData = MethodHandles.filterArguments(handle, 0, arguments);
// Collapse every data into one argument
int[] permutation = new int[arguments.length];
return MethodHandles.permuteArguments(
manyData, ExpressionHandles.COMPILED_EXPRESSION_SIG, permutation
);
}
// MH: (ExecutionData)T; (depends on target)
private MethodHandle getArgument(String fnName, MethodType type, int i, ParserRuleContext arg) {
// Pass variable handle in for modification?
String handleName = ExpressionHelper.getArgumentHandleName(fnName, type, i, arg);
if (handleName == null) {
return evaluateForValue(arg);
}
if (handleName.equals(WRAPPED_CONSTANT)) {
// pass arg into new LocalSlot.Constant
MethodHandle filter = evaluateForValue(arg);
filter = filter.asType(filter.type().unwrap());
return MethodHandles.collectArguments(
NEW_LS_CONSTANT, 0, filter
);
}
// small hack
CommonToken fake = new CommonToken(arg.start);
fake.setText(handleName);
return ExpressionHandles.mhGetVariable(fake);
}
@Override
public MethodHandle visitConstantExpression(ExpressionParser.ConstantExpressionContext ctx) {
try {
return ExpressionHandles.dropData(
MethodHandles.constant(Double.class, Double.parseDouble(ctx.getText()))
);
} catch (NumberFormatException e) {
// Rare, but might happen, e.g. if too many digits
throw ExpressionHelper.evalException(ctx, "Invalid constant: " + e.getMessage());
}
}
@Override
public MethodHandle visitIdExpr(ExpressionParser.IdExprContext ctx) {
Token source = ctx.source;
return ExpressionHandles.call(data -> ExpressionHandles.getSlotValue(data, source));
}
@Override
protected MethodHandle defaultResult() {
return DEFAULT_RESULT;
}
@Override
public MethodHandle visitChildren(RuleNode node) {
MethodHandle result = defaultResult();
int n = node.getChildCount();
for (int i = 0; i < n; i++) {
ParseTree c = node.getChild(i);
if (c instanceof TerminalNode && ((TerminalNode) c).getSymbol().getType() == Token.EOF) {
break;
}
MethodHandle childResult = c.accept(this);
if (c instanceof ParserRuleContext) {
checkHandle(childResult, (ParserRuleContext) c);
}
boolean returning = c instanceof ExpressionParser.ReturnStatementContext;
result = aggregateResult(result, childResult, returning);
if (returning) {
return result;
}
}
return result;
}
@Override
protected MethodHandle aggregateResult(MethodHandle aggregate, MethodHandle nextResult) {
throw new UnsupportedOperationException();
}
private MethodHandle aggregateResult(MethodHandle oldResult, MethodHandle result,
boolean keepDefault) {
// Execute `oldResult` but ignore its return value, then execute result and return that.
// If `oldResult` (the old value) is `defaultResult`, it's bogus, so just skip it
if (oldResult == DEFAULT_RESULT) {
return result;
}
if (result == DEFAULT_RESULT && !keepDefault) {
return oldResult;
}
// Add a dummy Double parameter to the end
MethodHandle dummyDouble = MethodHandles.dropArguments(
result, 1, Double.class
);
// Have oldResult turn it from data->Double
MethodHandle doubledData = MethodHandles.collectArguments(
dummyDouble, 1, oldResult
);
// Deduplicate the `data` parameter
return ExpressionHandles.dedupData(doubledData);
}
}

View File

@ -0,0 +1,34 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.invoke;
import org.antlr.v4.runtime.ParserRuleContext;
import java.lang.invoke.MethodHandle;
class ExecNode {
final ParserRuleContext ctx;
final MethodHandle handle;
ExecNode(ParserRuleContext ctx, MethodHandle handle) {
this.ctx = ctx;
this.handle = handle;
}
}

View File

@ -0,0 +1,73 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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 java.lang.invoke.LambdaConversionException;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static java.lang.invoke.MethodType.methodType;
/**
* Compiles an expression from an AST into {@link MethodHandle}s.
*/
public class ExpressionCompiler {
private static final String CE_EXECUTE = "execute";
private static final MethodType HANDLE_TO_CE =
methodType(CompiledExpression.class, MethodHandle.class);
private static final MethodHandle HANDLE_TO_CE_CONVERTER;
static {
MethodHandle handleInvoker = MethodHandles.invoker(ExpressionHandles.COMPILED_EXPRESSION_SIG);
try {
HANDLE_TO_CE_CONVERTER = LambdaMetafactory.metafactory(
MethodHandles.lookup(),
// Implementing CompiledExpression.execute
CE_EXECUTE,
// Take a handle, to be converted to CompiledExpression
HANDLE_TO_CE,
// Raw signature for SAM type
ExpressionHandles.COMPILED_EXPRESSION_SIG,
// Handle to call the captured handle.
handleInvoker,
// Actual signature at invoke time
ExpressionHandles.COMPILED_EXPRESSION_SIG
).dynamicInvoker().asType(HANDLE_TO_CE);
} catch (LambdaConversionException e) {
throw new IllegalStateException("Failed to load ExpressionCompiler MetaFactory", e);
}
}
public CompiledExpression compileExpression(ExpressionParser.AllStatementsContext root,
SetMultimap<String, MethodHandle> functions) {
MethodHandle invokable = root.accept(new CompilingVisitor(functions));
return (CompiledExpression) ExpressionHandles.safeInvoke(
HANDLE_TO_CE_CONVERTER, h -> h.invoke(invokable)
);
}
}

View File

@ -0,0 +1,339 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.internal.expression.invoke;
import com.google.common.base.Throwables;
import com.sk89q.worldedit.internal.expression.*;
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
import it.unimi.dsi.fastutil.doubles.Double2ObjectMaps;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.function.DoubleBinaryOperator;
import java.util.function.Supplier;
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.*;
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.methodType;
class ExpressionHandles {
static final MethodType COMPILED_EXPRESSION_SIG = methodType(Double.class, ExecutionData.class);
static final MethodHandle IS_NULL;
static final MethodHandle DOUBLE_TO_BOOL;
static final MethodHandle CALL_BINARY_OP;
static final MethodHandle NEW_LS_CONSTANT;
static final MethodHandle NULL_DOUBLE = dropData(constant(Double.class, null));
private static final MethodHandle EVAL_EXCEPTION_CONSTR;
private static final MethodHandle CALL_EXPRESSION;
private static final MethodHandle GET_VARIABLE;
private static final MethodHandle WHILE_FOR_LOOP_IMPL;
private static final MethodHandle DO_WHILE_LOOP_IMPL;
private static final MethodHandle SIMPLE_FOR_LOOP_IMPL;
private static final MethodHandle SWITCH_IMPL;
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
EVAL_EXCEPTION_CONSTR = lookup.findConstructor(
EvaluationException.class, methodType(void.class, int.class, String.class));
CALL_EXPRESSION = lookup.findVirtual(
CompiledExpression.class, "execute",
methodType(Double.class, ExecutionData.class));
GET_VARIABLE = lookup.findStatic(ExpressionHandles.class, "getVariable",
methodType(LocalSlot.Variable.class, ExecutionData.class, Token.class));
WHILE_FOR_LOOP_IMPL = lookup.findStatic(ExpressionHandles.class,
"whileForLoopImpl",
methodType(Double.class, ExecutionData.class, MethodHandle.class,
MethodHandle.class, ExecNode.class, MethodHandle.class));
DO_WHILE_LOOP_IMPL = lookup.findStatic(ExpressionHandles.class, "doWhileLoopImpl",
methodType(Double.class, ExecutionData.class, MethodHandle.class, ExecNode.class));
SIMPLE_FOR_LOOP_IMPL = lookup.findStatic(ExpressionHandles.class, "simpleForLoopImpl",
methodType(Double.class, ExecutionData.class, MethodHandle.class,
MethodHandle.class, Token.class, ExecNode.class));
SWITCH_IMPL = lookup.findStatic(ExpressionHandles.class, "switchImpl",
methodType(Double.class, ExecutionData.class, Double2ObjectMap.class,
MethodHandle.class, ExecNode.class));
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));
CALL_BINARY_OP = lookup.findVirtual(DoubleBinaryOperator.class, "applyAsDouble",
methodType(double.class, double.class, double.class));
NEW_LS_CONSTANT = lookup.findConstructor(LocalSlot.Constant.class,
methodType(void.class, double.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
private ExpressionHandles() {
}
static Object safeInvoke(MethodHandle handle, Invokable invokable) {
try {
return invokable.invoke(handle);
} catch (Throwable t) {
Throwables.throwIfUnchecked(t);
throw new RuntimeException(t);
}
}
static Object standardInvoke(MethodHandle handle, ExecutionData data) {
return safeInvoke(handle, h -> h.invoke(data));
}
static Object constantInvoke(MethodHandle handle) {
return standardInvoke(handle, ExecutionData.CONSTANT_EVALUATOR);
}
static MethodHandle dropData(MethodHandle handle) {
return dropArguments(handle, 0, ExecutionData.class);
}
static MethodHandle dedupData(MethodHandle doubleData) {
return permuteArguments(
doubleData, COMPILED_EXPRESSION_SIG,
0, 0
);
}
static LocalSlot.Variable initVariable(ExecutionData data, Token nameToken) {
String name = nameToken.getText();
return data.getSlots().initVariable(name)
.orElseThrow(() -> ExpressionHelper.evalException(
nameToken, "Cannot overwrite non-variable '" + name + "'"
));
}
private static Supplier<EvaluationException> varNotInitException(Token nameToken) {
return () -> ExpressionHelper.evalException(
nameToken, "'" + nameToken.getText() + "' is not initialized yet"
);
}
static MethodHandle mhGetVariable(Token nameToken) {
return insertArguments(GET_VARIABLE, 1, nameToken);
}
static LocalSlot.Variable getVariable(ExecutionData data, Token nameToken) {
String name = nameToken.getText();
LocalSlot slot = data.getSlots().getSlot(name)
.orElseThrow(varNotInitException(nameToken));
if (!(slot instanceof LocalSlot.Variable)) {
throw ExpressionHelper.evalException(
nameToken, "'" + name + "' is not a variable"
);
}
return (LocalSlot.Variable) slot;
}
static double getSlotValue(ExecutionData data, Token nameToken) {
String name = nameToken.getText();
return data.getSlots().getSlotValue(name)
.orElseThrow(varNotInitException(nameToken));
}
/**
* Returns a method handle that calls
* {@link EvaluationException#EvaluationException(int, String)} with the supplied arguments.
*/
private static MethodHandle evalException(ParserRuleContext ctx, String message) {
return insertArguments(EVAL_EXCEPTION_CONSTR, 0,
getErrorPosition(ctx.start), message);
}
/**
* Returns a method handle that takes no arguments, and throws the result of
* {@link #evalException(ParserRuleContext, String)}. It will additionally return Double.
*/
static MethodHandle throwEvalException(ParserRuleContext ctx, String message) {
// replace arg0 of `throw` with `evalException`
return collectArguments(
throwException(Double.class, EvaluationException.class),
0,
evalException(ctx, message)
);
}
private static boolean doubleToBool(double bool) {
return bool > 0;
}
static double boolToDouble(boolean bool) {
return bool ? 1 : 0;
}
/**
* Encapsulate the given code into a MethodHandle.
*/
static MethodHandle call(CompiledExpression runnable) {
return CALL_EXPRESSION.bindTo(runnable).asType(COMPILED_EXPRESSION_SIG);
}
static MethodHandle whileLoop(MethodHandle condition, ExecNode body) {
return insertArguments(WHILE_FOR_LOOP_IMPL, 1,
null, condition, body, null);
}
static MethodHandle forLoop(MethodHandle init,
MethodHandle condition,
ExecNode body,
MethodHandle update) {
return insertArguments(WHILE_FOR_LOOP_IMPL, 1,
init, condition, body, update);
}
private static Double whileForLoopImpl(ExecutionData data,
@Nullable MethodHandle init,
MethodHandle condition,
ExecNode body,
@Nullable MethodHandle update) {
Double result = null;
int iterations = 0;
if (init != null) {
standardInvoke(init, data);
}
while ((boolean) standardInvoke(condition, data)) {
checkIterations(iterations, body.ctx);
checkTimeout();
iterations++;
try {
result = (Double) standardInvoke(body.handle, data);
} catch (BreakException ex) {
if (!ex.doContinue) {
break;
}
}
if (update != null) {
standardInvoke(update, data);
}
}
return result;
}
static MethodHandle doWhileLoop(MethodHandle condition, ExecNode body) {
return insertArguments(DO_WHILE_LOOP_IMPL, 1, condition, body);
}
private static Double doWhileLoopImpl(ExecutionData data,
MethodHandle condition,
ExecNode body) {
Double result = null;
int iterations = 0;
do {
checkIterations(iterations, body.ctx);
checkTimeout();
iterations++;
try {
result = (Double) standardInvoke(body.handle, data);
} catch (BreakException ex) {
if (!ex.doContinue) {
break;
}
}
} while ((boolean) standardInvoke(condition, data));
return result;
}
static MethodHandle simpleForLoop(MethodHandle first,
MethodHandle last,
Token counter,
ExecNode body) {
return insertArguments(SIMPLE_FOR_LOOP_IMPL, 1,
first, last, counter, body);
}
private static Double simpleForLoopImpl(ExecutionData data,
MethodHandle getFirst,
MethodHandle getLast,
Token counterToken,
ExecNode body) {
Double result = null;
int iterations = 0;
double first = (double) standardInvoke(getFirst, data);
double last = (double) standardInvoke(getLast, data);
LocalSlot.Variable variable = initVariable(data, counterToken);
for (double i = first; i <= last; i++) {
checkIterations(iterations, body.ctx);
checkTimeout();
iterations++;
variable.setValue(i);
try {
result = (Double) standardInvoke(body.handle, data);
} catch (BreakException ex) {
if (!ex.doContinue) {
break;
}
}
}
return result;
}
static MethodHandle switchStatement(Double2ObjectMap<ExecNode> cases,
MethodHandle getValue,
@Nullable ExecNode defaultCase) {
return insertArguments(SWITCH_IMPL, 1, cases, getValue, defaultCase);
}
private static Double switchImpl(ExecutionData data,
Double2ObjectMap<ExecNode> cases,
MethodHandle getValue,
@Nullable ExecNode defaultCase) {
double value = (double) standardInvoke(getValue, data);
boolean matched = false;
Double evaluated = null;
boolean falling = false;
for (Double2ObjectMap.Entry<ExecNode> entry : Double2ObjectMaps.fastIterable(cases)) {
if (falling || entry.getDoubleKey() == value) {
matched = true;
try {
evaluated = (Double) standardInvoke(entry.getValue().handle, data);
falling = true;
} catch (BreakException brk) {
check(!brk.doContinue, entry.getValue().ctx, "Cannot continue in a switch");
falling = false;
break;
}
}
}
// This if is like the one in the loop, default's "case" is `!matched` & present
if ((falling || !matched) && defaultCase != null) {
try {
evaluated = (Double) standardInvoke(defaultCase.handle, data);
} catch (BreakException brk) {
check(!brk.doContinue, defaultCase.ctx, "Cannot continue in a switch");
}
}
return evaluated;
}
@FunctionalInterface
interface Invokable {
Object invoke(MethodHandle handle) throws Throwable;
}
}

View File

@ -20,7 +20,6 @@
package com.sk89q.worldedit.world.block;
import com.sk89q.worldedit.WorldEdit;
import com.google.common.collect.Iterables;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extent.Extent;
@ -33,20 +32,21 @@ import com.sk89q.worldedit.registry.NamespacedRegistry;
import com.sk89q.worldedit.registry.state.AbstractProperty;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.registry.state.PropertyKey;
import com.sk89q.worldedit.world.item.ItemType;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import com.sk89q.worldedit.world.item.ItemType;
import com.sk89q.worldedit.world.item.ItemTypes;
import com.sk89q.worldedit.world.registry.BlockMaterial;
import com.sk89q.worldedit.world.registry.LegacyMapper;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
public class BlockType implements FawePattern, Keyed {
@ -54,7 +54,8 @@ public class BlockType implements FawePattern, Keyed {
private final String id;
private final BlockTypesCache.Settings settings;
private final LazyReference<Integer> legacyId = LazyReference.from(() -> computeLegacy(0));
private final LazyReference<Integer> legacyData = LazyReference.from(() -> computeLegacy(1));
private boolean initItemType;
private ItemType itemType;
@ -103,6 +104,7 @@ public class BlockType implements FawePattern, Keyed {
return name;
}
}
/*
private BlockState computeDefaultState() {
@ -212,7 +214,7 @@ public class BlockType implements FawePattern, Keyed {
*/
AbstractProperty btp = this.settings.propertiesMap.get(prop.getName());
checkArgument(btp != null, "%s has no property named %s", this, prop.getName());
id = btp.modify(id, btp.getValueFor((String)value));
id = btp.modify(id, btp.getValueFor((String) value));
}
return withStateId(id);
}
@ -233,7 +235,7 @@ public class BlockType implements FawePattern, Keyed {
*/
@Nullable
public ItemType getItemType() {
if(!initItemType) {
if (!initItemType) {
initItemType = true;
itemType = ItemTypes.get(this.id);
}
@ -251,7 +253,7 @@ public class BlockType implements FawePattern, Keyed {
/**
* Gets the legacy ID. Needed for legacy reasons.
*
* <p>
* DO NOT USE THIS.
*
* @return legacy id or 0, if unknown
@ -264,7 +266,7 @@ public class BlockType implements FawePattern, Keyed {
/**
* The internal index of this type.
*
* <p>
* This number is not necessarily consistent across restarts.
*
* @return internal id
@ -288,7 +290,6 @@ public class BlockType implements FawePattern, Keyed {
return obj == this; // stop changing this to a shitty string comparison
}
@Override
public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException {
return set.setBlock(extent, getDefaultState());
@ -307,14 +308,25 @@ public class BlockType implements FawePattern, Keyed {
return new SingleBlockTypeMask(extent, this);
}
@Deprecated
public int getLegacyId() {
Integer id = LegacyMapper.getInstance().getLegacyCombined(this.getDefaultState());
if (id != null) {
return id >> 4;
} else {
return 0;
}
return legacyId.getValue();
}
/**
* Gets the legacy data. Needed for legacy reasons.
* <p>
* DO NOT USE THIS.
*
* @return legacy data or 0, if unknown
*/
@Deprecated
public int getLegacyData() {
return legacyData.getValue();
}
private int computeLegacy(int index) {
int[] legacy = LegacyMapper.getInstance().getLegacyFromBlock(this.getDefaultState());
return legacy != null ? legacy[index] : 0;
}
}

View File

@ -26,8 +26,11 @@ import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.registry.Keyed;
import com.sk89q.worldedit.registry.NamespacedRegistry;
import com.sk89q.worldedit.registry.RegistryItem;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.registry.BlockMaterial;
import com.sk89q.worldedit.world.registry.ItemMaterial;
import javax.annotation.Nullable;
@ -37,6 +40,9 @@ public class ItemType implements RegistryItem, Keyed {
private String id;
private String name;
private final LazyReference<ItemMaterial> itemMaterial
= LazyReference.from(() -> WorldEdit.getInstance().getPlatformManager()
.queryCapability(Capability.GAME_HOOKS).getRegistries().getItemRegistry().getMaterial(this));
private BlockType blockType;
private boolean initBlockType;
private BaseItem defaultState;
@ -113,6 +119,15 @@ public class ItemType implements RegistryItem, Keyed {
return this.defaultState;
}
/**
* Get the material for this ItemType.
*
* @return The material
*/
public ItemMaterial getMaterial() {
return itemMaterial.getValue();
}
@Override
public String toString() {
return getId();

View File

@ -114,6 +114,23 @@ public final class BundledItemData {
return idMap.get(id);
}
/**
* Get the material properties for the given item.
*
* @param id the string ID
* @return the material's properties, or null
*/
@Nullable
public ItemMaterial getMaterialById(String id) {
ItemEntry entry = findById(id);
if (entry != null) {
// FIXME: This should probably just be part of the JSON itself
return new SimpleItemMaterial(entry.maxStackSize, entry.maxDamage);
} else {
return null;
}
}
/**
* Get a singleton instance of this object.
*

View File

@ -29,14 +29,18 @@ import javax.annotation.Nullable;
*/
public class BundledItemRegistry implements ItemRegistry {
private BundledItemData.ItemEntry getEntryById(ItemType itemType) {
return BundledItemData.getInstance().findById(itemType.getId());
}
@Nullable
@Override
public String getName(ItemType itemType) {
String id = itemType.getId();
BundledItemData.ItemEntry itemEntry = BundledItemData.getInstance().findById(id);
BundledItemData.ItemEntry itemEntry = getEntryById(itemType);
if (itemEntry != null) {
String localized = itemEntry.localizedName;
if (localized.equals("Air")) {
String id = itemType.getId();
int c = id.indexOf(':');
return c < 0 ? id : id.substring(c + 1);
}
@ -44,4 +48,10 @@ public class BundledItemRegistry implements ItemRegistry {
}
return null;
}
@Nullable
@Override
public ItemMaterial getMaterial(ItemType itemType) {
return new PassthroughItemMaterial(BundledItemData.getInstance().getMaterialById(itemType.getId()));
}
}

View File

@ -0,0 +1,36 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.world.registry;
public interface ItemMaterial {
/**
* Gets the the maximum quantity of this item that can be in a single stack.
*
* @return the maximum quantity
*/
int getMaxStackSize();
/**
* Gets the the maximum damage this item can take before being broken.
*
* @return the maximum damage, or 0 if not applicable
*/
int getMaxDamage();
}

View File

@ -36,6 +36,15 @@ public interface ItemRegistry {
@Nullable
String getName(ItemType itemType);
/**
* Get the material for the given item.
*
* @param itemType the item
* @return the material, or null if the material information is not known
*/
@Nullable
ItemMaterial getMaterial(ItemType itemType);
/**
* Register all items
*/

View File

@ -21,21 +21,31 @@ package com.sk89q.worldedit.world.registry;
import javax.annotation.Nullable;
import static com.sk89q.worldedit.util.GuavaUtil.firstNonNull;
public class PassthroughBlockMaterial implements BlockMaterial {
@Nullable private final BlockMaterial blockMaterial;
private static final SimpleBlockMaterial DEFAULT_MATERIAL = new SimpleBlockMaterial();
static {
DEFAULT_MATERIAL.setFullCube(true);
DEFAULT_MATERIAL.setOpaque(true);
DEFAULT_MATERIAL.setSolid(true);
DEFAULT_MATERIAL.setTicksRandomly(true);
DEFAULT_MATERIAL.setMovementBlocker(true);
DEFAULT_MATERIAL.setBurnable(true);
DEFAULT_MATERIAL.setToolRequired(true);
}
private final BlockMaterial blockMaterial;
public PassthroughBlockMaterial(@Nullable BlockMaterial material) {
this.blockMaterial = material;
this.blockMaterial = firstNonNull(material, DEFAULT_MATERIAL);
}
@Override
public boolean isAir() {
if (blockMaterial == null) {
return false;
} else {
return blockMaterial.isAir();
}
return blockMaterial.isAir();
}
@Override
@ -49,172 +59,96 @@ public class PassthroughBlockMaterial implements BlockMaterial {
@Override
public boolean isFullCube() {
if (blockMaterial == null) {
return true;
} else {
return blockMaterial.isFullCube();
}
return blockMaterial.isFullCube();
}
@Override
public boolean isOpaque() {
if (blockMaterial == null) {
return true;
} else {
return blockMaterial.isOpaque();
}
return blockMaterial.isOpaque();
}
@Override
public boolean isPowerSource() {
if (blockMaterial == null) {
return false;
} else {
return blockMaterial.isPowerSource();
}
return blockMaterial.isPowerSource();
}
@Override
public boolean isLiquid() {
if (blockMaterial == null) {
return false;
} else {
return blockMaterial.isLiquid();
}
return blockMaterial.isLiquid();
}
@Override
public boolean isSolid() {
if (blockMaterial == null) {
return true;
} else {
return blockMaterial.isSolid();
}
return blockMaterial.isSolid();
}
@Override
public float getHardness() {
if (blockMaterial == null) {
return 0;
} else {
return blockMaterial.getHardness();
}
return blockMaterial.getHardness();
}
@Override
public float getResistance() {
if (blockMaterial == null) {
return 0;
} else {
return blockMaterial.getResistance();
}
return blockMaterial.getResistance();
}
@Override
public float getSlipperiness() {
if (blockMaterial == null) {
return 0;
} else {
return blockMaterial.getSlipperiness();
}
return blockMaterial.getSlipperiness();
}
@Override
public int getLightValue() {
if (blockMaterial == null) {
return 0;
} else {
return blockMaterial.getLightValue();
}
return blockMaterial.getLightValue();
}
@Override
public int getLightOpacity() {
if (blockMaterial == null) {
return 0;
} else {
return blockMaterial.getLightOpacity();
}
return blockMaterial.getLightOpacity();
}
@Override
public boolean isFragileWhenPushed() {
if (blockMaterial == null) {
return false;
} else {
return blockMaterial.isFragileWhenPushed();
}
return blockMaterial.isFragileWhenPushed();
}
@Override
public boolean isUnpushable() {
if (blockMaterial == null) {
return false;
} else {
return blockMaterial.isUnpushable();
}
return blockMaterial.isUnpushable();
}
@Override
public boolean isTicksRandomly() {
if (blockMaterial == null) {
return true;
} else {
return blockMaterial.isTicksRandomly();
}
return blockMaterial.isTicksRandomly();
}
@Override
public boolean isMovementBlocker() {
if (blockMaterial == null) {
return true;
} else {
return blockMaterial.isMovementBlocker();
}
return blockMaterial.isMovementBlocker();
}
@Override
public boolean isBurnable() {
if (blockMaterial == null) {
return true;
} else {
return blockMaterial.isBurnable();
}
return blockMaterial.isBurnable();
}
@Override
public boolean isToolRequired() {
if (blockMaterial == null) {
return true;
} else {
return blockMaterial.isToolRequired();
}
return blockMaterial.isToolRequired();
}
@Override
public boolean isReplacedDuringPlacement() {
if (blockMaterial == null) {
return false;
} else {
return blockMaterial.isReplacedDuringPlacement();
}
return blockMaterial.isReplacedDuringPlacement();
}
@Override
public boolean isTranslucent() {
if (blockMaterial == null) {
return !isOpaque();
} else {
return blockMaterial.isTranslucent();
}
return blockMaterial.isTranslucent();
}
@Override
public boolean hasContainer() {
if (blockMaterial == null) {
return false;
} else {
return blockMaterial.hasContainer();
}
return blockMaterial.hasContainer();
}
}

View File

@ -0,0 +1,45 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.world.registry;
import static com.sk89q.worldedit.util.GuavaUtil.firstNonNull;
import javax.annotation.Nullable;
public class PassthroughItemMaterial implements ItemMaterial {
private static final ItemMaterial DEFAULT_MATERIAL = new SimpleItemMaterial(0, 0);
private final ItemMaterial itemMaterial;
public PassthroughItemMaterial(@Nullable ItemMaterial material) {
this.itemMaterial = firstNonNull(material, DEFAULT_MATERIAL);
}
@Override
public int getMaxStackSize() {
return itemMaterial.getMaxStackSize();
}
@Override
public int getMaxDamage() {
return itemMaterial.getMaxDamage();
}
}

View File

@ -0,0 +1,41 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.world.registry;
class SimpleItemMaterial implements ItemMaterial {
private int maxStackSize;
private int maxDamage;
public SimpleItemMaterial(int maxStackSize, int maxDamage) {
this.maxStackSize = maxStackSize;
this.maxDamage = maxDamage;
}
@Override
public int getMaxStackSize() {
return maxStackSize;
}
@Override
public int getMaxDamage() {
return maxDamage;
}
}

View File

@ -397,7 +397,7 @@
"worldedit.undo.undone": "Undid {0} available edits.",
"worldedit.undo.none": "Nothing left to undo.",
"worldedit.redo.undone": "Redid {0} available edits.",
"worldedit.redo.redone": "Redid {0} available edits.",
"worldedit.redo.none": "Nothing left to redo.",
"worldedit.clearhistory.cleared": "History cleared.",
@ -590,6 +590,7 @@
"worldedit.tool.inspect.equip": "Inspect tool bound to {0}.",
"worldedit.tool.info.blockstate.hover": "Block state",
"worldedit.tool.info.internalid.hover": "Internal ID",
"worldedit.tool.info.legacy.hover": "Legacy id:data",
"worldedit.tool.info.light.hover": "Block Light/Light Above",
"worldedit.tool.none.equip": "Tool unbound from your current item.",
"worldedit.tool.selwand.equip": "Selection wand bound to {0}.",

View File

@ -91,7 +91,6 @@ class ExpressionTest extends BaseExpressionTest {
{
ExpressionException e = assertThrows(ExpressionException.class,
() -> compile("rotate(1, 2, 3)"));
e.printStackTrace();
assertEquals(7, e.getPosition(), "Error position");
}