mirror of https://github.com/Minestom/Minestom.git
Update
This commit is contained in:
parent
6884c11e38
commit
0e7630322f
|
@ -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<Map.Entry<Node, Object>> syntax = new ArrayList<>();
|
||||
final List<NodeResult> syntax = new ArrayList<>();
|
||||
final Node root = graph.root();
|
||||
final Set<CommandCondition> conditions = new HashSet<>();
|
||||
|
||||
Map.Entry<Node, Object> 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<String, Object> syntaxMapper(List<Map.Entry<Node, Object>> syntax) {
|
||||
return syntax.stream()
|
||||
private static Map<String, Object> syntaxMapper(List<NodeResult> syntax) {
|
||||
final Map<String, Object> 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<String, Supplier<?>> defaultValueSuppliers = syntax.get(syntax.size() - 1).node.executor().defaultValueSuppliers();
|
||||
if (defaultValueSuppliers != null) {
|
||||
final Map<String, ?> 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<Node, Object> parseChild(NodeGraph graph, Node parent, CommandReader reader) {
|
||||
for (Node child : graph.getChildren(parent)) {
|
||||
private static CommandCondition chainConditions(Set<CommandCondition> 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<String, Object> arguments) implements KnownCommandResult {
|
||||
}
|
||||
|
||||
private record NodeResult(Node node, Object value) {
|
||||
public String name() {
|
||||
return node.argument().getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CommandSender> 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<String, Supplier<?>> defaultValueSuppliers();
|
||||
}
|
||||
|
||||
sealed interface Builder permits GraphImpl.BuilderImpl {
|
||||
|
|
|
@ -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<Command> subcommands;
|
||||
|
@ -143,64 +142,9 @@ public class Command {
|
|||
public Collection<CommandSyntax> 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<CommandSyntax> optionalSyntaxes = new ArrayList<>();
|
||||
|
||||
// the 'args' array starts by all the required arguments, followed by the optional ones
|
||||
List<Argument<?>> requiredArguments = new ArrayList<>();
|
||||
Map<String, Supplier<Object>> 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<Object>) 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.<br>
|
||||
* Updating: If you were using this method to set<br>
|
||||
* - 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.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<String, Command> commandMap = new HashMap<>();
|
||||
private final Set<Command> commands = new HashSet<>();
|
||||
|
||||
private NodeGraph graph;
|
||||
private final Cache<String, CommandParser.Result> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import org.jetbrains.annotations.NotNull;
|
|||
|
||||
/**
|
||||
* Callback executed once a syntax has been found for a {@link Command}.
|
||||
* <p>
|
||||
* Warning: it could be the default executor from {@link Command#getDefaultExecutor()} if not null.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CommandExecutor {
|
||||
|
|
|
@ -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,
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue