geforkt von Mirrors/FastAsyncWorldEdit
Upstream and some refactoring
Note: Maybe this doesn't compile, ij is broken af smh, let's give it a try...
Dieser Commit ist enthalten in:
Ursprung
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.bukkit.adapter.SimpleBukkitAdapter;
|
||||||
import com.sk89q.worldedit.entity.Entity;
|
import com.sk89q.worldedit.entity.Entity;
|
||||||
import com.sk89q.worldedit.extension.input.ParserContext;
|
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.extension.platform.PlayerProxy;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.math.Vector3;
|
import com.sk89q.worldedit.math.Vector3;
|
||||||
@ -43,6 +44,7 @@ import com.sk89q.worldedit.world.item.ItemType;
|
|||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -108,6 +110,16 @@ public enum BukkitAdapter {
|
|||||||
return getAdapter().adapt(world);
|
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.
|
* Create a WorldEdit Player from a Bukkit Player.
|
||||||
*
|
*
|
||||||
@ -118,6 +130,21 @@ public enum BukkitAdapter {
|
|||||||
return WorldEditPlugin.getInstance().wrapPlayer(player);
|
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.
|
* Create a Bukkit Player from a WorldEdit Player.
|
||||||
*
|
*
|
||||||
|
@ -34,7 +34,6 @@ import org.bukkit.World;
|
|||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.command.BlockCommandSender;
|
import org.bukkit.command.BlockCommandSender;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -127,7 +126,10 @@ public class BukkitBlockCommandSender extends AbstractNonPlayerActor implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPermission(String permission, boolean value) {
|
public void setPermission(String permission, boolean value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockCommandSender getSender() {
|
||||||
|
return this.sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -101,6 +101,10 @@ public class BukkitCommandSender extends AbstractNonPlayerActor {
|
|||||||
return WorldEdit.getInstance().getConfiguration().defaultLocale;
|
return WorldEdit.getInstance().getConfiguration().defaultLocale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandSender getSender() {
|
||||||
|
return this.sender;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SessionKey getSessionKey() {
|
public SessionKey getSessionKey() {
|
||||||
return new SessionKey() {
|
return new SessionKey() {
|
||||||
|
@ -21,4 +21,5 @@
|
|||||||
* This package contains the old command system. It is no longer in use. Please switch
|
* This package contains the old command system. It is no longer in use. Please switch
|
||||||
* to Piston, Intake, ACF, or similar systems.
|
* to Piston, Intake, ACF, or similar systems.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
package com.sk89q.minecraft.util.commands;
|
package com.sk89q.minecraft.util.commands;
|
@ -131,7 +131,7 @@ public class HistoryCommands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (timesRedone > 0) {
|
if (timesRedone > 0) {
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.redo.undone", TextComponent.of(timesRedone)));
|
actor.printInfo(TranslatableComponent.of("worldedit.redo.redone", TextComponent.of(timesRedone)));
|
||||||
} else {
|
} else {
|
||||||
actor.printError(TranslatableComponent.of("worldedit.redo.none"));
|
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.LocalSession;
|
||||||
import com.sk89q.worldedit.entity.Player;
|
import com.sk89q.worldedit.entity.Player;
|
||||||
import com.sk89q.worldedit.extension.platform.Actor;
|
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.extension.platform.Platform;
|
||||||
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.util.Location;
|
import com.sk89q.worldedit.util.Location;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
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.util.formatting.text.format.TextColor;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
|
import com.sk89q.worldedit.world.registry.LegacyMapper;
|
||||||
|
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
@ -63,10 +64,15 @@ public class QueryTool implements BlockTool {
|
|||||||
final OptionalInt internalId = BlockStateIdAccess.getBlockStateId(block.toImmutableState());
|
final OptionalInt internalId = BlockStateIdAccess.getBlockStateId(block.toImmutableState());
|
||||||
if (internalId.isPresent()) {
|
if (internalId.isPresent()) {
|
||||||
builder.append(TextComponent.of(" (" + internalId.getAsInt() + ") ", TextColor.DARK_GRAY)
|
builder.append(TextComponent.of(" (" + internalId.getAsInt() + ") ", TextColor.DARK_GRAY)
|
||||||
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.internalid.hover"))));
|
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.internalid.hover"))));
|
||||||
|
}
|
||||||
|
final int[] legacy = LegacyMapper.getInstance().getLegacyFromBlock(block.toImmutableState());
|
||||||
|
if (legacy != null) {
|
||||||
|
builder.append(TextComponent.of(" (" + legacy[0] + ":" + legacy[1] + ") ", TextColor.DARK_GRAY)
|
||||||
|
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.legacy.hover"))));
|
||||||
}
|
}
|
||||||
builder.append(TextComponent.of(" (" + world.getBlockLightLevel(blockPoint) + "/"
|
builder.append(TextComponent.of(" (" + world.getBlockLightLevel(blockPoint) + "/"
|
||||||
+ world.getBlockLightLevel(blockPoint.add(0, 1, 0)) + ")", TextColor.WHITE)
|
+ world.getBlockLightLevel(blockPoint.add(0, 1, 0)) + ")", TextColor.WHITE)
|
||||||
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.light.hover"))));
|
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TranslatableComponent.of("worldedit.tool.info.light.hover"))));
|
||||||
|
|
||||||
player.print(builder.build());
|
player.print(builder.build());
|
||||||
|
@ -25,9 +25,12 @@ package com.sk89q.worldedit.internal.expression;
|
|||||||
*/
|
*/
|
||||||
public class BreakException extends RuntimeException {
|
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 final boolean doContinue;
|
||||||
|
|
||||||
public BreakException(boolean doContinue) {
|
private BreakException(boolean doContinue) {
|
||||||
super(doContinue ? "'continue' encountered outside a loop" : "'break' encountered outside a loop",
|
super(doContinue ? "'continue' encountered outside a loop" : "'break' encountered outside a loop",
|
||||||
null, true, false);
|
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;
|
package com.sk89q.worldedit.internal.expression;
|
||||||
|
|
||||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when there's a problem during expression evaluation.
|
* 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.WorldEdit;
|
||||||
import com.sk89q.worldedit.antlr.ExpressionLexer;
|
import com.sk89q.worldedit.antlr.ExpressionLexer;
|
||||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||||
|
import com.sk89q.worldedit.internal.expression.invoke.ExpressionCompiler;
|
||||||
import com.sk89q.worldedit.session.request.Request;
|
import com.sk89q.worldedit.session.request.Request;
|
||||||
import org.antlr.v4.runtime.CharStream;
|
import org.antlr.v4.runtime.CharStream;
|
||||||
import org.antlr.v4.runtime.CharStreams;
|
import org.antlr.v4.runtime.CharStreams;
|
||||||
@ -37,13 +38,7 @@ import java.lang.invoke.MethodHandle;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles and evaluates expressions.
|
* Compiles and evaluates expressions.
|
||||||
@ -83,14 +78,11 @@ public class Expression {
|
|||||||
|
|
||||||
private final SlotTable slots = new SlotTable();
|
private final SlotTable slots = new SlotTable();
|
||||||
private final List<String> providedSlots;
|
private final List<String> providedSlots;
|
||||||
private ExpressionParser.AllStatementsContext root;
|
private final ExpressionParser.AllStatementsContext root;
|
||||||
private final SetMultimap<String, MethodHandle> functions = Functions.getFunctionMap();
|
private final SetMultimap<String, MethodHandle> functions = Functions.getFunctionMap();
|
||||||
|
private final CompiledExpression compiledExpression;
|
||||||
private ExpressionEnvironment environment;
|
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 {
|
private Expression(String expression, String... variableNames) throws ExpressionException {
|
||||||
slots.putSlot("e", new LocalSlot.Constant(Math.E));
|
slots.putSlot("e", new LocalSlot.Constant(Math.E));
|
||||||
slots.putSlot("pi", new LocalSlot.Constant(Math.PI));
|
slots.putSlot("pi", new LocalSlot.Constant(Math.PI));
|
||||||
@ -119,6 +111,15 @@ public class Expression {
|
|||||||
throw new ParserException(parser.getState(), e);
|
throw new ParserException(parser.getState(), e);
|
||||||
}
|
}
|
||||||
ParseTreeWalker.DEFAULT.walk(new ExpressionValidator(slots.keySet(), functions), root);
|
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 {
|
public double evaluate(double... values) throws EvaluationException {
|
||||||
@ -177,7 +178,7 @@ public class Expression {
|
|||||||
private Double evaluateRoot() throws EvaluationException {
|
private Double evaluateRoot() throws EvaluationException {
|
||||||
pushInstance();
|
pushInstance();
|
||||||
try {
|
try {
|
||||||
return root.accept(new EvaluatingVisitor(slots, functions));
|
return compiledExpression.execute(new ExecutionData(slots, functions));
|
||||||
} finally {
|
} finally {
|
||||||
popInstance();
|
popInstance();
|
||||||
}
|
}
|
||||||
@ -196,10 +197,6 @@ public class Expression {
|
|||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Expression getInstance() {
|
|
||||||
return instance.get().peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pushInstance() {
|
private void pushInstance() {
|
||||||
Stack<Expression> threadLocalExprStack = instance.get();
|
Stack<Expression> threadLocalExprStack = instance.get();
|
||||||
if (threadLocalExprStack == null) {
|
if (threadLocalExprStack == null) {
|
||||||
|
@ -33,33 +33,51 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.ID;
|
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) {
|
if (!condition) {
|
||||||
throw evalException(ctx, message);
|
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(
|
return new EvaluationException(
|
||||||
ctx.getStart().getCharPositionInLine(),
|
getErrorPosition(token),
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void checkIterations(int iterations, ParserRuleContext ctx) {
|
public static void checkIterations(int iterations, ParserRuleContext ctx) {
|
||||||
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
|
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void checkTimeout() {
|
// Special argument handle names
|
||||||
|
|
||||||
|
public static void checkTimeout() {
|
||||||
if (Thread.interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
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) {
|
ExpressionParser.FunctionCallContext ctx) {
|
||||||
String fnName = ctx.name.getText();
|
String fnName = ctx.name.getText();
|
||||||
Set<MethodHandle> matchingFns = functions.get(fnName);
|
Set<MethodHandle> matchingFns = functions.get(fnName);
|
||||||
check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'");
|
check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'");
|
||||||
@ -79,34 +97,28 @@ class ExpressionHelper {
|
|||||||
}
|
}
|
||||||
// We matched no function, fail with appropriate message.
|
// We matched no function, fail with appropriate message.
|
||||||
String possibleCounts = matchingFns.stream()
|
String possibleCounts = matchingFns.stream()
|
||||||
.map(mh -> mh.isVarargsCollector()
|
.map(mh -> mh.isVarargsCollector()
|
||||||
? (mh.type().parameterCount() - 1) + "+"
|
? (mh.type().parameterCount() - 1) + "+"
|
||||||
: String.valueOf(mh.type().parameterCount()))
|
: String.valueOf(mh.type().parameterCount()))
|
||||||
.collect(Collectors.joining("/"));
|
.collect(Collectors.joining("/"));
|
||||||
throw evalException(ctx, "Incorrect number of arguments for function '" + fnName + "', " +
|
throw evalException(ctx, "Incorrect number of arguments for function '" + fnName + "', " +
|
||||||
"expected " + possibleCounts + ", " +
|
"expected " + possibleCounts + ", " +
|
||||||
"got " + ctx.args.size());
|
"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
|
* 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.
|
* {@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) {
|
ParserRuleContext arg) {
|
||||||
// Pass variable handle in for modification?
|
// Pass variable handle in for modification?
|
||||||
Class<?> pType = type.parameterType(i);
|
Class<?> pType = type.parameterType(i);
|
||||||
Optional<String> id = tryResolveId(arg);
|
Optional<String> id = tryResolveId(arg);
|
||||||
if (pType == LocalSlot.Variable.class) {
|
if (pType == LocalSlot.Variable.class) {
|
||||||
// MUST be an id
|
// MUST be an id
|
||||||
check(id.isPresent(), arg,
|
check(id.isPresent(), arg,
|
||||||
"Function '" + fnName + "' requires a variable in parameter " + i);
|
"Function '" + fnName + "' requires a variable in parameter " + i);
|
||||||
return id.get();
|
return id.get();
|
||||||
} else if (pType == LocalSlot.class) {
|
} else if (pType == LocalSlot.class) {
|
||||||
return id.orElse(WRAPPED_CONSTANT);
|
return id.orElse(WRAPPED_CONSTANT);
|
||||||
@ -116,7 +128,7 @@ class ExpressionHelper {
|
|||||||
|
|
||||||
private static Optional<String> tryResolveId(ParserRuleContext arg) {
|
private static Optional<String> tryResolveId(ParserRuleContext arg) {
|
||||||
Optional<ExpressionParser.WrappedExprContext> wrappedExprContext =
|
Optional<ExpressionParser.WrappedExprContext> wrappedExprContext =
|
||||||
tryAs(arg, ExpressionParser.WrappedExprContext.class);
|
tryAs(arg, ExpressionParser.WrappedExprContext.class);
|
||||||
if (wrappedExprContext.isPresent()) {
|
if (wrappedExprContext.isPresent()) {
|
||||||
return tryResolveId(wrappedExprContext.get().expression());
|
return tryResolveId(wrappedExprContext.get().expression());
|
||||||
}
|
}
|
||||||
@ -127,8 +139,8 @@ class ExpressionHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static <T extends ParserRuleContext> Optional<T> tryAs(
|
private static <T extends ParserRuleContext> Optional<T> tryAs(
|
||||||
ParserRuleContext ctx,
|
ParserRuleContext ctx,
|
||||||
Class<T> rule
|
Class<T> rule
|
||||||
) {
|
) {
|
||||||
if (rule.isInstance(ctx)) {
|
if (rule.isInstance(ctx)) {
|
||||||
return Optional.of(rule.cast(ctx));
|
return Optional.of(rule.cast(ctx));
|
||||||
@ -143,7 +155,4 @@ class ExpressionHelper {
|
|||||||
return tryAs(ctxs.get(0), rule);
|
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.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSetMultimap;
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
import com.google.common.collect.SetMultimap;
|
import com.google.common.collect.SetMultimap;
|
||||||
|
import com.google.common.collect.Multimaps;
|
||||||
import com.google.common.primitives.Doubles;
|
import com.google.common.primitives.Doubles;
|
||||||
import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
|
import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
|
||||||
import com.sk89q.worldedit.math.Vector3;
|
import com.sk89q.worldedit.math.Vector3;
|
||||||
@ -36,6 +37,8 @@ import java.lang.invoke.MethodHandle;
|
|||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
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;
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,7 +59,35 @@ final class Functions {
|
|||||||
throw new IllegalStateException(e);
|
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(
|
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;
|
package com.sk89q.worldedit.world.block;
|
||||||
|
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.sk89q.worldedit.WorldEditException;
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
import com.sk89q.worldedit.extension.platform.Capability;
|
import com.sk89q.worldedit.extension.platform.Capability;
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
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.AbstractProperty;
|
||||||
import com.sk89q.worldedit.registry.state.Property;
|
import com.sk89q.worldedit.registry.state.Property;
|
||||||
import com.sk89q.worldedit.registry.state.PropertyKey;
|
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.util.concurrency.LazyReference;
|
||||||
|
import com.sk89q.worldedit.world.item.ItemType;
|
||||||
import com.sk89q.worldedit.world.item.ItemTypes;
|
import com.sk89q.worldedit.world.item.ItemTypes;
|
||||||
import com.sk89q.worldedit.world.registry.BlockMaterial;
|
import com.sk89q.worldedit.world.registry.BlockMaterial;
|
||||||
import com.sk89q.worldedit.world.registry.LegacyMapper;
|
import com.sk89q.worldedit.world.registry.LegacyMapper;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import java.util.stream.IntStream;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public class BlockType implements FawePattern, Keyed {
|
public class BlockType implements FawePattern, Keyed {
|
||||||
|
|
||||||
@ -54,7 +54,8 @@ public class BlockType implements FawePattern, Keyed {
|
|||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final BlockTypesCache.Settings settings;
|
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 boolean initItemType;
|
||||||
private ItemType itemType;
|
private ItemType itemType;
|
||||||
|
|
||||||
@ -103,6 +104,7 @@ public class BlockType implements FawePattern, Keyed {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private BlockState computeDefaultState() {
|
private BlockState computeDefaultState() {
|
||||||
|
|
||||||
@ -212,7 +214,7 @@ public class BlockType implements FawePattern, Keyed {
|
|||||||
*/
|
*/
|
||||||
AbstractProperty btp = this.settings.propertiesMap.get(prop.getName());
|
AbstractProperty btp = this.settings.propertiesMap.get(prop.getName());
|
||||||
checkArgument(btp != null, "%s has no property named %s", this, prop.getName());
|
checkArgument(btp != null, "%s has no property named %s", this, prop.getName());
|
||||||
id = btp.modify(id, btp.getValueFor((String)value));
|
id = btp.modify(id, btp.getValueFor((String) value));
|
||||||
}
|
}
|
||||||
return withStateId(id);
|
return withStateId(id);
|
||||||
}
|
}
|
||||||
@ -233,7 +235,7 @@ public class BlockType implements FawePattern, Keyed {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public ItemType getItemType() {
|
public ItemType getItemType() {
|
||||||
if(!initItemType) {
|
if (!initItemType) {
|
||||||
initItemType = true;
|
initItemType = true;
|
||||||
itemType = ItemTypes.get(this.id);
|
itemType = ItemTypes.get(this.id);
|
||||||
}
|
}
|
||||||
@ -251,7 +253,7 @@ public class BlockType implements FawePattern, Keyed {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the legacy ID. Needed for legacy reasons.
|
* Gets the legacy ID. Needed for legacy reasons.
|
||||||
*
|
* <p>
|
||||||
* DO NOT USE THIS.
|
* DO NOT USE THIS.
|
||||||
*
|
*
|
||||||
* @return legacy id or 0, if unknown
|
* @return legacy id or 0, if unknown
|
||||||
@ -264,7 +266,7 @@ public class BlockType implements FawePattern, Keyed {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The internal index of this type.
|
* The internal index of this type.
|
||||||
*
|
* <p>
|
||||||
* This number is not necessarily consistent across restarts.
|
* This number is not necessarily consistent across restarts.
|
||||||
*
|
*
|
||||||
* @return internal id
|
* @return internal id
|
||||||
@ -288,7 +290,6 @@ public class BlockType implements FawePattern, Keyed {
|
|||||||
return obj == this; // stop changing this to a shitty string comparison
|
return obj == this; // stop changing this to a shitty string comparison
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException {
|
public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException {
|
||||||
return set.setBlock(extent, getDefaultState());
|
return set.setBlock(extent, getDefaultState());
|
||||||
@ -307,14 +308,25 @@ public class BlockType implements FawePattern, Keyed {
|
|||||||
return new SingleBlockTypeMask(extent, this);
|
return new SingleBlockTypeMask(extent, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public int getLegacyId() {
|
public int getLegacyId() {
|
||||||
Integer id = LegacyMapper.getInstance().getLegacyCombined(this.getDefaultState());
|
return legacyId.getValue();
|
||||||
if (id != null) {
|
}
|
||||||
return id >> 4;
|
|
||||||
} else {
|
/**
|
||||||
return 0;
|
* 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.Keyed;
|
||||||
import com.sk89q.worldedit.registry.NamespacedRegistry;
|
import com.sk89q.worldedit.registry.NamespacedRegistry;
|
||||||
import com.sk89q.worldedit.registry.RegistryItem;
|
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.BlockType;
|
||||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
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;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -37,6 +40,9 @@ public class ItemType implements RegistryItem, Keyed {
|
|||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String name;
|
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 BlockType blockType;
|
||||||
private boolean initBlockType;
|
private boolean initBlockType;
|
||||||
private BaseItem defaultState;
|
private BaseItem defaultState;
|
||||||
@ -113,6 +119,15 @@ public class ItemType implements RegistryItem, Keyed {
|
|||||||
return this.defaultState;
|
return this.defaultState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the material for this ItemType.
|
||||||
|
*
|
||||||
|
* @return The material
|
||||||
|
*/
|
||||||
|
public ItemMaterial getMaterial() {
|
||||||
|
return itemMaterial.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getId();
|
return getId();
|
||||||
|
@ -114,6 +114,23 @@ public final class BundledItemData {
|
|||||||
return idMap.get(id);
|
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.
|
* Get a singleton instance of this object.
|
||||||
*
|
*
|
||||||
|
@ -29,14 +29,18 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
public class BundledItemRegistry implements ItemRegistry {
|
public class BundledItemRegistry implements ItemRegistry {
|
||||||
|
|
||||||
|
private BundledItemData.ItemEntry getEntryById(ItemType itemType) {
|
||||||
|
return BundledItemData.getInstance().findById(itemType.getId());
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public String getName(ItemType itemType) {
|
public String getName(ItemType itemType) {
|
||||||
String id = itemType.getId();
|
BundledItemData.ItemEntry itemEntry = getEntryById(itemType);
|
||||||
BundledItemData.ItemEntry itemEntry = BundledItemData.getInstance().findById(id);
|
|
||||||
if (itemEntry != null) {
|
if (itemEntry != null) {
|
||||||
String localized = itemEntry.localizedName;
|
String localized = itemEntry.localizedName;
|
||||||
if (localized.equals("Air")) {
|
if (localized.equals("Air")) {
|
||||||
|
String id = itemType.getId();
|
||||||
int c = id.indexOf(':');
|
int c = id.indexOf(':');
|
||||||
return c < 0 ? id : id.substring(c + 1);
|
return c < 0 ? id : id.substring(c + 1);
|
||||||
}
|
}
|
||||||
@ -44,4 +48,10 @@ public class BundledItemRegistry implements ItemRegistry {
|
|||||||
}
|
}
|
||||||
return null;
|
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
|
@Nullable
|
||||||
String getName(ItemType itemType);
|
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
|
* Register all items
|
||||||
*/
|
*/
|
||||||
|
@ -21,21 +21,31 @@ package com.sk89q.worldedit.world.registry;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static com.sk89q.worldedit.util.GuavaUtil.firstNonNull;
|
||||||
|
|
||||||
public class PassthroughBlockMaterial implements BlockMaterial {
|
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) {
|
public PassthroughBlockMaterial(@Nullable BlockMaterial material) {
|
||||||
this.blockMaterial = material;
|
this.blockMaterial = firstNonNull(material, DEFAULT_MATERIAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAir() {
|
public boolean isAir() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isAir();
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isAir();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -49,172 +59,96 @@ public class PassthroughBlockMaterial implements BlockMaterial {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFullCube() {
|
public boolean isFullCube() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isFullCube();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isFullCube();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpaque() {
|
public boolean isOpaque() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isOpaque();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isOpaque();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPowerSource() {
|
public boolean isPowerSource() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isPowerSource();
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isPowerSource();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLiquid() {
|
public boolean isLiquid() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isLiquid();
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isLiquid();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSolid() {
|
public boolean isSolid() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isSolid();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isSolid();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getHardness() {
|
public float getHardness() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.getHardness();
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.getHardness();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getResistance() {
|
public float getResistance() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.getResistance();
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.getResistance();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getSlipperiness() {
|
public float getSlipperiness() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.getSlipperiness();
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.getSlipperiness();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLightValue() {
|
public int getLightValue() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.getLightValue();
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.getLightValue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLightOpacity() {
|
public int getLightOpacity() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.getLightOpacity();
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.getLightOpacity();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFragileWhenPushed() {
|
public boolean isFragileWhenPushed() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isFragileWhenPushed();
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isFragileWhenPushed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUnpushable() {
|
public boolean isUnpushable() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isUnpushable();
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isUnpushable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTicksRandomly() {
|
public boolean isTicksRandomly() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isTicksRandomly();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isTicksRandomly();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMovementBlocker() {
|
public boolean isMovementBlocker() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isMovementBlocker();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isMovementBlocker();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isBurnable() {
|
public boolean isBurnable() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isBurnable();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isBurnable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isToolRequired() {
|
public boolean isToolRequired() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isToolRequired();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isToolRequired();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReplacedDuringPlacement() {
|
public boolean isReplacedDuringPlacement() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isReplacedDuringPlacement();
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isReplacedDuringPlacement();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTranslucent() {
|
public boolean isTranslucent() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.isTranslucent();
|
||||||
return !isOpaque();
|
|
||||||
} else {
|
|
||||||
return blockMaterial.isTranslucent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasContainer() {
|
public boolean hasContainer() {
|
||||||
if (blockMaterial == null) {
|
return blockMaterial.hasContainer();
|
||||||
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.undone": "Undid {0} available edits.",
|
||||||
"worldedit.undo.none": "Nothing left to undo.",
|
"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.redo.none": "Nothing left to redo.",
|
||||||
"worldedit.clearhistory.cleared": "History cleared.",
|
"worldedit.clearhistory.cleared": "History cleared.",
|
||||||
|
|
||||||
@ -590,6 +590,7 @@
|
|||||||
"worldedit.tool.inspect.equip": "Inspect tool bound to {0}.",
|
"worldedit.tool.inspect.equip": "Inspect tool bound to {0}.",
|
||||||
"worldedit.tool.info.blockstate.hover": "Block state",
|
"worldedit.tool.info.blockstate.hover": "Block state",
|
||||||
"worldedit.tool.info.internalid.hover": "Internal ID",
|
"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.info.light.hover": "Block Light/Light Above",
|
||||||
"worldedit.tool.none.equip": "Tool unbound from your current item.",
|
"worldedit.tool.none.equip": "Tool unbound from your current item.",
|
||||||
"worldedit.tool.selwand.equip": "Selection wand bound to {0}.",
|
"worldedit.tool.selwand.equip": "Selection wand bound to {0}.",
|
||||||
|
@ -91,7 +91,6 @@ class ExpressionTest extends BaseExpressionTest {
|
|||||||
{
|
{
|
||||||
ExpressionException e = assertThrows(ExpressionException.class,
|
ExpressionException e = assertThrows(ExpressionException.class,
|
||||||
() -> compile("rotate(1, 2, 3)"));
|
() -> compile("rotate(1, 2, 3)"));
|
||||||
e.printStackTrace();
|
|
||||||
assertEquals(7, e.getPosition(), "Error position");
|
assertEquals(7, e.getPosition(), "Error position");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren