Initial cleanup attempt

Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
TheMode 2021-10-15 08:01:40 +02:00
parent 0bd519c894
commit 5ce7c6ccdb
9 changed files with 117 additions and 179 deletions

View File

@ -214,7 +214,7 @@ public final class CommandManager {
} }
final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult.command, final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult.command,
commandQueryResult.args, input, false, true, syntax -> true, argument -> true); commandQueryResult.content(), input, false, true, syntax -> true, argument -> true);
if (queryResult == null) { if (queryResult == null) {
// Invalid argument, return command node (default to root) // Invalid argument, return command node (default to root)
final int commandNode = commandIdentityMap.getOrDefault(commandQueryResult.command, 0); final int commandNode = commandIdentityMap.getOrDefault(commandQueryResult.command, 0);

View File

@ -7,7 +7,6 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class CommandData { public class CommandData {
private final Map<String, Object> dataMap = new ConcurrentHashMap<>(); private final Map<String, Object> dataMap = new ConcurrentHashMap<>();
public CommandData set(@NotNull String key, Object value) { public CommandData set(@NotNull String key, Object value) {
@ -15,8 +14,7 @@ public class CommandData {
return this; return this;
} }
@Nullable public <T> @Nullable T get(@NotNull String key) {
public <T> T get(@NotNull String key) {
return (T) dataMap.get(key); return (T) dataMap.get(key);
} }
@ -24,8 +22,7 @@ public class CommandData {
return dataMap.containsKey(key); return dataMap.containsKey(key);
} }
@NotNull public @NotNull Map<String, Object> getDataMap() {
public Map<String, Object> getDataMap() {
return dataMap; return dataMap;
} }
} }

View File

@ -20,11 +20,9 @@ import java.util.concurrent.TimeUnit;
/** /**
* Class responsible for parsing {@link Command}. * Class responsible for parsing {@link Command}.
*/ */
public class CommandDispatcher { public final class CommandDispatcher {
private final Map<String, Command> commandMap = new HashMap<>(); private final Map<String, Command> commandMap = new HashMap<>();
private final Set<Command> commands = new HashSet<>(); private final Set<Command> commands = new HashSet<>();
private final Cache<String, CommandResult> cache = Caffeine.newBuilder() private final Cache<String, CommandResult> cache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS) .expireAfterWrite(30, TimeUnit.SECONDS)
.build(); .build();
@ -37,7 +35,6 @@ public class CommandDispatcher {
*/ */
public void register(@NotNull Command command) { public void register(@NotNull Command command) {
this.commandMap.put(command.getName().toLowerCase(), command); this.commandMap.put(command.getName().toLowerCase(), command);
// Register aliases // Register aliases
final String[] aliases = command.getAliases(); final String[] aliases = command.getAliases();
if (aliases != null) { if (aliases != null) {
@ -45,20 +42,17 @@ public class CommandDispatcher {
this.commandMap.put(alias.toLowerCase(), command); this.commandMap.put(alias.toLowerCase(), command);
} }
} }
this.commands.add(command); this.commands.add(command);
} }
public void unregister(@NotNull Command command) { public void unregister(@NotNull Command command) {
this.commandMap.remove(command.getName().toLowerCase()); this.commandMap.remove(command.getName().toLowerCase());
final String[] aliases = command.getAliases(); final String[] aliases = command.getAliases();
if (aliases != null) { if (aliases != null) {
for (String alias : aliases) { for (String alias : aliases) {
this.commandMap.remove(alias.toLowerCase()); this.commandMap.remove(alias.toLowerCase());
} }
} }
this.commands.remove(command); this.commands.remove(command);
// Clear cache // Clear cache
@ -76,8 +70,7 @@ public class CommandDispatcher {
* @return the {@link Command} associated with the name, null if not any * @return the {@link Command} associated with the name, null if not any
*/ */
public @Nullable Command findCommand(@NotNull String commandName) { public @Nullable Command findCommand(@NotNull String commandName) {
commandName = commandName.toLowerCase(); return commandMap.getOrDefault(commandName.toLowerCase(), null);
return commandMap.getOrDefault(commandName, null);
} }
/** /**
@ -90,9 +83,7 @@ public class CommandDispatcher {
public @NotNull CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) { public @NotNull CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
CommandResult commandResult = parse(commandString); CommandResult commandResult = parse(commandString);
ParsedCommand parsedCommand = commandResult.parsedCommand; ParsedCommand parsedCommand = commandResult.parsedCommand;
if (parsedCommand != null) { if (parsedCommand != null) commandResult.commandData = parsedCommand.execute(source);
commandResult.commandData = parsedCommand.execute(source);
}
return commandResult; return commandResult;
} }
@ -117,16 +108,14 @@ public class CommandDispatcher {
final String commandName = parts[0]; final String commandName = parts[0];
final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString); final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString);
// Check if the command exists
if (commandQueryResult == null) { if (commandQueryResult == null) {
return CommandResult.of(CommandResult.Type.UNKNOWN, commandName); return CommandResult.of(CommandResult.Type.UNKNOWN, commandName);
} }
final Command command = commandQueryResult.command;
CommandResult result = new CommandResult(); CommandResult result = new CommandResult();
result.input = commandString; result.input = commandString;
// Find the used syntax and fill CommandResult#type and CommandResult#parsedCommand // Find the used syntax and fill CommandResult#type and CommandResult#parsedCommand
findParsedCommand(command, commandName, commandQueryResult.args, commandString, result); findParsedCommand(commandQueryResult, result);
// Cache result // Cache result
this.cache.put(commandString, result); this.cache.put(commandString, result);
@ -134,32 +123,17 @@ public class CommandDispatcher {
return result; return result;
} }
private @Nullable ParsedCommand findParsedCommand(@NotNull Command command, private void findParsedCommand(@NotNull CommandQueryResult queryResult,
@NotNull String commandName, @NotNull String[] args, @NotNull CommandResult result) {
@NotNull String commandString, final Command command = queryResult.command();
@NotNull CommandResult result) { final String commandString = queryResult.input();
final boolean hasArgument = args.length > 0;
// Search for subcommand
if (hasArgument) {
final String firstArgument = args[0];
for (Command subcommand : command.getSubcommands()) {
if (Command.isValidName(subcommand, firstArgument)) {
return findParsedCommand(subcommand,
firstArgument, Arrays.copyOfRange(args, 1, args.length),
commandString, result);
}
}
}
final String input = commandName + StringUtils.SPACE + String.join(StringUtils.SPACE, args);
ParsedCommand parsedCommand = new ParsedCommand(); ParsedCommand parsedCommand = new ParsedCommand();
parsedCommand.command = command; parsedCommand.command = command;
parsedCommand.commandString = commandString; parsedCommand.commandString = commandString;
// The default executor should be used if no argument is provided // The default executor should be used if no argument is provided
if (!hasArgument) { if (queryResult.argsInput().isEmpty()) {
Optional<CommandSyntax> optionalSyntax = command.getSyntaxes() Optional<CommandSyntax> optionalSyntax = command.getSyntaxes()
.stream() .stream()
.filter(syntax -> syntax.getArguments().length == 0) .filter(syntax -> syntax.getArguments().length == 0)
@ -170,21 +144,21 @@ public class CommandDispatcher {
final CommandSyntax syntax = optionalSyntax.get(); final CommandSyntax syntax = optionalSyntax.get();
parsedCommand.syntax = syntax; parsedCommand.syntax = syntax;
parsedCommand.executor = syntax.getExecutor(); parsedCommand.executor = syntax.getExecutor();
parsedCommand.context = new CommandContext(input); parsedCommand.context = new CommandContext(commandString);
result.type = CommandResult.Type.SUCCESS; result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand; result.parsedCommand = parsedCommand;
return parsedCommand; return;
} else { } else {
// No empty syntax, use default executor if any // No empty syntax, use default executor if any
final CommandExecutor defaultExecutor = command.getDefaultExecutor(); final CommandExecutor defaultExecutor = command.getDefaultExecutor();
if (defaultExecutor != null) { if (defaultExecutor != null) {
parsedCommand.executor = defaultExecutor; parsedCommand.executor = defaultExecutor;
parsedCommand.context = new CommandContext(input); parsedCommand.context = new CommandContext(commandString);
result.type = CommandResult.Type.SUCCESS; result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand; result.parsedCommand = parsedCommand;
return parsedCommand; return;
} }
} }
} }
@ -200,12 +174,12 @@ public class CommandDispatcher {
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder()); Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
for (CommandSyntax syntax : syntaxes) { for (CommandSyntax syntax : syntaxes) {
CommandParser.parse(syntax, syntax.getArguments(), args, commandString, validSyntaxes, syntaxesSuggestions); CommandParser.parse(syntax, syntax.getArguments(), queryResult, validSyntaxes, syntaxesSuggestions);
} }
// Check if there is at least one correct syntax // Check if there is at least one correct syntax
if (!validSyntaxes.isEmpty()) { if (!validSyntaxes.isEmpty()) {
CommandContext context = new CommandContext(input); CommandContext context = new CommandContext(commandString);
// Search the syntax with all perfect args // Search the syntax with all perfect args
final ValidSyntaxHolder finalValidSyntax = CommandParser.findMostCorrectSyntax(validSyntaxes, context); final ValidSyntaxHolder finalValidSyntax = CommandParser.findMostCorrectSyntax(validSyntaxes, context);
if (finalValidSyntax != null) { if (finalValidSyntax != null) {
@ -218,7 +192,7 @@ public class CommandDispatcher {
result.type = CommandResult.Type.SUCCESS; result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand; result.parsedCommand = parsedCommand;
return parsedCommand; return;
} }
} }
@ -238,13 +212,12 @@ public class CommandDispatcher {
result.type = CommandResult.Type.INVALID_SYNTAX; result.type = CommandResult.Type.INVALID_SYNTAX;
result.parsedCommand = parsedCommand; result.parsedCommand = parsedCommand;
return parsedCommand; return;
} }
} }
// No syntax found // No syntax found
result.type = CommandResult.Type.INVALID_SYNTAX; result.type = CommandResult.Type.INVALID_SYNTAX;
result.parsedCommand = ParsedCommand.withDefaultExecutor(command, input); result.parsedCommand = ParsedCommand.withDefaultExecutor(command, commandString);
return result.parsedCommand;
} }
} }

View File

@ -5,7 +5,6 @@ import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException; import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.parser.CommandParser; import net.minestom.server.command.builder.parser.CommandParser;
import net.minestom.server.command.builder.parser.ValidSyntaxHolder; import net.minestom.server.command.builder.parser.ValidSyntaxHolder;
import net.minestom.server.utils.StringUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@ -26,14 +25,12 @@ public class ArgumentGroup extends Argument<CommandContext> {
@Override @Override
public CommandContext parse(@NotNull String input) throws ArgumentSyntaxException { public CommandContext parse(@NotNull String input) throws ArgumentSyntaxException {
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(); List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>();
CommandParser.parse(null, group, input.split(StringUtils.SPACE), input, validSyntaxes, null); CommandParser.parse(null, group, input, input, validSyntaxes, null);
CommandContext context = new CommandContext(input); CommandContext context = new CommandContext(input);
CommandParser.findMostCorrectSyntax(validSyntaxes, context); CommandParser.findMostCorrectSyntax(validSyntaxes, context);
if (validSyntaxes.isEmpty()) { if (validSyntaxes.isEmpty())
throw new ArgumentSyntaxException("Invalid arguments", input, INVALID_ARGUMENTS_ERROR); throw new ArgumentSyntaxException("Invalid arguments", input, INVALID_ARGUMENTS_ERROR);
}
return context; return context;
} }

View File

@ -2,7 +2,10 @@ package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.arguments.*; import net.minestom.server.command.builder.arguments.*;
import net.minestom.server.command.builder.arguments.minecraft.*; 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.ArgumentDouble;
import net.minestom.server.command.builder.arguments.number.ArgumentFloat; 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.number.ArgumentInteger;
@ -41,10 +44,10 @@ public class ArgumentParser {
ARGUMENT_FUNCTION_MAP.put("time", ArgumentTime::new); ARGUMENT_FUNCTION_MAP.put("time", ArgumentTime::new);
ARGUMENT_FUNCTION_MAP.put("enchantment", ArgumentEnchantment::new); ARGUMENT_FUNCTION_MAP.put("enchantment", ArgumentEnchantment::new);
ARGUMENT_FUNCTION_MAP.put("particle", ArgumentParticle::new); ARGUMENT_FUNCTION_MAP.put("particle", ArgumentParticle::new);
ARGUMENT_FUNCTION_MAP.put("resourceLocation", ArgumentResourceLocation::new); ARGUMENT_FUNCTION_MAP.put("resourcelocation", ArgumentResourceLocation::new);
ARGUMENT_FUNCTION_MAP.put("potion", ArgumentPotionEffect::new); ARGUMENT_FUNCTION_MAP.put("potion", ArgumentPotionEffect::new);
ARGUMENT_FUNCTION_MAP.put("entityType", ArgumentEntityType::new); ARGUMENT_FUNCTION_MAP.put("entitytype", ArgumentEntityType::new);
ARGUMENT_FUNCTION_MAP.put("blockState", ArgumentBlockState::new); ARGUMENT_FUNCTION_MAP.put("blockstate", ArgumentBlockState::new);
ARGUMENT_FUNCTION_MAP.put("intrange", ArgumentIntRange::new); ARGUMENT_FUNCTION_MAP.put("intrange", ArgumentIntRange::new);
ARGUMENT_FUNCTION_MAP.put("floatrange", ArgumentFloatRange::new); ARGUMENT_FUNCTION_MAP.put("floatrange", ArgumentFloatRange::new);
@ -66,7 +69,6 @@ public class ArgumentParser {
@ApiStatus.Experimental @ApiStatus.Experimental
public static @NotNull Argument<?>[] generate(@NotNull String format) { public static @NotNull Argument<?>[] generate(@NotNull String format) {
List<Argument<?>> result = new ArrayList<>(); List<Argument<?>> result = new ArrayList<>();
// 0 = no state // 0 = no state
// 1 = inside angle bracket <> // 1 = inside angle bracket <>
int state = 0; int state = 0;
@ -75,11 +77,9 @@ public class ArgumentParser {
Function<String, Argument<?>> argumentFunction = null; Function<String, Argument<?>> argumentFunction = null;
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
// test: Integer<name> String<hey> // test: Integer<name> String<hey>
for (int i = 0; i < format.length(); i++) { for (int i = 0; i < format.length(); i++) {
char c = format.charAt(i); final char c = format.charAt(i);
// No state // No state
if (state == 0) { if (state == 0) {
if (c == ' ') { if (c == ' ') {
@ -133,17 +133,15 @@ public class ArgumentParser {
result.add(new ArgumentLiteral(argument)); result.add(new ArgumentLiteral(argument));
} }
} }
return result.toArray(Argument[]::new); return result.toArray(Argument[]::new);
} }
@Nullable public static @Nullable ArgumentResult validate(@NotNull Argument<?> argument,
public static ArgumentResult validate(@NotNull Argument<?> argument, @NotNull Argument<?>[] arguments, int argIndex,
@NotNull Argument<?>[] arguments, int argIndex, @NotNull String input, int inputIndex) {
@NotNull String[] inputArguments, int inputIndex) { final String[] inputArguments = input.split(StringUtils.SPACE);
final boolean end = inputIndex == inputArguments.length; // Stop if there is no input to analyze left
if (end) // Stop if there is no input to analyze left if (inputIndex == inputArguments.length) return null;
return null;
// the parsed argument value, null if incorrect // the parsed argument value, null if incorrect
Object parsedValue = null; Object parsedValue = null;
@ -162,13 +160,10 @@ public class ArgumentParser {
// Argument is supposed to take the rest of the command input // Argument is supposed to take the rest of the command input
for (int i = inputIndex; i < inputArguments.length; i++) { for (int i = inputIndex; i < inputArguments.length; i++) {
final String arg = inputArguments[i]; final String arg = inputArguments[i];
if (builder.length() > 0) if (builder.length() > 0) builder.append(StringUtils.SPACE);
builder.append(StringUtils.SPACE);
builder.append(arg); builder.append(arg);
} }
rawArg = builder.toString(); rawArg = builder.toString();
try { try {
parsedValue = argument.parse(rawArg); parsedValue = argument.parse(rawArg);
correct = true; correct = true;
@ -181,29 +176,22 @@ public class ArgumentParser {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (int i = inputIndex; i < inputArguments.length; i++) { for (int i = inputIndex; i < inputArguments.length; i++) {
builder.append(inputArguments[i]); builder.append(inputArguments[i]);
rawArg = builder.toString(); rawArg = builder.toString();
try { try {
parsedValue = argument.parse(rawArg); parsedValue = argument.parse(rawArg);
// Prevent quitting the parsing too soon if the argument // Prevent quitting the parsing too soon if the argument
// does not allow space // does not allow space
final boolean lastArgumentIteration = argIndex + 1 == arguments.length; final boolean lastArgumentIteration = argIndex + 1 == arguments.length;
if (lastArgumentIteration && i + 1 < inputArguments.length) { if (lastArgumentIteration && i + 1 < inputArguments.length) {
if (!argument.allowSpace()) if (!argument.allowSpace()) break;
break;
builder.append(StringUtils.SPACE); builder.append(StringUtils.SPACE);
continue; continue;
} }
correct = true; correct = true;
inputIndex = i + 1; inputIndex = i + 1;
break; break;
} catch (ArgumentSyntaxException exception) { } catch (ArgumentSyntaxException exception) {
argumentSyntaxException = exception; argumentSyntaxException = exception;
if (!argument.allowSpace()) { if (!argument.allowSpace()) {
// rawArg should be the remaining // rawArg should be the remaining
for (int j = i + 1; j < inputArguments.length; j++) { for (int j = i + 1; j < inputArguments.length; j++) {
@ -247,5 +235,4 @@ public class ArgumentParser {
// If correct // If correct
public Object parsedValue; public Object parsedValue;
} }
} }

View File

@ -11,7 +11,10 @@ import net.minestom.server.utils.StringUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import static net.minestom.server.command.builder.parser.ArgumentParser.validate; import static net.minestom.server.command.builder.parser.ArgumentParser.validate;
@ -19,51 +22,43 @@ import static net.minestom.server.command.builder.parser.ArgumentParser.validate
/** /**
* Class used to parse complete command inputs. * Class used to parse complete command inputs.
*/ */
public class CommandParser { public final class CommandParser {
private static final CommandManager COMMAND_MANAGER = MinecraftServer.getCommandManager(); private static final CommandManager COMMAND_MANAGER = MinecraftServer.getCommandManager();
@Nullable public static @Nullable CommandQueryResult findCommand(@Nullable Command parentCommand, @NotNull String commandName,
public static CommandQueryResult findCommand(@Nullable Command parentCommand, @NotNull String commandName, @NotNull String[] args) { @NotNull String argsInput, @NotNull String input) {
Command command = parentCommand == null ? COMMAND_MANAGER.getDispatcher().findCommand(commandName) : parentCommand; Command command = parentCommand == null ? COMMAND_MANAGER.getDispatcher().findCommand(commandName) : parentCommand;
if (command == null) { if (command == null) return null;
return null;
}
CommandQueryResult commandQueryResult = new CommandQueryResult();
commandQueryResult.command = command;
commandQueryResult.commandName = commandName;
commandQueryResult.args = args;
// Search for subcommand // Search for subcommand
if (args.length > 0) { final int nextArgIndex = argsInput.indexOf(StringUtils.SPACE);
final String subCommandName = args[0]; if (nextArgIndex != -1) {
final String subCommandName = argsInput.substring(0, nextArgIndex);
for (Command subcommand : command.getSubcommands()) { for (Command subcommand : command.getSubcommands()) {
if (Command.isValidName(subcommand, subCommandName)) { if (Command.isValidName(subcommand, subCommandName)) {
final String[] subArgs = Arrays.copyOfRange(args, 1, args.length); final String updated = StringUtils.trimLeft(argsInput.replaceFirst(commandName, ""));
commandQueryResult.command = subcommand; return findCommand(subcommand, subCommandName, updated, input);
commandQueryResult.commandName = subCommandName;
commandQueryResult.args = subArgs;
return findCommand(subcommand, subCommandName, subArgs);
} }
} }
} }
return new CommandQueryResult(command, argsInput, input);
return commandQueryResult;
} }
@Nullable public static @Nullable CommandQueryResult findCommand(@NotNull String input) {
public static CommandQueryResult findCommand(@NotNull String input) { final String commandName;
final String[] parts = input.split(StringUtils.SPACE); final String content;
final String commandName = parts[0]; final int commandNameEnd = input.indexOf(StringUtils.SPACE);
if (commandNameEnd != -1) {
String[] args = new String[parts.length - 1]; commandName = input.substring(0, commandNameEnd);
System.arraycopy(parts, 1, args, 0, args.length); content = StringUtils.trimLeft(input.replaceFirst(commandName, ""));
return CommandParser.findCommand(null, commandName, args); } else {
commandName = input;
content = "";
}
return CommandParser.findCommand(null, commandName, content, input);
} }
public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument<?>[] commandArguments, @NotNull String[] inputArguments, public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument<?>[] commandArguments,
@NotNull String commandString, @NotNull CommandQueryResult queryResult,
@Nullable List<ValidSyntaxHolder> validSyntaxes, @Nullable List<ValidSyntaxHolder> validSyntaxes,
@Nullable Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions) { @Nullable Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions) {
final Map<Argument<?>, ArgumentParser.ArgumentResult> argumentValueMap = new HashMap<>(); final Map<Argument<?>, ArgumentParser.ArgumentResult> argumentValueMap = new HashMap<>();
@ -76,10 +71,8 @@ public class CommandParser {
// Check the validity of the arguments... // Check the validity of the arguments...
for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) { for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) {
final Argument<?> argument = commandArguments[argIndex]; final Argument<?> argument = commandArguments[argIndex];
ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, inputArguments, inputIndex); ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, queryResult.argsInput(), inputIndex);
if (argumentResult == null) { if (argumentResult == null) break;
break;
}
// Update local var // Update local var
useRemaining = argumentResult.useRemaining; useRemaining = argumentResult.useRemaining;
@ -107,7 +100,7 @@ public class CommandParser {
if (commandArguments.length == argumentValueMap.size() || useRemaining) { if (commandArguments.length == argumentValueMap.size() || useRemaining) {
if (validSyntaxes != null) { if (validSyntaxes != null) {
ValidSyntaxHolder validSyntaxHolder = new ValidSyntaxHolder(); ValidSyntaxHolder validSyntaxHolder = new ValidSyntaxHolder();
validSyntaxHolder.commandString = commandString; validSyntaxHolder.commandString = queryResult.input();
validSyntaxHolder.syntax = syntax; validSyntaxHolder.syntax = syntax;
validSyntaxHolder.argumentResults = argumentValueMap; validSyntaxHolder.argumentResults = argumentValueMap;
@ -125,27 +118,20 @@ public class CommandParser {
* @param context the recipient of the argument parsed values * @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 * @return the command syntax with all of its arguments correct and with the most arguments count, null if not any
*/ */
@Nullable public static @Nullable ValidSyntaxHolder findMostCorrectSyntax(@NotNull List<ValidSyntaxHolder> validSyntaxes,
public static ValidSyntaxHolder findMostCorrectSyntax(@NotNull List<ValidSyntaxHolder> validSyntaxes, @NotNull CommandContext context) {
@NotNull CommandContext context) { if (validSyntaxes.isEmpty()) return null;
if (validSyntaxes.isEmpty()) {
return null;
}
ValidSyntaxHolder finalSyntax = null; ValidSyntaxHolder finalSyntax = null;
int maxArguments = 0; int maxArguments = 0;
CommandContext finalContext = null; CommandContext finalContext = null;
for (ValidSyntaxHolder validSyntaxHolder : validSyntaxes) { for (ValidSyntaxHolder validSyntaxHolder : validSyntaxes) {
final Map<Argument<?>, ArgumentParser.ArgumentResult> argsValues = validSyntaxHolder.argumentResults; final Map<Argument<?>, ArgumentParser.ArgumentResult> argsValues = validSyntaxHolder.argumentResults;
final int argsSize = argsValues.size(); final int argsSize = argsValues.size();
// Check if the syntax has more valid arguments // Check if the syntax has more valid arguments
if (argsSize > maxArguments) { if (argsSize > maxArguments) {
finalSyntax = validSyntaxHolder; finalSyntax = validSyntaxHolder;
maxArguments = argsSize; maxArguments = argsSize;
// Fill arguments map // Fill arguments map
finalContext = new CommandContext(validSyntaxHolder.commandString); finalContext = new CommandContext(validSyntaxHolder.commandString);
for (var entry : argsValues.entrySet()) { for (var entry : argsValues.entrySet()) {
@ -155,30 +141,21 @@ public class CommandParser {
} }
} }
} }
// Get the arguments values // Get the arguments values
if (finalSyntax != null) { if (finalSyntax != null) context.copy(finalContext);
context.copy(finalContext);
}
return finalSyntax; return finalSyntax;
} }
@Nullable public static @Nullable ArgumentQueryResult findEligibleArgument(@NotNull CommandQueryResult result,
public static ArgumentQueryResult findEligibleArgument(@NotNull Command command, String[] args, String commandString, boolean trailingSpace, boolean forceCorrect,
boolean trailingSpace, boolean forceCorrect, Predicate<CommandSyntax> syntaxPredicate,
Predicate<CommandSyntax> syntaxPredicate, Predicate<Argument<?>> argumentPredicate) {
Predicate<Argument<?>> argumentPredicate) { final String[] args = result.argsInput().split(StringUtils.SPACE);
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
Int2ObjectRBTreeMap<ArgumentQueryResult> suggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder()); Int2ObjectRBTreeMap<ArgumentQueryResult> suggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
for (CommandSyntax syntax : result.command().getSyntaxes()) {
if (!syntaxPredicate.test(syntax)) continue;
for (CommandSyntax syntax : syntaxes) { final CommandContext context = new CommandContext(result.input());
if (!syntaxPredicate.test(syntax)) {
continue;
}
final CommandContext context = new CommandContext(commandString);
final Argument<?>[] commandArguments = syntax.getArguments(); final Argument<?>[] commandArguments = syntax.getArguments();
int inputIndex = 0; int inputIndex = 0;
@ -187,7 +164,7 @@ public class CommandParser {
int maxArgIndex = 0; int maxArgIndex = 0;
for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) { for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) {
Argument<?> argument = commandArguments[argIndex]; Argument<?> argument = commandArguments[argIndex];
ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, args, inputIndex); ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, result.argsInput(), inputIndex);
if (argumentResult == null) { if (argumentResult == null) {
// Nothing to analyze, create a dummy object // Nothing to analyze, create a dummy object
argumentResult = new ArgumentParser.ArgumentResult(); argumentResult = new ArgumentParser.ArgumentResult();
@ -219,29 +196,17 @@ public class CommandParser {
} }
// Don't compute following arguments if the syntax is incorrect // Don't compute following arguments if the syntax is incorrect
if (!argumentResult.correct) { if (!argumentResult.correct) break;
break;
}
// Don't compute unrelated arguments // Don't compute unrelated arguments
final boolean isLast = inputIndex == args.length; final boolean isLast = inputIndex == args.length;
if (isLast && !trailingSpace) { if (isLast && !trailingSpace) break;
break;
}
}
if (maxArg != null) {
suggestions.put(maxArgIndex, maxArg);
} }
if (maxArg != null) suggestions.put(maxArgIndex, maxArg);
} }
if (suggestions.isEmpty()) { if (suggestions.isEmpty()) return null;
// No suggestion
return null;
}
final int max = suggestions.firstIntKey(); final int max = suggestions.firstIntKey();
return suggestions.get(max); return suggestions.get(max);
} }
} }

View File

@ -2,8 +2,26 @@ package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.Command;
public class CommandQueryResult { public final class CommandQueryResult {
public Command command; private final Command command;
public String commandName; private final String argsInput;
public String[] args; private final String input;
public CommandQueryResult(Command command, String argsInput, String input) {
this.command = command;
this.argsInput = argsInput;
this.input = input;
}
public Command command() {
return command;
}
public String argsInput() {
return argsInput;
}
public String input() {
return input;
}
} }

View File

@ -27,13 +27,8 @@ public class TabCompleteListener {
String args = commandString.replaceFirst(Pattern.quote(commandName), ""); String args = commandString.replaceFirst(Pattern.quote(commandName), "");
final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString); final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString);
if (commandQueryResult == null) { if (commandQueryResult == null) return;
// Command not found final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult, text.endsWith(StringUtils.SPACE), false,
return;
}
final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult.command,
commandQueryResult.args, commandString, text.endsWith(StringUtils.SPACE), false,
CommandSyntax::hasSuggestion, Argument::hasSuggestion); CommandSyntax::hasSuggestion, Argument::hasSuggestion);
if (queryResult == null) { if (queryResult == null) {
// Suggestible argument not found // Suggestible argument not found

View File

@ -21,6 +21,12 @@ public class StringUtils {
return count; return count;
} }
public static String trimLeft(String input) {
int i = 0;
while (i < input.length() && Character.isWhitespace(input.charAt(i))) i++;
return input.substring(i);
}
/** /**
* Applies the Jaro-Winkler distance algorithm to the given strings, providing information about the * Applies the Jaro-Winkler distance algorithm to the given strings, providing information about the
* similarity of them. * similarity of them.