This commit is contained in:
Noel Németh 2022-07-14 00:55:19 +02:00
parent 6884c11e38
commit 0e7630322f
6 changed files with 107 additions and 111 deletions

View File

@ -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();
}
}
}

View File

@ -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 {

View File

@ -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.");
}
/**

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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,
/**