aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <github@matdoes.dev>2022-01-09 22:33:45 -0600
committermat <github@matdoes.dev>2022-01-09 22:33:45 -0600
commit315f2258190b33c63df7797a97178019f5aea02b (patch)
tree1ec5f467e7bd42f7aed3aaadbcbc226f8a6ce4f2
parentd56f60c05f316ab4cc37ebe7a9ad4caf91a75de6 (diff)
downloadazalea-drasl-315f2258190b33c63df7797a97178019f5aea02b.tar.xz
add some more stuff from brigadier
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock3
-rw-r--r--azalea-brigadier/Cargo.toml3
-rw-r--r--azalea-brigadier/src/arguments/argument_type.rs4
-rw-r--r--azalea-brigadier/src/arguments/bool_argument_type.rs25
-rw-r--r--azalea-brigadier/src/arguments/mod.rs6
-rw-r--r--azalea-brigadier/src/builder/argument_builder.rs106
-rw-r--r--azalea-brigadier/src/builder/mod.rs3
-rw-r--r--azalea-brigadier/src/command.rs10
-rw-r--r--azalea-brigadier/src/command_dispatcher.rs23
-rw-r--r--azalea-brigadier/src/context/command_context.rs87
-rw-r--r--azalea-brigadier/src/context/command_context_builder.rs180
-rw-r--r--azalea-brigadier/src/context/parsed_argument.rs24
-rw-r--r--azalea-brigadier/src/context/parsed_command_node.rs22
-rw-r--r--azalea-brigadier/src/context/string_range.rs45
-rw-r--r--azalea-brigadier/src/context/suggestion_context.rs6
-rw-r--r--azalea-brigadier/src/lib.rs3
-rw-r--r--azalea-brigadier/src/redirect_modifier.rs8
-rw-r--r--azalea-brigadier/src/single_redirect_modifier.rs8
-rw-r--r--azalea-brigadier/src/suggestion/suggestion_provider.rs14
-rw-r--r--azalea-brigadier/src/tree/argument_command_node.rs118
-rw-r--r--azalea-brigadier/src/tree/command_node.rs52
-rw-r--r--azalea-brigadier/src/tree/literal_command_node.rs96
-rw-r--r--azalea-brigadier/src/tree/mod.rs4
-rw-r--r--azalea-brigadier/src/tree/root_command_node.rs61
25 files changed, 902 insertions, 10 deletions
diff --git a/.gitignore b/.gitignore
index ea8c4bf7..f97818c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/target
+/doc \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 67259fef..0eeb6520 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -70,6 +70,9 @@ dependencies = [
[[package]]
name = "azalea-brigadier"
version = "0.1.0"
+dependencies = [
+ "lazy_static",
+]
[[package]]
name = "azalea-chat"
diff --git a/azalea-brigadier/Cargo.toml b/azalea-brigadier/Cargo.toml
index c617ffb1..3694a4b7 100644
--- a/azalea-brigadier/Cargo.toml
+++ b/azalea-brigadier/Cargo.toml
@@ -1,8 +1,9 @@
[package]
+edition = "2021"
name = "azalea-brigadier"
version = "0.1.0"
-edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+lazy_static = "^1.4"
diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs
index 4c48d6bb..34d57285 100644
--- a/azalea-brigadier/src/arguments/argument_type.rs
+++ b/azalea-brigadier/src/arguments/argument_type.rs
@@ -5,7 +5,7 @@ use crate::{
suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder},
};
-pub trait ArgumentType<T> {
+pub trait ArgumentType {
// T parse(StringReader reader) throws CommandSyntaxException;
// default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
@@ -16,7 +16,7 @@ pub trait ArgumentType<T> {
// return Collections.emptyList();
// }
- fn parse(reader: &mut StringReader) -> Result<T, CommandSyntaxException>;
+ fn parse<T>(reader: &mut StringReader) -> Result<T, CommandSyntaxException>;
fn list_suggestions<S>(
context: &CommandContext<S>,
diff --git a/azalea-brigadier/src/arguments/bool_argument_type.rs b/azalea-brigadier/src/arguments/bool_argument_type.rs
index d4a33517..f4c03373 100644
--- a/azalea-brigadier/src/arguments/bool_argument_type.rs
+++ b/azalea-brigadier/src/arguments/bool_argument_type.rs
@@ -1,8 +1,19 @@
-struct BoolArgumentType {
- // private static final Collection<String> EXAMPLES = Arrays.asList("true", "false");
- const EXAMPLES: &'static [&'static str] = &["true", "false"];
-}
+use crate::context::command_context::CommandContext;
+
+use super::argument_type::ArgumentType;
+
+struct BoolArgumentType {}
+
+impl ArgumentType for BoolArgumentType {}
-impl ArgumentType for BoolArgumentType {
-
-} \ No newline at end of file
+impl BoolArgumentType {
+ const EXAMPLES: &'static [&'static str] = &["true", "false"];
+
+ fn bool() -> Self {
+ Self {}
+ }
+
+ fn get_bool<S>(context: CommandContext<S>, name: String) {
+ context.get_argument::<bool>(name)
+ }
+}
diff --git a/azalea-brigadier/src/arguments/mod.rs b/azalea-brigadier/src/arguments/mod.rs
index 50b0f09b..487c5db7 100644
--- a/azalea-brigadier/src/arguments/mod.rs
+++ b/azalea-brigadier/src/arguments/mod.rs
@@ -1 +1,7 @@
pub mod argument_type;
+pub mod bool_argument_type;
+pub mod double_argument_type;
+pub mod float_argument_type;
+pub mod integer_argument_type;
+pub mod long_argument_type;
+pub mod string_argument_type;
diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs
index e69de29b..8a64a9e4 100644
--- a/azalea-brigadier/src/builder/argument_builder.rs
+++ b/azalea-brigadier/src/builder/argument_builder.rs
@@ -0,0 +1,106 @@
+use crate::{
+ command::Command,
+ redirect_modifier::RedirectModifier,
+ single_redirect_modifier::SingleRedirectModifier,
+ tree::{command_node::CommandNode, root_command_node::RootCommandNode},
+};
+
+pub struct BaseArgumentBuilder<S, T>
+where
+ T: ArgumentBuilder<S, T>,
+{
+ arguments: RootCommandNode<S>,
+ command: dyn Command<S>,
+ requirement: dyn Fn(&S) -> bool,
+ target: Option<dyn CommandNode<S>>,
+ modifier: Option<dyn RedirectModifier<S>>,
+ forks: bool,
+}
+
+pub trait ArgumentBuilder<S, T> {
+ fn this() -> T;
+ fn build(self) -> dyn CommandNode<S>;
+}
+
+impl<S, T> BaseArgumentBuilder<S, T>
+where
+ T: ArgumentBuilder<S, T>,
+{
+ pub fn then(&mut self, command: dyn CommandNode<S>) -> Result<&mut T, String> {
+ if self.target.is_some() {
+ return Err("Cannot add children to a redirected node".to_string());
+ }
+ self.command = command;
+ Ok(self)
+ }
+
+ pub fn arguments(&self) -> &Vec<dyn CommandNode<S>> {
+ &self.arguments.get_children()
+ }
+
+ pub fn executes(&mut self, command: dyn Command<S>) -> &mut T {
+ self.command = command;
+ self
+ }
+
+ pub fn command(&self) -> dyn Command<S> {
+ self.command
+ }
+
+ pub fn requires(&mut self, requirement: dyn Fn(&S) -> bool) -> &mut T {
+ self.requirement = requirement;
+ self
+ }
+
+ pub fn requirement(&self) -> dyn Fn(&S) -> bool {
+ self.requirement
+ }
+
+ pub fn redirect(&mut self, target: dyn CommandNode<S>) -> &mut T {
+ self.forward(target, None, false)
+ }
+
+ pub fn redirect_modifier(
+ &mut self,
+ target: dyn CommandNode<S>,
+ modifier: dyn SingleRedirectModifier<S>,
+ ) -> &mut T {
+ // forward(target, modifier == null ? null : o -> Collections.singleton(modifier.apply(o)), false);
+ self.forward(target, modifier.map(|m| |o| vec![m.apply(o)]), false)
+ }
+
+ pub fn fork(
+ &mut self,
+ target: dyn CommandNode<S>,
+ modifier: dyn RedirectModifier<S>,
+ ) -> &mut T {
+ self.forward(target, Some(modifier), true)
+ }
+
+ pub fn forward(
+ &mut self,
+ target: dyn CommandNode<S>,
+ modifier: Option<dyn RedirectModifier<S>>,
+ fork: bool,
+ ) -> Result<&mut T, String> {
+ if !self.arguments.get_children().is_empty() {
+ return Err("Cannot forward a node with children".to_string());
+ }
+ self.target = Some(target);
+ self.modifier = modifier;
+ self.forks = fork;
+ Ok(self)
+ }
+
+ pub fn redirect(&self) -> Option<&dyn CommandNode<S>> {
+ self.target.as_ref()
+ }
+
+ pub fn redirect_modifier(&self) -> Option<&dyn RedirectModifier<S>> {
+ self.modifier.as_ref()
+ }
+
+ pub fn is_fork(&self) -> bool {
+ self.forks
+ }
+}
diff --git a/azalea-brigadier/src/builder/mod.rs b/azalea-brigadier/src/builder/mod.rs
index e69de29b..26f2f644 100644
--- a/azalea-brigadier/src/builder/mod.rs
+++ b/azalea-brigadier/src/builder/mod.rs
@@ -0,0 +1,3 @@
+pub mod argument_builder;
+pub mod literal_argument_builder;
+pub mod required_argument_builder;
diff --git a/azalea-brigadier/src/command.rs b/azalea-brigadier/src/command.rs
index e69de29b..a76454b7 100644
--- a/azalea-brigadier/src/command.rs
+++ b/azalea-brigadier/src/command.rs
@@ -0,0 +1,10 @@
+use crate::{
+ context::command_context::CommandContext,
+ exceptions::command_syntax_exception::CommandSyntaxException,
+};
+
+pub const SINGLE_SUCCESS: i32 = 1;
+
+pub trait Command<S> {
+ fn run(&self, context: &mut CommandContext<S>) -> Result<i32, CommandSyntaxException>;
+}
diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs
index e69de29b..c476a39b 100644
--- a/azalea-brigadier/src/command_dispatcher.rs
+++ b/azalea-brigadier/src/command_dispatcher.rs
@@ -0,0 +1,23 @@
+/// The core command dispatcher, for registering, parsing, and executing commands.
+/// The `S` generic is a custom "source" type, such as a user or originator of a command
+pub struct CommandDispatcher<S> {
+ root: RootCommandNode<S>,
+}
+
+impl<S> CommandDispatcher<S> {
+ /// The string required to separate individual arguments in an input string
+ ///
+ /// See: [`ARGUMENT_SEPARATOR_CHAR`]
+ const ARGUMENT_SEPARATOR: &'static str = " ";
+
+ /// The char required to separate individual arguments in an input string
+ ///
+ /// See: [`ARGUMENT_SEPARATOR`]
+ const ARGUMENT_SEPARATOR_CHAR: char = ' ';
+
+ const USAGE_OPTIONAL_OPEN: &'static str = "[";
+ const USAGE_OPTIONAL_CLOSE: &'static str = "]";
+ const USAGE_REQUIRED_OPEN: &'static str = "(";
+ const USAGE_REQUIRED_CLOSE: &'static str = ")";
+ const USAGE_OR: &'static str = "|";
+}
diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs
index ddbb447e..c6210a88 100644
--- a/azalea-brigadier/src/context/command_context.rs
+++ b/azalea-brigadier/src/context/command_context.rs
@@ -1,3 +1,90 @@
+use super::{
+ parsed_argument::ParsedArgument, parsed_command_node::ParsedCommandNode,
+ string_range::StringRange,
+};
+use crate::{
+ arguments::argument_type::ArgumentType, command::Command, redirect_modifier::RedirectModifier,
+ tree::command_node::CommandNode,
+};
+use std::collections::HashMap;
+
pub struct CommandContext<S> {
source: S,
+ input: String,
+ command: dyn Command<S>,
+ arguments: HashMap<String, ParsedArgument<S, dyn ArgumentType>>,
+ root_node: dyn CommandNode<S>,
+ nodes: Vec<ParsedCommandNode<S>>,
+ range: StringRange,
+ child: Option<CommandContext<S>>,
+ modifier: Option<dyn RedirectModifier<S>>,
+ forks: bool,
+}
+
+impl<S> CommandContext<S> {
+ pub fn clone_for(&self, source: S) -> Self {
+ if self.source == source {
+ return self.clone();
+ }
+ Self {
+ source,
+ input: self.input.clone(),
+ command: self.command.clone(),
+ arguments: self.arguments.clone(),
+ root_node: self.root_node.clone(),
+ nodes: self.nodes.clone(),
+ range: self.range.clone(),
+ child: self.child.clone(),
+ modifier: self.modifier.clone(),
+ forks: self.forks,
+ }
+ }
+
+ fn child(&self) -> &Option<CommandContext<S>> {
+ &self.child
+ }
+
+ fn last_child(&self) -> &CommandContext<S> {
+ let mut result = self;
+ while result.child.is_some() {
+ result = result.child.as_ref().unwrap();
+ }
+ result
+ }
+
+ fn command(&self) -> &dyn Command<S> {
+ &self.command
+ }
+
+ fn source(&self) -> &S {
+ &self.source
+ }
+
+ // public <V> V getArgument(final String name, final Class<V> clazz) {
+ // final ParsedArgument<S, ?> argument = arguments.get(name);
+
+ // if (argument == null) {
+ // throw new IllegalArgumentException("No such argument '" + name + "' exists on this command");
+ // }
+
+ // final Object result = argument.getResult();
+ // if (PRIMITIVE_TO_WRAPPER.getOrDefault(clazz, clazz).isAssignableFrom(result.getClass())) {
+ // return (V) result;
+ // } else {
+ // throw new IllegalArgumentException("Argument '" + name + "' is defined as " + result.getClass().getSimpleName() + ", not " + clazz);
+ // }
+ // }
+ fn get_argument<V>(&self, name: &str) -> Result<V, String> {
+ let argument = self.arguments.get(name);
+
+ if argument.is_none() {
+ return Err(format!(
+ "No such argument '{}' exists on this command",
+ name
+ ));
+ }
+
+ let result = argument.unwrap().result();
+ Ok(result)
+ }
}
diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs
index e69de29b..e74b5b1c 100644
--- a/azalea-brigadier/src/context/command_context_builder.rs
+++ b/azalea-brigadier/src/context/command_context_builder.rs
@@ -0,0 +1,180 @@
+use std::collections::HashMap;
+
+use crate::{
+ arguments::argument_type::ArgumentType, command::Command,
+ command_dispatcher::CommandDispatcher, redirect_modifier::RedirectModifier,
+ tree::command_node::CommandNode,
+};
+
+use super::{
+ command_context::CommandContext, parsed_argument::ParsedArgument,
+ parsed_command_node::ParsedCommandNode, string_range::StringRange,
+ suggestion_context::SuggestionContext,
+};
+
+// public class CommandContextBuilder<S> {
+// private final Map<String, ParsedArgument<S, ?>> arguments = new LinkedHashMap<>();
+// private final CommandNode<S> rootNode;
+// private final List<ParsedCommandNode<S>> nodes = new ArrayList<>();
+// private final CommandDispatcher<S> dispatcher;
+// private S source;
+// private Command<S> command;
+// private CommandContextBuilder<S> child;
+// private StringRange range;
+// private RedirectModifier<S> modifier = null;
+// private boolean forks;
+
+#[derive(Clone)]
+pub struct CommandContextBuilder<S> {
+ arguments: HashMap<String, ParsedArgument<S, dyn ArgumentType>>,
+ root_node: dyn CommandNode<S>,
+ nodes: Vec<ParsedCommandNode<S>>,
+ dispatcher: CommandDispatcher<S>,
+ source: S,
+ command: Box<dyn Command<S>>,
+ child: Option<CommandContextBuilder<S>>,
+ range: StringRange,
+ modifier: Option<Box<dyn RedirectModifier<S>>>,
+ forks: bool,
+}
+
+// public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source, final CommandNode<S> rootNode, final int start) {
+// this.rootNode = rootNode;
+// this.dispatcher = dispatcher;
+// this.source = source;
+// this.range = StringRange.at(start);
+// }
+
+impl<S> CommandContextBuilder<S> {
+ pub fn new(
+ dispatcher: CommandDispatcher<S>,
+ source: S,
+ root_node: dyn CommandNode<S>,
+ start: usize,
+ ) -> Self {
+ Self {
+ root_node,
+ dispatcher,
+ source,
+ range: StringRange::at(start),
+ ..Default::default()
+ }
+ }
+
+ pub fn with_source(mut self, source: S) -> Self {
+ self.source = source;
+ self
+ }
+
+ pub fn source(&self) -> &S {
+ &self.source
+ }
+
+ pub fn root_node(&self) -> &dyn CommandNode<S> {
+ &self.root_node
+ }
+
+ pub fn with_argument(
+ mut self,
+ name: String,
+ argument: ParsedArgument<S, dyn ArgumentType>,
+ ) -> Self {
+ self.arguments.insert(name, argument);
+ self
+ }
+
+ pub fn arguments(&self) -> &HashMap<String, ParsedArgument<S, dyn ArgumentType>> {
+ &self.arguments
+ }
+
+ pub fn with_command(mut self, command: Box<dyn Command<S>>) -> Self {
+ self.command = command;
+ self
+ }
+
+ pub fn with_node(mut self, node: dyn CommandNode<S>, range: StringRange) -> Self {
+ self.nodes.push(ParsedCommandNode::new(node, range));
+ self.range = StringRange::encompassing(&self.range, &range);
+ self.modifier = node.redirect_modifier();
+ self.forks = node.is_fork();
+ self
+ }
+
+ pub fn with_child(mut self, child: CommandContextBuilder<S>) -> Self {
+ self.child = Some(child);
+ self
+ }
+
+ pub fn child(&self) -> Option<&CommandContextBuilder<S>> {
+ self.child.as_ref()
+ }
+
+ pub fn last_child(&self) -> Option<&CommandContextBuilder<S>> {
+ let mut result = self;
+ while let Some(child) = result.child() {
+ result = child;
+ }
+ Some(result)
+ }
+
+ pub fn command(&self) -> &dyn Command<S> {
+ &*self.command
+ }
+
+ pub fn nodes(&self) -> &Vec<ParsedCommandNode<S>> {
+ &self.nodes
+ }
+
+ pub fn build(self, input: &str) -> CommandContext<S> {
+ CommandContext {
+ source: self.source,
+ input,
+ arguments: self.arguments,
+ command: self.command,
+ root_node: self.root_node,
+ nodes: self.nodes,
+ range: self.range,
+ child: self.child.map(|child| child.build(input)),
+ modifier: self.modifier,
+ forks: self.forks,
+ }
+ }
+
+ pub fn dispatcher(&self) -> &CommandDispatcher<S> {
+ &self.dispatcher
+ }
+
+ pub fn range(&self) -> &StringRange {
+ &self.range
+ }
+
+ pub fn find_suggestion_context(&self, cursor: i32) -> Result<SuggestionContext<S>, String> {
+ if self.range.start() <= cursor {
+ if self.range.end() < cursor {
+ if let Some(child) = self.child() {
+ child.find_suggestion_context(cursor);
+ } else if !self.nodes.is_empty() {
+ let last = self.nodes.last().unwrap();
+ let end = last.range().end() + 1;
+ return SuggestionContext::new(last.node(), end);
+ } else {
+ return SuggestionContext::new(self.root_node, self.range.start());
+ }
+ } else {
+ let prev = self.root_node;
+ for node in &self.nodes {
+ let node_range = node.range();
+ if node_range.start() <= cursor && cursor <= node_range.end() {
+ return SuggestionContext::new(prev, node_range.start());
+ }
+ prev = node.node();
+ }
+ if prev.is_none() {
+ return Err(String::from("Can't find node before cursor"));
+ }
+ return SuggestionContext::new(prev.unwrap(), self.range.start());
+ }
+ }
+ Err(String::from("Can't find node before cursor"))
+ }
+}
diff --git a/azalea-brigadier/src/context/parsed_argument.rs b/azalea-brigadier/src/context/parsed_argument.rs
index e69de29b..5f9c2cdb 100644
--- a/azalea-brigadier/src/context/parsed_argument.rs
+++ b/azalea-brigadier/src/context/parsed_argument.rs
@@ -0,0 +1,24 @@
+use super::string_range::StringRange;
+
+#[derive(PartialEq, Eq, Hash)]
+pub struct ParsedArgument<S, T> {
+ range: StringRange,
+ result: T,
+}
+
+impl<S, T> ParsedArgument<S, T> {
+ fn new(start: usize, end: usize, result: T) -> Self {
+ Self {
+ range: StringRange::between(start, end),
+ result,
+ }
+ }
+
+ fn range(&self) -> &StringRange {
+ &self.range
+ }
+
+ fn result(&self) -> &T {
+ &self.result
+ }
+}
diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs
index e69de29b..98e99959 100644
--- a/azalea-brigadier/src/context/parsed_command_node.rs
+++ b/azalea-brigadier/src/context/parsed_command_node.rs
@@ -0,0 +1,22 @@
+use super::string_range::StringRange;
+use crate::tree::command_node::CommandNode;
+
+#[derive(Hash, PartialEq, Eq, Debug, Clone)]
+pub struct ParsedCommandNode<S> {
+ node: dyn CommandNode<S>,
+ range: StringRange,
+}
+
+impl<S> ParsedCommandNode<S> {
+ fn new(node: dyn CommandNode<S>, range: StringRange) -> Self {
+ Self { node, range }
+ }
+
+ fn node(&self) -> &dyn CommandNode<S> {
+ &self.node
+ }
+
+ fn range(&self) -> &StringRange {
+ &self.range
+ }
+}
diff --git a/azalea-brigadier/src/context/string_range.rs b/azalea-brigadier/src/context/string_range.rs
index e69de29b..d775ab68 100644
--- a/azalea-brigadier/src/context/string_range.rs
+++ b/azalea-brigadier/src/context/string_range.rs
@@ -0,0 +1,45 @@
+use std::cmp;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct StringRange {
+ start: usize,
+ end: usize,
+}
+
+impl StringRange {
+ pub fn new(start: usize, end: usize) -> Self {
+ Self { start, end }
+ }
+
+ pub fn at(pos: usize) -> Self {
+ Self::new(pos, pos)
+ }
+
+ pub fn between(start: usize, end: usize) -> Self {
+ Self::new(start, end)
+ }
+
+ pub fn encompassing(a: &Self, b: &Self) -> Self {
+ Self::new(cmp::min(a.start, b.start), cmp::max(a.end, b.end))
+ }
+
+ pub fn start(&self) -> usize {
+ self.start
+ }
+
+ pub fn end(&self) -> usize {
+ self.end
+ }
+
+ pub fn get(&self, reader: &str) -> &str {
+ &reader[self.start..self.end]
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.start == self.end
+ }
+
+ pub fn length(&self) -> usize {
+ self.end - self.start
+ }
+}
diff --git a/azalea-brigadier/src/context/suggestion_context.rs b/azalea-brigadier/src/context/suggestion_context.rs
index e69de29b..540a5f23 100644
--- a/azalea-brigadier/src/context/suggestion_context.rs
+++ b/azalea-brigadier/src/context/suggestion_context.rs
@@ -0,0 +1,6 @@
+use crate::tree::command_node::CommandNode;
+
+pub struct SuggestionContext<S> {
+ parent: dyn CommandNode<S>,
+ start_pos: usize,
+}
diff --git a/azalea-brigadier/src/lib.rs b/azalea-brigadier/src/lib.rs
index d0966de3..b2345abb 100644
--- a/azalea-brigadier/src/lib.rs
+++ b/azalea-brigadier/src/lib.rs
@@ -1,3 +1,6 @@
+#[macro_use]
+extern crate lazy_static;
+
mod ambiguity_consumer;
mod arguments;
mod builder;
diff --git a/azalea-brigadier/src/redirect_modifier.rs b/azalea-brigadier/src/redirect_modifier.rs
index e69de29b..cfefd120 100644
--- a/azalea-brigadier/src/redirect_modifier.rs
+++ b/azalea-brigadier/src/redirect_modifier.rs
@@ -0,0 +1,8 @@
+use crate::{
+ context::command_context::CommandContext,
+ exceptions::command_syntax_exception::CommandSyntaxException,
+};
+
+pub trait RedirectModifier<S> {
+ fn apply(&self, context: CommandContext<S>) -> Result<Vec<S>, CommandSyntaxException>;
+}
diff --git a/azalea-brigadier/src/single_redirect_modifier.rs b/azalea-brigadier/src/single_redirect_modifier.rs
index e69de29b..dd63244d 100644
--- a/azalea-brigadier/src/single_redirect_modifier.rs
+++ b/azalea-brigadier/src/single_redirect_modifier.rs
@@ -0,0 +1,8 @@
+use crate::{
+ context::command_context::CommandContext,
+ exceptions::command_syntax_exception::CommandSyntaxException,
+};
+
+pub trait SingleRedirectModifier<S> {
+ fn apply(&self, context: CommandContext<S>) -> Result<S, CommandSyntaxException>;
+}
diff --git a/azalea-brigadier/src/suggestion/suggestion_provider.rs b/azalea-brigadier/src/suggestion/suggestion_provider.rs
index e69de29b..3027d460 100644
--- a/azalea-brigadier/src/suggestion/suggestion_provider.rs
+++ b/azalea-brigadier/src/suggestion/suggestion_provider.rs
@@ -0,0 +1,14 @@
+use crate::{
+ context::command_context::CommandContext,
+ exceptions::command_syntax_exception::CommandSyntaxException,
+};
+
+use super::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder};
+
+pub trait SuggestionProvider<S> {
+ fn suggestions(
+ &self,
+ context: &CommandContext<S>,
+ builder: &SuggestionsBuilder,
+ ) -> Result<Suggestions, CommandSyntaxException>;
+}
diff --git a/azalea-brigadier/src/tree/argument_command_node.rs b/azalea-brigadier/src/tree/argument_command_node.rs
index e69de29b..df7d3f5c 100644
--- a/azalea-brigadier/src/tree/argument_command_node.rs
+++ b/azalea-brigadier/src/tree/argument_command_node.rs
@@ -0,0 +1,118 @@
+use std::fmt::{Display, Formatter};
+
+use crate::{
+ arguments::argument_type::ArgumentType,
+ context::{
+ command_context::CommandContext, command_context_builder::CommandContextBuilder,
+ parsed_argument::ParsedArgument,
+ },
+ exceptions::command_syntax_exception::CommandSyntaxException,
+ string_reader::StringReader,
+ suggestion::{
+ suggestion_provider::SuggestionProvider, suggestions::Suggestions,
+ suggestions_builder::SuggestionsBuilder,
+ },
+};
+
+use super::command_node::{BaseCommandNode, CommandNode};
+
+const USAGE_ARGUMENT_OPEN: &str = "<";
+const USAGE_ARGUMENT_CLOSE: &str = ">";
+
+#[derive(Hash, PartialEq, Eq, Debug, Clone)]
+pub struct ArgumentCommandNode<S, T> {
+ name: String,
+ type_: dyn ArgumentType,
+ custom_suggestions: dyn SuggestionProvider<S>,
+ // Since Rust doesn't have extending, we put the struct this is extending as the "base" field
+ pub base: BaseCommandNode<S>,
+}
+
+impl<S, T> ArgumentCommandNode<S, T> {
+ fn get_type(&self) -> &dyn ArgumentType {
+ &self.type_
+ }
+
+ fn custom_suggestions(&self) -> &dyn SuggestionProvider<S> {
+ &self.custom_suggestions
+ }
+}
+
+impl<S, T> CommandNode<S> for ArgumentCommandNode<S, T> {
+ fn name(&self) -> &str {
+ &self.name
+ }
+
+ fn parse(
+ &self,
+ reader: StringReader,
+ context_builder: CommandContextBuilder<S>,
+ ) -> Result<(), CommandSyntaxException> {
+ // final int start = reader.getCursor();
+ // final T result = type.parse(reader);
+ // final ParsedArgument<S, T> parsed = new ParsedArgument<>(start, reader.getCursor(), result);
+
+ // contextBuilder.withArgument(name, parsed);
+ // contextBuilder.withNode(this, parsed.getRange());
+
+ let start = reader.get_cursor();
+ let result = self.get_type().parse(reader)?;
+ let parsed = ParsedArgument::new(start, reader.get_cursor(), result);
+
+ context_builder.with_argument(&self.name, parsed);
+ context_builder.with_node(self, parsed.get_range());
+
+ Ok(())
+ }
+
+ fn list_suggestions(
+ &self,
+ context: CommandContext<S>,
+ builder: SuggestionsBuilder,
+ ) -> Result<Suggestions, CommandSyntaxException> {
+ if self.custom_suggestions.is_none() {
+ self.get_type().list_suggestions(context, builder)
+ } else {
+ self.custom_suggestions.get_suggestions(context, builder)
+ }
+ }
+
+ fn is_valid_input(&self, input: &str) -> bool {
+ let reader = StringReader::new(input);
+ let result = self.get_type().parse(reader);
+ if result.is_ok() {
+ return !reader.can_read() || reader.peek() == ' ';
+ } else {
+ return false;
+ }
+ }
+
+ fn usage_text(&self) -> &str {
+ USAGE_ARGUMENT_OPEN + self.name + USAGE_ARGUMENT_CLOSE
+ }
+
+ fn create_builder(&self) -> RequiredArgumentBuilder<S, T> {
+ let builder = RequiredArgumentBuilder::argument(&self.name, &self.type_);
+ builder.requires(self.base.get_requirement());
+ builder.forward(
+ self.base.get_redirect(),
+ self.base.get_redirect_modifier(),
+ self.base.is_fork(),
+ );
+ builder.suggests(self.custom_suggestions());
+ if self.base.get_command() != None {
+ builder.executes(self.base.get_command().unwrap());
+ }
+ builder
+ }
+
+ fn get_examples(&self) -> Vec<String> {
+ self.type_.get_examples()
+ }
+}
+
+impl Display for ArgumentCommandNode<String, String> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "<argument {}: {}>", self.name, self.type_)
+ }
+}
diff --git a/azalea-brigadier/src/tree/command_node.rs b/azalea-brigadier/src/tree/command_node.rs
index e69de29b..286820b9 100644
--- a/azalea-brigadier/src/tree/command_node.rs
+++ b/azalea-brigadier/src/tree/command_node.rs
@@ -0,0 +1,52 @@
+use std::collections::HashMap;
+
+use crate::{
+ builder::argument_builder::ArgumentBuilder,
+ command::Command,
+ context::{command_context::CommandContext, command_context_builder::CommandContextBuilder},
+ exceptions::command_syntax_exception::CommandSyntaxException,
+ redirect_modifier::RedirectModifier,
+ string_reader::StringReader,
+ suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder},
+};
+
+use super::{argument_command_node::ArgumentCommandNode, literal_command_node::LiteralCommandNode};
+
+pub struct BaseCommandNode<S> {
+ // private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
+ // private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>();
+ // private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>();
+ // private final Predicate<S> requirement;
+ // private final CommandNode<S> redirect;
+ // private final RedirectModifier<S> modifier;
+ // private final boolean forks;
+ // private Command<S> command;
+ children: HashMap<String, dyn CommandNode<S>>,
+ literals: HashMap<String, LiteralCommandNode<S>>,
+ arguments: HashMap<String, ArgumentCommandNode<S, _>>,
+ requirement: Option<dyn Fn(&S) -> bool>,
+ redirect: Option<dyn CommandNode<S>>,
+ modifier: Option<dyn RedirectModifier<S>>,
+ forks: bool,
+ command: Option<dyn Command<S>>,
+}
+
+impl<S> BaseCommandNode<S> {}
+
+pub trait CommandNode<S> {
+ fn name(&self) -> &str;
+ fn usage_text(&self) -> &str;
+ fn parse(
+ &self,
+ reader: StringReader,
+ context_builder: CommandContextBuilder<S>,
+ ) -> Result<(), CommandSyntaxException>;
+ fn list_suggestions(
+ &self,
+ context: CommandContext<S>,
+ builder: SuggestionsBuilder,
+ ) -> Result<Suggestions, CommandSyntaxException>;
+ fn is_valid_input(&self, input: &str) -> bool;
+ fn create_builder<T>(&self) -> dyn ArgumentBuilder<S, T>;
+ fn get_examples(&self) -> Vec<String>;
+}
diff --git a/azalea-brigadier/src/tree/literal_command_node.rs b/azalea-brigadier/src/tree/literal_command_node.rs
index e69de29b..bb0e613c 100644
--- a/azalea-brigadier/src/tree/literal_command_node.rs
+++ b/azalea-brigadier/src/tree/literal_command_node.rs
@@ -0,0 +1,96 @@
+use crate::{
+ context::{command_context::CommandContext, command_context_builder::CommandContextBuilder},
+ exceptions::{
+ builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException,
+ },
+ string_reader::StringReader,
+ suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder},
+};
+
+use super::command_node::{BaseCommandNode, CommandNode};
+
+#[derive(Hash, PartialEq, Eq, Debug, Clone)]
+pub struct LiteralCommandNode<S> {
+ literal: String,
+ literal_lowercase: String,
+ // Since Rust doesn't have extending, we put the struct this is extending as the "base" field
+ pub base: BaseCommandNode<S>,
+}
+
+impl<S> LiteralCommandNode<S> {
+ pub fn literal(&self) -> &String {
+ &self.literal
+ }
+
+ pub fn parse(&self, reader: StringReader) -> i32 {
+ let start = reader.get_cursor();
+ if reader.can_read(self.literal.len()) {
+ let end = start + self.literal.len();
+ if reader.get_string()[start..end].eq(&self.literal) {
+ reader.set_cursor(end);
+ if !reader.can_read() || reader.peek() == ' ' {
+ return end as i32;
+ } else {
+ reader.set_cursor(start);
+ }
+ }
+ }
+ -1
+ }
+}
+
+impl<S> CommandNode<S> for LiteralCommandNode<S> {
+ fn name(&self) -> &str {
+ &self.literal
+ }
+
+ fn parse(
+ &self,
+ reader: StringReader,
+ context_builder: CommandContextBuilder<S>,
+ ) -> Result<(), CommandSyntaxException> {
+ let start = reader.get_cursor();
+ let end = self.parse(reader);
+ if end > -1 {
+ return Ok(());
+ }
+
+ Err(BuiltInExceptions::LiteralIncorrect {
+ expected: self.literal(),
+ }
+ .create_with_context(reader))
+ }
+
+ fn list_suggestions(
+ &self,
+ context: CommandContext<S>,
+ builder: SuggestionsBuilder,
+ ) -> Result<Suggestions, CommandSyntaxException> {
+ if self
+ .literal_lowercase
+ .starts_with(&builder.remaining_lowercase())
+ {
+ builder.suggest(self.literal())
+ } else {
+ Suggestions::empty()
+ }
+ }
+
+ fn is_valid_input(&self, input: &str) -> bool {
+ self.parse(StringReader::from(input)) > -1
+ }
+
+ fn usage_text(&self) -> &str {
+ self.literal
+ }
+
+ fn create_builder(&self) -> LiteralArgumentBuilder<S> {
+ let builder = LiteralArgumentBuilder::literal(self.literal());
+ builder.requires(self.requirement());
+ builder.forward(self.redirect(), self.redirect_modifier(), self.is_fork());
+ if self.command().is_some() {
+ builder.executes(self.command().unwrap());
+ }
+ builder
+ }
+}
diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs
index e69de29b..3dc22583 100644
--- a/azalea-brigadier/src/tree/mod.rs
+++ b/azalea-brigadier/src/tree/mod.rs
@@ -0,0 +1,4 @@
+pub mod argument_command_node;
+pub mod command_node;
+pub mod literal_command_node;
+pub mod root_command_node;
diff --git a/azalea-brigadier/src/tree/root_command_node.rs b/azalea-brigadier/src/tree/root_command_node.rs
index e69de29b..004ab6a8 100644
--- a/azalea-brigadier/src/tree/root_command_node.rs
+++ b/azalea-brigadier/src/tree/root_command_node.rs
@@ -0,0 +1,61 @@
+use std::fmt::{Display, Formatter};
+
+use crate::{
+ context::{command_context::CommandContext, command_context_builder::CommandContextBuilder},
+ exceptions::{
+ builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException,
+ },
+ string_reader::StringReader,
+ suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder},
+};
+
+use super::command_node::{BaseCommandNode, CommandNode};
+
+#[derive(Hash, PartialEq, Eq, Debug, Clone)]
+pub struct RootCommandNode<S> {
+ // Since Rust doesn't have extending, we put the struct this is extending as the "base" field
+ pub base: BaseCommandNode<S>,
+}
+
+impl<S> CommandNode<S> for RootCommandNode<S> {
+ fn name(&self) -> &str {
+ ""
+ }
+
+ fn parse(
+ &self,
+ reader: StringReader,
+ context_builder: CommandContextBuilder<S>,
+ ) -> Result<(), CommandSyntaxException> {
+ }
+
+ fn list_suggestions(
+ &self,
+ context: CommandContext<S>,
+ builder: SuggestionsBuilder,
+ ) -> Result<Suggestions, CommandSyntaxException> {
+ Suggestions::empty()
+ }
+
+ fn is_valid_input(&self, input: &str) -> bool {
+ false
+ }
+
+ fn usage_text(&self) -> &str {
+ ""
+ }
+
+ fn create_builder(&self) -> () {
+ panic!("Cannot convert root into a builder");
+ }
+
+ fn get_examples(&self) -> Vec<String> {
+ vec![]
+ }
+}
+
+impl Display for RootCommandNode<()> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "<root>")
+ }
+}