mirror of https://github.com/Minestom/Minestom.git
Cleanup old parser
This commit is contained in:
parent
3d3e44e3d7
commit
4284e059cb
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue