mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-23 01:37:37 +00:00
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:
parent
0bf6cfad8d
commit
b292416496
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
@ -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"));
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -65,6 +66,11 @@ public class QueryTool implements BlockTool {
|
||||
builder.append(TextComponent.of(" (" + internalId.getAsInt() + ") ", TextColor.DARK_GRAY)
|
||||
.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)
|
||||
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.light.hover"))));
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -33,32 +33,50 @@ 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(),
|
||||
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,
|
||||
public static MethodHandle resolveFunction(SetMultimap<String, MethodHandle> functions,
|
||||
ExpressionParser.FunctionCallContext ctx) {
|
||||
String fnName = ctx.name.getText();
|
||||
Set<MethodHandle> matchingFns = functions.get(fnName);
|
||||
@ -88,17 +106,11 @@ class ExpressionHelper {
|
||||
"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,
|
||||
public static String getArgumentHandleName(String fnName, MethodType type, int i,
|
||||
ParserRuleContext arg) {
|
||||
// Pass variable handle in for modification?
|
||||
Class<?> pType = type.parameterType(i);
|
||||
@ -143,7 +155,4 @@ class ExpressionHelper {
|
||||
return tryAs(ctxs.get(0), rule);
|
||||
}
|
||||
|
||||
private ExpressionHelper() {
|
||||
}
|
||||
|
||||
}
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
@ -21,22 +21,32 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMapColor() {
|
||||
@ -49,172 +59,96 @@ public class PassthroughBlockMaterial implements BlockMaterial {
|
||||
|
||||
@Override
|
||||
public boolean isFullCube() {
|
||||
if (blockMaterial == null) {
|
||||
return true;
|
||||
} else {
|
||||
return blockMaterial.isFullCube();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
if (blockMaterial == null) {
|
||||
return true;
|
||||
} else {
|
||||
return blockMaterial.isOpaque();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPowerSource() {
|
||||
if (blockMaterial == null) {
|
||||
return false;
|
||||
} else {
|
||||
return blockMaterial.isPowerSource();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLiquid() {
|
||||
if (blockMaterial == null) {
|
||||
return false;
|
||||
} else {
|
||||
return blockMaterial.isLiquid();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSolid() {
|
||||
if (blockMaterial == null) {
|
||||
return true;
|
||||
} else {
|
||||
return blockMaterial.isSolid();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHardness() {
|
||||
if (blockMaterial == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return blockMaterial.getHardness();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getResistance() {
|
||||
if (blockMaterial == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return blockMaterial.getResistance();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSlipperiness() {
|
||||
if (blockMaterial == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return blockMaterial.getSlipperiness();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightValue() {
|
||||
if (blockMaterial == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return blockMaterial.getLightValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightOpacity() {
|
||||
if (blockMaterial == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return blockMaterial.getLightOpacity();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFragileWhenPushed() {
|
||||
if (blockMaterial == null) {
|
||||
return false;
|
||||
} else {
|
||||
return blockMaterial.isFragileWhenPushed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnpushable() {
|
||||
if (blockMaterial == null) {
|
||||
return false;
|
||||
} else {
|
||||
return blockMaterial.isUnpushable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTicksRandomly() {
|
||||
if (blockMaterial == null) {
|
||||
return true;
|
||||
} else {
|
||||
return blockMaterial.isTicksRandomly();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMovementBlocker() {
|
||||
if (blockMaterial == null) {
|
||||
return true;
|
||||
} else {
|
||||
return blockMaterial.isMovementBlocker();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBurnable() {
|
||||
if (blockMaterial == null) {
|
||||
return true;
|
||||
} else {
|
||||
return blockMaterial.isBurnable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isToolRequired() {
|
||||
if (blockMaterial == null) {
|
||||
return true;
|
||||
} else {
|
||||
return blockMaterial.isToolRequired();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReplacedDuringPlacement() {
|
||||
if (blockMaterial == null) {
|
||||
return false;
|
||||
} else {
|
||||
return blockMaterial.isReplacedDuringPlacement();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTranslucent() {
|
||||
if (blockMaterial == null) {
|
||||
return !isOpaque();
|
||||
} else {
|
||||
return blockMaterial.isTranslucent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContainer() {
|
||||
if (blockMaterial == null) {
|
||||
return false;
|
||||
} else {
|
||||
return blockMaterial.hasContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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}.",
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user