Added functionality for finding ambiguities on-the-fly

This commit is contained in:
Nathan Adams
2018-01-30 16:16:36 +01:00
parent 3498398452
commit 69c2a248de
18 changed files with 194 additions and 40 deletions
@@ -0,0 +1,10 @@
package com.mojang.brigadier;
import com.mojang.brigadier.tree.CommandNode;
import java.util.Collection;
@FunctionalInterface
public interface AmbiguityConsumer<S> {
void ambiguous(final CommandNode<S> parent, final CommandNode<S> child, final CommandNode<S> sibling, final Collection<String> inputs);
}
@@ -427,6 +427,10 @@ public class CommandDispatcher<S> {
return node;
}
public void findAmbiguities(final AmbiguityConsumer<S> consumer) {
root.findAmbiguities(consumer);
}
private void addPaths(final CommandNode<S> node, final List<List<CommandNode<S>>> result, final List<CommandNode<S>> parents) {
final List<CommandNode<S>> current = new ArrayList<>(parents);
current.add(node);
@@ -7,12 +7,18 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
public interface ArgumentType<T> {
<S> T parse(StringReader reader, CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException;
<S> T parse(StringReader reader) throws CommandSyntaxException;
default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
return Suggestions.empty();
}
default Collection<String> getExamples() {
return Collections.emptyList();
}
}
@@ -7,9 +7,13 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
public class BoolArgumentType implements ArgumentType<Boolean> {
private static final Collection<String> EXAMPLES = Arrays.asList("true", "false");
private BoolArgumentType() {
}
@@ -22,7 +26,7 @@ public class BoolArgumentType implements ArgumentType<Boolean> {
}
@Override
public <S> Boolean parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
public <S> Boolean parse(final StringReader reader) throws CommandSyntaxException {
return reader.readBoolean();
}
@@ -36,4 +40,9 @@ public class BoolArgumentType implements ArgumentType<Boolean> {
}
return builder.buildFuture();
}
@Override
public Collection<String> getExamples() {
return EXAMPLES;
}
}
@@ -6,9 +6,13 @@ import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import java.util.Arrays;
import java.util.Collection;
public class DoubleArgumentType implements ArgumentType<Double> {
public static final ParameterizedCommandExceptionType ERROR_TOO_SMALL = new ParameterizedCommandExceptionType("argument.double.low", "Double must not be less than ${minimum}, found ${found}", "found", "minimum");
public static final ParameterizedCommandExceptionType ERROR_TOO_BIG = new ParameterizedCommandExceptionType("argument.double.big", "Double must not be more than ${maximum}, found ${found}", "found", "maximum");
private static final Collection<String> EXAMPLES = Arrays.asList("0", "1.2", ".5", "-1", "-.5", "-1234.56");
private final double minimum;
private final double maximum;
@@ -43,7 +47,7 @@ public class DoubleArgumentType implements ArgumentType<Double> {
}
@Override
public <S> Double parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
public <S> Double parse(final StringReader reader) throws CommandSyntaxException {
final int start = reader.getCursor();
final double result = reader.readDouble();
if (result < minimum) {
@@ -81,4 +85,9 @@ public class DoubleArgumentType implements ArgumentType<Double> {
return "double(" + minimum + ", " + maximum + ")";
}
}
@Override
public Collection<String> getExamples() {
return EXAMPLES;
}
}
@@ -6,9 +6,13 @@ import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import java.util.Arrays;
import java.util.Collection;
public class FloatArgumentType implements ArgumentType<Float> {
public static final ParameterizedCommandExceptionType ERROR_TOO_SMALL = new ParameterizedCommandExceptionType("argument.float.low", "Float must not be less than ${minimum}, found ${found}", "found", "minimum");
public static final ParameterizedCommandExceptionType ERROR_TOO_BIG = new ParameterizedCommandExceptionType("argument.float.big", "Float must not be more than ${maximum}, found ${found}", "found", "maximum");
private static final Collection<String> EXAMPLES = Arrays.asList("0", "1.2", ".5", "-1", "-.5", "-1234.56");
private final float minimum;
private final float maximum;
@@ -43,7 +47,7 @@ public class FloatArgumentType implements ArgumentType<Float> {
}
@Override
public <S> Float parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
public <S> Float parse(final StringReader reader) throws CommandSyntaxException {
final int start = reader.getCursor();
final float result = reader.readFloat();
if (result < minimum) {
@@ -81,4 +85,9 @@ public class FloatArgumentType implements ArgumentType<Float> {
return "float(" + minimum + ", " + maximum + ")";
}
}
@Override
public Collection<String> getExamples() {
return EXAMPLES;
}
}
@@ -6,9 +6,13 @@ import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import java.util.Arrays;
import java.util.Collection;
public class IntegerArgumentType implements ArgumentType<Integer> {
public static final ParameterizedCommandExceptionType ERROR_TOO_SMALL = new ParameterizedCommandExceptionType("argument.integer.low", "Integer must not be less than ${minimum}, found ${found}", "found", "minimum");
public static final ParameterizedCommandExceptionType ERROR_TOO_BIG = new ParameterizedCommandExceptionType("argument.integer.big", "Integer must not be more than ${maximum}, found ${found}", "found", "maximum");
private static final Collection<String> EXAMPLES = Arrays.asList("0", "123", "-123");
private final int minimum;
private final int maximum;
@@ -43,7 +47,7 @@ public class IntegerArgumentType implements ArgumentType<Integer> {
}
@Override
public <S> Integer parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
public <S> Integer parse(final StringReader reader) throws CommandSyntaxException {
final int start = reader.getCursor();
final int result = reader.readInt();
if (result < minimum) {
@@ -1,11 +1,13 @@
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.Arrays;
import java.util.Collection;
public class StringArgumentType implements ArgumentType<String> {
private final StringType type;
@@ -34,7 +36,7 @@ public class StringArgumentType implements ArgumentType<String> {
}
@Override
public <S> String parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
public <S> String parse(final StringReader reader) throws CommandSyntaxException {
if (type == StringType.GREEDY_PHRASE) {
final String text = reader.getRemaining();
reader.setCursor(reader.getTotalLength());
@@ -51,6 +53,11 @@ public class StringArgumentType implements ArgumentType<String> {
return "string()";
}
@Override
public Collection<String> getExamples() {
return type.getExamples();
}
public static String escapeIfRequired(final String input) {
for (final char c : input.toCharArray()) {
if (!StringReader.isAllowedInUnquotedString(c)) {
@@ -76,8 +83,18 @@ public class StringArgumentType implements ArgumentType<String> {
}
public enum StringType {
SINGLE_WORD,
QUOTABLE_PHRASE,
GREEDY_PHRASE,
SINGLE_WORD("word", "words_with_underscores"),
QUOTABLE_PHRASE("\"quoted phrase\"", "word", "\"\""),
GREEDY_PHRASE("word", "words with spaces", "\"and symbols\""),;
private final Collection<String> examples;
StringType(final String... examples) {
this.examples = Arrays.asList(examples);
}
public Collection<String> getExamples() {
return examples;
}
}
}
@@ -13,6 +13,7 @@ import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@@ -52,7 +53,7 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
@Override
public void parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
final int start = reader.getCursor();
final T result = type.parse(reader, contextBuilder);
final T result = type.parse(reader);
final ParsedArgument<S, T> parsed = new ParsedArgument<>(start, reader.getCursor(), result);
contextBuilder.withArgument(name, parsed);
@@ -80,6 +81,17 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
return builder;
}
@Override
public boolean isValidInput(final String input) {
try {
final StringReader reader = new StringReader(input);
type.parse(reader);
return !reader.canRead() || reader.peek() == ' ';
} catch (final CommandSyntaxException ignored) {
return false;
}
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
@@ -103,4 +115,9 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
protected String getSortedKey() {
return name;
}
@Override
public Collection<String> getExamples() {
return type.getExamples();
}
}
@@ -2,6 +2,8 @@ package com.mojang.brigadier.tree;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.brigadier.AmbiguityConsumer;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader;
@@ -16,6 +18,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -77,6 +80,18 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
child.addChild(grandchild);
}
} else {
for (final CommandNode<S> sibling : children.values()) {
for (final String example : node.getExamples()) {
if (sibling.isValidInput(example)) {
System.out.println("Ambiguity detected in " + getName() + ", siblings " + sibling.getName() + " and " + node.getName() + " can both parse '" + example + "' successfully");
}
}
for (final String example : sibling.getExamples()) {
if (node.isValidInput(example)) {
System.out.println("Ambiguity detected in " + getName() + ", siblings " + sibling.getName() + " and " + node.getName() + " can both parse '" + example + "' successfully");
}
}
}
children.put(node.getName(), node);
if (node instanceof LiteralCommandNode) {
literals.put(node.getName(), (LiteralCommandNode<S>) node);
@@ -88,6 +103,33 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
children = children.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
public void findAmbiguities(final AmbiguityConsumer<S> consumer) {
Set<String> matches = Sets.newHashSet();
for (final CommandNode<S> child : children.values()) {
for (final CommandNode<S> sibling : children.values()) {
if (child == sibling) {
continue;
}
for (final String input : child.getExamples()) {
if (sibling.isValidInput(input)) {
matches.add(input);
}
}
if (matches.size() > 0) {
consumer.ambiguous(this, child, sibling, matches);
matches = Sets.newHashSet();
}
}
child.findAmbiguities(consumer);
}
}
protected abstract boolean isValidInput(final String input);
@Override
public boolean equals(final Object o) {
if (this == o) return true;
@@ -153,4 +195,6 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
public boolean isFork() {
return forks;
}
public abstract Collection<String> getExamples();
}
@@ -12,6 +12,8 @@ import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@@ -36,18 +38,30 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
@Override
public void parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
final int start = reader.getCursor();
final int end = parse(reader);
if (end > -1) {
contextBuilder.withNode(this, StringRange.between(start, end));
return;
}
throw ERROR_INCORRECT_LITERAL.createWithContext(reader, literal);
}
private int parse(final StringReader reader) {
final int start = reader.getCursor();
if (reader.canRead(literal.length())) {
final int end = start + literal.length();
if (reader.getString().substring(start, end).equals(literal)) {
reader.setCursor(end);
contextBuilder.withNode(this, StringRange.between(start, end));
return;
if (!reader.canRead() || reader.peek() == ' ') {
return end;
} else {
reader.setCursor(start);
}
}
}
reader.setCursor(start);
throw ERROR_INCORRECT_LITERAL.createWithContext(reader, literal);
return -1;
}
@Override
@@ -59,6 +73,11 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
}
}
@Override
public boolean isValidInput(final String input) {
return parse(new StringReader(input)) > -1;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
@@ -97,4 +116,9 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
protected String getSortedKey() {
return literal;
}
@Override
public Collection<String> getExamples() {
return Collections.singleton(literal);
}
}
@@ -8,6 +8,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
@@ -35,6 +36,11 @@ public class RootCommandNode<S> extends CommandNode<S> {
return Suggestions.empty();
}
@Override
public boolean isValidInput(final String input) {
return false;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
@@ -51,4 +57,9 @@ public class RootCommandNode<S> extends CommandNode<S> {
protected String getSortedKey() {
return "";
}
@Override
public Collection<String> getExamples() {
return Collections.emptyList();
}
}