From 0e7630322fffb1cc2ede2d2fa4e38da8f64824f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noel=20N=C3=A9meth?= Date: Thu, 14 Jul 2022 00:55:19 +0200 Subject: [PATCH] Update --- .../server/command/CommandParser.java | 83 ++++++++++++----- .../net/minestom/server/command/Graph.java | 32 ++++++- .../server/command/builder/Command.java | 92 ++++--------------- .../command/builder/CommandDispatcher.java | 8 +- .../command/builder/CommandExecutor.java | 2 - .../server/command/builder/CommandResult.java | 1 - 6 files changed, 107 insertions(+), 111 deletions(-) diff --git a/src/main/java/net/minestom/server/command/CommandParser.java b/src/main/java/net/minestom/server/command/CommandParser.java index 8984cd11d..aed9f9377 100644 --- a/src/main/java/net/minestom/server/command/CommandParser.java +++ b/src/main/java/net/minestom/server/command/CommandParser.java @@ -1,15 +1,14 @@ package net.minestom.server.command; +import net.minestom.server.command.Graph.Node; import net.minestom.server.command.builder.*; import net.minestom.server.command.builder.arguments.Argument; import net.minestom.server.command.builder.condition.CommandCondition; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; import static net.minestom.server.command.builder.arguments.Argument.Result.SyntaxError; @@ -21,51 +20,79 @@ public final class CommandParser { //no instance } - public static Result parse(NodeGraph graph, String input) { + public static Result parse(Graph graph, String input) { final CharSequence withoutPrefix = input.trim().startsWith(COMMAND_PREFIX) ? input.subSequence(COMMAND_PREFIX.length(), input.length()) : input; // Create reader & parse final CommandReader reader = new CommandReader(withoutPrefix); - List> syntax = new ArrayList<>(); + final List syntax = new ArrayList<>(); + final Node root = graph.root(); + final Set conditions = new HashSet<>(); - Map.Entry result = parseChild(graph, graph.root(), reader); + NodeResult result; + Node parent = root; - while (result != null) { + while ((result = parseChild(parent, reader)) != null) { syntax.add(result); - final Node parent = result.getKey(); - if (result.getValue() instanceof SyntaxError e) { + // Create condition chain + final CommandCondition condition = result.node().executor().condition(); + if (condition != null) conditions.add(condition); + // Check parse result + if (result.value instanceof SyntaxError e) { // Syntax error stop at this arg - return new SyntaxErrorResult(withoutPrefix.toString(), parent.executionInfo().get().condition(), - parent.arg().getCallback(), e, syntaxMapper(syntax)); + return new SyntaxErrorResult(withoutPrefix.toString(), chainConditions(conditions), + parent.argument().getCallback(), e, syntaxMapper(syntax)); } - result = parseChild(graph, Objects.requireNonNullElse(graph.getRedirectTarget(parent), parent), reader); + parent = result.node; } if (syntax.size() < 1) { return new UnknownCommandResult(withoutPrefix.toString()); } else { - final Node lastNode = syntax.get(syntax.size() - 1).getKey(); - return new ValidCommandResult(withoutPrefix.toString(), lastNode.executionInfo().get().condition(), - lastNode.executionInfo().get().executor(), syntaxMapper(syntax)); + final Node lastNode = syntax.get(syntax.size() - 1).node; + if (lastNode.executor().executor() == null) { + // Syntax error + return new SyntaxErrorResult(withoutPrefix.toString(), chainConditions(conditions), + lastNode.executor().syntaxErrorCallback(), null, syntaxMapper(syntax)); + } + return new ValidCommandResult(withoutPrefix.toString(), chainConditions(conditions), + lastNode.executor().executor(), syntaxMapper(syntax)); } } - private static Map syntaxMapper(List> syntax) { - return syntax.stream() + private static Map syntaxMapper(List syntax) { + final Map providedArgs = syntax.stream() .skip(1) // skip root - .map(x -> Map.entry(x.getKey().realArg().getId(), x.getValue())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + .collect(Collectors.toMap(NodeResult::name, NodeResult::value)); + final Map> defaultValueSuppliers = syntax.get(syntax.size() - 1).node.executor().defaultValueSuppliers(); + if (defaultValueSuppliers != null) { + final Map defaults = defaultValueSuppliers.entrySet() + .stream() + .map(x -> Map.entry(x.getKey(), x.getValue().get())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + providedArgs.putAll(defaults); + } + return providedArgs; } - private static @Nullable Map.Entry parseChild(NodeGraph graph, Node parent, CommandReader reader) { - for (Node child : graph.getChildren(parent)) { + private static CommandCondition chainConditions(Set conditions) { + return (sender, commandString) -> { + for (CommandCondition condition : conditions) { + if (!condition.canUse(sender, commandString)) return false; + } + return true; + }; + } + + private static NodeResult parseChild(Node parent, CommandReader reader) { + for (Node child : parent.next()) { final int start = reader.cursor(); try { - final Argument.Result parse = child.realArg().parse(reader); + final Argument.Result parse = child.argument().parse(reader); if (parse instanceof Argument.Result.Success success) { - return Map.entry(child, success.value()); + return new NodeResult(child, success.value()); } else if (parse instanceof Argument.Result.SyntaxError syntaxError) { - return Map.entry(child, syntaxError); + return new NodeResult(child, syntaxError); } else { // Reset cursor & try next reader.setCursor(start); @@ -121,4 +148,10 @@ public final class CommandParser { private record ValidCommandResult(String input, CommandCondition condition, CommandExecutor executor, Map arguments) implements KnownCommandResult { } + + private record NodeResult(Node node, Object value) { + public String name() { + return node.argument().getId(); + } + } } diff --git a/src/main/java/net/minestom/server/command/Graph.java b/src/main/java/net/minestom/server/command/Graph.java index 1e0a3a11a..bc326f363 100644 --- a/src/main/java/net/minestom/server/command/Graph.java +++ b/src/main/java/net/minestom/server/command/Graph.java @@ -1,14 +1,20 @@ package net.minestom.server.command; +import net.minestom.server.command.builder.ArgumentCallback; import net.minestom.server.command.builder.Command; +import net.minestom.server.command.builder.CommandExecutor; import net.minestom.server.command.builder.arguments.Argument; +import net.minestom.server.command.builder.condition.CommandCondition; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.function.Supplier; sealed interface Graph permits GraphImpl { static @NotNull Builder builder(@NotNull Argument argument) { @@ -43,8 +49,32 @@ sealed interface Graph permits GraphImpl { @NotNull List<@NotNull Node> next(); } + // TODO rename to ExecutionInformation or similar to avoid confusion sealed interface Executor extends Predicate permits GraphImpl.ExecutorImpl { - // TODO execute the node + /** + * Non-null if the command has a default syntax error handler, must be present on + * declaring node and all subsequent ones, a sub command must continue with its own + * if present, otherwise the previous has to be propagated further. + */ + @Nullable ArgumentCallback syntaxErrorCallback(); + + /** + * Non-null if the command at this point considered executable, must be present + * on last required node and all subsequent optional nodes + */ + @Nullable CommandExecutor executor(); + + /** + * Non-null if the command or syntax has a condition, must be present + * only on nodes that specify it + */ + @Nullable CommandCondition condition(); + + /** + * Non-null if the node at this point considered executable and optional nodes are + * present after this node, this map must only contain suppliers for following nodes + */ + @Nullable Map> defaultValueSuppliers(); } sealed interface Builder permits GraphImpl.BuilderImpl { diff --git a/src/main/java/net/minestom/server/command/builder/Command.java b/src/main/java/net/minestom/server/command/builder/Command.java index 2f9f00e8d..641c90cee 100644 --- a/src/main/java/net/minestom/server/command/builder/Command.java +++ b/src/main/java/net/minestom/server/command/builder/Command.java @@ -19,7 +19,6 @@ import java.util.*; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Supplier; import java.util.stream.Stream; /** @@ -50,7 +49,7 @@ public class Command { private final String[] aliases; private final String[] names; - private CommandExecutor defaultExecutor; + private ArgumentCallback syntaxErrorCallback; private CommandCondition condition; private final List subcommands; @@ -143,64 +142,9 @@ public class Command { public Collection addConditionalSyntax(@Nullable CommandCondition commandCondition, @NotNull CommandExecutor executor, @NotNull Argument... args) { - // Check optional argument(s) - boolean hasOptional = false; - { - for (Argument argument : args) { - if (argument.isOptional()) { - hasOptional = true; - } - if (hasOptional && !argument.isOptional()) { - LOGGER.warn("Optional arguments are followed by a non-optional one, the default values will be ignored."); - hasOptional = false; - break; - } - } - } - - if (!hasOptional) { - final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args); - this.syntaxes.add(syntax); - return Collections.singleton(syntax); - } else { - List optionalSyntaxes = new ArrayList<>(); - - // the 'args' array starts by all the required arguments, followed by the optional ones - List> requiredArguments = new ArrayList<>(); - Map> defaultValuesMap = new HashMap<>(); - boolean optionalBranch = false; - int i = 0; - for (Argument argument : args) { - final boolean isLast = ++i == args.length; - if (argument.isOptional()) { - // Set default value - defaultValuesMap.put(argument.getId(), (Supplier) argument.getDefaultValue()); - - if (!optionalBranch && !requiredArguments.isEmpty()) { - // First optional argument, create a syntax with current cached arguments - final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap, - requiredArguments.toArray(new Argument[0])); - optionalSyntaxes.add(syntax); - optionalBranch = true; - } else { - // New optional argument, save syntax with current cached arguments and save default value - final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap, - requiredArguments.toArray(new Argument[0])); - optionalSyntaxes.add(syntax); - } - } - requiredArguments.add(argument); - if (isLast) { - // Create the last syntax - final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, defaultValuesMap, - requiredArguments.toArray(new Argument[0])); - optionalSyntaxes.add(syntax); - } - } - - this.syntaxes.addAll(optionalSyntaxes); - return optionalSyntaxes; - } + final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args); + this.syntaxes.add(syntax); + return Collections.singleton(syntax); } /** @@ -255,26 +199,24 @@ public class Command { return names; } - /** - * Gets the default {@link CommandExecutor} which is called when there is no argument - * or if no corresponding syntax has been found. - * - * @return the default executor, null if not any - * @see #setDefaultExecutor(CommandExecutor) - */ - @Nullable - public CommandExecutor getDefaultExecutor() { - return defaultExecutor; + public ArgumentCallback syntaxErrorCallback() { + return syntaxErrorCallback; + } + + public void setSyntaxErrorCallback(ArgumentCallback syntaxErrorCallback) { + this.syntaxErrorCallback = syntaxErrorCallback; } /** - * Sets the default {@link CommandExecutor}. - * - * @param executor the new default executor, null to remove it - * @see #getDefaultExecutor() + * This was an ambiguous method which is no longer supported by the new parser.
+ * Updating: If you were using this method to set
+ * - an executor for a command without arguments you should + * use {@link #addSyntax(CommandExecutor, Argument[])} without arguments + * - a syntax error handler you should use the new {@link #setSyntaxErrorCallback(ArgumentCallback)} */ + @Deprecated(forRemoval = true) public void setDefaultExecutor(@Nullable CommandExecutor executor) { - this.defaultExecutor = executor; + throw new RuntimeException("Unsupported operation! See method javadoc."); } /** diff --git a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java index 04762891e..ef9c614b2 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java +++ b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java @@ -4,8 +4,6 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import net.minestom.server.command.CommandParser; import net.minestom.server.command.CommandSender; -import net.minestom.server.command.GraphBuilder; -import net.minestom.server.command.NodeGraph; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -19,8 +17,6 @@ public class CommandDispatcher { //Todo maybe merge with manager? private final Map commandMap = new HashMap<>(); private final Set commands = new HashSet<>(); - - private NodeGraph graph; private final Cache cache = Caffeine.newBuilder() .expireAfterWrite(30, TimeUnit.SECONDS) .build(); @@ -43,7 +39,6 @@ public class CommandDispatcher { //Todo maybe merge with manager? } this.commands.add(command); - this.graph = GraphBuilder.forServer(this.commands); } public void unregister(@NotNull Command command) { @@ -57,7 +52,6 @@ public class CommandDispatcher { //Todo maybe merge with manager? } this.commands.remove(command); - this.graph = GraphBuilder.forServer(this.commands); // Clear cache this.cache.invalidateAll(); } @@ -91,6 +85,6 @@ public class CommandDispatcher { //Todo maybe merge with manager? } public @NotNull CommandParser.Result parse(@NotNull String commandString) { - return cache.get(commandString, command -> CommandParser.parse(graph, command)); + return null; } } diff --git a/src/main/java/net/minestom/server/command/builder/CommandExecutor.java b/src/main/java/net/minestom/server/command/builder/CommandExecutor.java index ca68c2165..cfaf0c417 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandExecutor.java +++ b/src/main/java/net/minestom/server/command/builder/CommandExecutor.java @@ -5,8 +5,6 @@ import org.jetbrains.annotations.NotNull; /** * Callback executed once a syntax has been found for a {@link Command}. - *

- * Warning: it could be the default executor from {@link Command#getDefaultExecutor()} if not null. */ @FunctionalInterface public interface CommandExecutor { diff --git a/src/main/java/net/minestom/server/command/builder/CommandResult.java b/src/main/java/net/minestom/server/command/builder/CommandResult.java index 61c68dd7b..d37ae36c8 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandResult.java +++ b/src/main/java/net/minestom/server/command/builder/CommandResult.java @@ -8,7 +8,6 @@ public record CommandResult(Type type, String input, CommandData commandData) { SUCCESS, /** * Command found, but the syntax is invalid. - * Executor sets to {@link Command#getDefaultExecutor()}. */ INVALID_SYNTAX, /**