mirror of https://github.com/Minestom/Minestom.git
213 lines
10 KiB
Java
213 lines
10 KiB
Java
package net.minestom.server.command.builder.parser;
|
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
|
|
import net.minestom.server.MinecraftServer;
|
|
import net.minestom.server.command.CommandManager;
|
|
import net.minestom.server.command.builder.Command;
|
|
import net.minestom.server.command.builder.CommandContext;
|
|
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.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
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 final CommandManager COMMAND_MANAGER = MinecraftServer.getCommandManager();
|
|
|
|
public static @Nullable CommandQueryResult findCommand(@Nullable Command parentCommand, @NotNull String commandName,
|
|
@NotNull String argsInput, @NotNull String input) {
|
|
Command command = parentCommand == null ? COMMAND_MANAGER.getDispatcher().findCommand(commandName) : parentCommand;
|
|
if (command == null) return null;
|
|
// Search for subcommand
|
|
final int nextArgIndex = argsInput.indexOf(StringUtils.SPACE);
|
|
if (nextArgIndex != -1) {
|
|
final String subCommandName = argsInput.substring(0, nextArgIndex);
|
|
for (Command subcommand : command.getSubcommands()) {
|
|
if (Command.isValidName(subcommand, subCommandName)) {
|
|
final String updated = StringUtils.trimLeft(argsInput.replaceFirst(commandName, ""));
|
|
return findCommand(subcommand, subCommandName, updated, input);
|
|
}
|
|
}
|
|
}
|
|
return new CommandQueryResult(command, argsInput, input);
|
|
}
|
|
|
|
public static @Nullable CommandQueryResult findCommand(@NotNull String input) {
|
|
final String commandName;
|
|
final String content;
|
|
final int commandNameEnd = input.indexOf(StringUtils.SPACE);
|
|
if (commandNameEnd != -1) {
|
|
commandName = input.substring(0, commandNameEnd);
|
|
content = StringUtils.trimLeft(input.replaceFirst(commandName, ""));
|
|
} else {
|
|
commandName = input;
|
|
content = "";
|
|
}
|
|
return CommandParser.findCommand(null, commandName, content, input);
|
|
}
|
|
|
|
public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument<?>[] commandArguments,
|
|
@NotNull CommandQueryResult queryResult,
|
|
@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, queryResult.argsInput(), 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) {
|
|
CommandSuggestionHolder suggestionHolder = new CommandSuggestionHolder();
|
|
suggestionHolder.syntax = syntax;
|
|
suggestionHolder.argumentSyntaxException = argumentResult.argumentSyntaxException;
|
|
suggestionHolder.argIndex = argIndex;
|
|
syntaxesSuggestions.put(argIndex, suggestionHolder);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add the syntax to the list of valid syntaxes if correct
|
|
if (syntaxCorrect) {
|
|
if (commandArguments.length == argumentValueMap.size() || useRemaining) {
|
|
if (validSyntaxes != null) {
|
|
ValidSyntaxHolder validSyntaxHolder = new ValidSyntaxHolder();
|
|
validSyntaxHolder.commandString = queryResult.input();
|
|
validSyntaxHolder.syntax = syntax;
|
|
validSyntaxHolder.argumentResults = argumentValueMap;
|
|
|
|
validSyntaxes.add(validSyntaxHolder);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public static @Nullable 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;
|
|
}
|
|
|
|
public static @Nullable ArgumentQueryResult findEligibleArgument(@NotNull CommandQueryResult result,
|
|
boolean trailingSpace, boolean forceCorrect,
|
|
Predicate<CommandSyntax> syntaxPredicate,
|
|
Predicate<Argument<?>> argumentPredicate) {
|
|
final String[] args = result.argsInput().split(StringUtils.SPACE);
|
|
Int2ObjectRBTreeMap<ArgumentQueryResult> suggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
|
|
for (CommandSyntax syntax : result.command().getSyntaxes()) {
|
|
if (!syntaxPredicate.test(syntax)) continue;
|
|
|
|
final CommandContext context = new CommandContext(result.input());
|
|
|
|
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, result.argsInput(), 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)) {
|
|
ArgumentQueryResult queryResult = new ArgumentQueryResult();
|
|
queryResult.syntax = syntax;
|
|
queryResult.argument = argument;
|
|
queryResult.context = context;
|
|
queryResult.input = argumentResult.rawArg;
|
|
|
|
maxArg = queryResult;
|
|
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()) return null;
|
|
final int max = suggestions.firstIntKey();
|
|
return suggestions.get(max);
|
|
}
|
|
}
|