Cleanup old parser

This commit is contained in:
Noel Németh 2022-07-07 23:21:14 +02:00
parent 3d3e44e3d7
commit 4284e059cb
14 changed files with 32 additions and 754 deletions

View File

@ -93,12 +93,12 @@ public final class CommandManager {
PlayerCommandEvent playerCommandEvent = new PlayerCommandEvent(player, command);
EventDispatcher.call(playerCommandEvent);
if (playerCommandEvent.isCancelled())
return CommandResult.of(CommandResult.Type.CANCELLED, command);
return new CommandResult(CommandResult.Type.CANCELLED, command, null);
command = playerCommandEvent.getCommand();
}
// Process the command
final CommandResult result = dispatcher.execute(sender, command);
if (result.getType() == CommandResult.Type.UNKNOWN) {
if (result.type() == CommandResult.Type.UNKNOWN) {
if (unknownCommandCallback != null) {
this.unknownCommandCallback.apply(sender, command);
}

View File

@ -1,11 +1,8 @@
package net.minestom.server.command;
import net.minestom.server.command.builder.ArgumentCallback;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.CommandExecutor;
import net.minestom.server.command.builder.*;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.utils.callback.CommandCallback;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -76,21 +73,24 @@ public final class CommandParser {
@NotNull String input();
@NotNull Map<String, Object> arguments();
default void execute(CommandSender sender, CommandContext context, @Nullable CommandCallback unknownCommandCallback) {
default CommandResult execute(CommandSender sender, CommandContext context) {
if (this instanceof UnknownCommandResult) {
if (unknownCommandCallback == null) return;
unknownCommandCallback.apply(sender, input());
return new CommandResult(CommandResult.Type.UNKNOWN, input(), null);
} else if (this instanceof KnownCommandResult result) {
final CommandCondition condition = result.condition();
final CommandData data = new CommandData(arguments());
if (condition != null && !condition.canUse(sender, input())) {
return; // TODO Should we call a callback here or just let the condition do the notifying?
return new CommandResult(CommandResult.Type.PRECONDITION_FAILED, input(), data);
}
if (result instanceof ValidCommandResult valid) {
valid.executor().apply(sender, context);
return new CommandResult(CommandResult.Type.SUCCESS, input(), data);
} else if (result instanceof SyntaxErrorResult invalid) {
invalid.callback().apply(sender, invalid.exception());
return new CommandResult(CommandResult.Type.INVALID_SYNTAX, input(), data);
}
}
return null;
}
}

View File

@ -6,7 +6,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
record NodeGraph(List<Node> nodes, Node root) {
public record NodeGraph(List<Node> nodes, Node root) {
public Node resolveId(int id) {
return nodes.get(id);

View File

@ -4,11 +4,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CommandData {
private final Map<String, Object> dataMap = new ConcurrentHashMap<>();
public record CommandData(Map<String, Object> dataMap) {
public CommandData set(@NotNull String key, Object value) {
this.dataMap.put(key, value);
@ -23,9 +20,4 @@ public class CommandData {
public boolean has(@NotNull String key) {
return dataMap.containsKey(key);
}
@NotNull
public Map<String, Object> getDataMap() {
return dataMap;
}
}

View File

@ -2,17 +2,10 @@ package net.minestom.server.command.builder;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandParser;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.GraphBuilder;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.parser.CommandParser;
import net.minestom.server.command.builder.parser.CommandQueryResult;
import net.minestom.server.command.builder.parser.CommandSuggestionHolder;
import net.minestom.server.command.builder.parser.ValidSyntaxHolder;
import net.minestom.server.utils.StringUtils;
import net.minestom.server.command.NodeGraph;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -22,12 +15,13 @@ import java.util.concurrent.TimeUnit;
/**
* Class responsible for parsing {@link Command}.
*/
public class CommandDispatcher {
public class CommandDispatcher { //Todo maybe merge with manager?
private final Map<String, Command> commandMap = new HashMap<>();
private final Set<Command> commands = new HashSet<>();
private final Cache<String, CommandResult> cache = Caffeine.newBuilder()
private NodeGraph graph;
private final Cache<String, CommandParser.Result> cache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.build();
@ -49,6 +43,7 @@ public class CommandDispatcher {
}
this.commands.add(command);
this.graph = GraphBuilder.forServer(this.commands);
}
public void unregister(@NotNull Command command) {
@ -62,7 +57,7 @@ public class CommandDispatcher {
}
this.commands.remove(command);
this.graph = GraphBuilder.forServer(this.commands);
// Clear cache
this.cache.invalidateAll();
}
@ -90,150 +85,8 @@ public class CommandDispatcher {
* @return the command result
*/
public @NotNull CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
//todo caching
final net.minestom.server.command.CommandParser.Result result = net.minestom.server.command.CommandParser.parse(GraphBuilder.forServer(this.commands), commandString);
result.execute(source, new CommandContext(commandString).setArgs(result.arguments()), MinecraftServer.getCommandManager().getUnknownCommandCallback());
return new CommandResult(); //todo rework result object
}
/**
* Parses the given command.
*
* @param commandString the command (containing the command name and the args if any)
* @return the parsing result
*/
public @NotNull CommandResult parse(@NotNull String commandString) {
commandString = commandString.trim();
// Verify if the result is cached
{
final CommandResult cachedResult = cache.getIfPresent(commandString);
if (cachedResult != null) {
return cachedResult;
}
}
// Split space
final String[] parts = commandString.split(StringUtils.SPACE);
final String commandName = parts[0];
final CommandQueryResult commandQueryResult = CommandParser.findCommand(this, commandString);
// Check if the command exists
if (commandQueryResult == null) {
return CommandResult.of(CommandResult.Type.UNKNOWN, commandName);
}
CommandResult result = new CommandResult();
result.input = commandString;
// Find the used syntax and fill CommandResult#type and CommandResult#parsedCommand
findParsedCommand( commandQueryResult, commandName, commandString, result);
// Cache result
this.cache.put(commandString, result);
return result;
}
private @NotNull ParsedCommand findParsedCommand(@NotNull CommandQueryResult commandQueryResult,
@NotNull String commandName,
@NotNull String commandString,
@NotNull CommandResult result) {
final Command command = commandQueryResult.command();
String[] args = commandQueryResult.args();
final boolean hasArgument = args.length > 0;
final String input = commandName + StringUtils.SPACE + String.join(StringUtils.SPACE, args);
ParsedCommand parsedCommand = new ParsedCommand();
parsedCommand.parents = commandQueryResult.parents();
parsedCommand.command = command;
parsedCommand.commandString = commandString;
// The default executor should be used if no argument is provided
if (!hasArgument) {
Optional<CommandSyntax> optionalSyntax = command.getSyntaxes()
.stream()
.filter(syntax -> syntax.getArguments().length == 0)
.findFirst();
if (optionalSyntax.isPresent()) {
// Empty syntax found
final CommandSyntax syntax = optionalSyntax.get();
parsedCommand.syntax = syntax;
parsedCommand.executor = syntax.getExecutor();
parsedCommand.context = new CommandContext(input);
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
} else {
// No empty syntax, use default executor if any
final CommandExecutor defaultExecutor = command.getDefaultExecutor();
if (defaultExecutor != null) {
parsedCommand.executor = defaultExecutor;
parsedCommand.context = new CommandContext(input);
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
}
}
}
// SYNTAXES PARSING
// All the registered syntaxes of the command
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
// Contains all the fully validated syntaxes (we later find the one with the most amount of arguments)
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(syntaxes.size());
// Contains all the syntaxes that are not fully correct, used to later, retrieve the "most correct syntax"
// Number of correct argument - The data about the failing argument
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
for (CommandSyntax syntax : syntaxes) {
CommandParser.parse(syntax, syntax.getArguments(), args, commandString, validSyntaxes, syntaxesSuggestions);
}
// Check if there is at least one correct syntax
if (!validSyntaxes.isEmpty()) {
CommandContext context = new CommandContext(input);
// Search the syntax with all perfect args
final ValidSyntaxHolder finalValidSyntax = CommandParser.findMostCorrectSyntax(validSyntaxes, context);
if (finalValidSyntax != null) {
// A fully correct syntax has been found, use it
final CommandSyntax syntax = finalValidSyntax.syntax();
parsedCommand.syntax = syntax;
parsedCommand.executor = syntax.getExecutor();
parsedCommand.context = context;
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
}
}
// No all-correct syntax, find the closest one to use the argument callback
if (!syntaxesSuggestions.isEmpty()) {
final int max = syntaxesSuggestions.firstIntKey(); // number of correct arguments in the most correct syntax
final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max);
final CommandSyntax syntax = suggestionHolder.syntax();
final ArgumentSyntaxException argumentSyntaxException = suggestionHolder.argumentSyntaxException();
final int argIndex = suggestionHolder.argIndex();
// Found the closest syntax with at least 1 correct argument
final Argument<?> argument = syntax.getArguments()[argIndex];
if (argument.hasErrorCallback() && argumentSyntaxException != null) {
parsedCommand.callback = argument.getCallback();
parsedCommand.argumentSyntaxException = argumentSyntaxException;
result.type = CommandResult.Type.INVALID_SYNTAX;
result.parsedCommand = parsedCommand;
return parsedCommand;
}
}
// No syntax found
result.type = CommandResult.Type.INVALID_SYNTAX;
result.parsedCommand = ParsedCommand.withDefaultExecutor(command, input);
return result.parsedCommand;
final CommandParser.Result result = cache.get(commandString, command -> CommandParser.parse(graph, command));
final CommandContext context = new CommandContext(commandString).setArgs(result.arguments());
return result.execute(source, context);
}
}

View File

@ -1,31 +1,6 @@
package net.minestom.server.command.builder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class CommandResult {
protected Type type = Type.UNKNOWN;
protected String input;
protected ParsedCommand parsedCommand;
protected CommandData commandData;
public @NotNull Type getType() {
return type;
}
public @NotNull String getInput() {
return input;
}
public @Nullable ParsedCommand getParsedCommand() {
return parsedCommand;
}
public @Nullable CommandData getCommandData() {
return commandData;
}
public record CommandResult(Type type, String input, CommandData commandData) {
public enum Type {
/**
* Command and syntax successfully found.
@ -40,16 +15,13 @@ public class CommandResult {
* Command cancelled by an event listener.
*/
CANCELLED,
/**
* Either {@link Command#getCondition()} or {@link CommandSyntax#getCommandCondition()} failed
*/
PRECONDITION_FAILED,
/**
* Command is not registered, it is also the default result type.
*/
UNKNOWN
}
public static @NotNull CommandResult of(@NotNull Type type, @NotNull String input) {
CommandResult result = new CommandResult();
result.type = type;
result.input = input;
return result;
}
}

View File

@ -1,109 +0,0 @@
package net.minestom.server.command.builder;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
/**
* Represents a {@link Command} ready to be executed (already parsed).
*/
public class ParsedCommand {
// Command
protected List<Command> parents;
protected Command command;
protected String commandString;
// Command Executor
protected CommandSyntax syntax;
protected CommandExecutor executor;
protected CommandContext context;
// Argument Callback
protected ArgumentCallback callback;
protected ArgumentSyntaxException argumentSyntaxException;
/**
* Executes the command for the given source.
* <p>
* The command will not be executed if {@link Command#getCondition()}
* is not validated.
*
* @param source the command source
* @return the command data, null if none
*/
public @Nullable CommandData execute(@NotNull CommandSender source) {
// Global listener
command.globalListener(source, Objects.requireNonNullElseGet(context, () -> new CommandContext(commandString)), commandString);
// Command condition check
{
// Parents
if (parents != null) {
for (Command parent : parents) {
final CommandCondition condition = parent.getCondition();
if (condition != null) {
final boolean result = condition.canUse(source, commandString);
if (!result) return null;
}
}
}
// Self
final CommandCondition condition = command.getCondition();
if (condition != null) {
final boolean result = condition.canUse(source, commandString);
if (!result) return null;
}
}
// Condition is respected
if (executor != null) {
// An executor has been found
if (syntax != null) {
// The executor is from a syntax
final CommandCondition commandCondition = syntax.getCommandCondition();
if (commandCondition == null || commandCondition.canUse(source, commandString)) {
context.retrieveDefaultValues(syntax.getDefaultValuesMap());
try {
executor.apply(source, context);
} catch (Throwable throwable) {
MinecraftServer.getExceptionManager().handleException(throwable);
}
}
} else {
// The executor is probably the default one
try {
executor.apply(source, context);
} catch (Throwable throwable) {
MinecraftServer.getExceptionManager().handleException(throwable);
}
}
} else if (callback != null && argumentSyntaxException != null) {
// No syntax has been validated but the faulty argument with a callback has been found
// Execute the faulty argument callback
callback.apply(source, argumentSyntaxException);
}
if (context == null) {
// Argument callbacks cannot return data
return null;
}
return context.getReturnData();
}
public static @NotNull ParsedCommand withDefaultExecutor(@NotNull Command command, @NotNull String input) {
ParsedCommand parsedCommand = new ParsedCommand();
parsedCommand.command = command;
parsedCommand.commandString = input;
parsedCommand.executor = command.getDefaultExecutor();
parsedCommand.context = new CommandContext(input);
return parsedCommand;
}
}

View File

@ -2,18 +2,18 @@ package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.arguments.*;
import net.minestom.server.command.builder.arguments.minecraft.*;
import net.minestom.server.command.builder.arguments.minecraft.registry.*;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentEnchantment;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentEntityType;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentParticle;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentPotionEffect;
import net.minestom.server.command.builder.arguments.number.ArgumentDouble;
import net.minestom.server.command.builder.arguments.number.ArgumentFloat;
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeBlockPosition;
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec2;
import net.minestom.server.command.builder.arguments.relative.ArgumentRelativeVec3;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.utils.StringUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@ -137,115 +137,4 @@ public class ArgumentParser {
return result.toArray(Argument[]::new);
}
@Nullable
public static ArgumentResult validate(@NotNull Argument<?> argument,
@NotNull Argument<?>[] arguments, int argIndex,
@NotNull String[] inputArguments, int inputIndex) {
final boolean end = inputIndex == inputArguments.length;
if (end) // Stop if there is no input to analyze left
return null;
// the parsed argument value, null if incorrect
Object parsedValue = null;
// the argument exception, null if the input is correct
ArgumentSyntaxException argumentSyntaxException = null;
// true if the arg is valid, false otherwise
boolean correct = false;
// The raw string value of the argument
String rawArg = null;
if (argument.useRemaining()) {
final boolean hasArgs = inputArguments.length > inputIndex;
// Verify if there is any string part available
if (hasArgs) {
StringBuilder builder = new StringBuilder();
// Argument is supposed to take the rest of the command input
for (int i = inputIndex; i < inputArguments.length; i++) {
final String arg = inputArguments[i];
if (builder.length() > 0)
builder.append(StringUtils.SPACE);
builder.append(arg);
}
rawArg = builder.toString();
try {
parsedValue = argument.parse(rawArg);
correct = true;
} catch (ArgumentSyntaxException exception) {
argumentSyntaxException = exception;
}
}
} else {
// Argument is either single-word or can accept optional delimited space(s)
StringBuilder builder = new StringBuilder();
for (int i = inputIndex; i < inputArguments.length; i++) {
builder.append(inputArguments[i]);
rawArg = builder.toString();
try {
parsedValue = argument.parse(rawArg);
// Prevent quitting the parsing too soon if the argument
// does not allow space
final boolean lastArgumentIteration = argIndex + 1 == arguments.length;
if (lastArgumentIteration && i + 1 < inputArguments.length) {
if (!argument.allowSpace())
break;
builder.append(StringUtils.SPACE);
continue;
}
correct = true;
inputIndex = i + 1;
break;
} catch (ArgumentSyntaxException exception) {
argumentSyntaxException = exception;
if (!argument.allowSpace()) {
// rawArg should be the remaining
for (int j = i + 1; j < inputArguments.length; j++) {
final String arg = inputArguments[j];
if (builder.length() > 0)
builder.append(StringUtils.SPACE);
builder.append(arg);
}
rawArg = builder.toString();
break;
}
builder.append(StringUtils.SPACE);
}
}
}
ArgumentResult argumentResult = new ArgumentResult();
argumentResult.argument = argument;
argumentResult.correct = correct;
argumentResult.inputIndex = inputIndex;
argumentResult.argumentSyntaxException = argumentSyntaxException;
argumentResult.useRemaining = argument.useRemaining();
argumentResult.rawArg = rawArg;
argumentResult.parsedValue = parsedValue;
return argumentResult;
}
public static class ArgumentResult {
public Argument<?> argument;
public boolean correct;
public int inputIndex;
public ArgumentSyntaxException argumentSyntaxException;
public boolean useRemaining;
public String rawArg;
// If correct
public Object parsedValue;
}
}

View File

@ -1,11 +0,0 @@
package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.Argument;
public record ArgumentQueryResult(CommandSyntax syntax,
Argument<?> argument,
CommandContext context,
String input) {
}

View File

@ -1,221 +0,0 @@
package net.minestom.server.command.builder.parser;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.CommandDispatcher;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.utils.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Predicate;
import static net.minestom.server.command.builder.parser.ArgumentParser.validate;
/**
* Class used to parse complete command inputs.
*/
public final class CommandParser {
private static @Nullable CommandQueryResult recursiveCommandQuery(@NotNull CommandDispatcher dispatcher,
List<Command> parents,
@Nullable Command parentCommand, @NotNull String commandName, @NotNull String[] args) {
Command command = parentCommand == null ? dispatcher.findCommand(commandName) : parentCommand;
if (command == null) return null;
CommandQueryResult commandQueryResult = new CommandQueryResult(parents, command, commandName, args);
// Search for subcommand
if (args.length > 0) {
final String subCommandName = args[0];
for (Command subcommand : command.getSubcommands()) {
if (Command.isValidName(subcommand, subCommandName)) {
final String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
parents.add(command);
return recursiveCommandQuery(dispatcher, parents, subcommand, subCommandName, subArgs);
}
}
}
return commandQueryResult;
}
public static @Nullable CommandQueryResult findCommand(@NotNull CommandDispatcher dispatcher, @NotNull String input) {
final String[] parts = input.split(StringUtils.SPACE);
final String commandName = parts[0];
String[] args = new String[parts.length - 1];
System.arraycopy(parts, 1, args, 0, args.length);
List<Command> parents = new ArrayList<>();
return recursiveCommandQuery(dispatcher, parents, null, commandName, args);
}
public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument<?>[] commandArguments, @NotNull String[] inputArguments,
@NotNull String commandString,
@Nullable List<ValidSyntaxHolder> validSyntaxes,
@Nullable Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions) {
final Map<Argument<?>, ArgumentParser.ArgumentResult> argumentValueMap = new HashMap<>();
boolean syntaxCorrect = true;
// The current index in the raw command string arguments
int inputIndex = 0;
boolean useRemaining = false;
// Check the validity of the arguments...
for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) {
final Argument<?> argument = commandArguments[argIndex];
ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, inputArguments, inputIndex);
if (argumentResult == null) {
break;
}
// Update local var
useRemaining = argumentResult.useRemaining;
inputIndex = argumentResult.inputIndex;
if (argumentResult.correct) {
argumentValueMap.put(argumentResult.argument, argumentResult);
} else {
// Argument is not correct, add it to the syntax suggestion with the number
// of correct argument(s) and do not check the next syntax argument
syntaxCorrect = false;
if (syntaxesSuggestions != null) {
syntaxesSuggestions.put(argIndex, new CommandSuggestionHolder(syntax, argumentResult.argumentSyntaxException, argIndex));
}
break;
}
}
// Add the syntax to the list of valid syntaxes if correct
if (syntaxCorrect) {
if (commandArguments.length == argumentValueMap.size() || useRemaining) {
if (validSyntaxes != null) {
validSyntaxes.add(new ValidSyntaxHolder(commandString, syntax, argumentValueMap));
}
}
}
}
/**
* Retrieves from the valid syntax map the arguments condition result and get the one with the most
* valid arguments.
*
* @param validSyntaxes the list containing all the valid syntaxes
* @param context the recipient of the argument parsed values
* @return the command syntax with all of its arguments correct and with the most arguments count, null if not any
*/
@Nullable
public static ValidSyntaxHolder findMostCorrectSyntax(@NotNull List<ValidSyntaxHolder> validSyntaxes,
@NotNull CommandContext context) {
if (validSyntaxes.isEmpty()) {
return null;
}
ValidSyntaxHolder finalSyntax = null;
int maxArguments = 0;
CommandContext finalContext = null;
for (ValidSyntaxHolder validSyntaxHolder : validSyntaxes) {
final Map<Argument<?>, ArgumentParser.ArgumentResult> argsValues = validSyntaxHolder.argumentResults();
final int argsSize = argsValues.size();
// Check if the syntax has more valid arguments
if (argsSize > maxArguments) {
finalSyntax = validSyntaxHolder;
maxArguments = argsSize;
// Fill arguments map
finalContext = new CommandContext(validSyntaxHolder.commandString());
for (var entry : argsValues.entrySet()) {
final Argument<?> argument = entry.getKey();
final ArgumentParser.ArgumentResult argumentResult = entry.getValue();
finalContext.setArg(argument.getId(), argumentResult.parsedValue, argumentResult.rawArg);
}
}
}
// Get the arguments values
if (finalSyntax != null) {
context.copy(finalContext);
}
return finalSyntax;
}
@Nullable
public static ArgumentQueryResult findEligibleArgument(@NotNull Command command, String[] args, String commandString,
boolean trailingSpace, boolean forceCorrect,
Predicate<CommandSyntax> syntaxPredicate,
Predicate<Argument<?>> argumentPredicate) {
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
Int2ObjectRBTreeMap<ArgumentQueryResult> suggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
for (CommandSyntax syntax : syntaxes) {
if (!syntaxPredicate.test(syntax)) {
continue;
}
final CommandContext context = new CommandContext(commandString);
final Argument<?>[] commandArguments = syntax.getArguments();
int inputIndex = 0;
ArgumentQueryResult maxArg = null;
int maxArgIndex = 0;
for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) {
Argument<?> argument = commandArguments[argIndex];
ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, args, inputIndex);
if (argumentResult == null) {
// Nothing to analyze, create a dummy object
argumentResult = new ArgumentParser.ArgumentResult();
argumentResult.argument = argument;
argumentResult.correct = false;
argumentResult.inputIndex = inputIndex;
argumentResult.rawArg = "";
}
// Update local var
inputIndex = argumentResult.inputIndex;
if (argumentResult.correct) {
// Fill context
context.setArg(argument.getId(), argumentResult.parsedValue, argumentResult.rawArg);
}
// Save result
if ((!forceCorrect || argumentResult.correct) &&
argumentPredicate.test(argument)) {
maxArg = new ArgumentQueryResult(syntax, argument, context, argumentResult.rawArg);
maxArgIndex = argIndex;
}
// Don't compute following arguments if the syntax is incorrect
if (!argumentResult.correct) {
break;
}
// Don't compute unrelated arguments
final boolean isLast = inputIndex == args.length;
if (isLast && !trailingSpace) {
break;
}
}
if (maxArg != null) {
suggestions.put(maxArgIndex, maxArg);
}
}
if (suggestions.isEmpty()) {
// No suggestion
return null;
}
final int max = suggestions.firstIntKey();
return suggestions.get(max);
}
}

View File

@ -1,11 +0,0 @@
package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.Command;
import java.util.List;
public record CommandQueryResult(List<Command> parents,
Command command,
String commandName,
String[] args) {
}

View File

@ -1,12 +0,0 @@
package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
/**
* Holds the data of an invalidated syntax.
*/
public record CommandSuggestionHolder(CommandSyntax syntax,
ArgumentSyntaxException argumentSyntaxException,
int argIndex) {
}

View File

@ -1,15 +0,0 @@
package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.Argument;
import java.util.Map;
/**
* Holds the data of a validated syntax.
*/
public record ValidSyntaxHolder(String commandString,
CommandSyntax syntax,
Map<Argument<?>, ArgumentParser.ArgumentResult> argumentResults) {
}

View File

@ -1,24 +1,12 @@
package net.minestom.server.listener;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandManager;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.CommandSyntax;
import net.minestom.server.command.builder.arguments.Argument;
import net.minestom.server.command.builder.parser.ArgumentQueryResult;
import net.minestom.server.command.builder.parser.CommandParser;
import net.minestom.server.command.builder.parser.CommandQueryResult;
import net.minestom.server.command.builder.suggestion.Suggestion;
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.client.play.ClientTabCompletePacket;
import net.minestom.server.network.packet.server.play.TabCompletePacket;
import net.minestom.server.utils.StringUtils;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.regex.Pattern;
public class TabCompleteListener {
public static void listener(ClientTabCompletePacket packet, Player player) {
@ -37,43 +25,6 @@ public class TabCompleteListener {
}
public static @Nullable Suggestion getSuggestion(CommandSender commandSender, String text) {
String commandString = text.replaceFirst(CommandManager.COMMAND_PREFIX, "");
String[] split = commandString.split(StringUtils.SPACE);
String commandName = split[0];
String args = commandString.replaceFirst(Pattern.quote(commandName), "");
final CommandQueryResult commandQueryResult = CommandParser.findCommand(MinecraftServer.getCommandManager().getDispatcher(), commandString);
if (commandQueryResult == null) {
// Command not found
return null;
}
final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult.command(),
commandQueryResult.args(), commandString, text.endsWith(StringUtils.SPACE), false,
CommandSyntax::hasSuggestion, Argument::hasSuggestion);
if (queryResult == null) {
// Suggestible argument not found
return null;
}
final Argument<?> argument = queryResult.argument();
final SuggestionCallback suggestionCallback = argument.getSuggestionCallback();
if (suggestionCallback != null) {
final String input = queryResult.input();
final int inputLength = input.length();
final int commandLength = Arrays.stream(split).map(String::length).reduce(0, Integer::sum) +
StringUtils.countMatches(args, StringUtils.SPACE_CHAR);
final int trailingSpaces = !input.isEmpty() ? text.length() - text.trim().length() : 0;
final int start = commandLength - inputLength + 1 - trailingSpaces;
Suggestion suggestion = new Suggestion(input, start, inputLength);
suggestionCallback.apply(commandSender, queryResult.context(), suggestion);
return suggestion;
}
return null;
return null; //TODO Reimplement with new system
}
}