diff --git a/.gitignore b/.gitignore
index 406cea85..2a29ef2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,4 +65,7 @@ gradle-app.setting
hs_err_pid*
-repo/
\ No newline at end of file
+repo/
+
+# vscode workspace settings
+.vscode
\ No newline at end of file
diff --git a/src/main/java/com/mojang/brigadier/Command.java b/src/main/java/com/mojang/brigadier/Command.java
index 248d7e4a..471e12e7 100644
--- a/src/main/java/com/mojang/brigadier/Command.java
+++ b/src/main/java/com/mojang/brigadier/Command.java
@@ -7,8 +7,8 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@FunctionalInterface
-public interface Command {
+public interface Command {
int SINGLE_SUCCESS = 1;
- int run(CommandContext context) throws CommandSyntaxException;
+ R run(CommandContext context) throws CommandSyntaxException;
}
diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java
index ca24830b..393c3f99 100644
--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java
+++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation and Serena Lynas. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
@@ -8,6 +8,8 @@
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.SuggestionContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.results.CommandResult;
+import com.mojang.brigadier.results.EmptyCommandResult;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.CommandNode;
@@ -62,7 +64,7 @@ public boolean test(final CommandNode input) {
return input != null && (input.getCommand() != null || input.getChildren().stream().anyMatch(hasCommand));
}
};
- private ResultConsumer consumer = (c, s, r) -> {
+ private ResultConsumer consumer = (c, s, r) -> {
};
/**
@@ -104,7 +106,7 @@ public LiteralCommandNode register(final LiteralArgumentBuilder command) {
*
* @param consumer the new result consumer to be called
*/
- public void setConsumer(final ResultConsumer consumer) {
+ public void setConsumer(final ResultConsumer consumer) {
this.consumer = consumer;
}
@@ -130,15 +132,17 @@ public void setConsumer(final ResultConsumer consumer) {
*
* @param input a command string to parse & execute
* @param source a custom "source" object, usually representing the originator of this command
- * @return a numeric result from a "command" that was performed
+ * @return if the command is forked, the amount of successful executes; otherwise, the result
+ * from the command that was run, or EmptyCommandResult if the command failed
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
* @see #parse(StringReader, Object)
* @see #execute(ParseResults)
+ * @see #execute(ParseResults, Object)
* @see #execute(StringReader, Object)
*/
- public int execute(final String input, final S source) throws CommandSyntaxException {
+ public Object execute(final String input, final S source) throws CommandSyntaxException {
return execute(new StringReader(input), source);
}
@@ -164,7 +168,46 @@ public int execute(final String input, final S source) throws CommandSyntaxExcep
*
* @param input a command string to parse & execute
* @param source a custom "source" object, usually representing the originator of this command
- * @return a numeric result from a "command" that was performed
+ * @param baseCommandResult the basic command result to combine the other results into.
+ * Defaults to an EmptyCommandResult.
+ * @return if the command is forked, the amount of successful executes; otherwise, the result
+ * from the command that was run, or EmptyCommandResult if the command failed
+ * @throws CommandSyntaxException if the command failed to parse or execute
+ * @throws RuntimeException if the command failed to execute and was not handled gracefully
+ * @see #parse(String, Object)
+ * @see #parse(StringReader, Object)
+ * @see #execute(ParseResults)
+ * @see #execute(ParseResults, Object)
+ * @see #execute(StringReader, Object)
+ */
+ public Object execute(final String input, final S source, final Object baseCommandResult) throws CommandSyntaxException {
+ return execute(new StringReader(input), source, baseCommandResult);
+ }
+
+ /**
+ * Parses and executes a given command.
+ *
+ *
This is a shortcut to first {@link #parse(StringReader, Object)} and then {@link #execute(ParseResults)}.
+ *
+ *
It is recommended to parse and execute as separate steps, as parsing is often the most expensive step, and easiest to cache.
+ *
+ *
If this command returns a value, then it successfully executed something. If it could not parse the command, or the execution was a failure,
+ * then an exception will be thrown. Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
+ * may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
+ * entirely on what command was performed.
+ *
+ *
If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
+ * A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
+ * 'amount of successful commands executes'.
+ *
+ *
After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
+ * will be notified of the result and success of the command. You can use that method to gather more meaningful
+ * results than this method will return, especially when a command forks.
+ *
+ * @param input a command string to parse & execute
+ * @param source a custom "source" object, usually representing the originator of this command
+ * @return if the command is forked, the amount of successful executes; otherwise, the result
+ * from the command that was run, or EmptyCommandResult if the command failed
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
@@ -172,11 +215,81 @@ public int execute(final String input, final S source) throws CommandSyntaxExcep
* @see #execute(ParseResults)
* @see #execute(String, Object)
*/
- public int execute(final StringReader input, final S source) throws CommandSyntaxException {
+ public Object execute(final StringReader input, final S source) throws CommandSyntaxException {
final ParseResults parse = parse(input, source);
return execute(parse);
}
+ /**
+ * Parses and executes a given command.
+ *
+ *
This is a shortcut to first {@link #parse(StringReader, Object)} and then {@link #execute(ParseResults)}.
+ *
+ *
It is recommended to parse and execute as separate steps, as parsing is often the most expensive step, and easiest to cache.
+ *
+ *
If this command returns a value, then it successfully executed something. If it could not parse the command, or the execution was a failure,
+ * then an exception will be thrown. Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
+ * may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
+ * entirely on what command was performed.
+ *
+ *
If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
+ * A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
+ * 'amount of successful commands executes'.
+ *
+ *
After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
+ * will be notified of the result and success of the command. You can use that method to gather more meaningful
+ * results than this method will return, especially when a command forks.
+ *
+ * @param input a command string to parse & execute
+ * @param source a custom "source" object, usually representing the originator of this command
+ * @param baseCommandResult the basic command result to combine the other results into.
+ * Defaults to an EmptyCommandResult.
+ * @return if the command is forked, the amount of successful executes; otherwise, the result
+ * from the command that was run, or EmptyCommandResult if the command failed
+ * @throws CommandSyntaxException if the command failed to parse or execute
+ * @throws RuntimeException if the command failed to execute and was not handled gracefully
+ * @see #parse(String, Object)
+ * @see #parse(StringReader, Object)
+ * @see #execute(ParseResults)
+ * @see #execute(String, Object)
+ */
+ public Object execute(final StringReader input, final S source, final Object baseCommandResult) throws CommandSyntaxException {
+ final ParseResults parse = parse(input, source);
+ return execute(parse, baseCommandResult);
+ }
+
+ /**
+ * Executes a given pre-parsed command.
+ *
+ *
If this command returns a value, then it successfully executed something. If the execution was a failure,
+ * then an exception will be thrown.
+ * Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
+ * may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
+ * entirely on what command was performed.
+ *
+ *
If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
+ * A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
+ * 'amount of successful commands executes'.
+ *
+ *
After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
+ * will be notified of the result and success of the command. You can use that method to gather more meaningful
+ * results than this method will return, especially when a command forks.
+ *
+ * @param parse the result of a successful {@link #parse(StringReader, Object)}
+ * @return if the command is forked, the amount of successful executes; otherwise, the result
+ * from the command that was run, or EmptyCommandResult if the command failed
+ * @throws CommandSyntaxException if the command failed to parse or execute
+ * @throws RuntimeException if the command failed to execute and was not handled gracefully
+ * @see #parse(String, Object)
+ * @see #parse(StringReader, Object)
+ * @see #execute(String, Object)
+ * @see #execute(StringReader, Object)
+ * @see #execute(ParseResults, Object)
+ */
+ public Object execute(final ParseResults parse) throws CommandSyntaxException {
+ return execute(parse, new EmptyCommandResult());
+ }
+
/**
* Executes a given pre-parsed command.
*
@@ -195,7 +308,10 @@ public int execute(final StringReader input, final S source) throws CommandSynta
* results than this method will return, especially when a command forks.
*
* @param parse the result of a successful {@link #parse(StringReader, Object)}
- * @return a numeric result from a "command" that was performed.
+ * @param baseCommandResult the basic command result to combine the other results into.
+ * Defaults to an EmptyCommandResult.
+ * @return if the command is forked, the amount of successful executes; otherwise, the result
+ * from the command that was run, or EmptyCommandResult if the command failed
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
@@ -203,7 +319,7 @@ public int execute(final StringReader input, final S source) throws CommandSynta
* @see #execute(String, Object)
* @see #execute(StringReader, Object)
*/
- public int execute(final ParseResults parse) throws CommandSyntaxException {
+ public Object execute(final ParseResults parse, final Object baseCommandResult) throws CommandSyntaxException {
if (parse.getReader().canRead()) {
if (parse.getExceptions().size() == 1) {
throw parse.getExceptions().values().iterator().next();
@@ -214,7 +330,7 @@ public int execute(final ParseResults parse) throws CommandSyntaxException {
}
}
- int result = 0;
+ Object result = baseCommandResult;
int successfulForks = 0;
boolean forked = false;
boolean foundCommand = false;
@@ -250,7 +366,7 @@ public int execute(final ParseResults parse) throws CommandSyntaxException {
}
}
} catch (final CommandSyntaxException ex) {
- consumer.onCommandComplete(context, false, 0);
+ consumer.onCommandComplete(context, false, new EmptyCommandResult());
if (!forked) {
throw ex;
}
@@ -260,12 +376,12 @@ public int execute(final ParseResults parse) throws CommandSyntaxException {
} else if (context.getCommand() != null) {
foundCommand = true;
try {
- final int value = context.getCommand().run(context);
- result += value;
+ final Object value = context.getCommand().run(context);
consumer.onCommandComplete(context, true, value);
+ result = CommandResult.combine(result, value);
successfulForks++;
} catch (final CommandSyntaxException ex) {
- consumer.onCommandComplete(context, false, 0);
+ consumer.onCommandComplete(context, false, new EmptyCommandResult());
if (!forked) {
throw ex;
}
@@ -278,7 +394,7 @@ public int execute(final ParseResults parse) throws CommandSyntaxException {
}
if (!foundCommand) {
- consumer.onCommandComplete(original, false, 0);
+ consumer.onCommandComplete(original, false, new EmptyCommandResult());
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parse.getReader());
}
@@ -311,6 +427,7 @@ public int execute(final ParseResults parse) throws CommandSyntaxException {
* @see #parse(StringReader, Object)
* @see #execute(ParseResults)
* @see #execute(String, Object)
+ * @see #execute(ParseResults, Object)
*/
public ParseResults parse(final String command, final S source) {
return parse(new StringReader(command), source);
diff --git a/src/main/java/com/mojang/brigadier/ResultConsumer.java b/src/main/java/com/mojang/brigadier/ResultConsumer.java
index 2f58dbcd..cdf2004b 100644
--- a/src/main/java/com/mojang/brigadier/ResultConsumer.java
+++ b/src/main/java/com/mojang/brigadier/ResultConsumer.java
@@ -6,6 +6,6 @@
import com.mojang.brigadier.context.CommandContext;
@FunctionalInterface
-public interface ResultConsumer {
- void onCommandComplete(CommandContext context, boolean success, int result);
+public interface ResultConsumer {
+ void onCommandComplete(CommandContext context, boolean success, R result);
}
diff --git a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java
index 899008b2..a71df955 100644
--- a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java
+++ b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java
@@ -15,7 +15,7 @@
public abstract class ArgumentBuilder> {
private final RootCommandNode arguments = new RootCommandNode<>();
- private Command command;
+ private Command command;
private Predicate requirement = s -> true;
private CommandNode target;
private RedirectModifier modifier = null;
@@ -43,12 +43,12 @@ public Collection> getArguments() {
return arguments.getChildren();
}
- public T executes(final Command command) {
+ public T executes(final Command command) {
this.command = command;
return getThis();
}
- public Command getCommand() {
+ public Command getCommand() {
return command;
}
diff --git a/src/main/java/com/mojang/brigadier/context/CommandContext.java b/src/main/java/com/mojang/brigadier/context/CommandContext.java
index bc5bb4a3..a1b5cd3a 100644
--- a/src/main/java/com/mojang/brigadier/context/CommandContext.java
+++ b/src/main/java/com/mojang/brigadier/context/CommandContext.java
@@ -28,7 +28,7 @@ public class CommandContext {
private final S source;
private final String input;
- private final Command command;
+ private final Command command;
private final Map> arguments;
private final CommandNode rootNode;
private final List> nodes;
@@ -37,7 +37,7 @@ public class CommandContext {
private final RedirectModifier modifier;
private final boolean forks;
- public CommandContext(final S source, final String input, final Map> arguments, final Command command, final CommandNode rootNode, final List> nodes, final StringRange range, final CommandContext child, final RedirectModifier modifier, boolean forks) {
+ public CommandContext(final S source, final String input, final Map> arguments, final Command command, final CommandNode rootNode, final List> nodes, final StringRange range, final CommandContext child, final RedirectModifier modifier, boolean forks) {
this.source = source;
this.input = input;
this.arguments = arguments;
@@ -69,7 +69,7 @@ public CommandContext getLastChild() {
return result;
}
- public Command getCommand() {
+ public Command getCommand() {
return command;
}
diff --git a/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java b/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java
index 1af2cb83..aae85655 100644
--- a/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java
+++ b/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java
@@ -19,7 +19,7 @@ public class CommandContextBuilder {
private final List> nodes = new ArrayList<>();
private final CommandDispatcher dispatcher;
private S source;
- private Command command;
+ private Command command;
private CommandContextBuilder child;
private StringRange range;
private RedirectModifier modifier = null;
@@ -54,7 +54,7 @@ public CommandContextBuilder withArgument(final String name, final ParsedArgu
return arguments;
}
- public CommandContextBuilder withCommand(final Command command) {
+ public CommandContextBuilder withCommand(final Command command) {
this.command = command;
return this;
}
@@ -95,7 +95,7 @@ public CommandContextBuilder getLastChild() {
return result;
}
- public Command getCommand() {
+ public Command getCommand() {
return command;
}
diff --git a/src/main/java/com/mojang/brigadier/results/CommandResult.java b/src/main/java/com/mojang/brigadier/results/CommandResult.java
new file mode 100644
index 00000000..1807a9c1
--- /dev/null
+++ b/src/main/java/com/mojang/brigadier/results/CommandResult.java
@@ -0,0 +1,65 @@
+// Copyright (c) Serena Lynas. All rights reserved.
+// Licensed under the MIT license.
+
+package com.mojang.brigadier.results;
+
+/**
+ * Optional interface for CommandResult
+ *
+ * Not all things returned from commands
+ * must implement this interface.
+ */
+public interface CommandResult {
+ /**
+ * Combine one command result with another, returning
+ * the combined result.
+ * @param other The other result to combine this result
+ * with.
+ * @return The combined result
+ */
+ default Object combine(final Object other) {
+ return ListCommandResult.from(this, other);
+ }
+
+ /**
+ *
Combine any objects, even if they are not Command Results.
+ * Target is the object which will be mutated to contain
+ * the combined target and source.
+ *
+ *
If either of the supplied objects are EmptyCommandResult, the
+ * non-empty result will be returned.
+ *
+ *
If the target implements CommandResult, the `combine` method
+ * will be called on the target: `target.combine(source)`
+ *
+ *
If the target and source are the same type of boxed primitive
+ * number (ie Integer, Long, Double, etc.), they will be added together.
+ *
+ *
Otherwise, a new ListCommandResult containing both target and source
+ * will be returned.
+ *
+ * @param target the object which the source will be combined into
+ * @param source the object to combine into the target
+ */
+ static Object combine(final Object target, final Object source) {
+ if (target instanceof CommandResult) {
+ return ((CommandResult)target).combine(source);
+ } else if (source instanceof EmptyCommandResult) {
+ return target;
+ } else if (target instanceof Byte && source instanceof Byte) {
+ return (byte) target + (byte) source;
+ } else if (target instanceof Short && source instanceof Short) {
+ return (short) target + (short) source;
+ } else if (target instanceof Integer && source instanceof Integer) {
+ return (int) target + (int) source;
+ } else if (target instanceof Long && source instanceof Long) {
+ return (long) source + (long) source;
+ } else if (source instanceof Float && source instanceof Float) {
+ return (float) source + (float) source;
+ } else if (source instanceof Double && target instanceof Double) {
+ return (double) source + (double) target;
+ } else {
+ return ListCommandResult.from(target, source);
+ }
+ }
+}
diff --git a/src/main/java/com/mojang/brigadier/results/EmptyCommandResult.java b/src/main/java/com/mojang/brigadier/results/EmptyCommandResult.java
new file mode 100644
index 00000000..03896e4b
--- /dev/null
+++ b/src/main/java/com/mojang/brigadier/results/EmptyCommandResult.java
@@ -0,0 +1,19 @@
+// Copyright (c) Serena Lynas. All rights reserved.
+// Licensed under the MIT license.
+
+package com.mojang.brigadier.results;
+
+/**
+ * Empty class which semantically represents
+ * an empty command result.
+ */
+public class EmptyCommandResult implements CommandResult {
+ /**
+ * Combines this result with another. Always overwrites
+ * the empty result.
+ */
+ @Override
+ public Object combine(final Object other) {
+ return other;
+ }
+}
diff --git a/src/main/java/com/mojang/brigadier/results/ListCommandResult.java b/src/main/java/com/mojang/brigadier/results/ListCommandResult.java
new file mode 100644
index 00000000..d2151a11
--- /dev/null
+++ b/src/main/java/com/mojang/brigadier/results/ListCommandResult.java
@@ -0,0 +1,31 @@
+// Copyright (c) Serena Lynas. All rights reserved.
+// Licensed under the MIT license.
+
+package com.mojang.brigadier.results;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ListCommandResult implements CommandResult {
+ private final List