aboutsummaryrefslogtreecommitdiff
path: root/azalea-brigadier/src
diff options
context:
space:
mode:
authormat <github@matdoes.dev>2022-04-24 17:37:57 -0500
committermat <github@matdoes.dev>2022-04-24 17:37:57 -0500
commit3e507f0db4020eaf406ba69aae3d4dc1301d29ac (patch)
treeca6c127c9db6dfd14511e98944fc031fe5f1e43a /azalea-brigadier/src
parent9f576c5600ba9a244bc0d433bb7de174284066a2 (diff)
parentb7641ff308aab7840d2a2253ae50f8ee496b2a97 (diff)
downloadazalea-drasl-3e507f0db4020eaf406ba69aae3d4dc1301d29ac.tar.xz
Merge branch 'main' into auth
Diffstat (limited to 'azalea-brigadier/src')
-rwxr-xr-xazalea-brigadier/src/arguments/argument_type.rs7
-rwxr-xr-xazalea-brigadier/src/arguments/integer_argument_type.rs54
-rwxr-xr-xazalea-brigadier/src/arguments/mod.rs4
-rwxr-xr-xazalea-brigadier/src/builder/argument_builder.rs137
-rwxr-xr-xazalea-brigadier/src/builder/literal_argument_builder.rs24
-rwxr-xr-xazalea-brigadier/src/builder/mod.rs3
-rwxr-xr-xazalea-brigadier/src/builder/required_argument_builder.rs45
-rwxr-xr-xazalea-brigadier/src/command_dispatcher.rs298
-rwxr-xr-xazalea-brigadier/src/context/command_context.rs80
-rwxr-xr-xazalea-brigadier/src/context/command_context_builder.rs116
-rwxr-xr-xazalea-brigadier/src/context/mod.rs11
-rwxr-xr-xazalea-brigadier/src/context/parsed_argument.rs8
-rwxr-xr-xazalea-brigadier/src/context/parsed_command_node.rs18
-rwxr-xr-xazalea-brigadier/src/context/string_range.rs45
-rwxr-xr-xazalea-brigadier/src/exceptions/builtin_exceptions.rs159
-rwxr-xr-xazalea-brigadier/src/exceptions/command_syntax_exception.rs91
-rwxr-xr-xazalea-brigadier/src/exceptions/mod.rs5
-rwxr-xr-xazalea-brigadier/src/lib.rs10
-rwxr-xr-xazalea-brigadier/src/message.rs14
-rwxr-xr-xazalea-brigadier/src/modifier.rs6
-rwxr-xr-xazalea-brigadier/src/parse_results.rs21
-rwxr-xr-xazalea-brigadier/src/string_reader.rs274
-rwxr-xr-xazalea-brigadier/src/tree/mod.rs259
23 files changed, 1689 insertions, 0 deletions
diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs
new file mode 100755
index 00000000..029e4696
--- /dev/null
+++ b/azalea-brigadier/src/arguments/argument_type.rs
@@ -0,0 +1,7 @@
+use std::{any::Any, rc::Rc};
+
+use crate::{exceptions::CommandSyntaxException, string_reader::StringReader};
+
+pub trait ArgumentType {
+ fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException>;
+}
diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs
new file mode 100755
index 00000000..336046a7
--- /dev/null
+++ b/azalea-brigadier/src/arguments/integer_argument_type.rs
@@ -0,0 +1,54 @@
+use std::{any::Any, rc::Rc};
+
+use crate::{
+ context::CommandContext,
+ exceptions::{BuiltInExceptions, CommandSyntaxException},
+ string_reader::StringReader,
+};
+
+use super::ArgumentType;
+
+#[derive(Default)]
+struct Integer {
+ pub minimum: Option<i32>,
+ pub maximum: Option<i32>,
+}
+
+impl ArgumentType for Integer {
+ fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
+ let start = reader.cursor;
+ let result = reader.read_int()?;
+ if let Some(minimum) = self.minimum {
+ if result < minimum {
+ reader.cursor = start;
+ return Err(BuiltInExceptions::IntegerTooSmall {
+ found: result,
+ min: minimum,
+ }
+ .create_with_context(reader));
+ }
+ }
+ if let Some(maximum) = self.maximum {
+ if result > maximum {
+ reader.cursor = start;
+ return Err(BuiltInExceptions::IntegerTooBig {
+ found: result,
+ max: maximum,
+ }
+ .create_with_context(reader));
+ }
+ }
+ Ok(Rc::new(result))
+ }
+}
+
+pub fn integer() -> impl ArgumentType {
+ Integer::default()
+}
+pub fn get_integer<S>(context: &CommandContext<S>, name: &str) -> Option<i32> {
+ context
+ .argument(name)
+ .unwrap()
+ .downcast_ref::<i32>()
+ .copied()
+}
diff --git a/azalea-brigadier/src/arguments/mod.rs b/azalea-brigadier/src/arguments/mod.rs
new file mode 100755
index 00000000..dec39297
--- /dev/null
+++ b/azalea-brigadier/src/arguments/mod.rs
@@ -0,0 +1,4 @@
+mod argument_type;
+pub mod integer_argument_type;
+
+pub use argument_type::ArgumentType;
diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs
new file mode 100755
index 00000000..d26b2a8a
--- /dev/null
+++ b/azalea-brigadier/src/builder/argument_builder.rs
@@ -0,0 +1,137 @@
+use crate::{context::CommandContext, modifier::RedirectModifier, tree::CommandNode};
+
+use super::{literal_argument_builder::Literal, required_argument_builder::Argument};
+use std::{cell::RefCell, fmt::Debug, rc::Rc};
+
+#[derive(Debug, Clone)]
+pub enum ArgumentBuilderType {
+ Literal(Literal),
+ Argument(Argument),
+}
+
+/// A node that hasn't yet been built.
+pub struct ArgumentBuilder<S> {
+ arguments: CommandNode<S>,
+
+ command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ requirement: Rc<dyn Fn(Rc<S>) -> bool>,
+ target: Option<Rc<RefCell<CommandNode<S>>>>,
+
+ forks: bool,
+ modifier: Option<Rc<RedirectModifier<S>>>,
+}
+
+impl<S> Clone for ArgumentBuilder<S> {
+ fn clone(&self) -> Self {
+ Self {
+ arguments: self.arguments.clone(),
+ command: self.command.clone(),
+ requirement: self.requirement.clone(),
+ target: self.target.clone(),
+ forks: self.forks,
+ modifier: self.modifier.clone(),
+ }
+ }
+}
+
+/// A node that isn't yet built.
+impl<S> ArgumentBuilder<S> {
+ pub fn new(value: ArgumentBuilderType) -> Self {
+ Self {
+ arguments: CommandNode {
+ value,
+ ..Default::default()
+ },
+ command: None,
+ requirement: Rc::new(|_| true),
+ forks: false,
+ modifier: None,
+ target: None,
+ }
+ }
+
+ pub fn then(&mut self, argument: ArgumentBuilder<S>) -> Self {
+ self.then_built(argument.build())
+ }
+
+ pub fn then_built(&mut self, argument: CommandNode<S>) -> Self {
+ self.arguments.add_child(&Rc::new(RefCell::new(argument)));
+ self.clone()
+ }
+
+ pub fn executes<F>(&mut self, f: F) -> Self
+ where
+ F: Fn(&CommandContext<S>) -> i32 + 'static,
+ {
+ self.command = Some(Rc::new(f));
+ self.clone()
+ }
+
+ pub fn requires<F>(&mut self, requirement: F) -> Self
+ where
+ F: Fn(Rc<S>) -> bool + 'static,
+ {
+ self.requirement = Rc::new(requirement);
+ self.clone()
+ }
+
+ pub fn redirect(&mut self, target: Rc<RefCell<CommandNode<S>>>) -> Self {
+ self.forward(target, None, false)
+ }
+
+ pub fn fork(
+ &mut self,
+ target: Rc<RefCell<CommandNode<S>>>,
+ modifier: Rc<RedirectModifier<S>>,
+ ) -> Self {
+ self.forward(target, Some(modifier), true)
+ }
+
+ pub fn forward(
+ &mut self,
+ target: Rc<RefCell<CommandNode<S>>>,
+ modifier: Option<Rc<RedirectModifier<S>>>,
+ fork: bool,
+ ) -> Self {
+ if !self.arguments.children.is_empty() {
+ panic!("Cannot forward a node with children");
+ }
+ self.target = Some(target);
+ self.modifier = modifier;
+ self.forks = fork;
+ self.clone()
+ }
+
+ pub fn build(self) -> CommandNode<S> {
+ let mut result = CommandNode {
+ value: self.arguments.value,
+ command: self.command,
+ requirement: self.requirement,
+ redirect: self.target,
+ modifier: self.modifier,
+ forks: self.forks,
+ arguments: Default::default(),
+ children: Default::default(),
+ literals: Default::default(),
+ };
+
+ for argument in self.arguments.children.values() {
+ result.add_child(argument);
+ }
+
+ result
+ }
+}
+
+impl<S> Debug for ArgumentBuilder<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ArgumentBuilder")
+ .field("arguments", &self.arguments)
+ // .field("command", &self.command)
+ // .field("requirement", &self.requirement)
+ .field("target", &self.target)
+ .field("forks", &self.forks)
+ // .field("modifier", &self.modifier)
+ .finish()
+ }
+}
diff --git a/azalea-brigadier/src/builder/literal_argument_builder.rs b/azalea-brigadier/src/builder/literal_argument_builder.rs
new file mode 100755
index 00000000..6627ffdc
--- /dev/null
+++ b/azalea-brigadier/src/builder/literal_argument_builder.rs
@@ -0,0 +1,24 @@
+use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
+
+#[derive(Debug, Clone, Default)]
+pub struct Literal {
+ pub value: String,
+}
+impl Literal {
+ pub fn new(value: &str) -> Self {
+ Self {
+ value: value.to_string(),
+ }
+ }
+}
+
+impl From<Literal> for ArgumentBuilderType {
+ fn from(literal: Literal) -> Self {
+ Self::Literal(literal)
+ }
+}
+
+/// Shortcut for creating a new literal builder node.
+pub fn literal<S>(value: &str) -> ArgumentBuilder<S> {
+ ArgumentBuilder::new(ArgumentBuilderType::Literal(Literal::new(value)))
+}
diff --git a/azalea-brigadier/src/builder/mod.rs b/azalea-brigadier/src/builder/mod.rs
new file mode 100755
index 00000000..26f2f644
--- /dev/null
+++ 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/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs
new file mode 100755
index 00000000..9d4d9e0a
--- /dev/null
+++ b/azalea-brigadier/src/builder/required_argument_builder.rs
@@ -0,0 +1,45 @@
+use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
+use crate::{
+ arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader,
+};
+use std::{any::Any, fmt::Debug, rc::Rc};
+
+/// An argument node type. The `T` type parameter is the type of the argument,
+/// which can be anything.
+#[derive(Clone)]
+pub struct Argument {
+ pub name: String,
+ parser: Rc<dyn ArgumentType>,
+}
+impl Argument {
+ pub fn new(name: &str, parser: Rc<dyn ArgumentType>) -> Self {
+ Self {
+ name: name.to_string(),
+ parser,
+ }
+ }
+
+ pub fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
+ self.parser.parse(reader)
+ }
+}
+
+impl From<Argument> for ArgumentBuilderType {
+ fn from(argument: Argument) -> Self {
+ Self::Argument(argument)
+ }
+}
+
+impl Debug for Argument {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Argument")
+ .field("name", &self.name)
+ // .field("parser", &self.parser)
+ .finish()
+ }
+}
+
+/// Shortcut for creating a new argument builder node.
+pub fn argument<S>(name: &str, parser: impl ArgumentType + 'static) -> ArgumentBuilder<S> {
+ ArgumentBuilder::new(Argument::new(name, Rc::new(parser)).into())
+}
diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs
new file mode 100755
index 00000000..eab42dd8
--- /dev/null
+++ b/azalea-brigadier/src/command_dispatcher.rs
@@ -0,0 +1,298 @@
+use crate::{
+ builder::argument_builder::ArgumentBuilder,
+ context::{CommandContext, CommandContextBuilder},
+ exceptions::{BuiltInExceptions, CommandSyntaxException},
+ parse_results::ParseResults,
+ string_reader::StringReader,
+ tree::CommandNode,
+};
+use std::{cell::RefCell, cmp::Ordering, collections::HashMap, marker::PhantomData, mem, rc::Rc};
+
+#[derive(Default)]
+pub struct CommandDispatcher<S> {
+ pub root: Rc<RefCell<CommandNode<S>>>,
+ _marker: PhantomData<S>,
+}
+
+impl<S> CommandDispatcher<S> {
+ pub fn new() -> Self {
+ Self {
+ root: Rc::new(RefCell::new(CommandNode::default())),
+ _marker: PhantomData,
+ }
+ }
+
+ pub fn register(&mut self, node: ArgumentBuilder<S>) -> Rc<RefCell<CommandNode<S>>> {
+ let build = Rc::new(RefCell::new(node.build()));
+ self.root.borrow_mut().add_child(&build);
+ build
+ }
+
+ pub fn parse(&self, command: StringReader, source: Rc<S>) -> ParseResults<S> {
+ let context = CommandContextBuilder::new(
+ Rc::new(self.clone()),
+ source,
+ self.root.clone(),
+ command.cursor(),
+ );
+ self.parse_nodes(&self.root, &command, context).unwrap()
+ }
+
+ fn parse_nodes(
+ &self,
+ node: &Rc<RefCell<CommandNode<S>>>,
+ original_reader: &StringReader,
+ context_so_far: CommandContextBuilder<S>,
+ ) -> Result<ParseResults<S>, CommandSyntaxException> {
+ let source = context_so_far.source.clone();
+ let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxException>::new();
+ let mut potentials: Vec<ParseResults<S>> = vec![];
+ let cursor = original_reader.cursor();
+
+ for child in node
+ .borrow()
+ .get_relevant_nodes(&mut original_reader.clone())
+ {
+ if !child.borrow().can_use(source.clone()) {
+ continue;
+ }
+ let mut context = context_so_far.clone();
+ let mut reader = original_reader.clone();
+
+ let parse_with_context_result =
+ child.borrow().parse_with_context(&mut reader, &mut context);
+ if let Err(ex) = parse_with_context_result {
+ errors.insert(
+ Rc::new((*child.borrow()).clone()),
+ BuiltInExceptions::DispatcherParseException {
+ message: ex.message(),
+ }
+ .create_with_context(&reader),
+ );
+ reader.cursor = cursor;
+ continue;
+ }
+ if reader.can_read() && reader.peek() != ' ' {
+ errors.insert(
+ Rc::new((*child.borrow()).clone()),
+ BuiltInExceptions::DispatcherExpectedArgumentSeparator
+ .create_with_context(&reader),
+ );
+ reader.cursor = cursor;
+ continue;
+ }
+
+ context.with_command(&child.borrow().command);
+ if reader.can_read_length(if child.borrow().redirect.is_none() {
+ 2
+ } else {
+ 1
+ }) {
+ reader.skip();
+ if let Some(redirect) = &child.borrow().redirect {
+ let child_context = CommandContextBuilder::new(
+ Rc::new(self.clone()),
+ source,
+ redirect.clone(),
+ reader.cursor,
+ );
+ let parse = self
+ .parse_nodes(redirect, &reader, child_context)
+ .expect("Parsing nodes failed");
+ context.with_child(Rc::new(parse.context));
+ return Ok(ParseResults {
+ context,
+ reader: parse.reader,
+ exceptions: parse.exceptions,
+ });
+ } else {
+ let parse = self
+ .parse_nodes(&child, &reader, context)
+ .expect("Parsing nodes failed");
+ potentials.push(parse);
+ }
+ } else {
+ potentials.push(ParseResults {
+ context,
+ reader,
+ exceptions: HashMap::new(),
+ });
+ }
+ }
+
+ if !potentials.is_empty() {
+ if potentials.len() > 1 {
+ potentials.sort_by(|a, b| {
+ if !a.reader.can_read() && b.reader.can_read() {
+ return Ordering::Less;
+ };
+ if a.reader.can_read() && !b.reader.can_read() {
+ return Ordering::Greater;
+ };
+ if a.exceptions.is_empty() && !b.exceptions.is_empty() {
+ return Ordering::Less;
+ };
+ if !a.exceptions.is_empty() && b.exceptions.is_empty() {
+ return Ordering::Greater;
+ };
+ Ordering::Equal
+ })
+ }
+ let best_potential = potentials.into_iter().next().unwrap();
+ return Ok(best_potential);
+ }
+
+ Ok(ParseResults {
+ context: context_so_far,
+ reader: original_reader.clone(),
+ exceptions: errors,
+ })
+ }
+
+ pub fn execute(
+ &self,
+ input: StringReader,
+ source: Rc<S>,
+ ) -> Result<i32, CommandSyntaxException> {
+ let parse = self.parse(input, source);
+ Self::execute_parsed(parse)
+ }
+
+ pub fn add_paths(
+ &self,
+ node: Rc<RefCell<CommandNode<S>>>,
+ result: &mut Vec<Vec<Rc<RefCell<CommandNode<S>>>>>,
+ parents: Vec<Rc<RefCell<CommandNode<S>>>>,
+ ) {
+ let mut current = parents;
+ current.push(node.clone());
+ result.push(current.clone());
+
+ for child in node.borrow().children.values() {
+ self.add_paths(child.clone(), result, current.clone());
+ }
+ }
+
+ pub fn get_path(&self, target: CommandNode<S>) -> Vec<String> {
+ let rc_target = Rc::new(RefCell::new(target));
+ let mut nodes: Vec<Vec<Rc<RefCell<CommandNode<S>>>>> = Vec::new();
+ self.add_paths(self.root.clone(), &mut nodes, vec![]);
+
+ for list in nodes {
+ if *list.last().expect("Nothing in list").borrow() == *rc_target.borrow() {
+ let mut result: Vec<String> = Vec::with_capacity(list.len());
+ for node in list {
+ if node != self.root {
+ result.push(node.borrow().name().to_string());
+ }
+ }
+ return result;
+ }
+ }
+ vec![]
+ }
+
+ pub fn find_node(&self, path: &[&str]) -> Option<Rc<RefCell<CommandNode<S>>>> {
+ let mut node = self.root.clone();
+ for name in path {
+ if let Some(child) = node.clone().borrow().child(name) {
+ node = child
+ } else {
+ return None;
+ }
+ }
+ Some(node)
+ }
+
+ /// Executes a given pre-parsed command.
+ pub fn execute_parsed(parse: ParseResults<S>) -> Result<i32, CommandSyntaxException> {
+ if parse.reader.can_read() {
+ if parse.exceptions.len() == 1 {
+ return Err(parse.exceptions.values().next().unwrap().clone());
+ }
+ if parse.context.range.is_empty() {
+ return Err(
+ BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
+ );
+ }
+ return Err(
+ BuiltInExceptions::DispatcherUnknownArgument.create_with_context(&parse.reader)
+ );
+ }
+ let mut result = 0i32;
+ let mut successful_forks = 0;
+ let mut forked = false;
+ let mut found_command = false;
+ let command = parse.reader.string();
+ let original = parse.context.build(command);
+ let mut contexts = vec![original];
+ let mut next: Vec<CommandContext<S>> = vec![];
+
+ while !contexts.is_empty() {
+ for context in contexts.iter() {
+ let child = &context.child;
+ if let Some(child) = child {
+ println!("aaaaaaa {:?}", child);
+ forked |= child.forks;
+ if child.has_nodes() {
+ found_command = true;
+ let modifier = &context.modifier;
+ if let Some(modifier) = modifier {
+ let results = modifier(context);
+ if let Ok(results) = results {
+ if !results.is_empty() {
+ next.extend(results.iter().map(|s| child.copy_for(s.clone())));
+ }
+ } else {
+ // TODO
+ // self.consumer.on_command_complete(context, false, 0);
+ if !forked {
+ return Err(results.err().unwrap());
+ }
+ }
+ } else {
+ next.push(child.copy_for(context.source.clone()));
+ }
+ }
+ } else if let Some(context_command) = &context.command {
+ found_command = true;
+
+ let value = context_command(context);
+ result += value;
+ // consumer.on_command_complete(context, true, value);
+ successful_forks += 1;
+
+ // TODO: allow context_command to error and handle those errors
+ }
+ }
+
+ // move next into contexts and clear next
+ mem::swap(&mut contexts, &mut next);
+ next.clear();
+ }
+
+ if !found_command {
+ // consumer.on_command_complete(original, false, 0);
+ return Err(
+ BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
+ );
+ }
+
+ // TODO: this is not how vanilla does it but it works
+ Ok(if successful_forks >= 2 {
+ successful_forks
+ } else {
+ result
+ })
+ // Ok(if forked { successful_forks } else { result })
+ }
+}
+
+impl<S> Clone for CommandDispatcher<S> {
+ fn clone(&self) -> Self {
+ Self {
+ root: self.root.clone(),
+ _marker: PhantomData,
+ }
+ }
+}
diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs
new file mode 100755
index 00000000..1834a73d
--- /dev/null
+++ b/azalea-brigadier/src/context/command_context.rs
@@ -0,0 +1,80 @@
+use super::{parsed_command_node::ParsedCommandNode, string_range::StringRange, ParsedArgument};
+use crate::{modifier::RedirectModifier, tree::CommandNode};
+use std::{any::Any, cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
+
+/// A built `CommandContextBuilder`.
+pub struct CommandContext<S> {
+ pub source: Rc<S>,
+ pub input: String,
+ pub arguments: HashMap<String, ParsedArgument>,
+ pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ pub root_node: Rc<RefCell<CommandNode<S>>>,
+ pub nodes: Vec<ParsedCommandNode<S>>,
+ pub range: StringRange,
+ pub child: Option<Rc<CommandContext<S>>>,
+ pub modifier: Option<Rc<RedirectModifier<S>>>,
+ pub forks: bool,
+}
+
+impl<S> Clone for CommandContext<S> {
+ fn clone(&self) -> Self {
+ Self {
+ source: self.source.clone(),
+ input: self.input.clone(),
+ arguments: self.arguments.clone(),
+ command: self.command.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,
+ }
+ }
+}
+
+impl<S> Debug for CommandContext<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CommandContext")
+ // .field("source", &self.source)
+ .field("input", &self.input)
+ // .field("arguments", &self.arguments)
+ // .field("command", &self.command)
+ // .field("root_node", &self.root_node)
+ // .field("nodes", &self.nodes)
+ .field("range", &self.range)
+ .field("child", &self.child)
+ // .field("modifier", &self.modifier)
+ .field("forks", &self.forks)
+ .finish()
+ }
+}
+
+impl<S> CommandContext<S> {
+ pub fn copy_for(&self, source: Rc<S>) -> Self {
+ if Rc::ptr_eq(&source, &self.source) {
+ return self.clone();
+ }
+ CommandContext {
+ source,
+ input: self.input.clone(),
+ arguments: self.arguments.clone(),
+ command: self.command.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,
+ }
+ }
+
+ pub fn has_nodes(&self) -> bool {
+ !self.nodes.is_empty()
+ }
+
+ pub fn argument(&self, name: &str) -> Option<Rc<dyn Any>> {
+ let argument = self.arguments.get(name);
+ argument.map(|a| a.result.clone())
+ }
+}
diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs
new file mode 100755
index 00000000..f192f6b7
--- /dev/null
+++ b/azalea-brigadier/src/context/command_context_builder.rs
@@ -0,0 +1,116 @@
+use super::{
+ command_context::CommandContext, parsed_command_node::ParsedCommandNode,
+ string_range::StringRange, ParsedArgument,
+};
+use crate::{command_dispatcher::CommandDispatcher, modifier::RedirectModifier, tree::CommandNode};
+use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
+
+pub struct CommandContextBuilder<S> {
+ pub arguments: HashMap<String, ParsedArgument>,
+ pub root: Rc<RefCell<CommandNode<S>>>,
+ pub nodes: Vec<ParsedCommandNode<S>>,
+ pub dispatcher: Rc<CommandDispatcher<S>>,
+ pub source: Rc<S>,
+ pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ pub child: Option<Rc<CommandContextBuilder<S>>>,
+ pub range: StringRange,
+ pub modifier: Option<Rc<RedirectModifier<S>>>,
+ pub forks: bool,
+}
+
+impl<S> Clone for CommandContextBuilder<S> {
+ fn clone(&self) -> Self {
+ Self {
+ arguments: self.arguments.clone(),
+ root: self.root.clone(),
+ nodes: self.nodes.clone(),
+ dispatcher: self.dispatcher.clone(),
+ source: self.source.clone(),
+ command: self.command.clone(),
+ child: self.child.clone(),
+ range: self.range.clone(),
+ modifier: self.modifier.clone(),
+ forks: self.forks,
+ }
+ }
+}
+
+impl<S> CommandContextBuilder<S> {
+ pub fn new(
+ dispatcher: Rc<CommandDispatcher<S>>,
+ source: Rc<S>,
+ root_node: Rc<RefCell<CommandNode<S>>>,
+ start: usize,
+ ) -> Self {
+ Self {
+ arguments: HashMap::new(),
+ root: root_node,
+ source,
+ range: StringRange::at(start),
+ command: None,
+ dispatcher,
+ nodes: vec![],
+ child: None,
+ modifier: None,
+ forks: false,
+ }
+ }
+
+ pub fn with_command(
+ &mut self,
+ command: &Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ ) -> &Self {
+ self.command = command.clone();
+ self
+ }
+ pub fn with_child(&mut self, child: Rc<CommandContextBuilder<S>>) -> &Self {
+ self.child = Some(child);
+ self
+ }
+ pub fn with_argument(&mut self, name: &str, argument: ParsedArgument) -> &Self {
+ self.arguments.insert(name.to_string(), argument);
+ self
+ }
+ pub fn with_node(&mut self, node: Rc<RefCell<CommandNode<S>>>, range: StringRange) -> &Self {
+ self.nodes.push(ParsedCommandNode {
+ node: node.clone(),
+ range: range.clone(),
+ });
+ self.range = StringRange::encompassing(&self.range, &range);
+ self.modifier = node.borrow().modifier.clone();
+ self.forks = node.borrow().forks;
+ self
+ }
+
+ pub fn build(&self, input: &str) -> CommandContext<S> {
+ CommandContext {
+ arguments: self.arguments.clone(),
+ root_node: self.root.clone(),
+ nodes: self.nodes.clone(),
+ source: self.source.clone(),
+ command: self.command.clone(),
+ child: self.child.clone().map(|c| Rc::new(c.build(input))),
+ range: self.range.clone(),
+ forks: self.forks,
+ modifier: self.modifier.clone(),
+ input: input.to_string(),
+ }
+ }
+}
+
+impl<S> Debug for CommandContextBuilder<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CommandContextBuilder")
+ // .field("arguments", &self.arguments)
+ .field("root", &self.root)
+ // .field("nodes", &self.nodes)
+ // .field("dispatcher", &self.dispatcher)
+ // .field("source", &self.source)
+ // .field("command", &self.command)
+ .field("child", &self.child)
+ .field("range", &self.range)
+ // .field("modifier", &self.modifier)
+ .field("forks", &self.forks)
+ .finish()
+ }
+}
diff --git a/azalea-brigadier/src/context/mod.rs b/azalea-brigadier/src/context/mod.rs
new file mode 100755
index 00000000..d535602a
--- /dev/null
+++ b/azalea-brigadier/src/context/mod.rs
@@ -0,0 +1,11 @@
+mod command_context;
+mod command_context_builder;
+mod parsed_argument;
+mod parsed_command_node;
+mod string_range;
+
+pub use command_context::CommandContext;
+pub use command_context_builder::CommandContextBuilder;
+pub use parsed_argument::ParsedArgument;
+pub use parsed_command_node::ParsedCommandNode;
+pub use string_range::StringRange;
diff --git a/azalea-brigadier/src/context/parsed_argument.rs b/azalea-brigadier/src/context/parsed_argument.rs
new file mode 100755
index 00000000..3302b1be
--- /dev/null
+++ b/azalea-brigadier/src/context/parsed_argument.rs
@@ -0,0 +1,8 @@
+use super::string_range::StringRange;
+use std::{any::Any, rc::Rc};
+
+#[derive(Clone)]
+pub struct ParsedArgument {
+ pub range: StringRange,
+ pub result: Rc<dyn Any>,
+}
diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs
new file mode 100755
index 00000000..ed49928d
--- /dev/null
+++ b/azalea-brigadier/src/context/parsed_command_node.rs
@@ -0,0 +1,18 @@
+use super::string_range::StringRange;
+use crate::tree::CommandNode;
+use std::{cell::RefCell, rc::Rc};
+
+#[derive(Debug)]
+pub struct ParsedCommandNode<S> {
+ pub node: Rc<RefCell<CommandNode<S>>>,
+ pub range: StringRange,
+}
+
+impl<S> Clone for ParsedCommandNode<S> {
+ fn clone(&self) -> Self {
+ Self {
+ node: self.node.clone(),
+ range: self.range.clone(),
+ }
+ }
+}
diff --git a/azalea-brigadier/src/context/string_range.rs b/azalea-brigadier/src/context/string_range.rs
new file mode 100755
index 00000000..8ca88624
--- /dev/null
+++ b/azalea-brigadier/src/context/string_range.rs
@@ -0,0 +1,45 @@
+use std::cmp;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
+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<'a>(&self, reader: &'a str) -> &'a 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/exceptions/builtin_exceptions.rs b/azalea-brigadier/src/exceptions/builtin_exceptions.rs
new file mode 100755
index 00000000..09951a03
--- /dev/null
+++ b/azalea-brigadier/src/exceptions/builtin_exceptions.rs
@@ -0,0 +1,159 @@
+use std::fmt;
+
+use crate::{message::Message, string_reader::StringReader};
+
+use super::command_syntax_exception::CommandSyntaxException;
+
+#[derive(Clone, PartialEq)]
+pub enum BuiltInExceptions {
+ DoubleTooSmall { found: f64, min: f64 },
+ DoubleTooBig { found: f64, max: f64 },
+
+ FloatTooSmall { found: f32, min: f32 },
+ FloatTooBig { found: f32, max: f32 },
+
+ IntegerTooSmall { found: i32, min: i32 },
+ IntegerTooBig { found: i32, max: i32 },
+
+ LongTooSmall { found: i64, min: i64 },
+ LongTooBig { found: i64, max: i64 },
+
+ LiteralIncorrect { expected: String },
+
+ ReaderExpectedStartOfQuote,
+ ReaderExpectedEndOfQuote,
+ ReaderInvalidEscape { character: char },
+ ReaderInvalidBool { value: String },
+ ReaderInvalidInt { value: String },
+ ReaderExpectedInt,
+ ReaderInvalidLong { value: String },
+ ReaderExpectedLong,
+ ReaderInvalidDouble { value: String },
+ ReaderExpectedDouble,
+ ReaderInvalidFloat { value: String },
+ ReaderExpectedFloat,
+ ReaderExpectedBool,
+ ReaderExpectedSymbol { symbol: char },
+
+ DispatcherUnknownCommand,
+ DispatcherUnknownArgument,
+ DispatcherExpectedArgumentSeparator,
+ DispatcherParseException { message: String },
+}
+
+impl fmt::Debug for BuiltInExceptions {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ BuiltInExceptions::DoubleTooSmall { found, min } => {
+ write!(f, "Double must not be less than {}, found {}", min, found)
+ }
+ BuiltInExceptions::DoubleTooBig { found, max } => {
+ write!(f, "Double must not be more than {}, found {}", max, found)
+ }
+
+ BuiltInExceptions::FloatTooSmall { found, min } => {
+ write!(f, "Float must not be less than {}, found {}", min, found)
+ }
+ BuiltInExceptions::FloatTooBig { found, max } => {
+ write!(f, "Float must not be more than {}, found {}", max, found)
+ }
+
+ BuiltInExceptions::IntegerTooSmall { found, min } => {
+ write!(f, "Integer must not be less than {}, found {}", min, found)
+ }
+ BuiltInExceptions::IntegerTooBig { found, max } => {
+ write!(f, "Integer must not be more than {}, found {}", max, found)
+ }
+
+ BuiltInExceptions::LongTooSmall { found, min } => {
+ write!(f, "Long must not be less than {}, found {}", min, found)
+ }
+ BuiltInExceptions::LongTooBig { found, max } => {
+ write!(f, "Long must not be more than {}, found {}", max, found)
+ }
+
+ BuiltInExceptions::LiteralIncorrect { expected } => {
+ write!(f, "Expected literal {}", expected)
+ }
+
+ BuiltInExceptions::ReaderExpectedStartOfQuote => {
+ write!(f, "Expected quote to start a string")
+ }
+ BuiltInExceptions::ReaderExpectedEndOfQuote => {
+ write!(f, "Unclosed quoted string")
+ }
+ BuiltInExceptions::ReaderInvalidEscape { character } => {
+ write!(
+ f,
+ "Invalid escape sequence '{}' in quoted string",
+ character
+ )
+ }
+ BuiltInExceptions::ReaderInvalidBool { value } => {
+ write!(
+ f,
+ "Invalid bool, expected true or false but found '{}'",
+ value
+ )
+ }
+ BuiltInExceptions::ReaderInvalidInt { value } => {
+ write!(f, "Invalid Integer '{}'", value)
+ }
+ BuiltInExceptions::ReaderExpectedInt => {
+ write!(f, "Expected Integer")
+ }
+ BuiltInExceptions::ReaderInvalidLong { value } => {
+ write!(f, "Invalid long '{}'", value)
+ }
+ BuiltInExceptions::ReaderExpectedLong => {
+ write!(f, "Expected long")
+ }
+ BuiltInExceptions::ReaderInvalidDouble { value } => {
+ write!(f, "Invalid double '{}'", value)
+ }
+ BuiltInExceptions::ReaderExpectedDouble => {
+ write!(f, "Expected double")
+ }
+ BuiltInExceptions::ReaderInvalidFloat { value } => {
+ write!(f, "Invalid Float '{}'", value)
+ }
+ BuiltInExceptions::ReaderExpectedFloat => {
+ write!(f, "Expected Float")
+ }
+ BuiltInExceptions::ReaderExpectedBool => {
+ write!(f, "Expected bool")
+ }
+ BuiltInExceptions::ReaderExpectedSymbol { symbol } => {
+ write!(f, "Expected '{}'", symbol)
+ }
+
+ BuiltInExceptions::DispatcherUnknownCommand => {
+ write!(f, "Unknown command")
+ }
+ BuiltInExceptions::DispatcherUnknownArgument => {
+ write!(f, "Incorrect argument for command")
+ }
+ BuiltInExceptions::DispatcherExpectedArgumentSeparator => {
+ write!(
+ f,
+ "Expected whitespace to end one argument, but found trailing data"
+ )
+ }
+ BuiltInExceptions::DispatcherParseException { message } => {
+ write!(f, "Could not parse command: {}", message)
+ }
+ }
+ }
+}
+
+impl BuiltInExceptions {
+ pub fn create(self) -> CommandSyntaxException {
+ let message = Message::from(format!("{:?}", self));
+ CommandSyntaxException::create(self, message)
+ }
+
+ pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxException {
+ let message = Message::from(format!("{:?}", self));
+ CommandSyntaxException::new(self, message, reader.string(), reader.cursor())
+ }
+}
diff --git a/azalea-brigadier/src/exceptions/command_syntax_exception.rs b/azalea-brigadier/src/exceptions/command_syntax_exception.rs
new file mode 100755
index 00000000..4bfe9cda
--- /dev/null
+++ b/azalea-brigadier/src/exceptions/command_syntax_exception.rs
@@ -0,0 +1,91 @@
+use std::{cmp, fmt};
+
+use super::builtin_exceptions::BuiltInExceptions;
+use crate::message::Message;
+
+#[derive(Clone, PartialEq)]
+pub struct CommandSyntaxException {
+ pub type_: BuiltInExceptions,
+ message: Message,
+ input: Option<String>,
+ cursor: Option<usize>,
+}
+
+const CONTEXT_AMOUNT: usize = 10;
+
+impl CommandSyntaxException {
+ pub fn new(type_: BuiltInExceptions, message: Message, input: &str, cursor: usize) -> Self {
+ Self {
+ type_,
+ message,
+ input: Some(input.to_string()),
+ cursor: Some(cursor),
+ }
+ }
+
+ pub fn create(type_: BuiltInExceptions, message: Message) -> Self {
+ Self {
+ type_,
+ message,
+ input: None,
+ cursor: None,
+ }
+ }
+
+ pub fn message(&self) -> String {
+ let mut message = self.message.string();
+ let context = self.context();
+ if let Some(context) = context {
+ message.push_str(&format!(
+ " at position {}: {}",
+ self.cursor.unwrap_or(usize::MAX),
+ context
+ ));
+ }
+ message
+ }
+
+ pub fn raw_message(&self) -> &Message {
+ &self.message
+ }
+
+ pub fn context(&self) -> Option<String> {
+ if let Some(input) = &self.input {
+ if let Some(cursor) = self.cursor {
+ let mut builder = String::new();
+ let cursor = cmp::min(input.len(), cursor);
+
+ if cursor > CONTEXT_AMOUNT {
+ builder.push_str("...");
+ }
+
+ builder.push_str(
+ &input
+ [(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
+ );
+ builder.push_str("<--[HERE]");
+
+ return Some(builder);
+ }
+ }
+ None
+ }
+
+ pub fn get_type(&self) -> &BuiltInExceptions {
+ &self.type_
+ }
+
+ pub fn input(&self) -> &Option<String> {
+ &self.input
+ }
+
+ pub fn cursor(&self) -> Option<usize> {
+ self.cursor
+ }
+}
+
+impl fmt::Debug for CommandSyntaxException {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.message())
+ }
+}
diff --git a/azalea-brigadier/src/exceptions/mod.rs b/azalea-brigadier/src/exceptions/mod.rs
new file mode 100755
index 00000000..6b9c8d62
--- /dev/null
+++ b/azalea-brigadier/src/exceptions/mod.rs
@@ -0,0 +1,5 @@
+mod builtin_exceptions;
+mod command_syntax_exception;
+
+pub use builtin_exceptions::BuiltInExceptions;
+pub use command_syntax_exception::CommandSyntaxException;
diff --git a/azalea-brigadier/src/lib.rs b/azalea-brigadier/src/lib.rs
new file mode 100755
index 00000000..a294eb19
--- /dev/null
+++ b/azalea-brigadier/src/lib.rs
@@ -0,0 +1,10 @@
+pub mod arguments;
+pub mod builder;
+pub mod command_dispatcher;
+pub mod context;
+pub mod exceptions;
+pub mod message;
+pub mod modifier;
+pub mod parse_results;
+pub mod string_reader;
+pub mod tree;
diff --git a/azalea-brigadier/src/message.rs b/azalea-brigadier/src/message.rs
new file mode 100755
index 00000000..75e07d4e
--- /dev/null
+++ b/azalea-brigadier/src/message.rs
@@ -0,0 +1,14 @@
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub struct Message(String);
+
+impl Message {
+ pub fn string(&self) -> String {
+ self.0.to_string()
+ }
+}
+
+impl From<String> for Message {
+ fn from(s: String) -> Self {
+ Self(s)
+ }
+}
diff --git a/azalea-brigadier/src/modifier.rs b/azalea-brigadier/src/modifier.rs
new file mode 100755
index 00000000..d40d59f9
--- /dev/null
+++ b/azalea-brigadier/src/modifier.rs
@@ -0,0 +1,6 @@
+use std::rc::Rc;
+
+use crate::{context::CommandContext, exceptions::CommandSyntaxException};
+
+pub type RedirectModifier<S> =
+ dyn Fn(&CommandContext<S>) -> Result<Vec<Rc<S>>, CommandSyntaxException>;
diff --git a/azalea-brigadier/src/parse_results.rs b/azalea-brigadier/src/parse_results.rs
new file mode 100755
index 00000000..3698ae82
--- /dev/null
+++ b/azalea-brigadier/src/parse_results.rs
@@ -0,0 +1,21 @@
+use crate::{
+ context::CommandContextBuilder, exceptions::CommandSyntaxException,
+ string_reader::StringReader, tree::CommandNode,
+};
+use std::{collections::HashMap, fmt::Debug, rc::Rc};
+
+pub struct ParseResults<S> {
+ pub context: CommandContextBuilder<S>,
+ pub reader: StringReader,
+ pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxException>,
+}
+
+impl<S> Debug for ParseResults<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ParseResults")
+ .field("context", &self.context)
+ // .field("reader", &self.reader)
+ .field("exceptions", &self.exceptions)
+ .finish()
+ }
+}
diff --git a/azalea-brigadier/src/string_reader.rs b/azalea-brigadier/src/string_reader.rs
new file mode 100755
index 00000000..9eb09b57
--- /dev/null
+++ b/azalea-brigadier/src/string_reader.rs
@@ -0,0 +1,274 @@
+use crate::exceptions::{BuiltInExceptions, CommandSyntaxException};
+use std::str::FromStr;
+
+#[derive(Clone)]
+pub struct StringReader {
+ string: String,
+ pub cursor: usize,
+}
+
+const SYNTAX_ESCAPE: char = '\\';
+const SYNTAX_DOUBLE_QUOTE: char = '"';
+const SYNTAX_SINGLE_QUOTE: char = '\'';
+
+impl From<String> for StringReader {
+ fn from(string: String) -> Self {
+ Self { string, cursor: 0 }
+ }
+}
+impl From<&str> for StringReader {
+ fn from(string: &str) -> Self {
+ Self {
+ string: string.to_string(),
+ cursor: 0,
+ }
+ }
+}
+
+impl StringReader {
+ pub fn string(&self) -> &str {
+ &self.string
+ }
+
+ pub fn remaining_length(&self) -> usize {
+ self.string.len() - self.cursor
+ }
+
+ pub fn total_length(&self) -> usize {
+ self.string.len()
+ }
+
+ pub fn get_read(&self) -> &str {
+ &self.string[..self.cursor]
+ }
+
+ pub fn remaining(&self) -> &str {
+ &self.string[self.cursor..]
+ }
+
+ pub fn can_read_length(&self, length: usize) -> bool {
+ self.cursor + length <= self.string.len()
+ }
+
+ pub fn can_read(&self) -> bool {
+ self.can_read_length(1)
+ }
+
+ pub fn peek(&self) -> char {
+ self.string.chars().nth(self.cursor).unwrap()
+ }
+
+ pub fn peek_offset(&self, offset: usize) -> char {
+ self.string.chars().nth(self.cursor + offset).unwrap()
+ }
+
+ pub fn cursor(&self) -> usize {
+ self.cursor
+ }
+
+ pub fn read(&mut self) -> char {
+ let c = self.peek();
+ self.cursor += 1;
+ c
+ }
+
+ pub fn skip(&mut self) {
+ self.cursor += 1;
+ }
+
+ pub fn is_allowed_number(c: char) -> bool {
+ ('0'..='9').contains(&c) || c == '.' || c == '-'
+ }
+
+ pub fn is_quoted_string_start(c: char) -> bool {
+ c == SYNTAX_DOUBLE_QUOTE || c == SYNTAX_SINGLE_QUOTE
+ }
+
+ pub fn skip_whitespace(&mut self) {
+ while self.can_read() && self.peek().is_whitespace() {
+ self.skip();
+ }
+ }
+
+ pub fn read_int(&mut self) -> Result<i32, CommandSyntaxException> {
+ let start = self.cursor;
+ while self.can_read() && StringReader::is_allowed_number(self.peek()) {
+ self.skip();
+ }
+ let number = &self.string[start..self.cursor];
+ if number.is_empty() {
+ return Err(BuiltInExceptions::ReaderExpectedInt.create_with_context(self));
+ }
+ let result = i32::from_str(number);
+ if result.is_err() {
+ self.cursor = start;
+ return Err(BuiltInExceptions::ReaderInvalidInt {
+ value: number.to_string(),
+ }
+ .create_with_context(self));
+ }
+
+ Ok(result.unwrap())
+ }
+
+ pub fn read_long(&mut self) -> Result<i64, CommandSyntaxException> {
+ let start = self.cursor;
+ while self.can_read() && StringReader::is_allowed_number(self.peek()) {
+ self.skip();
+ }
+ let number = &self.string[start..self.cursor];
+ if number.is_empty() {
+ return Err(BuiltInExceptions::ReaderExpectedLong.create_with_context(self));
+ }
+ let result = i64::from_str(number);
+ if result.is_err() {
+ self.cursor = start;
+ return Err(BuiltInExceptions::ReaderInvalidLong {
+ value: number.to_string(),
+ }
+ .create_with_context(self));
+ }
+
+ Ok(result.unwrap())
+ }
+
+ pub fn read_double(&mut self) -> Result<f64, CommandSyntaxException> {
+ let start = self.cursor;
+ while self.can_read() && StringReader::is_allowed_number(self.peek()) {
+ self.skip();
+ }
+ let number = &self.string[start..self.cursor];
+ if number.is_empty() {
+ return Err(BuiltInExceptions::ReaderExpectedDouble.create_with_context(self));
+ }
+ let result = f64::from_str(number);
+ if result.is_err() {
+ self.cursor = start;
+ return Err(BuiltInExceptions::ReaderInvalidDouble {
+ value: number.to_string(),
+ }
+ .create_with_context(self));
+ }
+
+ Ok(result.unwrap())
+ }
+
+ pub fn read_float(&mut self) -> Result<f32, CommandSyntaxException> {
+ let start = self.cursor;
+ while self.can_read() && StringReader::is_allowed_number(self.peek()) {
+ self.skip();
+ }
+ let number = &self.string[start..self.cursor];
+ if number.is_empty() {
+ return Err(BuiltInExceptions::ReaderExpectedFloat.create_with_context(self));
+ }
+ let result = f32::from_str(number);
+ if result.is_err() {
+ self.cursor = start;
+ return Err(BuiltInExceptions::ReaderInvalidFloat {
+ value: number.to_string(),
+ }
+ .create_with_context(self));
+ }
+
+ Ok(result.unwrap())
+ }
+
+ pub fn is_allowed_in_unquoted_string(c: char) -> bool {
+ ('0'..='9').contains(&c)
+ || ('A'..='Z').contains(&c)
+ || ('a'..='z').contains(&c)
+ || c == '_'
+ || c == '-'
+ || c == '.'
+ || c == '+'
+ }
+
+ pub fn read_unquoted_string(&mut self) -> &str {
+ let start = self.cursor;
+ while self.can_read() && StringReader::is_allowed_in_unquoted_string(self.peek()) {
+ self.skip();
+ }
+ &self.string[start..self.cursor]
+ }
+
+ pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxException> {
+ if !self.can_read() {
+ return Ok(String::new());
+ }
+ let next = self.peek();
+ if !StringReader::is_quoted_string_start(next) {
+ return Err(BuiltInExceptions::ReaderExpectedStartOfQuote.create_with_context(self));
+ }
+ self.skip();
+ self.read_string_until(next)
+ }
+
+ pub fn read_string_until(
+ &mut self,
+ terminator: char,
+ ) -> Result<String, CommandSyntaxException> {
+ let mut result = String::new();
+ let mut escaped = false;
+ while self.can_read() {
+ let c = self.read();
+ if escaped {
+ if c == terminator || c == SYNTAX_ESCAPE {
+ result.push(c);
+ escaped = false;
+ } else {
+ self.cursor -= 1;
+ return Err(BuiltInExceptions::ReaderInvalidEscape { character: c }
+ .create_with_context(self));
+ }
+ } else if c == SYNTAX_ESCAPE {
+ escaped = true;
+ } else if c == terminator {
+ return Ok(result);
+ } else {
+ result.push(c);
+ }
+ }
+
+ Err(BuiltInExceptions::ReaderExpectedEndOfQuote.create_with_context(self))
+ }
+
+ pub fn read_string(&mut self) -> Result<String, CommandSyntaxException> {
+ if !self.can_read() {
+ return Ok(String::new());
+ }
+ let next = self.peek();
+ if StringReader::is_quoted_string_start(next) {
+ self.skip();
+ return self.read_string_until(next);
+ }
+ Ok(self.read_unquoted_string().to_string())
+ }
+
+ pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxException> {
+ let start = self.cursor;
+ let value = self.read_string()?;
+ if value.is_empty() {
+ return Err(BuiltInExceptions::ReaderExpectedBool.create_with_context(self));
+ }
+
+ if value == "true" {
+ Ok(true)
+ } else if value == "false" {
+ Ok(false)
+ } else {
+ self.cursor = start;
+ Err(BuiltInExceptions::ReaderInvalidBool { value }.create_with_context(self))
+ }
+ }
+
+ pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxException> {
+ if !self.can_read() || self.peek() != c {
+ return Err(
+ BuiltInExceptions::ReaderExpectedSymbol { symbol: c }.create_with_context(self)
+ );
+ }
+ self.skip();
+ Ok(())
+ }
+}
diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs
new file mode 100755
index 00000000..b6181c73
--- /dev/null
+++ b/azalea-brigadier/src/tree/mod.rs
@@ -0,0 +1,259 @@
+use crate::{
+ builder::{
+ argument_builder::ArgumentBuilderType, literal_argument_builder::Literal,
+ required_argument_builder::Argument,
+ },
+ context::{CommandContext, CommandContextBuilder, ParsedArgument, StringRange},
+ exceptions::{BuiltInExceptions, CommandSyntaxException},
+ modifier::RedirectModifier,
+ string_reader::StringReader,
+};
+use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, ptr, rc::Rc};
+
+/// An ArgumentBuilder that has been built.
+#[non_exhaustive]
+pub struct CommandNode<S> {
+ pub value: ArgumentBuilderType,
+
+ pub children: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
+ pub literals: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
+ pub arguments: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
+
+ pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
+ pub requirement: Rc<dyn Fn(Rc<S>) -> bool>,
+ pub redirect: Option<Rc<RefCell<CommandNode<S>>>>,
+ pub forks: bool,
+ pub modifier: Option<Rc<RedirectModifier<S>>>,
+}
+
+impl<S> Clone for CommandNode<S> {
+ fn clone(&self) -> Self {
+ Self {
+ value: self.value.clone(),
+ children: self.children.clone(),
+ literals: self.literals.clone(),
+ arguments: self.arguments.clone(),
+ command: self.command.clone(),
+ requirement: self.requirement.clone(),
+ redirect: self.redirect.clone(),
+ forks: self.forks,
+ modifier: self.modifier.clone(),
+ }
+ }
+}
+
+impl<S> CommandNode<S> {
+ /// Gets the literal, or panics. You should use match if you're not certain about the type.
+ pub fn literal(&self) -> &Literal {
+ match self.value {
+ ArgumentBuilderType::Literal(ref literal) => literal,
+ _ => panic!("CommandNode::literal() called on non-literal node"),
+ }
+ }
+ /// Gets the argument, or panics. You should use match if you're not certain about the type.
+ pub fn argument(&self) -> &Argument {
+ match self.value {
+ ArgumentBuilderType::Argument(ref argument) => argument,
+ _ => panic!("CommandNode::argument() called on non-argument node"),
+ }
+ }
+
+ pub fn get_relevant_nodes(&self, input: &mut StringReader) -> Vec<Rc<RefCell<CommandNode<S>>>> {
+ let literals = &self.literals;
+
+ if !literals.is_empty() {
+ let cursor = input.cursor();
+ while input.can_read() && input.peek() != ' ' {
+ input.skip();
+ }
+ let text: String = input
+ .string()
+ .chars()
+ .skip(cursor)
+ .take(input.cursor() - cursor)
+ .collect();
+ input.cursor = cursor;
+ let literal = literals.get(&text);
+ if let Some(literal) = literal {
+ return vec![literal.clone()];
+ } else {
+ return self.arguments.values().cloned().collect();
+ }
+ } else {
+ self.arguments.values().cloned().collect()
+ }
+ }
+
+ pub fn can_use(&self, source: Rc<S>) -> bool {
+ (self.requirement)(source)
+ }
+
+ pub fn add_child(&mut self, node: &Rc<RefCell<CommandNode<S>>>) {
+ let child = self.children.get(node.borrow().name());
+ if let Some(child) = child {
+ // We've found something to merge onto
+ if let Some(command) = &node.borrow().command {
+ child.borrow_mut().command = Some(command.clone());
+ }
+ for grandchild in node.borrow().children.values() {
+ child.borrow_mut().add_child(grandchild);
+ }
+ } else {
+ self.children
+ .insert(node.borrow().name().to_string(), node.clone());
+ match &node.borrow().value {
+ ArgumentBuilderType::Literal(literal) => {
+ self.literals.insert(literal.value.clone(), node.clone());
+ }
+ ArgumentBuilderType::Argument(argument) => {
+ self.arguments.insert(argument.name.clone(), node.clone());
+ }
+ }
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ match &self.value {
+ ArgumentBuilderType::Argument(argument) => &argument.name,
+ ArgumentBuilderType::Literal(literal) => &literal.value,
+ }
+ }
+
+ pub fn child(&self, name: &str) -> Option<Rc<RefCell<CommandNode<S>>>> {
+ self.children.get(name).cloned()
+ }
+
+ pub fn parse_with_context(
+ &self,
+ reader: &mut StringReader,
+ context_builder: &mut CommandContextBuilder<S>,
+ ) -> Result<(), CommandSyntaxException> {
+ match self.value {
+ ArgumentBuilderType::Argument(ref argument) => {
+ let start = reader.cursor();
+ let result = argument.parse(reader)?;
+ let parsed = ParsedArgument {
+ range: StringRange::between(start, reader.cursor()),
+ result,
+ };
+
+ context_builder.with_argument(&argument.name, parsed.clone());
+ context_builder.with_node(Rc::new(RefCell::new(self.clone())), parsed.range);
+
+ Ok(())
+ }
+ ArgumentBuilderType::Literal(ref literal) => {
+ let start = reader.cursor();
+ let end = self.parse(reader);
+
+ if let Some(end) = end {
+ context_builder.with_node(
+ Rc::new(RefCell::new(self.clone())),
+ StringRange::between(start, end),
+ );
+ return Ok(());
+ }
+
+ Err(BuiltInExceptions::LiteralIncorrect {
+ expected: literal.value.clone(),
+ }
+ .create_with_context(reader))
+ }
+ }
+ }
+
+ fn parse(&self, reader: &mut StringReader) -> Option<usize> {
+ match self.value {
+ ArgumentBuilderType::Argument(_) => {
+ panic!("Can't parse argument.")
+ }
+ ArgumentBuilderType::Literal(ref literal) => {
+ let start = reader.cursor();
+ if reader.can_read_length(literal.value.len()) {
+ let end = start + literal.value.len();
+ if reader
+ .string()
+ .get(start..end)
+ .expect("Couldn't slice reader correctly?")
+ == literal.value
+ {
+ reader.cursor = end;
+ if !reader.can_read() || reader.peek() == ' ' {
+ return Some(end);
+ } else {
+ reader.cursor = start;
+ }
+ }
+ }
+ }
+ }
+ None
+ }
+}
+
+impl<S> Debug for CommandNode<S> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CommandNode")
+ .field("value", &self.value)
+ .field("children", &self.children)
+ .field("command", &self.command.is_some())
+ // .field("requirement", &self.requirement)
+ .field("redirect", &self.redirect)
+ .field("forks", &self.forks)
+ // .field("modifier", &self.modifier)
+ .finish()
+ }
+}
+
+impl<S> Default for CommandNode<S> {
+ fn default() -> Self {
+ Self {
+ value: ArgumentBuilderType::Literal(Literal::default()),
+
+ children: HashMap::new(),
+ literals: HashMap::new(),
+ arguments: HashMap::new(),
+
+ command: None,
+ requirement: Rc::new(|_| true),
+ redirect: None,
+ forks: false,
+ modifier: None,
+ }
+ }
+}
+
+impl<S> Hash for CommandNode<S> {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ // hash the children
+ for (k, v) in &self.children {
+ k.hash(state);
+ v.borrow().hash(state);
+ }
+ // i hope this works because if doesn't then that'll be a problem
+ ptr::hash(&self.command, state);
+ }
+}
+
+impl<S> PartialEq for CommandNode<S> {
+ fn eq(&self, other: &Self) -> bool {
+ if self.children != other.children {
+ return false;
+ }
+ if let Some(selfexecutes) = &self.command {
+ // idk how to do this better since we can't compare `dyn Fn`s
+ if let Some(otherexecutes) = &other.command {
+ #[allow(clippy::vtable_address_comparisons)]
+ if !Rc::ptr_eq(selfexecutes, otherexecutes) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else if other.command.is_some() {
+ return false;
+ }
+ true
+ }
+}
+impl<S> Eq for CommandNode<S> {}