diff options
| author | mat <github@matdoes.dev> | 2022-04-24 17:37:57 -0500 |
|---|---|---|
| committer | mat <github@matdoes.dev> | 2022-04-24 17:37:57 -0500 |
| commit | 3e507f0db4020eaf406ba69aae3d4dc1301d29ac (patch) | |
| tree | ca6c127c9db6dfd14511e98944fc031fe5f1e43a /azalea-brigadier/src | |
| parent | 9f576c5600ba9a244bc0d433bb7de174284066a2 (diff) | |
| parent | b7641ff308aab7840d2a2253ae50f8ee496b2a97 (diff) | |
| download | azalea-drasl-3e507f0db4020eaf406ba69aae3d4dc1301d29ac.tar.xz | |
Merge branch 'main' into auth
Diffstat (limited to 'azalea-brigadier/src')
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> {} |
