Fixed competing overloads not allowing other ones to try to parse
This commit is contained in:
@@ -16,7 +16,9 @@ import com.mojang.brigadier.tree.RootCommandNode;
|
|||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@@ -103,22 +105,34 @@ public class CommandDispatcher<S> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParseResults<S> parse(final String command, final S source) throws CommandSyntaxException {
|
public ParseResults<S> parse(final String command, final S source) {
|
||||||
final StringReader reader = new StringReader(command);
|
final StringReader reader = new StringReader(command);
|
||||||
final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source);
|
final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source);
|
||||||
return parseNodes(root, reader, context, context, null);
|
return parseNodes(root, reader, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ParseResults<S> parseNodes(final CommandNode<S> node, final StringReader reader, final CommandContextBuilder<S> contextBuilder, CommandContextBuilder<S> rootContext, final CommandContextBuilder<S> parentContext) throws CommandSyntaxException {
|
private static class PartialParse<S> {
|
||||||
final S source = contextBuilder.getSource();
|
public final CommandContextBuilder<S> context;
|
||||||
final Map<CommandNode<S>, CommandSyntaxException> errors = Maps.newHashMap();
|
public final ParseResults<S> parse;
|
||||||
|
|
||||||
|
private PartialParse(final CommandContextBuilder<S> context, final ParseResults<S> parse) {
|
||||||
|
this.context = context;
|
||||||
|
this.parse = parse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParseResults<S> parseNodes(final CommandNode<S> node, final StringReader originalReader, final CommandContextBuilder<S> contextSoFar) {
|
||||||
|
final S source = contextSoFar.getSource();
|
||||||
|
final Map<CommandNode<S>, CommandSyntaxException> errors = Maps.newLinkedHashMap();
|
||||||
|
final List<PartialParse<S>> potentials = Lists.newArrayList();
|
||||||
|
final int cursor = originalReader.getCursor();
|
||||||
|
|
||||||
for (final CommandNode<S> child : node.getChildren()) {
|
for (final CommandNode<S> child : node.getChildren()) {
|
||||||
if (!child.canUse(source)) {
|
if (!child.canUse(source)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final CommandContextBuilder<S> context = contextBuilder.copy();
|
final CommandContextBuilder<S> context = contextSoFar.copy();
|
||||||
final int cursor = reader.getCursor();
|
final StringReader reader = new StringReader(originalReader);
|
||||||
try {
|
try {
|
||||||
child.parse(reader, context);
|
child.parse(reader, context);
|
||||||
if (reader.canRead()) {
|
if (reader.canRead()) {
|
||||||
@@ -132,34 +146,46 @@ public class CommandDispatcher<S> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rootContext == contextBuilder) {
|
|
||||||
rootContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentContext != null) {
|
|
||||||
parentContext.withChild(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.withCommand(child.getCommand());
|
context.withCommand(child.getCommand());
|
||||||
if (reader.canRead()) {
|
if (reader.canRead()) {
|
||||||
reader.skip();
|
reader.skip();
|
||||||
if (child.getRedirect() != null) {
|
if (child.getRedirect() != null) {
|
||||||
final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source);
|
final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source);
|
||||||
childContext.withNode(child.getRedirect(), "");
|
childContext.withNode(child.getRedirect(), "");
|
||||||
return parseNodes(child.getRedirect(), reader, childContext, rootContext, context);
|
final ParseResults<S> parse = parseNodes(child.getRedirect(), reader, childContext);
|
||||||
|
context.withChild(parse.getContext());
|
||||||
|
return new ParseResults<>(context, parse.getReader(), parse.getExceptions());
|
||||||
} else {
|
} else {
|
||||||
return parseNodes(child, reader, context, rootContext, parentContext);
|
final ParseResults<S> parse = parseNodes(child, reader, context);
|
||||||
|
potentials.add(new PartialParse<>(context, parse));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return new ParseResults<>(rootContext);
|
potentials.add(new PartialParse<>(context, new ParseResults<>(context, reader, Collections.emptyMap())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentContext != null) {
|
if (!potentials.isEmpty()) {
|
||||||
parentContext.withChild(contextBuilder);
|
final List<PartialParse<S>> sorted = Lists.newArrayList(potentials);
|
||||||
|
sorted.sort((a, b) -> {
|
||||||
|
if (!a.parse.getReader().canRead() && b.parse.getReader().canRead()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.parse.getReader().canRead() && !b.parse.getReader().canRead()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (a.parse.getExceptions().isEmpty() && !b.parse.getExceptions().isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!a.parse.getExceptions().isEmpty() && b.parse.getExceptions().isEmpty()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
final PartialParse<S> likely = sorted.get(0);
|
||||||
|
return likely.parse;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ParseResults<>(rootContext, reader, errors);
|
return new ParseResults<>(contextSoFar, originalReader, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getAllUsage(final CommandNode<S> node, final S source, final boolean restricted) {
|
public String[] getAllUsage(final CommandNode<S> node, final S source, final boolean restricted) {
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ public class StringReader implements ImmutableStringReader {
|
|||||||
private final String string;
|
private final String string;
|
||||||
private int cursor;
|
private int cursor;
|
||||||
|
|
||||||
|
public StringReader(final StringReader other) {
|
||||||
|
this.string = other.string;
|
||||||
|
this.cursor = other.cursor;
|
||||||
|
}
|
||||||
|
|
||||||
public StringReader(final String string) {
|
public StringReader(final String string) {
|
||||||
this.string = string;
|
this.string = string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,61 @@ public class CommandDispatcherTest {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Test
|
@Test
|
||||||
public void testExecuteRedirected() throws Exception {
|
public void testExecuteAmbiguiousParentSubcommand() throws Exception {
|
||||||
|
final Command<Object> subCommand = mock(Command.class);
|
||||||
|
when(subCommand.run(any())).thenReturn(100);
|
||||||
|
|
||||||
|
subject.register(
|
||||||
|
literal("test")
|
||||||
|
.then(
|
||||||
|
argument("incorrect", integer())
|
||||||
|
.executes(command)
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
argument("right", integer())
|
||||||
|
.then(
|
||||||
|
argument("sub", integer())
|
||||||
|
.executes(subCommand)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(subject.execute("test 1 2", source), is(100));
|
||||||
|
verify(subCommand).run(any(CommandContext.class));
|
||||||
|
verify(command, never()).run(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void testExecuteAmbiguiousParentSubcommandViaRedirect() throws Exception {
|
||||||
|
final Command<Object> subCommand = mock(Command.class);
|
||||||
|
when(subCommand.run(any())).thenReturn(100);
|
||||||
|
|
||||||
|
final LiteralCommandNode<Object> real = subject.register(
|
||||||
|
literal("test")
|
||||||
|
.then(
|
||||||
|
argument("incorrect", integer())
|
||||||
|
.executes(command)
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
argument("right", integer())
|
||||||
|
.then(
|
||||||
|
argument("sub", integer())
|
||||||
|
.executes(subCommand)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
subject.register(literal("redirect").redirect(real));
|
||||||
|
|
||||||
|
assertThat(subject.execute("redirect 1 2", source), is(100));
|
||||||
|
verify(subCommand).run(any(CommandContext.class));
|
||||||
|
verify(command, never()).run(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test
|
||||||
|
public void testExecuteRedirectedMultipleTimes() throws Exception {
|
||||||
subject.register(literal("actual").executes(command));
|
subject.register(literal("actual").executes(command));
|
||||||
subject.register(literal("redirected").redirect(subject.getRoot(), Collections::singleton));
|
subject.register(literal("redirected").redirect(subject.getRoot(), Collections::singleton));
|
||||||
|
|
||||||
@@ -199,7 +253,7 @@ public class CommandDispatcherTest {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Test
|
@Test
|
||||||
public void testExecuteRedirectedMultipleTimes() throws Exception {
|
public void testExecuteRedirected() throws Exception {
|
||||||
final Function<CommandContext<Object>, Collection<Object>> modifier = mock(Function.class);
|
final Function<CommandContext<Object>, Collection<Object>> modifier = mock(Function.class);
|
||||||
final Object source1 = new Object();
|
final Object source1 = new Object();
|
||||||
final Object source2 = new Object();
|
final Object source2 = new Object();
|
||||||
@@ -237,7 +291,7 @@ public class CommandDispatcherTest {
|
|||||||
} catch (final CommandSyntaxException ex) {
|
} catch (final CommandSyntaxException ex) {
|
||||||
assertThat(ex.getType(), is(CommandDispatcher.ERROR_UNKNOWN_COMMAND));
|
assertThat(ex.getType(), is(CommandDispatcher.ERROR_UNKNOWN_COMMAND));
|
||||||
assertThat(ex.getData(), is(Collections.emptyMap()));
|
assertThat(ex.getData(), is(Collections.emptyMap()));
|
||||||
assertThat(ex.getCursor(), is(0));
|
assertThat(ex.getCursor(), is(5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,11 +99,7 @@ public class CommandDispatcherUsagesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CommandNode<Object> get(final String command) {
|
private CommandNode<Object> get(final String command) {
|
||||||
try {
|
|
||||||
return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator());
|
return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator());
|
||||||
} catch (final CommandSyntaxException e) {
|
|
||||||
throw new AssertionError("get() failed unexpectedly", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user