mirror of https://github.com/Minestom/Minestom.git
Argument API
This commit is contained in:
parent
9dab3183e5
commit
0abcc9f010
|
@ -0,0 +1,96 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.command.builder.ArgumentCallback;
|
||||
import net.minestom.server.command.builder.CommandContext;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
interface Arg<T> {
|
||||
static <T> @NotNull Arg<T> arg(@NotNull String id, @NotNull Parser<T> parser, @Nullable Suggestion.Type suggestionType) {
|
||||
return new ArgImpl<>(id, parser, suggestionType, null, null);
|
||||
}
|
||||
|
||||
static <T> @NotNull Arg<T> arg(@NotNull String id, @NotNull Parser<T> parser) {
|
||||
return arg(id, parser, null);
|
||||
}
|
||||
|
||||
static @NotNull Arg<String> literalArg(@NotNull String id) {
|
||||
return arg(id, Parser.Literal(id), null);
|
||||
}
|
||||
|
||||
@NotNull String id();
|
||||
|
||||
@NotNull Parser<T> parser();
|
||||
|
||||
Suggestion.@UnknownNullability Type suggestionType();
|
||||
|
||||
@Nullable Supplier<@NotNull T> defaultValue();
|
||||
|
||||
@NotNull Arg<T> defaultValue(@Nullable Supplier<@NotNull T> defaultValue);
|
||||
|
||||
default @NotNull Arg<T> defaultValue(@NotNull T defaultValue) {
|
||||
return defaultValue(() -> defaultValue);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@Nullable ArgumentCallback callback();
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@NotNull Arg<T> callback(@Nullable ArgumentCallback callback);
|
||||
|
||||
interface Suggestion {
|
||||
sealed interface Type
|
||||
permits ArgImpl.SuggestionTypeImpl {
|
||||
@NotNull String name();
|
||||
|
||||
@NotNull Entry suggest(@NotNull CommandSender sender, @NotNull CommandContext context);
|
||||
|
||||
static @NotNull Type recipes() {
|
||||
return ArgImpl.SuggestionTypeImpl.RECIPES;
|
||||
}
|
||||
|
||||
static @NotNull Type sounds() {
|
||||
return ArgImpl.SuggestionTypeImpl.SOUNDS;
|
||||
}
|
||||
|
||||
static @NotNull Type entities() {
|
||||
return ArgImpl.SuggestionTypeImpl.ENTITIES;
|
||||
}
|
||||
|
||||
static @NotNull Type askServer(@NotNull Callback callback) {
|
||||
return ArgImpl.SuggestionTypeImpl.askServer(callback);
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Entry
|
||||
permits ArgImpl.SuggestionEntryImpl {
|
||||
static @NotNull Entry of(int start, int length, @NotNull List<Match> matches) {
|
||||
return new ArgImpl.SuggestionEntryImpl(start, length, matches);
|
||||
}
|
||||
|
||||
int start();
|
||||
|
||||
int length();
|
||||
|
||||
@NotNull List<@NotNull Match> matches();
|
||||
|
||||
sealed interface Match
|
||||
permits ArgImpl.MatchImpl {
|
||||
@NotNull String text();
|
||||
|
||||
@Nullable Component tooltip();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Callback {
|
||||
@NotNull Entry apply(@NotNull CommandSender sender, @NotNull CommandContext context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.command.builder.ArgumentCallback;
|
||||
import net.minestom.server.command.builder.CommandContext;
|
||||
import net.minestom.server.command.builder.arguments.*;
|
||||
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.number.ArgumentLong;
|
||||
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
|
||||
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.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
record ArgImpl<T>(String id, Parser<T> parser, Suggestion.Type suggestionType,
|
||||
Supplier<T> defaultValue, ArgumentCallback callback) implements Arg<T> {
|
||||
static <T> ArgImpl<T> fromLegacy(Argument<T> argument) {
|
||||
return new ArgImpl<>(argument.getId(), retrieveParser(argument),
|
||||
retrieveSuggestion(argument), argument.getDefaultValue(), retrieveCallback(argument));
|
||||
}
|
||||
|
||||
private static <T> Parser<T> retrieveParser(Argument<T> argument) {
|
||||
var parserFun = ConversionMap.PARSERS.get(argument.getClass());
|
||||
final Parser<T> parser;
|
||||
if (parserFun != null) {
|
||||
parser = parserFun.apply(argument);
|
||||
} else {
|
||||
// TODO remove legacy conversion
|
||||
parser = Parser.custom(ParserSpec.legacy(argument));
|
||||
}
|
||||
assert parser != null;
|
||||
return parser;
|
||||
}
|
||||
|
||||
private static Suggestion.Type retrieveSuggestion(Argument<?> argument) {
|
||||
final var type = argument.suggestionType();
|
||||
if (type == null) return null;
|
||||
return switch (type) {
|
||||
case ALL_RECIPES -> Suggestion.Type.recipes();
|
||||
case AVAILABLE_SOUNDS -> Suggestion.Type.sounds();
|
||||
case SUMMONABLE_ENTITIES -> Suggestion.Type.entities();
|
||||
case ASK_SERVER -> Suggestion.Type.askServer((sender, context) -> {
|
||||
final SuggestionCallback suggestionCallback = argument.getSuggestionCallback();
|
||||
assert suggestionCallback != null;
|
||||
final String input = context.getInput();
|
||||
|
||||
final int lastSpace = input.lastIndexOf(" ");
|
||||
|
||||
final int start = lastSpace + 2;
|
||||
final int length = input.length() - lastSpace - 1;
|
||||
|
||||
final var sug = new net.minestom.server.command.builder.suggestion.Suggestion(input, start, length);
|
||||
suggestionCallback.apply(sender, context, sug);
|
||||
|
||||
return new SuggestionEntryImpl(sug.getStart(), sug.getLength(),
|
||||
sug.getEntries().stream().map(entry -> (Suggestion.Entry.Match) new MatchImpl(entry.getEntry(), entry.getTooltip())).toList());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private static ArgumentCallback retrieveCallback(Argument<?> argument) {
|
||||
final ArgumentCallback callback = argument.getCallback();
|
||||
if (callback == null) return null;
|
||||
return (sender, context) -> {
|
||||
callback.apply(sender, context);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Arg<T> defaultValue(@Nullable Supplier<@NotNull T> defaultValue) {
|
||||
return new ArgImpl<>(id, parser, suggestionType, defaultValue, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Arg<T> callback(@Nullable ArgumentCallback callback) {
|
||||
return new ArgImpl<>(id, parser, suggestionType, defaultValue, callback);
|
||||
}
|
||||
|
||||
record SuggestionTypeImpl(String name, Suggestion.Callback callback) implements Suggestion.Type {
|
||||
static final Suggestion.Type RECIPES = new SuggestionTypeImpl("minecraft:all_recipes", null);
|
||||
static final Suggestion.Type SOUNDS = new SuggestionTypeImpl("minecraft:available_sounds", null);
|
||||
static final Suggestion.Type ENTITIES = new SuggestionTypeImpl("minecraft:summonable_entities", null);
|
||||
|
||||
static Suggestion.Type askServer(Suggestion.Callback callback) {
|
||||
return new SuggestionTypeImpl("minecraft:ask_server", callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Suggestion.Entry suggest(@NotNull CommandSender sender, @NotNull CommandContext context) {
|
||||
final Suggestion.Callback callback = this.callback;
|
||||
if (callback == null) {
|
||||
throw new IllegalStateException("Suggestion type is not supported");
|
||||
}
|
||||
return callback.apply(sender, context);
|
||||
}
|
||||
}
|
||||
|
||||
record SuggestionEntryImpl(int start, int length, List<Match> matches) implements Suggestion.Entry {
|
||||
SuggestionEntryImpl {
|
||||
matches = List.copyOf(matches);
|
||||
}
|
||||
}
|
||||
|
||||
record MatchImpl(String text, Component tooltip) implements Suggestion.Entry.Match {
|
||||
}
|
||||
|
||||
static final class ConversionMap {
|
||||
private static final Map<Class<? extends Argument>, Function<Argument, Parser>> PARSERS = new ConversionMap()
|
||||
.append(ArgumentLiteral.class, arg -> Parser.Literal(arg.getId()))
|
||||
.append(ArgumentBoolean.class, arg -> Parser.Boolean())
|
||||
.append(ArgumentFloat.class, arg -> Parser.Float().min(arg.getMin()).max(arg.getMax()))
|
||||
.append(ArgumentDouble.class, arg -> Parser.Double().min(arg.getMin()).max(arg.getMax()))
|
||||
.append(ArgumentInteger.class, arg -> Parser.Integer().min(arg.getMin()).max(arg.getMax()))
|
||||
.append(ArgumentLong.class, arg -> Parser.Long().min(arg.getMin()).max(arg.getMax()))
|
||||
.append(ArgumentWord.class, arg -> {
|
||||
final String[] restrictions = arg.getRestrictions();
|
||||
if (restrictions != null && restrictions.length > 0) {
|
||||
return Parser.Literals(restrictions);
|
||||
} else {
|
||||
return Parser.String();
|
||||
}
|
||||
})
|
||||
.append(ArgumentString.class, arg -> Parser.String().type(Parser.StringParser.Type.QUOTED))
|
||||
.append(ArgumentStringArray.class, arg -> Parser.String().type(Parser.StringParser.Type.GREEDY))
|
||||
.toMap();
|
||||
|
||||
private final Map<Class<? extends Argument>, Function<Argument, Parser>> parsers = new HashMap<>();
|
||||
|
||||
<T, A extends Argument<T>> ConversionMap append(Class<A> legacyType, Function<A, Parser<?>> converter) {
|
||||
this.parsers.put(legacyType, arg -> converter.apply((A) arg));
|
||||
return this;
|
||||
}
|
||||
|
||||
Map<Class<? extends Argument>, Function<Argument, Parser>> toMap() {
|
||||
return Map.copyOf(parsers);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.suggestion.Suggestion;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
@ -33,7 +32,7 @@ public interface CommandParser {
|
|||
@Nullable Suggestion suggestion(CommandSender sender);
|
||||
|
||||
@ApiStatus.Internal
|
||||
List<Argument<?>> args();
|
||||
List<Arg<?>> args();
|
||||
|
||||
sealed interface UnknownCommand extends Result
|
||||
permits CommandParserImpl.UnknownCommandResult {
|
||||
|
|
|
@ -5,12 +5,10 @@ import net.minestom.server.command.builder.ArgumentCallback;
|
|||
import net.minestom.server.command.builder.CommandContext;
|
||||
import net.minestom.server.command.builder.CommandData;
|
||||
import net.minestom.server.command.builder.CommandExecutor;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.condition.CommandCondition;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.command.builder.suggestion.Suggestion;
|
||||
import net.minestom.server.command.builder.suggestion.SuggestionCallback;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import net.minestom.server.command.builder.suggestion.SuggestionEntry;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -20,7 +18,6 @@ import java.util.ArrayDeque;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -63,17 +60,17 @@ final class CommandParserImpl implements CommandParser {
|
|||
return (sender, context) -> globalListeners.forEach(x -> x.apply(sender, context));
|
||||
}
|
||||
|
||||
SuggestionCallback extractSuggestionCallback() {
|
||||
return nodeResults.peekLast().callback;
|
||||
Arg.Suggestion.Type extractSuggestion() {
|
||||
return nodeResults.peekLast().suggestionType;
|
||||
}
|
||||
|
||||
Map<String, ArgumentResult<Object>> collectArguments() {
|
||||
Map<String, ParserSpec.Result<Object>> collectArguments() {
|
||||
return nodeResults.stream()
|
||||
.skip(1) // skip root
|
||||
.collect(Collectors.toUnmodifiableMap(NodeResult::name, NodeResult::argumentResult));
|
||||
}
|
||||
|
||||
List<Argument<?>> getArgs() {
|
||||
List<Arg<?>> getArgs() {
|
||||
return nodeResults.stream().map(x -> x.node.argument()).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -87,30 +84,31 @@ final class CommandParserImpl implements CommandParser {
|
|||
Node parent = graph.root();
|
||||
while ((result = parseChild(parent, reader)) != null) {
|
||||
chain.append(result);
|
||||
if (result.argumentResult instanceof ArgumentResult.SyntaxError<?> e) {
|
||||
final Node node = result.node;
|
||||
if (result.argumentResult instanceof ParserSpec.Result.SyntaxError<?> e) {
|
||||
// Syntax error stop at this arg
|
||||
final ArgumentCallback argumentCallback = parent.argument().getCallback();
|
||||
final ArgumentCallback argumentCallback = node.argument().callback();
|
||||
if (argumentCallback == null && chain.defaultExecutor != null) {
|
||||
return ValidCommand.defaultExecutor(input, chain);
|
||||
} else {
|
||||
return new InvalidCommand(input, chain.mergedConditions(),
|
||||
argumentCallback, e, chain.collectArguments(), chain.mergedGlobalExecutors(),
|
||||
chain.extractSuggestionCallback(), chain.getArgs());
|
||||
chain.extractSuggestion(), chain.getArgs());
|
||||
}
|
||||
}
|
||||
parent = result.node;
|
||||
parent = node;
|
||||
}
|
||||
// Check children for arguments with default values
|
||||
do {
|
||||
Node tmp = parent;
|
||||
parent = null;
|
||||
for (Node child : tmp.next()) {
|
||||
final Argument<?> argument = child.argument();
|
||||
final Supplier<?> defaultSupplier = argument.getDefaultValue();
|
||||
final Arg<?> argument = child.argument();
|
||||
final Supplier<?> defaultSupplier = argument.defaultValue();
|
||||
if (defaultSupplier != null) {
|
||||
final Object value = defaultSupplier.get();
|
||||
final ArgumentResult<Object> argumentResult = new ArgumentResult.Success<>(value, "");
|
||||
chain.append(new NodeResult(child, argumentResult, argument.getSuggestionCallback()));
|
||||
final ParserSpec.Result<Object> argumentResult = ParserSpec.Result.success("", -1, value);
|
||||
chain.append(new NodeResult(child, argumentResult, null));
|
||||
parent = child;
|
||||
break;
|
||||
}
|
||||
|
@ -120,7 +118,8 @@ final class CommandParserImpl implements CommandParser {
|
|||
final NodeResult lastNode = chain.nodeResults.peekLast();
|
||||
if (lastNode == null) return UnknownCommandResult.INSTANCE;
|
||||
// Verify syntax(s)
|
||||
final CommandExecutor executor = nullSafeGetter(lastNode.node().execution(), Graph.Execution::executor);
|
||||
final Graph.Execution execution = lastNode.node.execution();
|
||||
final CommandExecutor executor = execution != null ? execution.executor() : null;
|
||||
if (executor == null) {
|
||||
// Syntax error
|
||||
if (chain.defaultExecutor != null) {
|
||||
|
@ -140,34 +139,27 @@ final class CommandParserImpl implements CommandParser {
|
|||
return ValidCommand.executor(input, chain, executor);
|
||||
}
|
||||
|
||||
@Contract("null, _ -> null; !null, null -> fail; !null, !null -> _")
|
||||
private static <R, T> @Nullable R nullSafeGetter(@Nullable T obj, Function<T, R> getter) {
|
||||
return obj == null ? null : getter.apply(obj);
|
||||
}
|
||||
|
||||
private static NodeResult parseChild(Node parent, CommandStringReader reader) {
|
||||
if (!reader.hasRemaining()) return null;
|
||||
for (Node child : parent.next()) {
|
||||
final Argument<?> argument = child.argument();
|
||||
final int start = reader.cursor();
|
||||
final ArgumentResult<?> parse = parse(argument, reader);
|
||||
if (parse instanceof ArgumentResult.Success<?> success) {
|
||||
return new NodeResult(child, (ArgumentResult<Object>) success,
|
||||
argument.getSuggestionCallback());
|
||||
} else if (parse instanceof ArgumentResult.SyntaxError<?> syntaxError) {
|
||||
return new NodeResult(child, (ArgumentResult<Object>) syntaxError,
|
||||
argument.getSuggestionCallback());
|
||||
} else {
|
||||
// Reset cursor & try next
|
||||
reader.cursor(start);
|
||||
final List<Node> children = parent.next();
|
||||
for (Node child : children) {
|
||||
final ParserSpec<?> spec = child.argument().parser().spec();
|
||||
final ParserSpec.Result<?> parse = parse(spec, reader);
|
||||
if (parse instanceof ParserSpec.Result.Success<?> success) {
|
||||
return new NodeResult(child, (ParserSpec.Result<Object>) success,
|
||||
null);
|
||||
} else if (parse instanceof ParserSpec.Result.SyntaxError<?> syntaxError) {
|
||||
return new NodeResult(child, (ParserSpec.Result<Object>) syntaxError,
|
||||
null);
|
||||
}
|
||||
}
|
||||
for (Node node : parent.next()) {
|
||||
final SuggestionCallback suggestionCallback = node.argument().getSuggestionCallback();
|
||||
if (suggestionCallback != null) {
|
||||
// No argument found, find syntax error from suggestion type
|
||||
for (Node node : children) {
|
||||
final Arg.Suggestion.Type suggestionType = node.argument().suggestionType();
|
||||
if (suggestionType != null) {
|
||||
return new NodeResult(parent,
|
||||
new ArgumentResult.SyntaxError<>("None of the arguments were compatible, but a suggestion callback was found.", "", -1),
|
||||
suggestionCallback);
|
||||
ParserSpec.Result.error("", "None of the arguments were compatible, but a suggestion callback was found.", -1),
|
||||
suggestionType);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -187,7 +179,7 @@ final class CommandParserImpl implements CommandParser {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<Argument<?>> args() {
|
||||
public List<Arg<?>> args() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -197,35 +189,38 @@ final class CommandParserImpl implements CommandParser {
|
|||
|
||||
@Nullable CommandCondition condition();
|
||||
|
||||
@NotNull Map<String, ArgumentResult<Object>> arguments();
|
||||
@NotNull Map<String, ParserSpec.Result<Object>> arguments();
|
||||
|
||||
CommandExecutor globalListener();
|
||||
|
||||
@Nullable SuggestionCallback suggestionCallback();
|
||||
@Nullable Arg.Suggestion.Type suggestionType();
|
||||
|
||||
@Override
|
||||
default @Nullable Suggestion suggestion(CommandSender sender) {
|
||||
final SuggestionCallback callback = suggestionCallback();
|
||||
if (callback == null) return null;
|
||||
final int lastSpace = input().lastIndexOf(" ");
|
||||
final Suggestion suggestion = new Suggestion(input(), lastSpace + 2, input().length() - lastSpace - 1);
|
||||
final Arg.Suggestion.Type suggestionType = suggestionType();
|
||||
if (suggestionType == null) return null;
|
||||
final CommandContext context = createCommandContext(input(), arguments());
|
||||
callback.apply(sender, context, suggestion);
|
||||
final Arg.Suggestion.Entry result = suggestionType.suggest(sender, context);
|
||||
|
||||
Suggestion suggestion = new Suggestion(input(), result.start(), result.length());
|
||||
for (var match : result.matches()) {
|
||||
suggestion.addEntry(new SuggestionEntry(match.text(), match.tooltip()));
|
||||
}
|
||||
return suggestion;
|
||||
}
|
||||
}
|
||||
|
||||
record InvalidCommand(String input, CommandCondition condition, ArgumentCallback callback,
|
||||
ArgumentResult.SyntaxError<?> error,
|
||||
@NotNull Map<String, ArgumentResult<Object>> arguments, CommandExecutor globalListener,
|
||||
@Nullable SuggestionCallback suggestionCallback, List<Argument<?>> args)
|
||||
ParserSpec.Result.SyntaxError<?> error,
|
||||
@NotNull Map<String, ParserSpec.Result<Object>> arguments, CommandExecutor globalListener,
|
||||
@Nullable Arg.Suggestion.Type suggestionType, List<Arg<?>> args)
|
||||
implements InternalKnownCommand, Result.KnownCommand.Invalid {
|
||||
|
||||
static InvalidCommand invalid(String input, Chain chain) {
|
||||
return new InvalidCommand(input, chain.mergedConditions(),
|
||||
null/*todo command syntax callback*/,
|
||||
new ArgumentResult.SyntaxError<>("Command has trailing data.", null, -1),
|
||||
chain.collectArguments(), chain.mergedGlobalExecutors(), chain.extractSuggestionCallback(), chain.getArgs());
|
||||
ParserSpec.Result.error("", "Command has trailing data.", -1),
|
||||
chain.collectArguments(), chain.mergedGlobalExecutors(), chain.extractSuggestion(), chain.getArgs());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -235,18 +230,19 @@ final class CommandParserImpl implements CommandParser {
|
|||
}
|
||||
|
||||
record ValidCommand(String input, CommandCondition condition, CommandExecutor executor,
|
||||
@NotNull Map<String, ArgumentResult<Object>> arguments,
|
||||
CommandExecutor globalListener, @Nullable SuggestionCallback suggestionCallback, List<Argument<?>> args)
|
||||
@NotNull Map<String, ParserSpec.Result<Object>> arguments,
|
||||
CommandExecutor globalListener, @Nullable Arg.Suggestion.Type suggestionType,
|
||||
List<Arg<?>> args)
|
||||
implements InternalKnownCommand, Result.KnownCommand.Valid {
|
||||
|
||||
static ValidCommand defaultExecutor(String input, Chain chain) {
|
||||
return new ValidCommand(input, chain.mergedConditions(), chain.defaultExecutor, chain.collectArguments(),
|
||||
chain.mergedGlobalExecutors(), chain.extractSuggestionCallback(), chain.getArgs());
|
||||
chain.mergedGlobalExecutors(), chain.extractSuggestion(), chain.getArgs());
|
||||
}
|
||||
|
||||
static ValidCommand executor(String input, Chain chain, CommandExecutor executor) {
|
||||
return new ValidCommand(input, chain.mergedConditions(), executor, chain.collectArguments(), chain.mergedGlobalExecutors(),
|
||||
chain.extractSuggestionCallback(), chain.getArgs());
|
||||
chain.extractSuggestion(), chain.getArgs());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -266,7 +262,7 @@ final class CommandParserImpl implements CommandParser {
|
|||
|
||||
record ValidExecutableCmd(CommandCondition condition, CommandExecutor globalListener, CommandExecutor executor,
|
||||
String input,
|
||||
Map<String, ArgumentResult<Object>> arguments) implements ExecutableCommand {
|
||||
Map<String, ParserSpec.Result<Object>> arguments) implements ExecutableCommand {
|
||||
@Override
|
||||
public @NotNull Result execute(@NotNull CommandSender sender) {
|
||||
final CommandContext context = createCommandContext(input, arguments);
|
||||
|
@ -287,8 +283,8 @@ final class CommandParserImpl implements CommandParser {
|
|||
}
|
||||
|
||||
record InvalidExecutableCmd(CommandCondition condition, CommandExecutor globalListener, ArgumentCallback callback,
|
||||
ArgumentResult.SyntaxError<?> error, String input,
|
||||
Map<String, ArgumentResult<Object>> arguments) implements ExecutableCommand {
|
||||
ParserSpec.Result.SyntaxError<?> error, String input,
|
||||
Map<String, ParserSpec.Result<Object>> arguments) implements ExecutableCommand {
|
||||
@Override
|
||||
public @NotNull Result execute(@NotNull CommandSender sender) {
|
||||
globalListener().apply(sender, createCommandContext(input, arguments));
|
||||
|
@ -297,19 +293,19 @@ final class CommandParserImpl implements CommandParser {
|
|||
return ExecutionResultImpl.PRECONDITION_FAILED;
|
||||
}
|
||||
if (callback != null)
|
||||
callback.apply(sender, new ArgumentSyntaxException(error.message(), error.input(), error.code()));
|
||||
callback.apply(sender, new ArgumentSyntaxException(error.message(), error.input(), error.error()));
|
||||
return ExecutionResultImpl.INVALID_SYNTAX;
|
||||
}
|
||||
}
|
||||
|
||||
private static CommandContext createCommandContext(String input, Map<String, ArgumentResult<Object>> arguments) {
|
||||
private static CommandContext createCommandContext(String input, Map<String, ParserSpec.Result<Object>> arguments) {
|
||||
final CommandContext context = new CommandContext(input);
|
||||
for (var entry : arguments.entrySet()) {
|
||||
final String identifier = entry.getKey();
|
||||
final ArgumentResult<Object> value = entry.getValue();
|
||||
final ParserSpec.Result<Object> value = entry.getValue();
|
||||
|
||||
final Object argOutput = value instanceof ArgumentResult.Success<Object> success ? success.value() : null;
|
||||
final String argInput = value instanceof ArgumentResult.Success<Object> success ? success.input() : "";
|
||||
final Object argOutput = value instanceof ParserSpec.Result.Success<Object> success ? success.value() : null;
|
||||
final String argInput = value instanceof ParserSpec.Result.Success<Object> success ? success.input() : "";
|
||||
|
||||
context.setArg(identifier, argOutput, argInput);
|
||||
}
|
||||
|
@ -324,9 +320,9 @@ final class CommandParserImpl implements CommandParser {
|
|||
static final ExecutableCommand.Result INVALID_SYNTAX = new ExecutionResultImpl(Type.INVALID_SYNTAX, null);
|
||||
}
|
||||
|
||||
private record NodeResult(Node node, ArgumentResult<Object> argumentResult, SuggestionCallback callback) {
|
||||
private record NodeResult(Node node, ParserSpec.Result<Object> argumentResult, Arg.Suggestion.Type suggestionType) {
|
||||
public String name() {
|
||||
return node.argument().getId();
|
||||
return node.argument().id();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -342,31 +338,6 @@ final class CommandParserImpl implements CommandParser {
|
|||
return cursor < input.length();
|
||||
}
|
||||
|
||||
String readWord() {
|
||||
final String input = this.input;
|
||||
final int cursor = this.cursor;
|
||||
|
||||
final int i = input.indexOf(' ', cursor);
|
||||
if (i == -1) {
|
||||
this.cursor = input.length() + 1;
|
||||
return input.substring(cursor);
|
||||
}
|
||||
final String read = input.substring(cursor, i);
|
||||
this.cursor += read.length() + 1;
|
||||
return read;
|
||||
}
|
||||
|
||||
String readRemaining() {
|
||||
final String input = this.input;
|
||||
final String result = input.substring(cursor);
|
||||
this.cursor = input.length();
|
||||
return result;
|
||||
}
|
||||
|
||||
int cursor() {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void cursor(int cursor) {
|
||||
assert cursor >= 0 && cursor <= input.length();
|
||||
this.cursor = cursor;
|
||||
|
@ -375,49 +346,16 @@ final class CommandParserImpl implements CommandParser {
|
|||
|
||||
// ARGUMENT
|
||||
|
||||
private static <T> ArgumentResult<T> parse(Argument<T> argument, CommandStringReader reader) {
|
||||
// Handle specific type without loop
|
||||
try {
|
||||
// Single word argument
|
||||
if (!argument.allowSpace()) {
|
||||
final String word = reader.readWord();
|
||||
return new ArgumentResult.Success<>(argument.parse(word), word);
|
||||
}
|
||||
// Complete input argument
|
||||
if (argument.useRemaining()) {
|
||||
final String remaining = reader.readRemaining();
|
||||
return new ArgumentResult.Success<>(argument.parse(remaining), remaining);
|
||||
}
|
||||
} catch (ArgumentSyntaxException ignored) {
|
||||
return new ArgumentResult.IncompatibleType<>();
|
||||
}
|
||||
// Bruteforce
|
||||
assert argument.allowSpace() && !argument.useRemaining();
|
||||
StringBuilder current = new StringBuilder(reader.readWord());
|
||||
while (true) {
|
||||
try {
|
||||
final String input = current.toString();
|
||||
return new ArgumentResult.Success<>(argument.parse(input), input);
|
||||
} catch (ArgumentSyntaxException ignored) {
|
||||
if (!reader.hasRemaining()) break;
|
||||
current.append(" ");
|
||||
current.append(reader.readWord());
|
||||
}
|
||||
}
|
||||
return new ArgumentResult.IncompatibleType<>();
|
||||
}
|
||||
|
||||
private sealed interface ArgumentResult<R> {
|
||||
record Success<T>(T value, String input)
|
||||
implements ArgumentResult<T> {
|
||||
}
|
||||
|
||||
record IncompatibleType<T>()
|
||||
implements ArgumentResult<T> {
|
||||
}
|
||||
|
||||
record SyntaxError<T>(String message, String input, int code)
|
||||
implements ArgumentResult<T> {
|
||||
private static <T> ParserSpec.Result<T> parse(ParserSpec<T> spec, CommandStringReader reader) {
|
||||
final String input = reader.input;
|
||||
final ParserSpec.Result<T> result = spec.read(input, reader.cursor);
|
||||
if (result instanceof ParserSpec.Result.Success<T> success) {
|
||||
// Increment index by 1 to be at next word
|
||||
int index = success.index();
|
||||
if (index < input.length()) index++;
|
||||
assert index >= 0 && index <= input.length() : "index out of bounds: " + index + " > " + input.length() + " for " + input;
|
||||
reader.cursor(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package net.minestom.server.command;
|
|||
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandExecutor;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.condition.CommandCondition;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
@ -14,11 +13,11 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Predicate;
|
||||
|
||||
sealed interface Graph permits GraphImpl {
|
||||
static @NotNull Builder builder(@NotNull Argument<?> argument, @Nullable Execution execution) {
|
||||
static @NotNull Builder builder(@NotNull Arg<?> argument, @Nullable Execution execution) {
|
||||
return new GraphImpl.BuilderImpl(argument, execution);
|
||||
}
|
||||
|
||||
static @NotNull Builder builder(@NotNull Argument<?> argument) {
|
||||
static @NotNull Builder builder(@NotNull Arg<?> argument) {
|
||||
return new GraphImpl.BuilderImpl(argument, null);
|
||||
}
|
||||
|
||||
|
@ -43,7 +42,7 @@ sealed interface Graph permits GraphImpl {
|
|||
boolean compare(@NotNull Graph graph, @NotNull Comparator comparator);
|
||||
|
||||
sealed interface Node permits GraphImpl.NodeImpl {
|
||||
@NotNull Argument<?> argument();
|
||||
@NotNull Arg<?> argument();
|
||||
|
||||
@UnknownNullability Execution execution();
|
||||
|
||||
|
@ -69,15 +68,15 @@ sealed interface Graph permits GraphImpl {
|
|||
}
|
||||
|
||||
sealed interface Builder permits GraphImpl.BuilderImpl {
|
||||
@NotNull Builder append(@NotNull Argument<?> argument, @Nullable Execution execution, @NotNull Consumer<Builder> consumer);
|
||||
@NotNull Builder append(@NotNull Arg<?> argument, @Nullable Execution execution, @NotNull Consumer<Builder> consumer);
|
||||
|
||||
@NotNull Builder append(@NotNull Argument<?> argument, @Nullable Execution execution);
|
||||
@NotNull Builder append(@NotNull Arg<?> argument, @Nullable Execution execution);
|
||||
|
||||
default @NotNull Builder append(@NotNull Argument<?> argument, @NotNull Consumer<Builder> consumer) {
|
||||
default @NotNull Builder append(@NotNull Arg<?> argument, @NotNull Consumer<Builder> consumer) {
|
||||
return append(argument, null, consumer);
|
||||
}
|
||||
|
||||
default @NotNull Builder append(@NotNull Argument<?> argument) {
|
||||
default @NotNull Builder append(@NotNull Arg<?> argument) {
|
||||
return append(argument, (Execution) null);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,62 @@ package net.minestom.server.command;
|
|||
import net.minestom.server.command.builder.arguments.*;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
final class GraphConverter {
|
||||
private static final Map<Class<? extends Parser<?>>, String> parserNames = Map.of(
|
||||
Parser.BooleanParser.class, "brigadier:bool",
|
||||
Parser.DoubleParser.class, "brigadier:double",
|
||||
Parser.FloatParser.class, "brigadier:float",
|
||||
Parser.IntegerParser.class, "brigadier:integer",
|
||||
Parser.LongParser.class, "brigadier:long",
|
||||
Parser.StringParser.class, "brigadier:string"
|
||||
);
|
||||
|
||||
private static final Map<Class<? extends Parser<?>>, Function<Parser<?>, byte[]>> propertiesFunctions = Map.ofEntries(
|
||||
numberProps(Parser.DoubleParser.class, BinaryWriter::writeDouble, Parser.DoubleParser::min, Parser.DoubleParser::max),
|
||||
numberProps(Parser.FloatParser.class, BinaryWriter::writeFloat, Parser.FloatParser::min, Parser.FloatParser::max),
|
||||
numberProps(Parser.IntegerParser.class, BinaryWriter::writeInt, Parser.IntegerParser::min, Parser.IntegerParser::max),
|
||||
numberProps(Parser.LongParser.class, BinaryWriter::writeLong, Parser.LongParser::min, Parser.LongParser::max),
|
||||
propEntry(Parser.StringParser.class, p -> BinaryWriter.makeArray(w -> w.writeVarInt(switch (p.type()) {
|
||||
case WORD -> 0;
|
||||
case QUOTED -> 1;
|
||||
case GREEDY -> 2;
|
||||
})))
|
||||
);
|
||||
|
||||
private static <T extends Parser<?>> Map.Entry<Class<? extends Parser<?>>, Function<Parser<?>, byte[]>> propEntry(Class<T> parser, Function<T, byte[]> func) {
|
||||
return Map.entry(parser, p -> func.apply(parser.cast(p)));
|
||||
}
|
||||
|
||||
private static <T extends Number, P extends Parser<T>> Map.Entry<Class<? extends Parser<?>>, Function<Parser<?>, byte[]>>
|
||||
numberProps(Class<P> parserClass, BiConsumer<BinaryWriter, T> numWriter, Function<P, T> minGetter, Function<P, T> maxGetter) {
|
||||
return propEntry(parserClass, p -> BinaryWriter.makeArray(w -> {
|
||||
final T min = minGetter.apply(p);
|
||||
final T max = maxGetter.apply(p);
|
||||
if (min != null && max != null) {
|
||||
w.write(0x03);
|
||||
numWriter.accept(w, min);
|
||||
numWriter.accept(w, max);
|
||||
} else if (min != null) {
|
||||
w.write(0x01);
|
||||
numWriter.accept(w, min);
|
||||
} else if (max != null) {
|
||||
w.write(0x02);
|
||||
numWriter.accept(w, max);
|
||||
} else {
|
||||
w.write(0x00);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private GraphConverter() {
|
||||
//no instance
|
||||
}
|
||||
|
@ -19,7 +67,7 @@ final class GraphConverter {
|
|||
public static DeclareCommandsPacket createPacket(Graph graph, @Nullable Player player) {
|
||||
List<DeclareCommandsPacket.Node> nodes = new ArrayList<>();
|
||||
List<BiConsumer<Graph, Integer>> redirects = new ArrayList<>();
|
||||
Map<Argument<?>, Integer> argToPacketId = new HashMap<>();
|
||||
Map<Arg<?>, Integer> argToPacketId = new HashMap<>();
|
||||
final AtomicInteger idSource = new AtomicInteger(0);
|
||||
final int rootId = append(graph.root(), nodes, redirects, idSource, null, player, argToPacketId)[0];
|
||||
for (var r : redirects) {
|
||||
|
@ -30,13 +78,14 @@ final class GraphConverter {
|
|||
|
||||
private static int[] append(Graph.Node graphNode, List<DeclareCommandsPacket.Node> to,
|
||||
List<BiConsumer<Graph, Integer>> redirects, AtomicInteger id, @Nullable AtomicInteger redirect,
|
||||
@Nullable Player player, Map<Argument<?>, Integer> argToPacketId) {
|
||||
@Nullable Player player, Map<Arg<?>, Integer> argToPacketId) {
|
||||
final Graph.Execution execution = graphNode.execution();
|
||||
if (player != null && execution != null) {
|
||||
if (!execution.test(player)) return new int[0];
|
||||
}
|
||||
|
||||
final Argument<?> argument = graphNode.argument();
|
||||
final Arg<?> arg = graphNode.argument();
|
||||
final Parser<?> parser = arg.parser();
|
||||
final List<Graph.Node> children = graphNode.next();
|
||||
|
||||
final DeclareCommandsPacket.Node node = new DeclareCommandsPacket.Node();
|
||||
|
@ -55,12 +104,12 @@ final class GraphConverter {
|
|||
}
|
||||
}
|
||||
node.children = packetNodeChildren;
|
||||
if (argument instanceof ArgumentLiteral literal) {
|
||||
if (literal.getId().isEmpty()) {
|
||||
if (parser instanceof Parser.LiteralParser literal) {
|
||||
if (literal.literal().isEmpty()) {
|
||||
node.flags = 0; //root
|
||||
} else {
|
||||
node.flags = literal(false, false);
|
||||
node.name = argument.getId();
|
||||
node.name = arg.id();
|
||||
if (redirect != null) {
|
||||
node.flags |= 0x8;
|
||||
redirects.add((graph, root) -> node.redirectedNode = redirect.get());
|
||||
|
@ -68,108 +117,121 @@ final class GraphConverter {
|
|||
}
|
||||
to.add(node);
|
||||
return new int[]{id.getAndIncrement()};
|
||||
} else if (parser instanceof Parser.LiteralsParser literalsArg) {
|
||||
return spreadLiteral(to, redirects, redirect, node, literalsArg.literals(), id);
|
||||
} else {
|
||||
if (argument instanceof ArgumentCommand argCmd) {
|
||||
node.flags = literal(false, true);
|
||||
node.name = argument.getId();
|
||||
final String shortcut = argCmd.getShortcut();
|
||||
if (shortcut.isEmpty()) {
|
||||
redirects.add((graph, root) -> node.redirectedNode = root);
|
||||
} else {
|
||||
redirects.add((graph, root) -> {
|
||||
final List<Argument<?>> args = CommandParser.parser().parse(graph, shortcut).args();
|
||||
final Argument<?> last = args.get(args.size() - 1);
|
||||
if (last.allowSpace()) {
|
||||
node.redirectedNode = argToPacketId.get(args.get(args.size()-2));
|
||||
} else {
|
||||
node.redirectedNode = argToPacketId.get(last);
|
||||
if (parser.spec() instanceof ParserSpecImpl.Legacy<?> legacyArg) {
|
||||
final Argument<?> argument = legacyArg.argument();
|
||||
if (argument instanceof ArgumentLiteral literal) {
|
||||
if (literal.getId().isEmpty()) {
|
||||
node.flags = 0; //root
|
||||
} else {
|
||||
node.flags = literal(false, false);
|
||||
node.name = argument.getId();
|
||||
if (redirect != null) {
|
||||
node.flags |= 0x8;
|
||||
redirects.add((graph, root) -> node.redirectedNode = redirect.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
to.add(node);
|
||||
}
|
||||
to.add(node);
|
||||
return new int[]{id.getAndIncrement()};
|
||||
} else if (argument instanceof ArgumentCommand argCmd) {
|
||||
node.flags = literal(false, true);
|
||||
node.name = argument.getId();
|
||||
final String shortcut = argCmd.getShortcut();
|
||||
if (shortcut.isEmpty()) {
|
||||
redirects.add((graph, root) -> node.redirectedNode = root);
|
||||
} else {
|
||||
redirects.add((graph, root) -> {
|
||||
node.redirectedNode = argToPacketId.get(findRedirectTargetForArgCmdShortcut(graph, argCmd.getShortcut()));
|
||||
});
|
||||
}
|
||||
to.add(node);
|
||||
|
||||
return new int[]{id.getAndIncrement()};
|
||||
} else if (argument instanceof ArgumentEnum<?> || (argument instanceof ArgumentWord word && word.hasRestrictions())) {
|
||||
List<String> entries = argument instanceof ArgumentEnum<?> ?
|
||||
((ArgumentEnum<?>) argument).entries() :
|
||||
Arrays.stream(((ArgumentWord) argument).getRestrictions()).toList();
|
||||
final int[] res = new int[entries.size()];
|
||||
for (int i = 0; i < res.length; i++) {
|
||||
String entry = entries.get(i);
|
||||
final DeclareCommandsPacket.Node subNode = new DeclareCommandsPacket.Node();
|
||||
subNode.children = node.children;
|
||||
subNode.flags = literal(false, false);
|
||||
subNode.name = entry;
|
||||
return new int[]{id.getAndIncrement()};
|
||||
} else if (argument instanceof ArgumentEnum<?> || (argument instanceof ArgumentWord word && word.hasRestrictions())) {
|
||||
return spreadLiteral(to, redirects, redirect, node, argument instanceof ArgumentEnum<?> ?
|
||||
((ArgumentEnum<?>) argument).entries() :
|
||||
Arrays.stream(((ArgumentWord) argument).getRestrictions()).toList(), id);
|
||||
} else if (argument instanceof ArgumentGroup special) {
|
||||
List<Argument<?>> entries = special.group();
|
||||
int[] res = null;
|
||||
int[] last = new int[0];
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
Arg<?> entry = ArgImpl.fromLegacy(entries.get(i));
|
||||
if (i == entries.size() - 1) {
|
||||
// Last will be the parent of next args
|
||||
final int[] l = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, redirects,
|
||||
id, redirect, player, argToPacketId);
|
||||
for (int n : l) {
|
||||
to.get(n).children = node.children;
|
||||
}
|
||||
for (int n : last) {
|
||||
to.get(n).children = l;
|
||||
}
|
||||
return res == null ? l : res;
|
||||
} else if (i == 0) {
|
||||
// First will be the children & parent of following
|
||||
res = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, redirects, id,
|
||||
null, player, argToPacketId);
|
||||
last = res;
|
||||
} else {
|
||||
final int[] l = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, redirects,
|
||||
id, null, player, argToPacketId);
|
||||
for (int n : last) {
|
||||
to.get(n).children = l;
|
||||
}
|
||||
last = l;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Arg group must have child args.");
|
||||
} else if (argument instanceof ArgumentLoop<?> special) {
|
||||
AtomicInteger r = new AtomicInteger();
|
||||
int[] res = new int[special.arguments().size()];
|
||||
List<? extends Argument<?>> arguments = special.arguments();
|
||||
for (int i = 0, appendIndex = 0; i < arguments.size(); i++) {
|
||||
final int[] append = append(new GraphImpl.NodeImpl(ArgImpl.fromLegacy(arguments.get(i)), null, List.of()), to,
|
||||
redirects, id, r, player, argToPacketId);
|
||||
if (append.length == 1) {
|
||||
res[appendIndex++] = append[0];
|
||||
} else {
|
||||
res = Arrays.copyOf(res, res.length + append.length - 1);
|
||||
System.arraycopy(append, 0, res, appendIndex, append.length);
|
||||
appendIndex += append.length;
|
||||
}
|
||||
}
|
||||
r.set(id.get());
|
||||
return res;
|
||||
} else {
|
||||
// Normal legacy arg
|
||||
final boolean hasSuggestion = argument.hasSuggestion();
|
||||
node.flags = arg(false, hasSuggestion);
|
||||
node.name = argument.getId();
|
||||
node.parser = argument.parser();
|
||||
node.properties = argument.nodeProperties();
|
||||
if (redirect != null) {
|
||||
subNode.flags |= 0x8;
|
||||
redirects.add((graph, root) -> subNode.redirectedNode = redirect.get());
|
||||
node.flags |= 0x8;
|
||||
redirects.add((graph, root) -> node.redirectedNode = redirect.get());
|
||||
}
|
||||
to.add(subNode);
|
||||
res[i] = id.getAndIncrement();
|
||||
}
|
||||
return res;
|
||||
} else if (argument instanceof ArgumentGroup special) {
|
||||
List<Argument<?>> entries = special.group();
|
||||
int[] res = null;
|
||||
int[] last = new int[0];
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
Argument<?> entry = entries.get(i);
|
||||
if (i == entries.size() - 1) {
|
||||
// Last will be the parent of next args
|
||||
final int[] l = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, redirects,
|
||||
id, redirect, player, argToPacketId);
|
||||
for (int n : l) {
|
||||
to.get(n).children = node.children;
|
||||
}
|
||||
for (int n : last) {
|
||||
to.get(n).children = l;
|
||||
}
|
||||
return res == null ? l : res;
|
||||
} else if (i == 0) {
|
||||
// First will be the children & parent of following
|
||||
res = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, redirects, id,
|
||||
null, player, argToPacketId);
|
||||
last = res;
|
||||
} else {
|
||||
final int[] l = append(new GraphImpl.NodeImpl(entry, null, List.of()), to, redirects,
|
||||
id, null, player, argToPacketId);
|
||||
for (int n : last) {
|
||||
to.get(n).children = l;
|
||||
}
|
||||
last = l;
|
||||
if (hasSuggestion) {
|
||||
node.suggestionsType = argument.suggestionType().getIdentifier();
|
||||
}
|
||||
to.add(node);
|
||||
return new int[]{id.getAndIncrement()};
|
||||
}
|
||||
throw new RuntimeException("Arg group must have child args.");
|
||||
} else if (argument instanceof ArgumentLoop special) {
|
||||
AtomicInteger r = new AtomicInteger();
|
||||
int[] res = new int[special.arguments().size()];
|
||||
List<?> arguments = special.arguments();
|
||||
for (int i = 0, appendIndex = 0; i < arguments.size(); i++) {
|
||||
Object arg = arguments.get(i);
|
||||
final int[] append = append(new GraphImpl.NodeImpl((Argument<?>) arg, null, List.of()), to,
|
||||
redirects, id, r, player, argToPacketId);
|
||||
if (append.length == 1) {
|
||||
res[appendIndex++] = append[0];
|
||||
} else {
|
||||
res = Arrays.copyOf(res, res.length + append.length - 1);
|
||||
System.arraycopy(append, 0, res, appendIndex, append.length);
|
||||
appendIndex += append.length;
|
||||
}
|
||||
}
|
||||
r.set(id.get());
|
||||
return res;
|
||||
} else {
|
||||
final boolean hasSuggestion = argument.hasSuggestion();
|
||||
// Normal arg
|
||||
final boolean hasSuggestion = arg.suggestionType() != null;
|
||||
node.flags = arg(false, hasSuggestion);
|
||||
node.name = argument.getId();
|
||||
node.parser = argument.parser();
|
||||
node.properties = argument.nodeProperties();
|
||||
node.name = arg.id();
|
||||
node.parser = getParserName(arg);
|
||||
node.properties = getProperties(arg);
|
||||
if (redirect != null) {
|
||||
node.flags |= 0x8;
|
||||
redirects.add((graph, root) -> node.redirectedNode = redirect.get());
|
||||
}
|
||||
if (hasSuggestion) {
|
||||
node.suggestionsType = argument.suggestionType().getIdentifier();
|
||||
node.suggestionsType = arg.suggestionType().name();
|
||||
}
|
||||
to.add(node);
|
||||
return new int[]{id.getAndIncrement()};
|
||||
|
@ -177,6 +239,29 @@ final class GraphConverter {
|
|||
}
|
||||
}
|
||||
|
||||
private static int[] spreadLiteral(List<DeclareCommandsPacket.Node> nodeList,
|
||||
List<BiConsumer<Graph, Integer>> redirects,
|
||||
@Nullable AtomicInteger redirect,
|
||||
DeclareCommandsPacket.Node node,
|
||||
Collection<String> entries,
|
||||
AtomicInteger id) {
|
||||
final int[] res = new int[entries.size()];
|
||||
int i = 0;
|
||||
for (String entry : entries) {
|
||||
final DeclareCommandsPacket.Node subNode = new DeclareCommandsPacket.Node();
|
||||
subNode.children = node.children;
|
||||
subNode.flags = literal(false, false);
|
||||
subNode.name = entry;
|
||||
if (redirect != null) {
|
||||
subNode.flags |= 0x8;
|
||||
redirects.add((graph, root) -> subNode.redirectedNode = redirect.get());
|
||||
}
|
||||
nodeList.add(subNode);
|
||||
res[i++] = id.getAndIncrement();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static byte literal(boolean executable, boolean hasRedirect) {
|
||||
return DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL, executable, hasRedirect, false);
|
||||
}
|
||||
|
@ -184,4 +269,32 @@ final class GraphConverter {
|
|||
private static byte arg(boolean executable, boolean hasSuggestion) {
|
||||
return DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.ARGUMENT, executable, false, hasSuggestion);
|
||||
}
|
||||
|
||||
private static byte @Nullable [] getProperties(Arg<?> arg) {
|
||||
final Parser<?> parser = arg.parser();
|
||||
if (parser.spec() instanceof ParserSpecImpl.Legacy<?> legacy) {
|
||||
return legacy.argument().nodeProperties();
|
||||
} else {
|
||||
final Function<Parser<?>, byte[]> parserFunction = propertiesFunctions.get(parser.getClass().getInterfaces()[0]);
|
||||
if (parserFunction == null) return null;
|
||||
return parserFunction.apply(parser);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getParserName(Arg<?> arg) {
|
||||
if (arg.parser().spec() instanceof ParserSpecImpl.Legacy<?> legacy) {
|
||||
return legacy.argument().parser();
|
||||
} else {
|
||||
final Class<?> parserClass = arg.parser().getClass().getInterfaces()[0];
|
||||
final String s = parserNames.get(parserClass);
|
||||
if (s == null) throw new RuntimeException("Unsupported parser type: " + parserClass.getSimpleName());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private static Arg<?> findRedirectTargetForArgCmdShortcut(Graph graph, String shortcut) {
|
||||
// TODO verify if this works as intended in every case
|
||||
final List<Arg<?>> args = CommandParser.parser().parse(graph, shortcut).args();
|
||||
return args.get(args.size() - 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,9 @@ import java.util.*;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.Literal;
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.Word;
|
||||
import static net.minestom.server.command.Arg.arg;
|
||||
import static net.minestom.server.command.Arg.literalArg;
|
||||
import static net.minestom.server.command.Parser.Literals;
|
||||
|
||||
record GraphImpl(NodeImpl root) implements Graph {
|
||||
static GraphImpl fromCommand(Command command) {
|
||||
|
@ -26,7 +27,7 @@ record GraphImpl(NodeImpl root) implements Graph {
|
|||
|
||||
static GraphImpl merge(List<Graph> graphs) {
|
||||
final List<Node> children = graphs.stream().map(Graph::root).toList();
|
||||
final NodeImpl root = new NodeImpl(Literal(""), null, children);
|
||||
final NodeImpl root = new NodeImpl(literalArg(""), null, children);
|
||||
return new GraphImpl(root);
|
||||
}
|
||||
|
||||
|
@ -35,13 +36,13 @@ record GraphImpl(NodeImpl root) implements Graph {
|
|||
return compare(root, graph.root(), comparator);
|
||||
}
|
||||
|
||||
record BuilderImpl(Argument<?> argument, List<BuilderImpl> children, Execution execution) implements Graph.Builder {
|
||||
public BuilderImpl(Argument<?> argument, Execution execution) {
|
||||
record BuilderImpl(Arg<?> argument, List<BuilderImpl> children, Execution execution) implements Graph.Builder {
|
||||
public BuilderImpl(Arg<?> argument, Execution execution) {
|
||||
this(argument, new ArrayList<>(), execution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Graph.@NotNull Builder append(@NotNull Argument<?> argument, @Nullable Execution execution,
|
||||
public Graph.@NotNull Builder append(@NotNull Arg<?> argument, @Nullable Execution execution,
|
||||
@NotNull Consumer<Graph.Builder> consumer) {
|
||||
BuilderImpl builder = new BuilderImpl(argument, execution);
|
||||
consumer.accept(builder);
|
||||
|
@ -50,7 +51,7 @@ record GraphImpl(NodeImpl root) implements Graph {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Graph.@NotNull Builder append(@NotNull Argument<?> argument, @Nullable Execution execution) {
|
||||
public Graph.@NotNull Builder append(@NotNull Arg<?> argument, @Nullable Execution execution) {
|
||||
this.children.add(new BuilderImpl(argument, List.of(), execution));
|
||||
return this;
|
||||
}
|
||||
|
@ -61,7 +62,7 @@ record GraphImpl(NodeImpl root) implements Graph {
|
|||
}
|
||||
}
|
||||
|
||||
record NodeImpl(Argument<?> argument, ExecutionImpl execution, List<Graph.Node> next) implements Graph.Node {
|
||||
record NodeImpl(Arg<?> argument, ExecutionImpl execution, List<Graph.Node> next) implements Graph.Node {
|
||||
static NodeImpl fromBuilder(BuilderImpl builder) {
|
||||
final List<BuilderImpl> children = builder.children;
|
||||
Node[] nodes = new NodeImpl[children.size()];
|
||||
|
@ -113,9 +114,9 @@ record GraphImpl(NodeImpl root) implements Graph {
|
|||
}
|
||||
}
|
||||
|
||||
private record ConversionNode(Argument<?> argument, ExecutionImpl execution,
|
||||
Map<Argument<?>, ConversionNode> nextMap) {
|
||||
ConversionNode(Argument<?> argument, ExecutionImpl execution) {
|
||||
private record ConversionNode(Arg<?> argument, ExecutionImpl execution,
|
||||
Map<Arg<?>, ConversionNode> nextMap) {
|
||||
ConversionNode(Arg<?> argument, ExecutionImpl execution) {
|
||||
this(argument, execution, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
|
@ -133,7 +134,8 @@ record GraphImpl(NodeImpl root) implements Graph {
|
|||
ConversionNode syntaxNode = root;
|
||||
for (Argument<?> arg : syntax.getArguments()) {
|
||||
boolean last = arg == syntax.getArguments()[syntax.getArguments().length - 1];
|
||||
syntaxNode = syntaxNode.nextMap.computeIfAbsent(arg, argument -> {
|
||||
final Arg<?> convertedArgument = ArgImpl.fromLegacy(arg);
|
||||
syntaxNode = syntaxNode.nextMap.computeIfAbsent(convertedArgument, argument -> {
|
||||
var ex = last ? ExecutionImpl.fromSyntax(syntax) : null;
|
||||
return new ConversionNode(argument, ex);
|
||||
});
|
||||
|
@ -147,19 +149,23 @@ record GraphImpl(NodeImpl root) implements Graph {
|
|||
}
|
||||
|
||||
static ConversionNode rootConv(Collection<Command> commands) {
|
||||
Map<Argument<?>, ConversionNode> next = new LinkedHashMap<>(commands.size());
|
||||
Map<Arg<?>, ConversionNode> next = new LinkedHashMap<>(commands.size());
|
||||
for (Command command : commands) {
|
||||
final ConversionNode conv = fromCommand(command);
|
||||
next.put(conv.argument, conv);
|
||||
}
|
||||
return new ConversionNode(Literal(""), null, next);
|
||||
return new ConversionNode(literalArg(""), null, next);
|
||||
}
|
||||
}
|
||||
|
||||
static Argument<String> commandToArgument(Command command) {
|
||||
final String[] aliases = command.getNames();
|
||||
if (aliases.length == 1) return Literal(aliases[0]);
|
||||
return Word(command.getName()).from(command.getNames());
|
||||
static Arg<String> commandToArgument(Command command) {
|
||||
final String commandName = command.getName();
|
||||
final String[] names = command.getNames();
|
||||
if (names.length == 1) {
|
||||
return literalArg(commandName);
|
||||
} else {
|
||||
return arg(commandName, Literals(names));
|
||||
}
|
||||
}
|
||||
|
||||
static boolean compare(@NotNull Node first, Node second, @NotNull Comparator comparator) {
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
sealed interface Parser<T> {
|
||||
static @NotNull LiteralParser Literal(@NotNull String literal) {
|
||||
return ParserImpl.LITERAL.literal(literal);
|
||||
}
|
||||
|
||||
static @NotNull LiteralsParser Literals(@NotNull Set<String> literals) {
|
||||
return ParserImpl.LITERALS.literals(literals);
|
||||
}
|
||||
|
||||
static @NotNull LiteralsParser Literals(@NotNull String @NotNull ... literals) {
|
||||
return ParserImpl.LITERALS.literals(literals);
|
||||
}
|
||||
|
||||
static @NotNull BooleanParser Boolean() {
|
||||
return ParserImpl.BOOLEAN;
|
||||
}
|
||||
|
||||
static @NotNull FloatParser Float() {
|
||||
return ParserImpl.FLOAT;
|
||||
}
|
||||
|
||||
static @NotNull DoubleParser Double() {
|
||||
return ParserImpl.DOUBLE;
|
||||
}
|
||||
|
||||
static @NotNull IntegerParser Integer() {
|
||||
return ParserImpl.INTEGER;
|
||||
}
|
||||
|
||||
static @NotNull LongParser Long() {
|
||||
return ParserImpl.LONG;
|
||||
}
|
||||
|
||||
static @NotNull StringParser String() {
|
||||
return ParserImpl.STRING;
|
||||
}
|
||||
|
||||
static <T> @NotNull Custom<T> custom(@NotNull ParserSpec<T> spec) {
|
||||
return new ParserImpl.CustomImpl<>(spec);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
static <T> @NotNull Custom<T> legacy(@NotNull Argument<T> argument) {
|
||||
return custom(ParserSpec.legacy(argument));
|
||||
}
|
||||
|
||||
@NotNull ParserSpec<T> spec();
|
||||
|
||||
sealed interface LiteralParser extends Parser<String>
|
||||
permits ParserImpl.LiteralParserImpl {
|
||||
@NotNull String literal();
|
||||
|
||||
@NotNull LiteralParser literal(@NotNull String literal);
|
||||
}
|
||||
|
||||
sealed interface LiteralsParser extends Parser<String>
|
||||
permits ParserImpl.LiteralsParserImpl {
|
||||
@Unmodifiable
|
||||
@NotNull Set<String> literals();
|
||||
|
||||
@NotNull LiteralsParser literals(@NotNull Set<String> literals);
|
||||
|
||||
default @NotNull LiteralsParser literals(@NotNull String @NotNull ... literals) {
|
||||
return literals(Set.of(literals));
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface BooleanParser extends Parser<Boolean>
|
||||
permits ParserImpl.BooleanParserImpl {
|
||||
}
|
||||
|
||||
sealed interface FloatParser extends Parser<Float>
|
||||
permits ParserImpl.FloatParserImpl {
|
||||
@Nullable Float max();
|
||||
|
||||
@Nullable Float min();
|
||||
|
||||
@NotNull FloatParser max(@Nullable Float max);
|
||||
|
||||
@NotNull FloatParser min(@Nullable Float min);
|
||||
}
|
||||
|
||||
sealed interface DoubleParser extends Parser<Double>
|
||||
permits ParserImpl.DoubleParserImpl {
|
||||
@Nullable Double max();
|
||||
|
||||
@Nullable Double min();
|
||||
|
||||
@NotNull DoubleParser max(@Nullable Double max);
|
||||
|
||||
@NotNull DoubleParser min(@Nullable Double min);
|
||||
}
|
||||
|
||||
sealed interface IntegerParser extends Parser<Integer>
|
||||
permits ParserImpl.IntegerParserImpl {
|
||||
@Nullable Integer max();
|
||||
|
||||
@Nullable Integer min();
|
||||
|
||||
@NotNull IntegerParser max(@Nullable Integer max);
|
||||
|
||||
@NotNull IntegerParser min(@Nullable Integer min);
|
||||
}
|
||||
|
||||
sealed interface LongParser extends Parser<Long>
|
||||
permits ParserImpl.LongParserImpl {
|
||||
@Nullable Long max();
|
||||
|
||||
@Nullable Long min();
|
||||
|
||||
@NotNull LongParser max(@Nullable Long max);
|
||||
|
||||
@NotNull LongParser min(@Nullable Long min);
|
||||
}
|
||||
|
||||
sealed interface StringParser extends Parser<String>
|
||||
permits ParserImpl.StringParserImpl {
|
||||
@NotNull Type type();
|
||||
|
||||
@NotNull StringParser type(@NotNull Type type);
|
||||
|
||||
enum Type {
|
||||
WORD,
|
||||
QUOTED,
|
||||
GREEDY
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Custom<T> extends Parser<T>
|
||||
permits ParserImpl.CustomImpl {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
final class ParserImpl {
|
||||
static final LiteralParserImpl LITERAL = new LiteralParserImpl("");
|
||||
static final LiteralsParserImpl LITERALS = new LiteralsParserImpl(Set.of());
|
||||
static final BooleanParserImpl BOOLEAN = new BooleanParserImpl();
|
||||
static final FloatParserImpl FLOAT = new FloatParserImpl(null, null);
|
||||
static final DoubleParserImpl DOUBLE = new DoubleParserImpl(null, null);
|
||||
static final IntegerParserImpl INTEGER = new IntegerParserImpl(null, null);
|
||||
static final LongParserImpl LONG = new LongParserImpl(null, null);
|
||||
|
||||
static final StringParserImpl STRING = new StringParserImpl(Parser.StringParser.Type.WORD);
|
||||
|
||||
record LiteralParserImpl(String literal) implements Parser.LiteralParser {
|
||||
@Override
|
||||
public @NotNull LiteralParser literal(@NotNull String literal) {
|
||||
return new LiteralParserImpl(literal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ParserSpec<String> spec() {
|
||||
return ParserSpec.constant(ParserSpec.Type.WORD, literal);
|
||||
}
|
||||
}
|
||||
|
||||
record LiteralsParserImpl(Set<String> literals) implements Parser.LiteralsParser {
|
||||
LiteralsParserImpl {
|
||||
literals = Set.copyOf(literals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LiteralsParser literals(@NotNull Set<String> literals) {
|
||||
return new LiteralsParserImpl(literals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ParserSpec<String> spec() {
|
||||
return ParserSpec.constants(ParserSpec.Type.WORD, literals);
|
||||
}
|
||||
}
|
||||
|
||||
record BooleanParserImpl() implements Parser.BooleanParser {
|
||||
@Override
|
||||
public @NotNull ParserSpec<Boolean> spec() {
|
||||
return ParserSpec.Type.BOOLEAN;
|
||||
}
|
||||
}
|
||||
|
||||
record FloatParserImpl(Float min, Float max) implements Parser.FloatParser {
|
||||
private static final ParserSpec<Float> DEFAULT_SPEC = ParserSpec.Type.FLOAT;
|
||||
|
||||
@Override
|
||||
public @NotNull FloatParser max(@Nullable Float max) {
|
||||
return new FloatParserImpl(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FloatParser min(@Nullable Float min) {
|
||||
return new FloatParserImpl(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ParserSpec<Float> spec() {
|
||||
if (min == null && max == null) {
|
||||
return ParserSpec.Type.FLOAT;
|
||||
} else {
|
||||
return ParserSpec.specialized(DEFAULT_SPEC,
|
||||
result -> {
|
||||
final Float value = result.value();
|
||||
if (min != null && value < min)
|
||||
return ParserSpec.Result.error(result.input(), "value is too low", 2);
|
||||
if (max != null && value > max)
|
||||
return ParserSpec.Result.error(result.input(), "value is too high", 3);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record DoubleParserImpl(Double min, Double max) implements Parser.DoubleParser {
|
||||
private static final ParserSpec<Double> DEFAULT_SPEC = ParserSpec.Type.DOUBLE;
|
||||
|
||||
@Override
|
||||
public @NotNull DoubleParser max(@Nullable Double max) {
|
||||
return new DoubleParserImpl(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DoubleParser min(@Nullable Double min) {
|
||||
return new DoubleParserImpl(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ParserSpec<Double> spec() {
|
||||
if (min == null && max == null) {
|
||||
return DEFAULT_SPEC;
|
||||
} else {
|
||||
return ParserSpec.specialized(DEFAULT_SPEC,
|
||||
result -> {
|
||||
final Double value = result.value();
|
||||
if (min != null && value < min)
|
||||
return ParserSpec.Result.error(result.input(), "value is too low", 2);
|
||||
if (max != null && value > max)
|
||||
return ParserSpec.Result.error(result.input(), "value is too high", 3);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record IntegerParserImpl(Integer min, Integer max) implements Parser.IntegerParser {
|
||||
private static final ParserSpec<Integer> DEFAULT_SPEC = ParserSpec.Type.INTEGER;
|
||||
|
||||
@Override
|
||||
public @NotNull IntegerParser max(@Nullable Integer max) {
|
||||
return new IntegerParserImpl(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntegerParser min(@Nullable Integer min) {
|
||||
return new IntegerParserImpl(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ParserSpec<Integer> spec() {
|
||||
if (min == null && max == null) {
|
||||
return DEFAULT_SPEC;
|
||||
} else {
|
||||
return ParserSpec.specialized(DEFAULT_SPEC,
|
||||
result -> {
|
||||
final Integer value = result.value();
|
||||
if (min != null && value < min)
|
||||
return ParserSpec.Result.error(result.input(), "value is too low", 2);
|
||||
if (max != null && value > max)
|
||||
return ParserSpec.Result.error(result.input(), "value is too high", 3);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record LongParserImpl(Long min, Long max) implements Parser.LongParser {
|
||||
private static final ParserSpec<Long> DEFAULT_SPEC = ParserSpec.Type.LONG;
|
||||
|
||||
@Override
|
||||
public @NotNull LongParser max(@Nullable Long max) {
|
||||
return new LongParserImpl(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LongParser min(@Nullable Long min) {
|
||||
return new LongParserImpl(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ParserSpec<Long> spec() {
|
||||
if (min == null && max == null) {
|
||||
return DEFAULT_SPEC;
|
||||
} else {
|
||||
return ParserSpec.specialized(DEFAULT_SPEC,
|
||||
result -> {
|
||||
final Long value = result.value();
|
||||
if (min != null && value < min)
|
||||
return ParserSpec.Result.error(result.input(), "value is too low", 2);
|
||||
if (max != null && value > max)
|
||||
return ParserSpec.Result.error(result.input(), "value is too high", 3);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record StringParserImpl(StringParser.Type type) implements Parser.StringParser {
|
||||
@Override
|
||||
public @NotNull StringParser type(@NotNull Type type) {
|
||||
return new StringParserImpl(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ParserSpec<String> spec() {
|
||||
return switch (type) {
|
||||
case WORD -> ParserSpec.Type.WORD;
|
||||
case QUOTED -> ParserSpec.Type.QUOTED_PHRASE;
|
||||
case GREEDY -> ParserSpec.Type.GREEDY_PHRASE;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
record CustomImpl<T>(ParserSpec<T> spec) implements Parser.Custom<T> {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
sealed interface ParserSpec<T>
|
||||
permits ParserSpec.Type, ParserSpecImpl.Constant1, ParserSpecImpl.ConstantN,
|
||||
ParserSpecImpl.Legacy, ParserSpecImpl.Reader, ParserSpecImpl.Specialized {
|
||||
|
||||
static <T> @NotNull ParserSpec<T> constant(@NotNull Type<T> type, @NotNull T constant) {
|
||||
return new ParserSpecImpl.Constant1<>(type, constant);
|
||||
}
|
||||
|
||||
static <T> @NotNull ParserSpec<T> constants(@NotNull Type<T> type, @NotNull Set<@NotNull T> constants) {
|
||||
return new ParserSpecImpl.ConstantN<>(type, constants);
|
||||
}
|
||||
|
||||
static <T> @NotNull ParserSpec<T> reader(@NotNull BiFunction<@NotNull String, @NotNull Integer, @Nullable Result<T>> reader) {
|
||||
return new ParserSpecImpl.Reader<>(reader);
|
||||
}
|
||||
|
||||
static <T> @NotNull ParserSpec<T> specialized(@NotNull ParserSpec<T> spec,
|
||||
@NotNull Function<Result.@NotNull Success<T>, @NotNull Result<T>> filter) {
|
||||
return new ParserSpecImpl.Specialized<>(spec, filter);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
static <T> @NotNull ParserSpec<T> legacy(@NotNull Argument<T> argument) {
|
||||
return new ParserSpecImpl.Legacy<>(argument);
|
||||
}
|
||||
|
||||
@NotNull Result<T> read(@NotNull String input, int startIndex);
|
||||
|
||||
default @NotNull Result<T> read(@NotNull String input) {
|
||||
return read(input, 0);
|
||||
}
|
||||
|
||||
default @Nullable T readExact(@NotNull String input) {
|
||||
final Result<T> result = read(input);
|
||||
return result instanceof Result.Success<T> success && success.index() == input.length() ?
|
||||
success.value() : null;
|
||||
}
|
||||
|
||||
sealed interface Type<T> extends ParserSpec<T>
|
||||
permits ParserSpecTypes.TypeImpl {
|
||||
Type<Boolean> BOOLEAN = ParserSpecTypes.BOOLEAN;
|
||||
Type<Float> FLOAT = ParserSpecTypes.FLOAT;
|
||||
Type<Double> DOUBLE = ParserSpecTypes.DOUBLE;
|
||||
Type<Integer> INTEGER = ParserSpecTypes.INTEGER;
|
||||
Type<Long> LONG = ParserSpecTypes.LONG;
|
||||
|
||||
Type<String> WORD = ParserSpecTypes.WORD;
|
||||
Type<String> QUOTED_PHRASE = ParserSpecTypes.QUOTED_PHRASE;
|
||||
Type<String> GREEDY_PHRASE = ParserSpecTypes.GREEDY_PHRASE;
|
||||
|
||||
@NotNull ParserSpec.Result<T> equals(@NotNull String input, int startIndex, @NotNull T constant);
|
||||
|
||||
@NotNull ParserSpec.Result<T> find(@NotNull String input, int startIndex, @NotNull Set<@NotNull T> constants);
|
||||
|
||||
@Nullable T equalsExact(@NotNull String input, @NotNull T constant);
|
||||
|
||||
@Nullable T findExact(@NotNull String input, @NotNull Set<@NotNull T> constants);
|
||||
}
|
||||
|
||||
|
||||
sealed interface Result<T> {
|
||||
static <T> Result.@NotNull Success<T> success(@NotNull String input, int index, @NotNull T value) {
|
||||
return new ParserSpecTypes.ResultSuccessImpl<>(input, index, value);
|
||||
}
|
||||
|
||||
static <T> Result.@NotNull SyntaxError<T> error(@NotNull String input, @NotNull String message, int error) {
|
||||
return new ParserSpecTypes.ResultErrorImpl<>(input, message, error);
|
||||
}
|
||||
|
||||
static <T> Result.@NotNull IncompatibleType<T> incompatible() {
|
||||
return new ParserSpecTypes.ResultIncompatibleImpl<>();
|
||||
}
|
||||
|
||||
sealed interface Success<T> extends Result<T>
|
||||
permits ParserSpecTypes.ResultSuccessImpl {
|
||||
|
||||
@NotNull String input();
|
||||
|
||||
/**
|
||||
* Indicates how much data was read from the input
|
||||
*
|
||||
* @return the index of the next unread character
|
||||
*/
|
||||
int index();
|
||||
|
||||
@NotNull T value();
|
||||
}
|
||||
|
||||
sealed interface IncompatibleType<T> extends Result<T>
|
||||
permits ParserSpecTypes.ResultIncompatibleImpl {
|
||||
}
|
||||
|
||||
sealed interface SyntaxError<T> extends Result<T>
|
||||
permits ParserSpecTypes.ResultErrorImpl {
|
||||
|
||||
@NotNull String input();
|
||||
|
||||
@NotNull String message();
|
||||
|
||||
int error();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static net.minestom.server.command.ParserSpec.Result.*;
|
||||
|
||||
final class ParserSpecImpl {
|
||||
|
||||
/**
|
||||
* Reads from a trusted type, and compare to the constant.
|
||||
* <p>
|
||||
* Reading can be optimized using a raw string comparison to avoid parsing altogether.
|
||||
*/
|
||||
record Constant1<T>(Type<T> type, T constant) implements ParserSpec<T> {
|
||||
@Override
|
||||
public @NotNull Result<T> read(@NotNull String input, int startIndex) {
|
||||
return type.equals(input, startIndex, constant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T readExact(@NotNull String input) {
|
||||
return type.equalsExact(input, constant);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from a trusted type, and compare to a set of constants.
|
||||
* <p>
|
||||
* Reading can be optimized using map lookups.
|
||||
*
|
||||
* @see Constant1 for raw string comparison, also relevant here
|
||||
*/
|
||||
record ConstantN<T>(Type<T> type, Set<T> constants) implements ParserSpec<T> {
|
||||
ConstantN {
|
||||
constants = Set.copyOf(constants);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Result<T> read(@NotNull String input, int startIndex) {
|
||||
return type.find(input, startIndex, constants);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T readExact(@NotNull String input) {
|
||||
return type.findExact(input, constants);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from arbitrary code.
|
||||
* <p>
|
||||
* Cannot be optimized at all, but more flexible.
|
||||
*/
|
||||
record Reader<T>(BiFunction<String, Integer, Result<T>> reader) implements ParserSpec<T> {
|
||||
@Override
|
||||
public @NotNull Result<T> read(@NotNull String input, int startIndex) {
|
||||
return reader.apply(input, startIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reuses an existing spec but with an additional filter.
|
||||
* <p>
|
||||
* The filter means that the parsec input has to pass through the arbitrary function, limiting potential optimizations.
|
||||
*/
|
||||
record Specialized<T>(ParserSpec<T> spec, Function<Success<T>, Result<T>> filter) implements ParserSpec<T> {
|
||||
@Override
|
||||
public @NotNull Result<T> read(@NotNull String input, int startIndex) {
|
||||
final Result<T> result = spec.read(input);
|
||||
if (!(result instanceof Result.Success<T> success)) return result;
|
||||
return filter.apply(success);
|
||||
}
|
||||
}
|
||||
|
||||
record Legacy<T>(Argument<T> argument) implements ParserSpec<T> {
|
||||
@Override
|
||||
public @NotNull Result<T> read(@NotNull String input, int startIndex) {
|
||||
final String sub = input.substring(startIndex);
|
||||
final String[] split = sub.split(" ");
|
||||
// Handle specific type without loop
|
||||
try {
|
||||
// Single word argument
|
||||
if (!argument.allowSpace()) {
|
||||
final String word = split[0];
|
||||
final int index = startIndex + word.length();
|
||||
final T value = argument.parse(word);
|
||||
return success(input, index, value);
|
||||
}
|
||||
// Complete input argument
|
||||
if (argument.useRemaining()) {
|
||||
final T value = argument.parse(sub);
|
||||
return success(input, input.length(), value);
|
||||
}
|
||||
} catch (ArgumentSyntaxException exception) {
|
||||
return error(exception.getInput(), exception.getMessage(), exception.getErrorCode());
|
||||
}
|
||||
// Bruteforce
|
||||
assert argument.allowSpace() && !argument.useRemaining();
|
||||
StringBuilder current = new StringBuilder();
|
||||
|
||||
ArgumentSyntaxException lastException = null;
|
||||
for (String word : split) {
|
||||
if (!current.isEmpty()) current.append(' ');
|
||||
current.append(word);
|
||||
try {
|
||||
final String result = current.toString();
|
||||
final T value = argument.parse(result);
|
||||
final int index = result.length() + startIndex;
|
||||
return success(result, index, value);
|
||||
} catch (ArgumentSyntaxException exception) {
|
||||
lastException = exception;
|
||||
}
|
||||
}
|
||||
if (lastException != null) {
|
||||
return error(lastException.getInput(), lastException.getMessage(), lastException.getErrorCode());
|
||||
}
|
||||
return incompatible();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.utils.StringReaderUtils;
|
||||
import net.minestom.server.utils.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static net.minestom.server.command.ParserSpec.Result.*;
|
||||
|
||||
final class ParserSpecTypes {
|
||||
static final ParserSpec.Type<Boolean> BOOLEAN = ParserSpecTypes.builder((input, startIndex) -> {
|
||||
final int index = input.indexOf(' ', startIndex);
|
||||
if (index == -1) {
|
||||
// Whole input is a float
|
||||
final String word = input.substring(startIndex);
|
||||
final Boolean value = word.equals("true") ? Boolean.TRUE : word.equals("false") ? Boolean.FALSE : null;
|
||||
if (value == null) return incompatible();
|
||||
return success(word, input.length(), value);
|
||||
} else {
|
||||
// Part of input is a float
|
||||
final String word = input.substring(startIndex, index);
|
||||
final Boolean value = word.equals("true") ? Boolean.TRUE : word.equals("false") ? Boolean.FALSE : null;
|
||||
if (value == null) return incompatible();
|
||||
return success(word, index, value);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
static final ParserSpec.Type<Float> FLOAT = ParserSpecTypes.builder((input, startIndex) -> {
|
||||
final int index = input.indexOf(' ', startIndex);
|
||||
final String word = index == -1 ? input.substring(startIndex) : input.substring(startIndex, index);
|
||||
final int resultIndex = index == -1 ? input.length() : index;
|
||||
try {
|
||||
final float value = Float.parseFloat(word);
|
||||
return success(word, resultIndex, value);
|
||||
} catch (NumberFormatException e) {
|
||||
return incompatible();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
static final ParserSpec.Type<Double> DOUBLE = ParserSpecTypes.builder((input, startIndex) -> {
|
||||
final int index = input.indexOf(' ', startIndex);
|
||||
final String word = index == -1 ? input.substring(startIndex) : input.substring(startIndex, index);
|
||||
final int resultIndex = index == -1 ? input.length() : index;
|
||||
try {
|
||||
final double value = Double.parseDouble(word);
|
||||
return success(word, resultIndex, value);
|
||||
} catch (NumberFormatException e) {
|
||||
return incompatible();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
static final ParserSpec.Type<Integer> INTEGER = ParserSpecTypes.builder((input, startIndex) -> {
|
||||
final int index = input.indexOf(' ', startIndex);
|
||||
final String word = index == -1 ? input.substring(startIndex) : input.substring(startIndex, index);
|
||||
final int resultIndex = index == -1 ? input.length() : index;
|
||||
try {
|
||||
final int value = Integer.parseInt(input, startIndex, resultIndex, 10);
|
||||
return success(word, resultIndex, value);
|
||||
} catch (NumberFormatException e) {
|
||||
return incompatible();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
static final ParserSpec.Type<Long> LONG = ParserSpecTypes.builder((input, startIndex) -> {
|
||||
final int index = input.indexOf(' ', startIndex);
|
||||
final String word = index == -1 ? input.substring(startIndex) : input.substring(startIndex, index);
|
||||
final int resultIndex = index == -1 ? input.length() : index;
|
||||
try {
|
||||
final long value = Long.parseLong(input, startIndex, resultIndex, 10);
|
||||
return success(word, resultIndex, value);
|
||||
} catch (NumberFormatException e) {
|
||||
return incompatible();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
static final ParserSpec.Type<String> WORD = ParserSpecTypes.builder((input, startIndex) -> {
|
||||
final int index = input.indexOf(' ', startIndex);
|
||||
if (index == -1) {
|
||||
// No space found, so it's a word
|
||||
final String word = input.substring(startIndex);
|
||||
return success(word, input.length(), word);
|
||||
} else {
|
||||
// Space found, substring the word
|
||||
final String word = input.substring(startIndex, index);
|
||||
return success(word, index, word);
|
||||
}
|
||||
})
|
||||
.equals((input, startIndex, constant) -> {
|
||||
final int length = constant.length();
|
||||
if (input.regionMatches(startIndex, constant, 0, length)) {
|
||||
final int index = startIndex + length;
|
||||
return success(constant, index, constant);
|
||||
} else {
|
||||
return incompatible();
|
||||
}
|
||||
})
|
||||
.find((input, startIndex, constants) -> {
|
||||
for (String constant : constants) {
|
||||
final int length = constant.length();
|
||||
if (input.regionMatches(startIndex, constant, 0, length)) {
|
||||
final int index = startIndex + length;
|
||||
return success(constant, index, constant);
|
||||
}
|
||||
}
|
||||
return incompatible();
|
||||
})
|
||||
.equalsExact((input, constant) -> input.equals(constant) ? constant : null)
|
||||
.findExact((input, constants) -> constants.contains(input) ? input : null)
|
||||
.build();
|
||||
static final ParserSpec.Type<String> QUOTED_PHRASE = ParserSpecTypes.builder((input, startIndex) -> {
|
||||
final int inclusiveEnd = StringReaderUtils.endIndexOfQuotableString(input, startIndex);
|
||||
if (inclusiveEnd == -1) {
|
||||
return incompatible();
|
||||
} else {
|
||||
final char type = input.charAt(startIndex);
|
||||
final int exclusiveEnd = inclusiveEnd + 1;
|
||||
if (type == '"' || type == '\'') {
|
||||
// Quoted
|
||||
return success(input.substring(startIndex, exclusiveEnd), exclusiveEnd,
|
||||
StringUtils.unescapeJavaString(input.substring(startIndex + 1, inclusiveEnd)));
|
||||
} else {
|
||||
// Unquoted
|
||||
final String substring = input.substring(startIndex, exclusiveEnd);
|
||||
return success(substring, exclusiveEnd, substring);
|
||||
}
|
||||
}
|
||||
})
|
||||
.build();
|
||||
static final ParserSpec.Type<String> GREEDY_PHRASE = ParserSpecTypes.builder((input, startIndex) -> {
|
||||
final String result = input.substring(startIndex);
|
||||
return success(result, input.length(), result);
|
||||
})
|
||||
.build();
|
||||
|
||||
static <T> Builder<T> builder(Functions.Read<T> read) {
|
||||
return new Builder<>(read);
|
||||
}
|
||||
|
||||
private interface Functions {
|
||||
@FunctionalInterface
|
||||
interface Read<T> {
|
||||
ParserSpec.Result<T> read(String input, int startIndex);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Find<T> {
|
||||
ParserSpec.Result<T> find(String input, int startIndex, Set<T> constants);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Equals<T> {
|
||||
ParserSpec.Result<T> equals(String input, int startIndex, T constant);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ReadExact<T> {
|
||||
T readExact(String input);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface FindExact<T> {
|
||||
T findExact(String input, Set<T> constants);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface EqualsExact<T> {
|
||||
T equalsExact(String input, T constant);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Builder<T> {
|
||||
final Functions.Read<T> read;
|
||||
Functions.Equals<T> equals;
|
||||
Functions.Find<T> find;
|
||||
|
||||
Functions.ReadExact<T> readExact;
|
||||
Functions.EqualsExact<T> equalsExact;
|
||||
Functions.FindExact<T> findExact;
|
||||
|
||||
Builder(Functions.Read<T> read) {
|
||||
this.read = read;
|
||||
}
|
||||
|
||||
public Builder<T> equals(Functions.Equals<T> equals) {
|
||||
this.equals = equals;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> find(Functions.Find<T> find) {
|
||||
this.find = find;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> readExact(Functions.ReadExact<T> exact) {
|
||||
this.readExact = exact;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> equalsExact(Functions.EqualsExact<T> equalsExact) {
|
||||
this.equalsExact = equalsExact;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> findExact(Functions.FindExact<T> findExact) {
|
||||
this.findExact = findExact;
|
||||
return this;
|
||||
}
|
||||
|
||||
ParserSpec.Type<T> build() {
|
||||
return new TypeImpl<>(read, equals, find, readExact, equalsExact, findExact);
|
||||
}
|
||||
}
|
||||
|
||||
record TypeImpl<T>(Functions.Read<T> read,
|
||||
Functions.Equals<T> equals, Functions.Find<T> find,
|
||||
Functions.ReadExact<T> readExact,
|
||||
Functions.EqualsExact<T> equalsExact, Functions.FindExact<T> findExact)
|
||||
implements ParserSpec.Type<T> {
|
||||
|
||||
TypeImpl {
|
||||
// Create fallback if no specialized function is provided
|
||||
equals = Objects.requireNonNullElse(equals, (input, startIndex, constant) -> {
|
||||
final ParserSpec.Result<T> result = read(input, startIndex);
|
||||
assertInput(result, input);
|
||||
if (result instanceof Result.Success<T> success && !constant.equals(success.value())) {
|
||||
return error(success.input(), "Expected constant '" + constant + "' but found '" + success.value() + "'", 0);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
find = Objects.requireNonNullElse(find, (input, startIndex, constants) -> {
|
||||
final ParserSpec.Result<T> result = read(input, startIndex);
|
||||
assertInput(result, input);
|
||||
if (result instanceof Result.Success<T> success && !constants.contains(success.value())) {
|
||||
return error(success.input(), "Expected constants '" + constants + "' but found '" + success.value() + "'", 0);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
readExact = Objects.requireNonNullElse(readExact, (input) -> {
|
||||
final ParserSpec.Result<T> result = read(input, 0);
|
||||
if (result instanceof Result.Success<T> success && input.length() == success.index()) {
|
||||
assertInput(result, input);
|
||||
return success.value();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
equalsExact = Objects.requireNonNullElse(equalsExact, (input, constant) -> {
|
||||
final T value = readExact(input);
|
||||
return Objects.equals(value, constant) ? constant : null;
|
||||
});
|
||||
findExact = Objects.requireNonNullElse(findExact, (input, constants) -> {
|
||||
final T value = readExact(input);
|
||||
return constants.contains(value) ? value : null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParserSpec.@NotNull Result<T> read(@NotNull String input, int startIndex) {
|
||||
return read.read(input, startIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParserSpec.@NotNull Result<T> equals(@NotNull String input, int startIndex, @NotNull T constant) {
|
||||
return equals.equals(input, startIndex, constant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParserSpec.@NotNull Result<T> find(@NotNull String input, int startIndex, @NotNull Set<@NotNull T> constants) {
|
||||
return find.find(input, startIndex, constants);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T readExact(@NotNull String input) {
|
||||
return readExact.readExact(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T equalsExact(@NotNull String input, @NotNull T constant) {
|
||||
return equalsExact.equalsExact(input, constant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T findExact(@NotNull String input, @NotNull Set<@NotNull T> constants) {
|
||||
return findExact.findExact(input, constants);
|
||||
}
|
||||
}
|
||||
|
||||
record ResultSuccessImpl<T>(String input, int index, T value) implements ParserSpec.Result.Success<T> {
|
||||
}
|
||||
|
||||
record ResultIncompatibleImpl<T>() implements ParserSpec.Result.IncompatibleType<T> {
|
||||
}
|
||||
|
||||
record ResultErrorImpl<T>(String input, String message, int error) implements ParserSpec.Result.SyntaxError<T> {
|
||||
}
|
||||
|
||||
static void assertInput(ParserSpec.Result<?> result, String input) {
|
||||
assert result != null : "Result must not be null";
|
||||
assert !(result instanceof ParserSpec.Result.Success<?> su)
|
||||
|| su.input().equals(input) : "input mismatch: " + result + " != " + input;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package net.minestom.server.utils;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class StringReaderUtils {
|
||||
private StringReaderUtils() {
|
||||
//no instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the first unescaped escapable character
|
||||
*
|
||||
* @param charSequence the sequence to start
|
||||
* @param start inclusive start position
|
||||
* @param escapable the escapable character to find
|
||||
* @param escape escape character
|
||||
* @return the index of the first unescaped escapable character or -1 if it doesn't have an end
|
||||
*/
|
||||
public static int nextIndexOfEscapable(CharSequence charSequence, int start, char escapable, char escape) {
|
||||
boolean wasEscape = false;
|
||||
for (int i = start; i < charSequence.length(); i++) {
|
||||
if (wasEscape) {
|
||||
wasEscape = false;
|
||||
} else {
|
||||
final char charAt = charSequence.charAt(i);
|
||||
if (charAt == escapable) return i;
|
||||
if (charAt == escape) wasEscape = true;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int nextIndexOf(CharSequence charSequence, int start, char c) {
|
||||
for (int i = start; i < charSequence.length(); i++) {
|
||||
if (charSequence.charAt(i) == c) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int endIndexOfQuotableString(CharSequence charSequence, int start) {
|
||||
final char type = charSequence.charAt(start);
|
||||
final int offsetStart = start + 1;
|
||||
if (type == '\'') {
|
||||
return nextIndexOfEscapable(charSequence, offsetStart, '\'', '\\');
|
||||
} else if (type == '"') {
|
||||
return nextIndexOfEscapable(charSequence, offsetStart, '"', '\\');
|
||||
} else {
|
||||
int res = nextIndexOf(charSequence, offsetStart, ' ');
|
||||
res = res == -1 ? charSequence.length() - 1 : res - 1;
|
||||
final int a, b;
|
||||
if (((a = nextIndexOf(charSequence, offsetStart, '"')) > -1 && a <= res) ||
|
||||
((b = nextIndexOf(charSequence, offsetStart, '\'')) > -1 && b <= res)) return -1;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.Double;
|
||||
import java.lang.Float;
|
||||
import java.lang.Integer;
|
||||
import java.lang.Long;
|
||||
import java.util.Set;
|
||||
|
||||
import static net.minestom.server.command.Parser.Boolean;
|
||||
import static net.minestom.server.command.Parser.Double;
|
||||
import static net.minestom.server.command.Parser.Float;
|
||||
import static net.minestom.server.command.Parser.Integer;
|
||||
import static net.minestom.server.command.Parser.Long;
|
||||
import static net.minestom.server.command.Parser.String;
|
||||
import static net.minestom.server.command.Parser.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ArgParserTest {
|
||||
|
||||
@Test
|
||||
public void literal() {
|
||||
LiteralParser parser = Literal("test");
|
||||
assertNotNull(parser);
|
||||
assertEquals("test", parser.literal());
|
||||
|
||||
parser = parser.literal("test2");
|
||||
assertNotNull(parser);
|
||||
assertEquals("test2", parser.literal());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void literals() {
|
||||
LiteralsParser parser = Literals("test");
|
||||
assertNotNull(parser);
|
||||
assertEquals(Set.of("test"), parser.literals());
|
||||
|
||||
parser = parser.literals("first", "second");
|
||||
assertNotNull(parser);
|
||||
assertEquals(Set.of("first", "second"), parser.literals());
|
||||
|
||||
parser = parser.literals("third");
|
||||
assertNotNull(parser);
|
||||
assertEquals(Set.of("third"), parser.literals());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void booleanTest() {
|
||||
BooleanParser parser = Boolean();
|
||||
assertNotNull(parser);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void floatTest() {
|
||||
FloatParser parser = Float();
|
||||
Float min = parser.min();
|
||||
Float max = parser.max();
|
||||
assertNull(min);
|
||||
assertNull(max);
|
||||
|
||||
parser = parser.min(1f);
|
||||
assertEquals(1f, parser.min());
|
||||
assertNull(parser.max());
|
||||
|
||||
parser = parser.max(2f);
|
||||
assertEquals(1f, parser.min());
|
||||
assertEquals(2f, parser.max());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doubleTest() {
|
||||
DoubleParser parser = Double();
|
||||
Double min = parser.min();
|
||||
Double max = parser.max();
|
||||
assertNull(min);
|
||||
assertNull(max);
|
||||
|
||||
parser = parser.min(1d);
|
||||
assertEquals(1d, parser.min());
|
||||
assertNull(parser.max());
|
||||
|
||||
parser = parser.max(2d);
|
||||
assertEquals(1d, parser.min());
|
||||
assertEquals(2d, parser.max());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void integerTest() {
|
||||
IntegerParser parser = Integer();
|
||||
Integer min = parser.min();
|
||||
Integer max = parser.max();
|
||||
assertNull(min);
|
||||
assertNull(max);
|
||||
|
||||
parser = parser.min(1);
|
||||
assertEquals(1, parser.min());
|
||||
assertNull(parser.max());
|
||||
|
||||
parser = parser.max(2);
|
||||
assertEquals(1, parser.min());
|
||||
assertEquals(2, parser.max());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longTest() {
|
||||
LongParser parser = Long();
|
||||
Long min = parser.min();
|
||||
Long max = parser.max();
|
||||
assertNull(min);
|
||||
assertNull(max);
|
||||
|
||||
parser = parser.min(1L);
|
||||
assertEquals(1L, parser.min());
|
||||
assertNull(parser.max());
|
||||
|
||||
parser = parser.max(2L);
|
||||
assertEquals(1L, parser.min());
|
||||
assertEquals(2L, parser.max());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringTest() {
|
||||
StringParser parser = String();
|
||||
assertEquals(StringParser.Type.WORD, parser.type());
|
||||
|
||||
parser = parser.type(StringParser.Type.QUOTED);
|
||||
assertEquals(StringParser.Type.QUOTED, parser.type());
|
||||
|
||||
parser = parser.type(StringParser.Type.GREEDY);
|
||||
assertEquals(StringParser.Type.GREEDY, parser.type());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.Integer;
|
||||
import java.lang.String;
|
||||
import java.util.Set;
|
||||
|
||||
import static net.minestom.server.command.Parser.Boolean;
|
||||
import static net.minestom.server.command.Parser.Double;
|
||||
import static net.minestom.server.command.Parser.Float;
|
||||
import static net.minestom.server.command.Parser.Integer;
|
||||
import static net.minestom.server.command.Parser.Long;
|
||||
import static net.minestom.server.command.Parser.String;
|
||||
import static net.minestom.server.command.Parser.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ArgSpecTest {
|
||||
|
||||
@Test
|
||||
public void literalParse() {
|
||||
// Exact parsing
|
||||
assertValidSpecExact(Literal("test"), "test", "test");
|
||||
|
||||
assertInvalidSpecExact(Literal("test"), "text");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(Literal("test"), "test", 4, "test");
|
||||
assertValidSpec(Literal("test"), "test", 4, "test 5");
|
||||
|
||||
assertInvalidSpec(Literal("test"), "text");
|
||||
assertInvalidSpec(Literal("test"), "5");
|
||||
assertInvalidSpec(Literal("test"), "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void literalsParse() {
|
||||
// Exact parsing
|
||||
assertValidSpecExact(Literals("test"), "test", "test");
|
||||
assertValidSpecExact(Literals("first", "second"), "first", "first");
|
||||
assertValidSpecExact(Literals(Set.of("first", "second")), "first", "first");
|
||||
assertValidSpecExact(Literals("first", "second"), "second", "second");
|
||||
|
||||
assertInvalidSpecExact(Literals("test"), "text");
|
||||
assertInvalidSpecExact(Literals("first", "second"), "first second");
|
||||
assertInvalidSpecExact(Literals("first", "second"), "second first");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(Literals("test"), "test", 4, "test");
|
||||
assertValidSpec(Literals("test"), "test", 4, "test 5");
|
||||
assertValidSpec(Literals("first", "second"), "first", 5, "first");
|
||||
assertValidSpec(Literals("first", "second"), "first", 5, "first second");
|
||||
assertValidSpec(Literals("first", "second"), "second", 6, "second");
|
||||
assertValidSpec(Literals("first", "second"), "second", 6, "second first");
|
||||
|
||||
assertInvalidSpec(Literals("test"), "text");
|
||||
assertInvalidSpec(Literals("test"), "5");
|
||||
assertInvalidSpec(Literals("test"), "");
|
||||
assertInvalidSpec(Literals("first", "second"), "text");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void booleanParse() {
|
||||
// Exact parsing
|
||||
assertValidSpecExact(Boolean(), true, "true");
|
||||
assertValidSpecExact(Boolean(), false, "false");
|
||||
|
||||
assertInvalidSpecExact(Boolean(), "truee");
|
||||
assertInvalidSpecExact(Boolean(), "falsee");
|
||||
assertInvalidSpecExact(Boolean(), "TRuE");
|
||||
assertInvalidSpecExact(Boolean(), "ttrue");
|
||||
assertInvalidSpecExact(Boolean(), "t true");
|
||||
assertInvalidSpecExact(Boolean(), " false");
|
||||
assertInvalidSpecExact(Boolean(), " true");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(Boolean(), true, 4, "true");
|
||||
assertValidSpec(Boolean(), false, 5, "false");
|
||||
assertValidSpec(Boolean(), true, 4, "true test");
|
||||
assertValidSpec(Boolean(), false, 5, "false test");
|
||||
|
||||
assertInvalidSpec(Boolean(), "text");
|
||||
assertInvalidSpec(Boolean(), "text text");
|
||||
assertInvalidSpec(Boolean(), "text 55");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void floatParse() {
|
||||
// Exact parsing
|
||||
assertValidSpecExact(Float(), 1f, "1");
|
||||
assertValidSpecExact(Float(), 1.5f, "1.5");
|
||||
assertValidSpecExact(Float(), -1.5f, "-1.5");
|
||||
assertValidSpecExact(Float(), -99f, "-99");
|
||||
assertValidSpecExact(Float().min(5f).max(10f), 5f, "5");
|
||||
assertValidSpecExact(Float().min(5f), 5f, "5");
|
||||
assertValidSpecExact(Float().max(10f), -99f, "-99");
|
||||
assertValidSpecExact(Float().min(5f).max(10f), 10f, "10");
|
||||
|
||||
assertInvalidSpecExact(Float(), "text");
|
||||
assertInvalidSpecExact(Float(), "text text");
|
||||
assertInvalidSpecExact(Float(), "1 1");
|
||||
assertInvalidSpecExact(Float().min(5f), "-5");
|
||||
assertInvalidSpecExact(Float().min(5f), "4");
|
||||
assertInvalidSpecExact(Float().max(10f), "11");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(Float(), 1f, 1, "1");
|
||||
assertValidSpec(Float(), 11f, 2, "11");
|
||||
assertValidSpec(Float(), 5f, 3, "5.0 1");
|
||||
assertValidSpec(Float(), 55f, 2, "55 1");
|
||||
assertValidSpec(Float(), 55f, 2, "55 text");
|
||||
|
||||
assertInvalidSpec(Float(), "text");
|
||||
assertInvalidSpec(Float(), "text text");
|
||||
assertInvalidSpec(Float(), "text 55");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doubleParse() {
|
||||
// Exact parsing
|
||||
assertValidSpecExact(Double(), 1d, "1");
|
||||
assertValidSpecExact(Double(), 1.5d, "1.5");
|
||||
assertValidSpecExact(Double(), -1.5d, "-1.5");
|
||||
assertValidSpecExact(Double(), -99d, "-99");
|
||||
assertValidSpecExact(Double().min(5d).max(10d), 5d, "5");
|
||||
assertValidSpecExact(Double().min(5d), 5d, "5");
|
||||
assertValidSpecExact(Double().max(10d), -99d, "-99");
|
||||
assertValidSpecExact(Double().min(5d).max(10d), 10d, "10");
|
||||
|
||||
assertInvalidSpecExact(Double(), "text");
|
||||
assertInvalidSpecExact(Double(), "text text");
|
||||
assertInvalidSpecExact(Double(), "1 1");
|
||||
assertInvalidSpecExact(Double().min(5d), "-5");
|
||||
assertInvalidSpecExact(Double().min(5d), "4");
|
||||
assertInvalidSpecExact(Double().max(10d), "11");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(Double(), 1d, 1, "1");
|
||||
assertValidSpec(Double(), 11d, 2, "11");
|
||||
assertValidSpec(Double(), 5d, 3, "5.0 1");
|
||||
assertValidSpec(Double(), 55d, 2, "55 1");
|
||||
assertValidSpec(Double(), 55d, 2, "55 text");
|
||||
|
||||
assertInvalidSpec(Double(), "text");
|
||||
assertInvalidSpec(Double(), "text text");
|
||||
assertInvalidSpec(Double(), "text 55");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void integerParse() {
|
||||
// Exact parsing
|
||||
assertValidSpecExact(Integer(), 1, "1");
|
||||
assertValidSpecExact(Integer(), -99, "-99");
|
||||
assertValidSpecExact(Integer().min(5).max(10), 5, "5");
|
||||
assertValidSpecExact(Integer().min(5), 5, "5");
|
||||
assertValidSpecExact(Integer().max(10), -99, "-99");
|
||||
assertValidSpecExact(Integer().min(5).max(10), 10, "10");
|
||||
|
||||
assertInvalidSpecExact(Integer(), "text");
|
||||
assertInvalidSpecExact(Integer(), "text text");
|
||||
assertInvalidSpecExact(Integer(), "1 1");
|
||||
assertInvalidSpecExact(Integer().min(5), "-5");
|
||||
assertInvalidSpecExact(Integer().min(5), "4");
|
||||
assertInvalidSpecExact(Integer().max(10), "11");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(Integer(), 1, 1, "1");
|
||||
assertValidSpec(Integer(), 11, 2, "11");
|
||||
assertValidSpec(Integer(), 5, 1, "5 1");
|
||||
assertValidSpec(Integer(), 55, 2, "55 1");
|
||||
assertValidSpec(Integer(), 55, 2, "55 text");
|
||||
|
||||
assertInvalidSpec(Integer(), "text");
|
||||
assertInvalidSpec(Integer(), "text text");
|
||||
assertInvalidSpec(Integer(), "text 55");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longParse() {
|
||||
// Exact parsing
|
||||
assertValidSpecExact(Long(), 1L, "1");
|
||||
assertValidSpecExact(Long(), -99L, "-99");
|
||||
assertValidSpecExact(Long().min(5L).max(10L), 5L, "5");
|
||||
assertValidSpecExact(Long().min(5L), 5L, "5");
|
||||
assertValidSpecExact(Long().max(10L), -99L, "-99");
|
||||
assertValidSpecExact(Long().min(5L).max(10L), 10L, "10");
|
||||
|
||||
assertInvalidSpecExact(Long(), "text");
|
||||
assertInvalidSpecExact(Long(), "text text");
|
||||
assertInvalidSpecExact(Long(), "1 1");
|
||||
assertInvalidSpecExact(Long().min(5L), "-5");
|
||||
assertInvalidSpecExact(Long().min(5L), "4");
|
||||
assertInvalidSpecExact(Long().max(10L), "11");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(Long(), 1L, 1, "1");
|
||||
assertValidSpec(Long(), 11L, 2, "11");
|
||||
assertValidSpec(Long(), 5L, 1, "5 1");
|
||||
assertValidSpec(Long(), 55L, 2, "55 1");
|
||||
assertValidSpec(Long(), 55L, 2, "55 text");
|
||||
|
||||
assertInvalidSpec(Long(), "text");
|
||||
assertInvalidSpec(Long(), "text text");
|
||||
assertInvalidSpec(Long(), "text 55");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringParse() {
|
||||
// Exact parsing
|
||||
assertValidSpecExact(String(), "test", "test");
|
||||
assertValidSpecExact(String().type(StringParser.Type.GREEDY), "test 1 2 3", "test 1 2 3");
|
||||
assertValidSpecExact(String().type(StringParser.Type.QUOTED), "test", "test");
|
||||
assertValidSpecExact(String().type(StringParser.Type.QUOTED), "Hey there", """
|
||||
"Hey there"\
|
||||
""");
|
||||
assertValidSpecExact(String().type(StringParser.Type.QUOTED), "Hey there", """
|
||||
"Hey there"\
|
||||
""");
|
||||
assertValidSpecExact(String().type(StringParser.Type.QUOTED), "text", "text");
|
||||
|
||||
assertInvalidSpecExact(String().type(StringParser.Type.QUOTED), """
|
||||
"Hey\
|
||||
""");
|
||||
assertInvalidSpecExact(String().type(StringParser.Type.QUOTED), """
|
||||
there"\
|
||||
""");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(String(), "test", 4, "test");
|
||||
assertValidSpec(String(), "test", 4, "test a");
|
||||
assertValidSpec(String().type(StringParser.Type.GREEDY), "test 1 2 3", 10, "test 1 2 3");
|
||||
assertValidSpec(String().type(StringParser.Type.QUOTED), "Hey there", 11, """
|
||||
"Hey there"\
|
||||
""");
|
||||
assertValidSpec(String().type(StringParser.Type.QUOTED), "Hey there", 12, """
|
||||
"Hey there"\
|
||||
""");
|
||||
assertValidSpec(String().type(StringParser.Type.QUOTED), "Hey there", 11, """
|
||||
"Hey there" test\
|
||||
""");
|
||||
|
||||
assertValidSpec(String().type(StringParser.Type.QUOTED), "text", 4, "text");
|
||||
assertValidSpec(String().type(StringParser.Type.QUOTED), "text", 4, "text test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customSingleParse() {
|
||||
Custom<Integer> parser = custom(ParserSpec.Type.INTEGER);
|
||||
assertValidSpec(parser, 1, 1, "1");
|
||||
assertValidSpec(parser, 11, 2, "11");
|
||||
assertValidSpec(parser, 5, 1, "5 1");
|
||||
assertValidSpec(parser, 55, 2, "55 1");
|
||||
assertValidSpec(parser, 55, 2, "55 text");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customReaderParse() {
|
||||
Custom<Integer> parser = custom(ParserSpec.reader((s, startIndex) -> {
|
||||
final String input = s.substring(startIndex);
|
||||
if (!input.startsWith("1")) return null;
|
||||
return ParserSpec.Result.success("1", startIndex + 1, 1);
|
||||
}));
|
||||
assertValidSpec(parser, 1, 1, "1");
|
||||
assertInvalidSpec(parser, "5 1");
|
||||
assertValidSpec(parser, 1, 1, "1 55");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customLegacyParse() {
|
||||
final ParserSpec<String> spec = ParserSpec.legacy(ArgumentType.String("test"));
|
||||
final Custom<String> parser = Parser.custom(spec);
|
||||
assertValidSpecExact(parser, "test", "test");
|
||||
assertValidSpecExact(parser, "Hey there", """
|
||||
"Hey there"\
|
||||
""");
|
||||
assertValidSpecExact(parser, "Hey there", """
|
||||
"Hey there"\
|
||||
""");
|
||||
assertValidSpecExact(parser, "text", "text");
|
||||
|
||||
assertInvalidSpecExact(parser, """
|
||||
"Hey\
|
||||
""");
|
||||
assertInvalidSpecExact(parser, """
|
||||
there"\
|
||||
""");
|
||||
|
||||
// Sequence parsing
|
||||
assertValidSpec(parser, "Hey there", 11, """
|
||||
"Hey there"\
|
||||
""");
|
||||
assertValidSpec(parser, "Hey there", 12, """
|
||||
"Hey there"\
|
||||
""");
|
||||
assertValidSpec(parser, "Hey there", 11, """
|
||||
"Hey there" test\
|
||||
""");
|
||||
|
||||
assertValidSpec(parser, "text", 4, "text");
|
||||
}
|
||||
|
||||
static <T> void assertValidSpec(Parser<T> parser, T expectedValue, int expectedIndex, String input) {
|
||||
final ParserSpec<T> spec = parser.spec();
|
||||
final ParserSpec.Result.Success<T> result = (ParserSpec.Result.Success<T>) spec.read(input);
|
||||
assertNotNull(result);
|
||||
assertEquals(input.substring(0, expectedIndex), result.input(), "Invalid input(" + expectedIndex + ") for '" + input + "'");
|
||||
assertEquals(expectedValue, result.value(), "Invalid value");
|
||||
assertEquals(expectedIndex, result.index(), "Invalid index");
|
||||
|
||||
// Assert read with non-zero initial index
|
||||
input = "1 " + input;
|
||||
expectedIndex += 2;
|
||||
final ParserSpec.Result.Success<T> result2 = (ParserSpec.Result.Success<T>) spec.read(input, 2);
|
||||
assertNotNull(result2);
|
||||
assertEquals(input.substring(2, expectedIndex), result2.input(), "Invalid input(" + expectedIndex + ") for '" + input + "'");
|
||||
assertEquals(expectedValue, result2.value(), "Invalid value");
|
||||
assertEquals(expectedIndex, result2.index(), "Invalid index");
|
||||
}
|
||||
|
||||
static <T> void assertInvalidSpec(Parser<T> parser, String input) {
|
||||
final ParserSpec<?> spec = parser.spec();
|
||||
final ParserSpec.Result<?> result = spec.read(input);
|
||||
if (result instanceof ParserSpec.Result.Success<?>) {
|
||||
fail("Expected failure for '" + input + "'");
|
||||
}
|
||||
}
|
||||
|
||||
static <T> void assertValidSpecExact(Parser<T> parser, T expected, String input) {
|
||||
final ParserSpec<T> spec = parser.spec();
|
||||
final T result = spec.readExact(input);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
static void assertInvalidSpecExact(Parser<?> parser, String input) {
|
||||
final ParserSpec<?> spec = parser.spec();
|
||||
assertNull(spec.readExact(input));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static net.minestom.server.command.Arg.arg;
|
||||
import static net.minestom.server.command.Arg.literalArg;
|
||||
import static net.minestom.server.command.Parser.Boolean;
|
||||
import static net.minestom.server.command.Parser.Double;
|
||||
import static net.minestom.server.command.Parser.Float;
|
||||
import static net.minestom.server.command.Parser.Integer;
|
||||
import static net.minestom.server.command.Parser.Long;
|
||||
import static net.minestom.server.command.Parser.String;
|
||||
import static net.minestom.server.command.Parser.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
public class ArgTest {
|
||||
@Test
|
||||
public void basic() {
|
||||
var arg = arg("id", Integer());
|
||||
assertEquals("id", arg.id());
|
||||
assertEquals(Integer(), arg.parser());
|
||||
assertNull(arg.suggestionType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equality() {
|
||||
assertEquals(literalArg("test"), literalArg("test"));
|
||||
assertEquals(arg("id", Integer()), arg("id", Integer()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversionParser() {
|
||||
// Boolean
|
||||
assertParserConversion(Boolean(), ArgumentType.Boolean("id"));
|
||||
// Float
|
||||
assertParserConversion(Float(), ArgumentType.Float("id"));
|
||||
assertParserConversion(Float().min(5f), ArgumentType.Float("id").min(5f));
|
||||
assertParserConversion(Float().max(5f), ArgumentType.Float("id").max(5f));
|
||||
assertParserConversion(Float().min(5f).max(5f), ArgumentType.Float("id").min(5f).max(5f));
|
||||
// Double
|
||||
assertParserConversion(Double(), ArgumentType.Double("id"));
|
||||
assertParserConversion(Double().min(5d), ArgumentType.Double("id").min(5d));
|
||||
assertParserConversion(Double().max(5d), ArgumentType.Double("id").max(5d));
|
||||
assertParserConversion(Double().min(5d).max(5d), ArgumentType.Double("id").min(5d).max(5d));
|
||||
// Integer
|
||||
assertParserConversion(Integer(), ArgumentType.Integer("id"));
|
||||
assertParserConversion(Integer().min(5), ArgumentType.Integer("id").min(5));
|
||||
assertParserConversion(Integer().max(5), ArgumentType.Integer("id").max(5));
|
||||
assertParserConversion(Integer().min(5).max(5), ArgumentType.Integer("id").min(5).max(5));
|
||||
// Long
|
||||
assertParserConversion(Long(), ArgumentType.Long("id"));
|
||||
assertParserConversion(Long().min(5L), ArgumentType.Long("id").min(5L));
|
||||
assertParserConversion(Long().max(5L), ArgumentType.Long("id").max(5L));
|
||||
assertParserConversion(Long().min(5L).max(5L), ArgumentType.Long("id").min(5L).max(5L));
|
||||
// Word
|
||||
assertParserConversion(String(), ArgumentType.Word("id"));
|
||||
assertParserConversion(String().type(StringParser.Type.QUOTED), ArgumentType.String("id"));
|
||||
assertParserConversion(String().type(StringParser.Type.GREEDY), ArgumentType.StringArray("id"));
|
||||
assertParserConversion(Literals("first", "second"), ArgumentType.Word("id").from("first", "second"));
|
||||
}
|
||||
|
||||
static <T> void assertParserConversion(Parser<?> expected, Argument<?> argument) {
|
||||
final Arg<?> converted = ArgImpl.fromLegacy(argument);
|
||||
final Parser<?> convertedParser = converted.parser();
|
||||
assertEquals(expected, convertedParser);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CommandCallbackTest {
|
||||
|
||||
@Test
|
||||
public void argCallback() {
|
||||
var command = new Command("name");
|
||||
var arg = ArgumentType.Integer("number");
|
||||
|
||||
AtomicInteger callback = new AtomicInteger(-1);
|
||||
|
||||
command.setDefaultExecutor((sender, context) -> callback.set(0));
|
||||
|
||||
arg.setCallback((sender, exception) -> callback.set(1));
|
||||
command.addSyntax((sender, context) -> callback.set(2), arg);
|
||||
|
||||
var manager = new CommandManager();
|
||||
manager.register(command);
|
||||
|
||||
manager.executeServerCommand("name a");
|
||||
assertEquals(1, callback.get());
|
||||
|
||||
callback.set(-1);
|
||||
manager.executeServerCommand("name 1");
|
||||
assertEquals(2, callback.get());
|
||||
}
|
||||
}
|
|
@ -6,6 +6,14 @@ import net.minestom.server.command.builder.arguments.ArgumentType;
|
|||
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.String;
|
||||
|
||||
import static net.minestom.server.command.Arg.arg;
|
||||
import static net.minestom.server.command.Arg.literalArg;
|
||||
import static net.minestom.server.command.ArgImpl.fromLegacy;
|
||||
import static net.minestom.server.command.Parser.Integer;
|
||||
import static net.minestom.server.command.Parser.String;
|
||||
import static net.minestom.server.command.Parser.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
@ -63,8 +71,8 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void singleCommandTwoEnum() {
|
||||
var graph = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.Enum("bar", A.class), b -> b.append(ArgumentType.Enum("baz", B.class)))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(fromLegacy(ArgumentType.Enum("bar", A.class)), b -> b.append(fromLegacy(ArgumentType.Enum("baz", B.class))))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo=%
|
||||
|
@ -77,8 +85,8 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void singleCommandRestrictedWord() {
|
||||
var graph = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.Word("bar").from("A", "B", "C"))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(arg("bar", Literals("A", "B", "C")))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo=%
|
||||
|
@ -90,8 +98,8 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void singleCommandWord() {
|
||||
var graph = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.Word("bar"))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(arg("bar", String()))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo=%
|
||||
|
@ -103,8 +111,8 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void singleCommandCommandAfterEnum() {
|
||||
var graph = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.Enum("bar", A.class), b -> b.append(ArgumentType.Command("baz")))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(fromLegacy(ArgumentType.Enum("bar", A.class)), b -> b.append(fromLegacy(ArgumentType.Command("baz"))))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo baz=%
|
||||
|
@ -118,11 +126,11 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void twoCommandIntEnumInt() {
|
||||
var graph = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.Integer("int1"), b -> b.append(ArgumentType.Enum("test", A.class), c -> c.append(ArgumentType.Integer("int2"))))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(arg("int1", Integer()), b -> b.append(fromLegacy(ArgumentType.Enum("test", A.class)), c -> c.append(arg("int2", Integer()))))
|
||||
.build();
|
||||
var graph2 = Graph.builder(ArgumentType.Literal("bar"))
|
||||
.append(ArgumentType.Integer("int3"), b -> b.append(ArgumentType.Enum("test", B.class), c -> c.append(ArgumentType.Integer("int4"))))
|
||||
var graph2 = Graph.builder(literalArg("bar"))
|
||||
.append(arg("int3", Integer()), b -> b.append(fromLegacy(ArgumentType.Enum("test", B.class)), c -> c.append(arg("int4", Integer()))))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo bar=%
|
||||
|
@ -140,9 +148,9 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void singleCommandTwoGroupOfIntInt() {
|
||||
var graph = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.Group("1", ArgumentType.Integer("int1"), ArgumentType.Integer("int2")),
|
||||
b -> b.append(ArgumentType.Group("2", ArgumentType.Integer("int3"), ArgumentType.Integer("int4"))))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(fromLegacy(ArgumentType.Group("1", ArgumentType.Integer("int1"), ArgumentType.Integer("int2"))),
|
||||
b -> b.append(fromLegacy(ArgumentType.Group("2", ArgumentType.Integer("int3"), ArgumentType.Integer("int4")))))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo=%
|
||||
|
@ -154,12 +162,13 @@ public class CommandPacketTest {
|
|||
int3->int4
|
||||
""", graph);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void twoEnumAndOneLiteralChild() {
|
||||
var graph = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.Enum("a", A.class))
|
||||
.append(ArgumentType.Literal("l"))
|
||||
.append(ArgumentType.Enum("b", B.class))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(fromLegacy(ArgumentType.Enum("a", A.class)))
|
||||
.append(literalArg("l"))
|
||||
.append(fromLegacy(ArgumentType.Enum("b", B.class)))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo l=%
|
||||
|
@ -171,7 +180,7 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void commandAliasWithoutArg() {
|
||||
var graph = Graph.builder(ArgumentType.Word("foo").from("foo", "bar"))
|
||||
var graph = Graph.builder(arg("foo", Literals("foo", "bar")))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo bar=%
|
||||
|
@ -181,8 +190,8 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void commandAliasWithArg() {
|
||||
var graph = Graph.builder(ArgumentType.Word("foo").from("foo", "bar"))
|
||||
.append(ArgumentType.Literal("l"))
|
||||
var graph = Graph.builder(arg("foo", Literals("foo", "bar")))
|
||||
.append(literalArg("l"))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo bar l=%
|
||||
|
@ -193,11 +202,11 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void cmdArgShortcut() {
|
||||
var foo = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.String("msg"))
|
||||
var foo = Graph.builder(literalArg("foo"))
|
||||
.append(arg("msg", String().type(StringParser.Type.QUOTED)))
|
||||
.build();
|
||||
var bar = Graph.builder(ArgumentType.Literal("bar"))
|
||||
.append(ArgumentType.Command("cmd").setShortcut("foo"))
|
||||
var bar = Graph.builder(literalArg("bar"))
|
||||
.append(fromLegacy(ArgumentType.Command("cmd").setShortcut("foo")))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo bar cmd=%
|
||||
|
@ -211,11 +220,11 @@ public class CommandPacketTest {
|
|||
|
||||
@Test
|
||||
public void cmdArgShortcutWithPartialArg() {
|
||||
var foo = Graph.builder(ArgumentType.Literal("foo"))
|
||||
.append(ArgumentType.String("msg"))
|
||||
var foo = Graph.builder(literalArg("foo"))
|
||||
.append(arg("msg", String().type(StringParser.Type.QUOTED)))
|
||||
.build();
|
||||
var bar = Graph.builder(ArgumentType.Literal("bar"))
|
||||
.append(ArgumentType.Command("cmd").setShortcut("foo \"prefix "))
|
||||
var bar = Graph.builder(literalArg("bar"))
|
||||
.append(fromLegacy(ArgumentType.Command("cmd").setShortcut("foo \"prefix ")))
|
||||
.build();
|
||||
assertPacketGraph("""
|
||||
foo bar cmd=%
|
||||
|
|
|
@ -4,11 +4,16 @@ import net.minestom.server.command.builder.arguments.ArgumentType;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.String;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.Literal;
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.Word;
|
||||
import static net.minestom.server.command.Arg.arg;
|
||||
import static net.minestom.server.command.Arg.literalArg;
|
||||
import static net.minestom.server.command.ArgImpl.fromLegacy;
|
||||
import static net.minestom.server.command.Parser.Integer;
|
||||
import static net.minestom.server.command.Parser.String;
|
||||
import static net.minestom.server.command.Parser.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class CommandParseTest {
|
||||
|
@ -16,7 +21,7 @@ public class CommandParseTest {
|
|||
@Test
|
||||
public void singleParameterlessCommand() {
|
||||
final AtomicBoolean b = new AtomicBoolean();
|
||||
var foo = Graph.merge(Graph.builder(Literal("foo"), createExecutor(b)).build());
|
||||
var foo = Graph.merge(Graph.builder(literalArg("foo"), createExecutor(b)).build());
|
||||
assertValid(foo, "foo", b);
|
||||
assertUnknown(foo, "bar");
|
||||
assertSyntaxError(foo, "foo bar baz");
|
||||
|
@ -27,8 +32,8 @@ public class CommandParseTest {
|
|||
final AtomicBoolean b = new AtomicBoolean();
|
||||
final AtomicBoolean b1 = new AtomicBoolean();
|
||||
var graph = Graph.merge(
|
||||
Graph.builder(Literal("foo"), createExecutor(b)).build(),
|
||||
Graph.builder(Literal("bar"), createExecutor(b1)).build()
|
||||
Graph.builder(literalArg("foo"), createExecutor(b)).build(),
|
||||
Graph.builder(literalArg("bar"), createExecutor(b1)).build()
|
||||
);
|
||||
assertValid(graph, "foo", b);
|
||||
assertValid(graph, "bar", b1);
|
||||
|
@ -41,11 +46,11 @@ public class CommandParseTest {
|
|||
public void singleCommandWithMultipleSyntax() {
|
||||
final AtomicBoolean add = new AtomicBoolean();
|
||||
final AtomicBoolean action = new AtomicBoolean();
|
||||
var foo = Graph.merge(Graph.builder(Literal("foo"))
|
||||
.append(Literal("add"),
|
||||
x -> x.append(Word("name"), createExecutor(add)))
|
||||
.append(Word("action").from("inc", "dec"),
|
||||
x -> x.append(ArgumentType.Integer("num"), createExecutor(action)))
|
||||
var foo = Graph.merge(Graph.builder(literalArg("foo"))
|
||||
.append(literalArg("add"),
|
||||
x -> x.append(arg("name", String()), createExecutor(add)))
|
||||
.append(arg("action", Literals("inc", "dec")),
|
||||
x -> x.append(arg("num", Integer()), createExecutor(action)))
|
||||
.build());
|
||||
assertValid(foo, "foo add test", add);
|
||||
assertValid(foo, "foo add inc", add);
|
||||
|
@ -66,11 +71,11 @@ public class CommandParseTest {
|
|||
public void singleCommandOptionalArgs() {
|
||||
final AtomicBoolean b = new AtomicBoolean();
|
||||
final AtomicReference<String> expectedFirstArg = new AtomicReference<>("T");
|
||||
var foo = Graph.merge(Graph.builder(Literal("foo"))
|
||||
.append(Word("a").setDefaultValue("A"),
|
||||
x -> x.append(Word("b").setDefaultValue("B"),
|
||||
x1 -> x1.append(Word("c").setDefaultValue("C"),
|
||||
x2 -> x2.append(Word("d").setDefaultValue("D"),
|
||||
var foo = Graph.merge(Graph.builder(literalArg("foo"))
|
||||
.append(arg("a", String()).defaultValue("A"),
|
||||
x -> x.append(arg("b", String()).defaultValue("B"),
|
||||
x1 -> x1.append(arg("c", String()).defaultValue("C"),
|
||||
x2 -> x2.append(arg("d", String()).defaultValue("D"),
|
||||
new GraphImpl.ExecutionImpl(null, null, null,
|
||||
(sender, context) -> {
|
||||
b.set(true);
|
||||
|
@ -89,8 +94,8 @@ public class CommandParseTest {
|
|||
public void singleCommandSingleEnumArg() {
|
||||
enum A {a, b}
|
||||
final AtomicBoolean b = new AtomicBoolean();
|
||||
var foo = Graph.merge(Graph.builder(Literal("foo"))
|
||||
.append(ArgumentType.Enum("test", A.class), createExecutor(b))
|
||||
var foo = Graph.merge(Graph.builder(literalArg("foo"))
|
||||
.append(fromLegacy(ArgumentType.Enum("test", A.class)), createExecutor(b))
|
||||
.build());
|
||||
assertValid(foo, "foo a", b);
|
||||
assertValid(foo, "foo b", b);
|
||||
|
@ -101,7 +106,7 @@ public class CommandParseTest {
|
|||
@Test
|
||||
public void aliasWithoutArgs() {
|
||||
final AtomicBoolean b = new AtomicBoolean();
|
||||
var foo = Graph.merge(Graph.builder(Word("").from("foo", "bar"), createExecutor(b))
|
||||
var foo = Graph.merge(Graph.builder(arg("", Literals("foo", "bar")), createExecutor(b))
|
||||
.build());
|
||||
assertValid(foo, "foo", b);
|
||||
assertValid(foo, "bar", b);
|
||||
|
@ -111,8 +116,8 @@ public class CommandParseTest {
|
|||
@Test
|
||||
public void aliasWithArgs() {
|
||||
final AtomicBoolean b = new AtomicBoolean();
|
||||
var foo = Graph.merge(Graph.builder(Word("").from("foo", "bar"))
|
||||
.append(ArgumentType.Integer("test"), createExecutor(b))
|
||||
var foo = Graph.merge(Graph.builder(arg("", Literals("foo", "bar")))
|
||||
.append(arg("test", Integer()), createExecutor(b))
|
||||
.build());
|
||||
assertValid(foo, "foo 1", b);
|
||||
assertValid(foo, "bar 1", b);
|
||||
|
|
|
@ -2,6 +2,7 @@ package net.minestom.server.command;
|
|||
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandContext;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -51,4 +52,28 @@ public class CommandTest {
|
|||
assertTrue(checkSet.get());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConflictingSyntaxAndSubcommand() {
|
||||
final CommandManager manager = new CommandManager();
|
||||
|
||||
final AtomicBoolean subcommandRun = new AtomicBoolean();
|
||||
final AtomicBoolean syntaxRun = new AtomicBoolean();
|
||||
|
||||
final Command command = new Command("command");
|
||||
command.addSubcommand(new Command("subcommand") {
|
||||
{
|
||||
addSyntax((sender, ctx) -> subcommandRun.set(true));
|
||||
}
|
||||
});
|
||||
var argument = ArgumentType.String("id");
|
||||
command.addSyntax((sender, ctx) -> syntaxRun.set(true), argument);
|
||||
|
||||
manager.register(command);
|
||||
|
||||
manager.executeServerCommand("command subcommand");
|
||||
|
||||
assertTrue(subcommandRun.get());
|
||||
assertFalse(syntaxRun.get());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,42 +2,42 @@ package net.minestom.server.command;
|
|||
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.CommandContext;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.Enum;
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.Integer;
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.*;
|
||||
import static net.minestom.server.command.Arg.arg;
|
||||
import static net.minestom.server.command.Arg.literalArg;
|
||||
import static net.minestom.server.command.Parser.Integer;
|
||||
import static net.minestom.server.command.Parser.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class GraphConversionTest {
|
||||
@Test
|
||||
public void empty() {
|
||||
final Command foo = new Command("foo");
|
||||
var graph = Graph.builder(Literal("foo")).build();
|
||||
var graph = Graph.builder(literalArg("foo")).build();
|
||||
assertEqualsGraph(graph, foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleLiteral() {
|
||||
final Command foo = new Command("foo");
|
||||
var first = Literal("first");
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, first);
|
||||
var graph = Graph.builder(Literal("foo"))
|
||||
.append(first).build();
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, ArgumentType.Literal("first"));
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(literalArg("first")).build();
|
||||
assertEqualsGraph(graph, foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void literalsPath() {
|
||||
final Command foo = new Command("foo");
|
||||
var first = Literal("first");
|
||||
var second = Literal("second");
|
||||
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, first);
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, second);
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, ArgumentType.Literal("first"));
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, ArgumentType.Literal("second"));
|
||||
|
||||
var graph = Graph.builder(Literal("foo"))
|
||||
.append(first).append(second)
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(literalArg("first"))
|
||||
.append(literalArg("second"))
|
||||
.build();
|
||||
assertEqualsGraph(graph, foo);
|
||||
}
|
||||
|
@ -47,18 +47,17 @@ public class GraphConversionTest {
|
|||
enum A {A, B, C, D, E}
|
||||
final Command foo = new Command("foo");
|
||||
|
||||
var bar = Literal("bar");
|
||||
var a = ArgumentType.Enum("a", A.class);
|
||||
|
||||
var baz = Literal("baz");
|
||||
var a = Enum("a", A.class);
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor,
|
||||
ArgumentType.Literal("bar"));
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor,
|
||||
ArgumentType.Literal("baz"), a);
|
||||
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, bar);
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, baz, a);
|
||||
|
||||
var graph = Graph.builder(Literal("foo"))
|
||||
.append(bar)
|
||||
.append(baz, builder ->
|
||||
builder.append(a))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(literalArg("bar"))
|
||||
.append(literalArg("baz"), builder ->
|
||||
builder.append(arg("a", legacy(a))))
|
||||
.build();
|
||||
assertEqualsGraph(graph, foo);
|
||||
}
|
||||
|
@ -67,15 +66,14 @@ public class GraphConversionTest {
|
|||
public void doubleSyntaxMerge() {
|
||||
final Command foo = new Command("foo");
|
||||
|
||||
var bar = Literal("bar");
|
||||
var number = Integer("number");
|
||||
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, bar);
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor, bar, number);
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor,
|
||||
ArgumentType.Literal("bar"));
|
||||
foo.addSyntax(GraphConversionTest::dummyExecutor,
|
||||
ArgumentType.Literal("bar"), ArgumentType.Integer("number"));
|
||||
|
||||
// The two syntax shall start from the same node
|
||||
var graph = Graph.builder(Literal("foo"))
|
||||
.append(bar, builder -> builder.append(number))
|
||||
var graph = Graph.builder(literalArg("foo"))
|
||||
.append(literalArg("bar"), builder -> builder.append(arg("number", Integer())))
|
||||
.build();
|
||||
assertEqualsGraph(graph, foo);
|
||||
}
|
||||
|
@ -85,18 +83,18 @@ public class GraphConversionTest {
|
|||
final Command main = new Command("main");
|
||||
final Command sub = new Command("sub");
|
||||
|
||||
var bar = Literal("bar");
|
||||
var number = Integer("number");
|
||||
|
||||
sub.addSyntax(GraphConversionTest::dummyExecutor, bar);
|
||||
sub.addSyntax(GraphConversionTest::dummyExecutor, bar, number);
|
||||
sub.addSyntax(GraphConversionTest::dummyExecutor,
|
||||
ArgumentType.Literal("bar"));
|
||||
sub.addSyntax(GraphConversionTest::dummyExecutor,
|
||||
ArgumentType.Literal("bar"), ArgumentType.Integer("number"));
|
||||
|
||||
main.addSubcommand(sub);
|
||||
|
||||
// The two syntax shall start from the same node
|
||||
var graph = Graph.builder(Literal("main"))
|
||||
.append(Literal("sub"), builder ->
|
||||
builder.append(bar, builder1 -> builder1.append(number)))
|
||||
var graph = Graph.builder(literalArg("main"))
|
||||
.append(literalArg("sub"), builder ->
|
||||
builder.append(literalArg("bar"),
|
||||
builder1 -> builder1.append(arg("number", Integer()))))
|
||||
.build();
|
||||
assertEqualsGraph(graph, main);
|
||||
}
|
||||
|
@ -104,14 +102,14 @@ public class GraphConversionTest {
|
|||
@Test
|
||||
public void alias() {
|
||||
final Command main = new Command("main", "alias");
|
||||
var graph = Graph.builder(Word("main").from("main", "alias")).build();
|
||||
var graph = Graph.builder(arg("main", Literals("main", "alias"))).build();
|
||||
assertEqualsGraph(graph, main);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aliases() {
|
||||
final Command main = new Command("main", "first", "second");
|
||||
var graph = Graph.builder(Word("main").from("main", "first", "second")).build();
|
||||
var graph = Graph.builder(arg("main", Literals("main", "first", "second"))).build();
|
||||
assertEqualsGraph(graph, main);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.Literal;
|
||||
import static net.minestom.server.command.Arg.literalArg;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class GraphMergeTest {
|
||||
|
@ -14,31 +14,31 @@ public class GraphMergeTest {
|
|||
public void commands() {
|
||||
var foo = new Command("foo");
|
||||
var bar = new Command("bar");
|
||||
var result = Graph.builder(Literal(""))
|
||||
.append(Literal("foo"))
|
||||
.append(Literal("bar"))
|
||||
var result = Graph.builder(literalArg(""))
|
||||
.append(literalArg("foo"))
|
||||
.append(literalArg("bar"))
|
||||
.build();
|
||||
assertEqualsGraph(result, Graph.merge(List.of(foo, bar)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty() {
|
||||
var graph1 = Graph.builder(Literal("foo")).build();
|
||||
var graph2 = Graph.builder(Literal("bar")).build();
|
||||
var result = Graph.builder(Literal(""))
|
||||
.append(Literal("foo"))
|
||||
.append(Literal("bar"))
|
||||
var graph1 = Graph.builder(literalArg("foo")).build();
|
||||
var graph2 = Graph.builder(literalArg("bar")).build();
|
||||
var result = Graph.builder(literalArg(""))
|
||||
.append(literalArg("foo"))
|
||||
.append(literalArg("bar"))
|
||||
.build();
|
||||
assertEqualsGraph(result, Graph.merge(graph1, graph2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void literals() {
|
||||
var graph1 = Graph.builder(Literal("foo")).append(Literal("1")).build();
|
||||
var graph2 = Graph.builder(Literal("bar")).append(Literal("2")).build();
|
||||
var result = Graph.builder(Literal(""))
|
||||
.append(Literal("foo"), builder -> builder.append(Literal("1")))
|
||||
.append(Literal("bar"), builder -> builder.append(Literal("2")))
|
||||
var graph1 = Graph.builder(literalArg("foo")).append(literalArg("1")).build();
|
||||
var graph2 = Graph.builder(literalArg("bar")).append(literalArg("2")).build();
|
||||
var result = Graph.builder(literalArg(""))
|
||||
.append(literalArg("foo"), builder -> builder.append(literalArg("1")))
|
||||
.append(literalArg("bar"), builder -> builder.append(literalArg("2")))
|
||||
.build();
|
||||
assertEqualsGraph(result, Graph.merge(graph1, graph2));
|
||||
}
|
||||
|
|
|
@ -6,35 +6,36 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import static net.minestom.server.command.Arg.literalArg;
|
||||
import static net.minestom.server.command.builder.arguments.ArgumentType.Literal;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class GraphTest {
|
||||
@Test
|
||||
public void empty() {
|
||||
var result = Graph.builder(Literal(""))
|
||||
var result = Graph.builder(literalArg(""))
|
||||
.build();
|
||||
var node = result.root();
|
||||
assertEquals(Literal(""), node.argument());
|
||||
assertEquals(literalArg(""), node.argument());
|
||||
assertTrue(node.next().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void next() {
|
||||
var result = Graph.builder(Literal(""))
|
||||
.append(Literal("foo"))
|
||||
var result = Graph.builder(literalArg(""))
|
||||
.append(literalArg("foo"))
|
||||
.build();
|
||||
var node = result.root();
|
||||
assertEquals(Literal(""), node.argument());
|
||||
assertEquals(literalArg(""), node.argument());
|
||||
assertEquals(1, node.next().size());
|
||||
assertEquals(Literal("foo"), node.next().get(0).argument());
|
||||
assertEquals(literalArg("foo"), node.next().get(0).argument());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void immutableNextBuilder() {
|
||||
var result = Graph.builder(Literal(""))
|
||||
.append(Literal("foo"))
|
||||
.append(Literal("bar"))
|
||||
var result = Graph.builder(literalArg(""))
|
||||
.append(literalArg("foo"))
|
||||
.append(literalArg("bar"))
|
||||
.build();
|
||||
var node = result.root();
|
||||
assertThrows(Exception.class, () -> result.root().next().add(node));
|
||||
|
|
Loading…
Reference in New Issue