mirror of
https://github.com/Minestom/Minestom.git
synced 2024-09-28 22:47:41 +02:00
Command parsing cleanup + preparation for suggestion & advanced caching
This commit is contained in:
parent
94526b218f
commit
bca2434cff
@ -15,7 +15,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Class responsible for parsing {@link Command}.
|
||||
@ -124,11 +123,8 @@ public class CommandDispatcher {
|
||||
return CommandResult.of(CommandResult.Type.UNKNOWN, commandName);
|
||||
}
|
||||
|
||||
// Removes the command's name + the space after
|
||||
String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(StringUtils.SPACE);
|
||||
if (args.length == 1 && args[0].length() == 0) {
|
||||
args = new String[0];
|
||||
}
|
||||
String[] args = new String[parts.length - 1];
|
||||
System.arraycopy(parts, 1, args, 0, args.length);
|
||||
|
||||
CommandResult result = new CommandResult();
|
||||
result.input = commandString;
|
||||
|
@ -0,0 +1,9 @@
|
||||
package net.minestom.server.command.builder.parser;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
|
||||
public class ArgumentQueryResult {
|
||||
public Argument<?> argument;
|
||||
public int start;
|
||||
public String input;
|
||||
}
|
@ -2,6 +2,7 @@ package net.minestom.server.command.builder.parser;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
|
||||
import net.minestom.server.command.builder.Arguments;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandSyntax;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
@ -9,108 +10,43 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
public class CommandParser {
|
||||
|
||||
public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument<?>[] commandArguments, @NotNull String[] inputArguments,
|
||||
@Nullable List<ValidSyntaxHolder> validSyntaxes,
|
||||
@Nullable Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions) {
|
||||
final Map<Argument<?>, Object> argsValues = new HashMap<>();
|
||||
final Map<Argument<?>, Object> argumentValueMap = new HashMap<>();
|
||||
|
||||
boolean syntaxCorrect = true;
|
||||
// The current index in the raw command string arguments
|
||||
int splitIndex = 0;
|
||||
int inputIndex = 0;
|
||||
|
||||
boolean useRemaining = false;
|
||||
// Check the validity of the arguments...
|
||||
for (int argCount = 0; argCount < commandArguments.length; argCount++) {
|
||||
final boolean lastArgumentIteration = argCount + 1 == commandArguments.length;
|
||||
final Argument<?> argument = commandArguments[argCount];
|
||||
useRemaining = argument.useRemaining();
|
||||
|
||||
final boolean end = splitIndex == inputArguments.length;
|
||||
if (end) // True if there is no input to analyze left
|
||||
for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) {
|
||||
ArgumentResult argumentResult = validate(commandArguments, argIndex, inputArguments, inputIndex);
|
||||
if (argumentResult == null) {
|
||||
break;
|
||||
|
||||
// the parsed argument value, null if incorrect
|
||||
Object parsedValue;
|
||||
// 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 representing the correct argument syntax
|
||||
StringBuilder argValue = new StringBuilder();
|
||||
|
||||
if (useRemaining) {
|
||||
final boolean hasArgs = inputArguments.length > splitIndex;
|
||||
// Verify if there is any string part available
|
||||
if (hasArgs) {
|
||||
// Argument is supposed to take the rest of the command input
|
||||
for (int i = splitIndex; i < inputArguments.length; i++) {
|
||||
final String arg = inputArguments[i];
|
||||
if (argValue.length() > 0)
|
||||
argValue.append(StringUtils.SPACE);
|
||||
argValue.append(arg);
|
||||
}
|
||||
|
||||
final String argValueString = argValue.toString();
|
||||
|
||||
try {
|
||||
parsedValue = argument.parse(argValueString);
|
||||
correct = true;
|
||||
argsValues.put(argument, parsedValue);
|
||||
} catch (ArgumentSyntaxException exception) {
|
||||
argumentSyntaxException = exception;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Argument is either single-word or can accept optional delimited space(s)
|
||||
for (int i = splitIndex; i < inputArguments.length; i++) {
|
||||
final String rawArg = inputArguments[i];
|
||||
|
||||
argValue.append(rawArg);
|
||||
|
||||
final String argValueString = argValue.toString();
|
||||
|
||||
try {
|
||||
parsedValue = argument.parse(argValueString);
|
||||
|
||||
// Prevent quitting the parsing too soon if the argument
|
||||
// does not allow space
|
||||
if (lastArgumentIteration && i + 1 < inputArguments.length) {
|
||||
if (!argument.allowSpace())
|
||||
break;
|
||||
argValue.append(StringUtils.SPACE);
|
||||
continue;
|
||||
}
|
||||
|
||||
correct = true;
|
||||
argsValues.put(argument, parsedValue);
|
||||
splitIndex = i + 1;
|
||||
break;
|
||||
} catch (ArgumentSyntaxException exception) {
|
||||
argumentSyntaxException = exception;
|
||||
|
||||
if (!argument.allowSpace())
|
||||
break;
|
||||
argValue.append(StringUtils.SPACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!correct) {
|
||||
// Update local var
|
||||
useRemaining = argumentResult.useRemaining;
|
||||
inputIndex = argumentResult.inputIndex;
|
||||
|
||||
if (argumentResult.correct) {
|
||||
argumentValueMap.put(argumentResult.argument, argumentResult.parsedValue);
|
||||
} 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 = argumentSyntaxException;
|
||||
suggestionHolder.argIndex = argCount;
|
||||
syntaxesSuggestions.put(argCount, suggestionHolder);
|
||||
suggestionHolder.argumentSyntaxException = argumentResult.argumentSyntaxException;
|
||||
suggestionHolder.argIndex = argIndex;
|
||||
syntaxesSuggestions.put(argIndex, suggestionHolder);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -118,11 +54,11 @@ public class CommandParser {
|
||||
|
||||
// Add the syntax to the list of valid syntaxes if correct
|
||||
if (syntaxCorrect) {
|
||||
if (commandArguments.length == argsValues.size() || useRemaining) {
|
||||
if (commandArguments.length == argumentValueMap.size() || useRemaining) {
|
||||
if (validSyntaxes != null) {
|
||||
ValidSyntaxHolder validSyntaxHolder = new ValidSyntaxHolder();
|
||||
validSyntaxHolder.syntax = syntax;
|
||||
validSyntaxHolder.argumentsValue = argsValues;
|
||||
validSyntaxHolder.argumentsValue = argumentValueMap;
|
||||
|
||||
validSyntaxes.add(validSyntaxHolder);
|
||||
}
|
||||
@ -179,4 +115,165 @@ public class CommandParser {
|
||||
return finalSyntax;
|
||||
}
|
||||
|
||||
public static ArgumentQueryResult findSuggestibleArgument(@NotNull Command command, String[] args, String rawCommandText) {
|
||||
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
|
||||
|
||||
Int2ObjectRBTreeMap<ArgumentQueryResult> suggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
|
||||
|
||||
for (CommandSyntax syntax : syntaxes) {
|
||||
Argument<?>[] commandArguments = syntax.getArguments();
|
||||
int inputIndex = 0;
|
||||
|
||||
ArgumentQueryResult maxArg = null;
|
||||
int maxArgIndex = 0;
|
||||
for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) {
|
||||
ArgumentResult argumentResult = validate(commandArguments, argIndex, args, inputIndex);
|
||||
if (argumentResult == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Update local var
|
||||
inputIndex = argumentResult.inputIndex;
|
||||
|
||||
final Argument<?> argument = argumentResult.argument;
|
||||
if (argument.hasSuggestion()) {
|
||||
ArgumentQueryResult queryResult = new ArgumentQueryResult();
|
||||
queryResult.argument = argumentResult.argument;
|
||||
queryResult.input = argumentResult.rawArg;
|
||||
queryResult.start = rawCommandText.length() - argumentResult.rawArg.length();
|
||||
|
||||
maxArg = queryResult;
|
||||
maxArgIndex = argIndex;
|
||||
}
|
||||
|
||||
if (!argumentResult.correct) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (maxArg != null) {
|
||||
suggestions.put(maxArgIndex, maxArg);
|
||||
}
|
||||
}
|
||||
|
||||
if (suggestions.isEmpty()) {
|
||||
// No suggestion
|
||||
return null;
|
||||
}
|
||||
|
||||
final int max = suggestions.firstIntKey();
|
||||
return suggestions.get(max);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ArgumentResult validate(@NotNull Argument<?>[] arguments, int argIndex,
|
||||
@NotNull String[] inputArguments, int inputIndex) {
|
||||
final Argument<?> argument = arguments[argIndex];
|
||||
|
||||
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.isEmpty())
|
||||
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.isEmpty())
|
||||
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;
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,13 @@
|
||||
package net.minestom.server.listener;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.command.CommandManager;
|
||||
import net.minestom.server.command.CommandProcessor;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
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.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.command.builder.parser.ArgumentQueryResult;
|
||||
import net.minestom.server.command.builder.parser.CommandParser;
|
||||
import net.minestom.server.command.builder.parser.CommandSuggestionHolder;
|
||||
import net.minestom.server.command.builder.parser.ValidSyntaxHolder;
|
||||
import net.minestom.server.command.builder.suggestion.Suggestion;
|
||||
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
|
||||
import net.minestom.server.entity.Player;
|
||||
@ -19,10 +15,6 @@ import net.minestom.server.network.packet.client.play.ClientTabCompletePacket;
|
||||
import net.minestom.server.network.packet.server.play.TabCompletePacket;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TabCompleteListener {
|
||||
@ -38,56 +30,44 @@ public class TabCompleteListener {
|
||||
String commandName = split[0];
|
||||
final CommandDispatcher commandDispatcher = MinecraftServer.getCommandManager().getDispatcher();
|
||||
final Command command = commandDispatcher.findCommand(commandName);
|
||||
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
|
||||
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(syntaxes.size());
|
||||
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
|
||||
|
||||
String[] args = commandString.replaceFirst(Pattern.quote(commandName), "").trim().split(StringUtils.SPACE);
|
||||
if (args.length == 1 && args[0].length() == 0) {
|
||||
args = new String[0];
|
||||
String[] args = new String[split.length - 1];
|
||||
System.arraycopy(split, 1, args, 0, args.length);
|
||||
|
||||
final ArgumentQueryResult queryResult = CommandParser.findSuggestibleArgument(command, args, text);
|
||||
if (queryResult == null) {
|
||||
System.out.println("STOP");
|
||||
return;
|
||||
}
|
||||
|
||||
for (CommandSyntax syntax : syntaxes) {
|
||||
CommandParser.parse(syntax, syntax.getArguments(), args, validSyntaxes, syntaxesSuggestions);
|
||||
final Argument<?> argument = queryResult.argument;
|
||||
|
||||
final SuggestionCallback suggestionCallback = argument.suggestionCallback;
|
||||
if (suggestionCallback != null) {
|
||||
final int argumentLength = queryResult.input.length();
|
||||
final int start = text.length() - argumentLength;
|
||||
// TODO fix start
|
||||
//Suggestion suggestion = new Suggestion(queryResult.start, queryResult.input.length());
|
||||
Suggestion suggestion = new Suggestion(start, argumentLength);
|
||||
suggestionCallback.apply(suggestion, commandString);
|
||||
|
||||
TabCompletePacket tabCompletePacket = new TabCompletePacket();
|
||||
tabCompletePacket.transactionId = packet.transactionId;
|
||||
tabCompletePacket.start = suggestion.getStart();
|
||||
tabCompletePacket.length = suggestion.getLength();
|
||||
tabCompletePacket.matches = suggestion.getEntries()
|
||||
.stream()
|
||||
.map(suggestionEntry -> {
|
||||
TabCompletePacket.Match match = new TabCompletePacket.Match();
|
||||
match.match = suggestionEntry.getEntry();
|
||||
match.hasTooltip = suggestionEntry.getTooltip() != null;
|
||||
match.tooltip = suggestionEntry.getTooltip();
|
||||
return match;
|
||||
}).toArray(TabCompletePacket.Match[]::new);
|
||||
|
||||
player.getPlayerConnection().sendPacket(tabCompletePacket);
|
||||
}
|
||||
|
||||
if (!syntaxesSuggestions.isEmpty()) {
|
||||
final int max = syntaxesSuggestions.firstIntKey();
|
||||
final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max);
|
||||
final CommandSyntax syntax = suggestionHolder.syntax;
|
||||
final ArgumentSyntaxException argumentSyntaxException = suggestionHolder.argumentSyntaxException;
|
||||
final int argIndex = suggestionHolder.argIndex;
|
||||
final Argument<?> argument = syntax.getArguments()[argIndex];
|
||||
|
||||
final SuggestionCallback suggestionCallback = argument.suggestionCallback;
|
||||
if (suggestionCallback != null) {
|
||||
final int argumentLength = argumentSyntaxException != null ? argumentSyntaxException.getInput().length() :
|
||||
Integer.MAX_VALUE;
|
||||
final int start = text.length() - argumentLength;
|
||||
Suggestion suggestion = new Suggestion(start, argumentLength);
|
||||
suggestionCallback.apply(suggestion, commandString);
|
||||
|
||||
TabCompletePacket tabCompletePacket = new TabCompletePacket();
|
||||
tabCompletePacket.transactionId = packet.transactionId;
|
||||
tabCompletePacket.start = suggestion.getStart();
|
||||
tabCompletePacket.length = suggestion.getLength();
|
||||
tabCompletePacket.matches = suggestion.getEntries()
|
||||
.stream()
|
||||
.map(suggestionEntry -> {
|
||||
TabCompletePacket.Match match = new TabCompletePacket.Match();
|
||||
match.match = suggestionEntry.getEntry();
|
||||
match.hasTooltip = suggestionEntry.getTooltip() != null;
|
||||
match.tooltip = suggestionEntry.getTooltip();
|
||||
return match;
|
||||
}).toArray(TabCompletePacket.Match[]::new);
|
||||
|
||||
player.getPlayerConnection().sendPacket(tabCompletePacket);
|
||||
}
|
||||
System.out.println("arg: " + argument.getClass());
|
||||
}
|
||||
|
||||
System.out.println("test " + syntaxesSuggestions.size() + " " + validSyntaxes.size());
|
||||
|
||||
if (true)
|
||||
return;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import net.minestom.server.command.builder.Arguments;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.suggestion.SuggestionEntry;
|
||||
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.String;
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.*;
|
||||
|
||||
public class TestCommand extends Command {
|
||||
|
||||
@ -15,11 +15,19 @@ public class TestCommand extends Command {
|
||||
super("testcmd");
|
||||
setDefaultExecutor(this::usage);
|
||||
|
||||
addSyntax((sender, args) -> {
|
||||
System.out.println("test: " + args.get("msg"));
|
||||
}, String("msg").setSuggestionCallback((suggestion, input) -> {
|
||||
var test1 = Integer("msg").setSuggestionCallback((suggestion, input) -> {
|
||||
suggestion.addEntry(new SuggestionEntry(input, ColoredText.of(ChatColor.RED, "Hover")));
|
||||
}));
|
||||
});
|
||||
|
||||
var test2 = Word("msg2").setSuggestionCallback((suggestion, input) -> {
|
||||
suggestion.addEntry(new SuggestionEntry(input, ColoredText.of(ChatColor.BRIGHT_GREEN, "GHRTEG")));
|
||||
});
|
||||
|
||||
var test3 = String("msg3");
|
||||
|
||||
addSyntax((sender, args) -> {
|
||||
System.out.println("COMMAND SYNTAX");
|
||||
}, test3, test1);
|
||||
}
|
||||
|
||||
private void usage(CommandSender sender, Arguments arguments) {
|
||||
|
Loading…
Reference in New Issue
Block a user