diff options
| author | mat <github@matdoes.dev> | 2022-04-17 14:02:13 -0500 |
|---|---|---|
| committer | mat <github@matdoes.dev> | 2022-04-17 14:02:13 -0500 |
| commit | a72a47ced76065caf739898954cd18edbc39174b (patch) | |
| tree | 5526c7663f253bbd7c8318b9d98413f1f2074852 /azalea-brigadier/src | |
| parent | 4ff67d4917ce333232189e86aee09f2d82451fc6 (diff) | |
| download | azalea-drasl-a72a47ced76065caf739898954cd18edbc39174b.tar.xz | |
Rewrite brigadier
Diffstat (limited to 'azalea-brigadier/src')
51 files changed, 1102 insertions, 1824 deletions
diff --git a/azalea-brigadier/src/ambiguity_consumer.rs b/azalea-brigadier/src/ambiguity_consumer.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/ambiguity_consumer.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs deleted file mode 100644 index 37cc9354..00000000 --- a/azalea-brigadier/src/arguments/argument_type.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::any::Any; - -use super::bool_argument_type::BoolArgumentType; -use crate::{ - context::command_context::CommandContext, - exceptions::command_syntax_exception::CommandSyntaxException, - string_reader::StringReader, - suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}, -}; -use dyn_clonable::*; - -/* -#[derive(Types)] -enum BrigadierTypes { - Entity(EntityArgumentType) -} - -=== - -enum BrigadierTypes { - Bool(BoolArgumentType) - - Entity(EntityArgumentType) -} - -impl Types for BrigadierTypes { - fn inner(&self) -> dyn ArgumentType<dyn Types> { - match self { - Bool(t) => t, - Entity(t) => t - } - } -} -*/ - -pub trait ArgumentType { - type Into; - // T parse(StringReader reader) throws CommandSyntaxException; - - // default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) { - // return Suggestions.empty(); - // } - - // default Collection<String> getExamples() { - // return Collections.emptyList(); - // } - - fn parse(&self, reader: &mut StringReader) -> Result<Self::Into, CommandSyntaxException>; - - fn list_suggestions<S>( - &self, - context: &CommandContext<S>, - builder: &SuggestionsBuilder, - ) -> Result<Suggestions, CommandSyntaxException> - where - Self: Sized, - S: Sized; - - fn get_examples(&self) -> Vec<String>; -} diff --git a/azalea-brigadier/src/arguments/bool_argument_type.rs b/azalea-brigadier/src/arguments/bool_argument_type.rs deleted file mode 100644 index c06f40c1..00000000 --- a/azalea-brigadier/src/arguments/bool_argument_type.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::{ - context::command_context::CommandContext, - exceptions::command_syntax_exception::CommandSyntaxException, - string_reader::StringReader, - suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}, -}; - -use super::argument_type::ArgumentType; - -#[derive(Clone)] -pub struct BoolArgumentType {} - -impl ArgumentType for BoolArgumentType { - type Into = bool; - - fn parse(&self, reader: &mut StringReader) -> Result<Self::Into, CommandSyntaxException> { - Ok(reader.read_boolean()?) - } - - fn list_suggestions<S>( - &self, - context: &CommandContext<S>, - builder: &mut SuggestionsBuilder, - ) -> Result<Suggestions, CommandSyntaxException> - where - S: Sized, - { - // if ("true".startsWith(builder.getRemainingLowerCase())) { - // builder.suggest("true"); - // } - // if ("false".startsWith(builder.getRemainingLowerCase())) { - // builder.suggest("false"); - // } - // return builder.buildFuture(); - if "true".starts_with(builder.remaining_lowercase()) { - builder.suggest("true"); - } - if "false".starts_with(builder.remaining_lowercase()) { - builder.suggest("false"); - } - Ok(builder.build()) - } - - fn get_examples(&self) -> Vec<String> { - vec![] - } -} - -impl BoolArgumentType { - const EXAMPLES: &'static [&'static str] = &["true", "false"]; - - fn bool() -> Self { - Self {} - } - - fn get_bool<S>(context: CommandContext<S>, name: String) { - context.get_argument::<bool>(name) - } -} diff --git a/azalea-brigadier/src/arguments/double_argument_type.rs b/azalea-brigadier/src/arguments/double_argument_type.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/arguments/double_argument_type.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/arguments/float_argument_type.rs b/azalea-brigadier/src/arguments/float_argument_type.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/arguments/float_argument_type.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/arguments/integer_argument_type.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/arguments/long_argument_type.rs b/azalea-brigadier/src/arguments/long_argument_type.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/arguments/long_argument_type.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/arguments/mod.rs b/azalea-brigadier/src/arguments/mod.rs deleted file mode 100644 index 487c5db7..00000000 --- a/azalea-brigadier/src/arguments/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod argument_type; -pub mod bool_argument_type; -pub mod double_argument_type; -pub mod float_argument_type; -pub mod integer_argument_type; -pub mod long_argument_type; -pub mod string_argument_type; diff --git a/azalea-brigadier/src/arguments/string_argument_type.rs b/azalea-brigadier/src/arguments/string_argument_type.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/arguments/string_argument_type.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs index d0770be2..1fb775c2 100644 --- a/azalea-brigadier/src/builder/argument_builder.rs +++ b/azalea-brigadier/src/builder/argument_builder.rs @@ -1,137 +1,178 @@ -use crate::{ - arguments::argument_type::ArgumentType, - command::Command, - redirect_modifier::RedirectModifier, - single_redirect_modifier::SingleRedirectModifier, - tree::{ - command_node::{BaseCommandNode, CommandNodeTrait}, - root_command_node::RootCommandNode, - }, -}; -use std::fmt::Debug; - -pub struct BaseArgumentBuilder<'a, S> { - arguments: RootCommandNode<'a, S>, - command: Option<Box<dyn Command<S>>>, - requirement: Box<dyn Fn(&S) -> bool>, - target: Option<Box<dyn CommandNodeTrait<S>>>, - modifier: Option<Box<dyn RedirectModifier<S>>>, - forks: bool, +use crate::{context::CommandContext, modifier::RedirectModifier, tree::CommandNode}; + +use super::{literal_argument_builder::Literal, required_argument_builder::Argument}; +use std::{any::Any, cell::RefCell, collections::BTreeMap, fmt::Debug, rc::Rc}; + +#[derive(Debug, Clone)] +pub enum ArgumentBuilderType { + Literal(Literal), + Argument(Argument), } -pub trait ArgumentBuilder<S> { - fn build(self) -> Box<dyn CommandNodeTrait<S>>; +/// A node that hasn't yet been built. +#[derive(Clone)] +pub struct ArgumentBuilder<S: Any + Clone> { + value: ArgumentBuilderType, + + children: BTreeMap<String, Rc<RefCell<CommandNode<S>>>>, + literals: BTreeMap<String, Rc<RefCell<CommandNode<S>>>>, + arguments: BTreeMap<String, Rc<RefCell<CommandNode<S>>>>, + + executes: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>, + requirement: Rc<dyn Fn(Rc<S>) -> bool>, + forks: bool, + modifier: Option<Rc<dyn RedirectModifier<S>>>, } -impl<'a, S> BaseArgumentBuilder<'a, S> { - pub fn then(&mut self, argument: Box<dyn ArgumentBuilder<S>>) -> Result<&mut Self, String> { - if self.target.is_some() { - return Err("Cannot add children to a redirected node".to_string()); - } - self.arguments.add_child(argument.build()); - Ok(self) - } +// todo: maybe remake this to be based on a CommandNode like vanilla does? - pub fn arguments(&self) -> &Vec<&dyn CommandNodeTrait<S>> { - &self.arguments.get_children() +/// A node that isn't yet built. +impl<S: Any + Clone> ArgumentBuilder<S> { + pub fn new(value: ArgumentBuilderType) -> Self { + Self { + value, + children: BTreeMap::new(), + literals: BTreeMap::new(), + arguments: BTreeMap::new(), + executes: None, + requirement: Rc::new(|_| true), + forks: false, + modifier: None, + } } - pub fn executes(&mut self, command: Box<dyn Command<S>>) -> &mut Self { - self.command = Some(command); + pub fn then(&mut self, node: ArgumentBuilder<S>) -> &mut Self { + let built_node = node.build(); + let name = built_node.name(); + let node_reference = Rc::new(RefCell::new(built_node.clone())); + self.children + .insert(name.to_string(), node_reference.clone()); + match &built_node.value { + ArgumentBuilderType::Literal(literal) => { + self.literals.insert(name.to_string(), node_reference); + } + ArgumentBuilderType::Argument(argument) => { + self.arguments.insert(name.to_string(), node_reference); + } + } self } - pub fn command(&self) -> Option<Box<dyn Command<S>>> { - self.command + pub fn executes<F>(&mut self, f: F) -> Self + where + F: Fn(&CommandContext<S>) -> i32 + 'static, + { + self.executes = Some(Rc::new(f)); + self.clone() } - pub fn requires(&mut self, requirement: Box<dyn Fn(&S) -> bool>) -> &mut Self { - self.requirement = requirement; - self - } + pub fn build(self) -> CommandNode<S> { + println!("building {:?}", self); + CommandNode { + value: self.value, - pub fn requirement(&self) -> Box<dyn Fn(&S) -> bool> { - self.requirement - } + children: self.children, + literals: self.literals, + arguments: self.arguments, - pub fn redirect(&mut self, target: Box<dyn CommandNodeTrait<S>>) -> &mut Self { - self.forward(target, None, false) + command: self.executes.clone(), + requirement: self.requirement.clone(), + redirect: None, + forks: self.forks, + modifier: self.modifier, + } } +} - pub fn redirect_modifier( - &mut self, - target: &dyn CommandNodeTrait<S>, - modifier: &dyn SingleRedirectModifier<S>, - ) -> &mut Self { - // forward(target, modifier == null ? null : o -> Collections.singleton(modifier.apply(o)), false); - self.forward(target, modifier.map(|m| |o| vec![m.apply(o)]), false) +impl<S: Any + Clone> Debug for ArgumentBuilder<S> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ArgumentBuilder") + .field("value", &self.value) + .field("children", &self.children) + .field("literals", &self.literals) + .field("arguments", &self.arguments) + .field("executes", &self.executes.is_some()) + // .field("requirement", &self.requirement) + .field("forks", &self.forks) + // .field("modifier", &self.modifier) + .finish() } +} - pub fn fork( - &mut self, - target: &dyn CommandNodeTrait<S>, - modifier: &dyn RedirectModifier<S>, - ) -> &mut Self { - self.forward(target, Some(modifier), true) - } +#[cfg(test)] +mod tests { + use std::rc::Rc; - pub fn forward( - &mut self, - target: Option<Box<dyn CommandNodeTrait<S>>>, - modifier: Option<&dyn RedirectModifier<S>>, - fork: bool, - ) -> Result<&mut Self, String> { - if !self.arguments.get_children().is_empty() { - return Err("Cannot forward a node with children".to_string()); - } - self.target = target; - self.modifier = modifier; - self.forks = fork; - Ok(self) - } + use crate::{ + builder::{literal_argument_builder::literal, required_argument_builder::argument}, + parsers::integer, + }; - pub fn get_redirect(&self) -> Option<&dyn CommandNodeTrait<S>> { - self.target.as_ref() - } + use super::ArgumentBuilder; - pub fn get_redirect_modifier(&self) -> Option<&dyn RedirectModifier<S>> { - self.modifier.as_ref() - } + // public class ArgumentBuilderTest { + // private TestableArgumentBuilder<Object> builder; - pub fn is_fork(&self) -> bool { - self.forks - } + // @Before + // public void setUp() throws Exception { + // builder = new TestableArgumentBuilder<>(); + // } - pub fn build(self) -> BaseCommandNode<'a, S> { - let result: BaseCommandNode<'a, S> = BaseCommandNode { - command: self.command, - requirement: self.requirement, - redirect: self.target, - modifier: self.modifier, - forks: self.forks, + // @Test + // public void testArguments() throws Exception { + // final RequiredArgumentBuilder<Object, ?> argument = argument("bar", integer()); - arguments: Default::default(), - children: Default::default(), - literals: Default::default(), - }; + // builder.then(argument); - for argument in self.arguments() { - result.add_child(argument); - } + // assertThat(builder.getArguments(), hasSize(1)); + // assertThat(builder.getArguments(), hasItem((CommandNode<Object>) argument.build())); + // } - result - } -} + #[test] + fn test_arguments() { + let mut builder: ArgumentBuilder<()> = literal("foo"); -impl<S> Default for BaseArgumentBuilder<'_, S> { - fn default() -> Self { - Self { - arguments: Default::default(), - command: Default::default(), - requirement: Default::default(), - target: Default::default(), - modifier: Default::default(), - forks: Default::default(), - } + let argument: ArgumentBuilder<()> = argument("bar", integer()); + builder.then(argument.clone()); + assert_eq!(builder.children.len(), 1); + let built_argument = Rc::new(argument.build()); + assert!(builder + .children + .values() + .any(|e| *e.borrow() == *built_argument)); } + + // @Test + // public void testRedirect() throws Exception { + // final CommandNode<Object> target = mock(CommandNode.class); + // builder.redirect(target); + // assertThat(builder.getRedirect(), is(target)); + // } + + // @Test(expected = IllegalStateException.class) + // public void testRedirect_withChild() throws Exception { + // final CommandNode<Object> target = mock(CommandNode.class); + // builder.then(literal("foo")); + // builder.redirect(target); + // } + + // @Test(expected = IllegalStateException.class) + // public void testThen_withRedirect() throws Exception { + // final CommandNode<Object> target = mock(CommandNode.class); + // builder.redirect(target); + // builder.then(literal("foo")); + // } + + // private static class TestableArgumentBuilder<S> extends ArgumentBuilder<S, TestableArgumentBuilder<S>> { + // @Override + // protected TestableArgumentBuilder<S> getThis() { + // return this; + // } + + // @Override + // public CommandNode<S> build() { + // return null; + // } + // } + // } } diff --git a/azalea-brigadier/src/builder/literal_argument_builder.rs b/azalea-brigadier/src/builder/literal_argument_builder.rs index 4a95755c..d8898540 100644 --- a/azalea-brigadier/src/builder/literal_argument_builder.rs +++ b/azalea-brigadier/src/builder/literal_argument_builder.rs @@ -1,51 +1,35 @@ -use super::argument_builder::{ArgumentBuilder, BaseArgumentBuilder}; +use std::any::Any; + use crate::{ - arguments::argument_type::ArgumentType, - command::Command, - redirect_modifier::RedirectModifier, - tree::{ - command_node::CommandNodeTrait, literal_command_node::LiteralCommandNode, - root_command_node::RootCommandNode, + context::CommandContextBuilder, + exceptions::{ + builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, }, + string_range::StringRange, + string_reader::StringReader, }; -use std::fmt::Debug; -pub struct LiteralArgumentBuilder<S> { - arguments: RootCommandNode<S>, - command: Option<Box<dyn Command<S>>>, - requirement: Box<dyn Fn(&S) -> bool>, - target: Option<Box<dyn CommandNodeTrait<S>>>, - modifier: Option<Box<dyn RedirectModifier<S>>>, - forks: bool, - literal: String, -} +use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType}; -impl<S> LiteralArgumentBuilder<S> { - pub fn new(literal: String) -> Self { +#[derive(Debug, Clone, Default)] +pub struct Literal { + pub value: String, +} +impl Literal { + pub fn new(value: &str) -> Self { Self { - literal, - arguments: RootCommandNode::new(), - command: None, - requirement: Box::new(|_| true), - target: None, - modifier: None, - forks: false, + value: value.to_string(), } } +} - pub fn literal(name: String) -> Self { - Self::new(name) +impl From<Literal> for ArgumentBuilderType { + fn from(literal: Literal) -> Self { + Self::Literal(literal) } } -impl<S> ArgumentBuilder<S> for LiteralArgumentBuilder<S> { - fn build(self) -> Box<dyn CommandNodeTrait<S>> { - let result = LiteralCommandNode::new(self.literal, self.base.build()); - - for argument in self.base.arguments() { - result.add_child(argument); - } - - Box::new(result) - } +/// Shortcut for creating a new literal builder node. +pub fn literal<S: Any + Clone>(value: &str) -> ArgumentBuilder<S> { + ArgumentBuilder::new(ArgumentBuilderType::Literal(Literal::new(value))) } diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs index fe6f2ecc..95f4da01 100644 --- a/azalea-brigadier/src/builder/required_argument_builder.rs +++ b/azalea-brigadier/src/builder/required_argument_builder.rs @@ -1,91 +1,46 @@ -use super::argument_builder::BaseArgumentBuilder; -use crate::{ - arguments::argument_type::ArgumentType, - command::Command, - redirect_modifier::RedirectModifier, - suggestion::suggestion_provider::SuggestionProvider, - tree::{ - argument_command_node::ArgumentCommandNode, - command_node::{BaseCommandNode, CommandNodeTrait}, - root_command_node::RootCommandNode, - }, -}; -use std::any::Any; -use std::fmt::Debug; - -pub struct RequiredArgumentBuilder<'a, S> { - arguments: RootCommandNode<'a, S>, - command: Option<Box<dyn Command<S>>>, - requirement: Box<dyn Fn(&S) -> bool>, - target: Option<Box<dyn CommandNodeTrait<S>>>, - modifier: Option<Box<dyn RedirectModifier<S>>>, - forks: bool, - - name: String, - type_: Box<dyn ArgumentType<Into = dyn Any>>, - suggestions_provider: Option<Box<dyn SuggestionProvider<S>>>, +use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType}; +use crate::{parsers::Parser, 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 Parser>, } - -impl<'a, S> RequiredArgumentBuilder<'a, S> { - pub fn new(name: String, type_: Box<dyn ArgumentType<Into = dyn Any>>) -> Self { +impl Argument { + pub fn new(name: &str, parser: Rc<dyn Parser>) -> Self { Self { - name, - type_: type_, - suggestions_provider: None, - arguments: RootCommandNode::new(), - command: None, - requirement: Box::new(|_| true), - target: None, - modifier: None, - forks: false, + name: name.to_string(), + parser: parser, } } - pub fn argument(name: String, type_: Box<dyn ArgumentType<Into = dyn Any>>) -> Self { - Self::new(name, type_) - } - - pub fn suggests(mut self, provider: Box<dyn SuggestionProvider<S>>) -> Self { - self.suggestions_provider = Some(provider); - self - } - - pub fn suggestions_provider(&self) -> Option<Box<dyn SuggestionProvider<S>>> { - self.suggestions_provider + pub fn parse(&self, reader: &mut StringReader) -> Option<Rc<dyn Any>> { + self.parser.parse(reader) } +} - pub fn get_type(&self) -> Box<dyn ArgumentType<Into = dyn Any>> { - self.type_ +impl From<Argument> for ArgumentBuilderType { + fn from(argument: Argument) -> Self { + Self::Argument(argument) } +} - pub fn name(&self) -> &str { - &self.name +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() } +} - // final ArgumentCommandNode<S> result = new ArgumentCommandNode<>(getName(), getType(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider()); - - // for (final CommandNode<S> argument : getArguments()) { - // result.addChild(argument); - // } - - // return result; - pub fn build(self) -> ArgumentCommandNode<'a, S> { - let result = ArgumentCommandNode { - name: self.name, - type_: self.type_, - command: self.base.command(), - requirement: self.base.requirement(), - redirect: self.base.get_redirect(), - modifier: self.base.get_redirect_modifier(), - forks: self.base.forks, - custom_suggestions: self.base.custom_suggestions, - ..ArgumentCommandNode::default() - }; - - for argument in self.base.arguments() { - result.add_child(argument); - } - - result - } +/// Shortcut for creating a new argument builder node. +pub fn argument<'a, S: Any + Clone>( + name: &'a str, + parser: impl Parser + 'static, +) -> ArgumentBuilder<S> { + ArgumentBuilder::new(Argument::new(name, Rc::new(parser)).into()) } diff --git a/azalea-brigadier/src/command.rs b/azalea-brigadier/src/command.rs deleted file mode 100644 index a529f23c..00000000 --- a/azalea-brigadier/src/command.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::{ - context::command_context::CommandContext, - exceptions::command_syntax_exception::CommandSyntaxException, -}; -use dyn_clonable::*; - -pub const SINGLE_SUCCESS: i32 = 1; - -#[clonable] -pub trait Command<S>: Clone { - fn run(&self, context: &mut CommandContext<S>) -> Result<i32, CommandSyntaxException>; -} diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs deleted file mode 100644 index f8ffddff..00000000 --- a/azalea-brigadier/src/command_dispatcher.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{arguments::argument_type::ArgumentType, tree::root_command_node::RootCommandNode}; -use std::fmt::Debug; - -/// The core command dispatcher, for registering, parsing, and executing commands. -/// The `S` generic is a custom "source" type, such as a user or originator of a command -#[derive(Default, Clone)] -pub struct CommandDispatcher<S> { - root: RootCommandNode<S>, -} - -impl<S> CommandDispatcher<S> { - /// The string required to separate individual arguments in an input string - /// - /// See: [`ARGUMENT_SEPARATOR_CHAR`] - const ARGUMENT_SEPARATOR: &'static str = " "; - - /// The char required to separate individual arguments in an input string - /// - /// See: [`ARGUMENT_SEPARATOR`] - const ARGUMENT_SEPARATOR_CHAR: char = ' '; - - const USAGE_OPTIONAL_OPEN: &'static str = "["; - const USAGE_OPTIONAL_CLOSE: &'static str = "]"; - const USAGE_REQUIRED_OPEN: &'static str = "("; - const USAGE_REQUIRED_CLOSE: &'static str = ")"; - const USAGE_OR: &'static str = "|"; - - /// Create a new [`CommandDispatcher`] with the specified root node. - /// This is often useful to copy existing or pre-defined command trees. - /// # Example - /// ``` - /// use azalea_brigadier::{ - /// command_dispatcher::CommandDispatcher, - /// tree::root_command_node::RootCommandNode, - /// }; - /// - /// let mut dispatcher = CommandDispatcher::new(RootCommandNode::new()); - /// ``` - /// # Arguments - /// * `root` - the existing [`RootCommandNode`] to use as the basis for this tree - /// # Returns - /// A new [`CommandDispatcher`] with the specified root node. - fn new(root: RootCommandNode<S>) -> Self { - Self { root } - } -} diff --git a/azalea-brigadier/src/context.rs b/azalea-brigadier/src/context.rs new file mode 100644 index 00000000..6d4dec88 --- /dev/null +++ b/azalea-brigadier/src/context.rs @@ -0,0 +1,145 @@ +use std::{any::Any, cell::RefCell, collections::HashMap, fmt::Debug, ptr, rc::Rc}; + +use crate::{ + dispatcher::CommandDispatcher, modifier::RedirectModifier, string_range::StringRange, + tree::CommandNode, +}; + +#[derive(Clone)] +pub struct CommandContextBuilder<S: Any + Clone> { + pub arguments: HashMap<String, ParsedArgument>, + pub root: Rc<RefCell<CommandNode<S>>>, + pub nodes: Vec<Rc<CommandNode<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<dyn RedirectModifier<S>>>, + pub forks: bool, +} + +impl<S: Any + Clone> CommandContextBuilder<S> { + // CommandDispatcher<S> dispatcher, final S source, final CommandNode<S> rootNode, final int start + 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![], + // rootNode, + // start, + 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<CommandNode<S>>, range: StringRange) -> &Self { + self.nodes.push(node.clone()); + self.range = StringRange::encompassing(&self.range, &range); + self.modifier = node.modifier.clone(); + self.forks = node.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: Any + Clone> 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() + } +} + +#[derive(Clone)] +pub struct ParsedArgument { + pub range: StringRange, + pub result: Rc<dyn Any>, +} + +#[derive(Clone)] +/// A built `CommandContextBuilder`. +pub struct CommandContext<S: Any + Clone> { + 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<Rc<CommandNode<S>>>, + pub range: StringRange, + pub child: Option<Rc<CommandContext<S>>>, + pub modifier: Option<Rc<dyn RedirectModifier<S>>>, + pub forks: bool, +} + +impl<S: Any + Clone> CommandContext<S> { + pub fn copy_for(&self, source: Rc<S>) -> Self { + if Rc::ptr_eq(&source, &self.source) { + return self.clone(); + } + return 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 { + return !self.nodes.is_empty(); + } +} diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs deleted file mode 100644 index 8db1487f..00000000 --- a/azalea-brigadier/src/context/command_context.rs +++ /dev/null @@ -1,93 +0,0 @@ -use super::{ - parsed_argument::ParsedArgument, parsed_command_node::ParsedCommandNode, - string_range::StringRange, -}; -use crate::{ - arguments::argument_type::ArgumentType, command::Command, redirect_modifier::RedirectModifier, - tree::command_node::CommandNodeTrait, -}; -use std::{any::Any, collections::HashMap}; - -pub struct CommandContext<'a, S> { - source: S, - input: String, - command: &'a dyn Command<S>, - arguments: HashMap<String, ParsedArgument<Box<dyn Any>>>, - root_node: &'a dyn CommandNodeTrait<S>, - nodes: Vec<ParsedCommandNode<S>>, - range: StringRange, - child: Option<&'a CommandContext<'a, S>>, - modifier: Option<&'a dyn RedirectModifier<S>>, - forks: bool, -} - -impl<S> CommandContext<'_, S> -where - S: PartialEq, -{ - pub fn clone_for(&self, source: S) -> Self { - if self.source == source { - return *self; - } - Self { - source, - input: self.input.clone(), - command: self.command.clone(), - arguments: self.arguments.clone(), - root_node: self.root_node.clone(), - nodes: self.nodes.clone(), - range: self.range.clone(), - child: self.child.clone(), - modifier: self.modifier.clone(), - forks: self.forks, - } - } - - fn child(&self) -> &Option<CommandContext<S>> { - &self.child - } - - fn last_child(&self) -> &CommandContext<S> { - let mut result = self; - while result.child.is_some() { - result = result.child.as_ref().unwrap(); - } - result - } - - fn command(&self) -> &dyn Command<S> { - &self.command - } - - fn source(&self) -> &S { - &self.source - } - - // public <V> V getArgument(final String name, final Class<V> clazz) { - // final ParsedArgument<S, ?> argument = arguments.get(name); - - // if (argument == null) { - // throw new IllegalArgumentException("No such argument '" + name + "' exists on this command"); - // } - - // final Object result = argument.getResult(); - // if (PRIMITIVE_TO_WRAPPER.getOrDefault(clazz, clazz).isAssignableFrom(result.getClass())) { - // return (V) result; - // } else { - // throw new IllegalArgumentException("Argument '" + name + "' is defined as " + result.getClass().getSimpleName() + ", not " + clazz); - // } - // } - fn get_argument<V>(&self, name: &str) -> Result<V, String> { - let argument = self.arguments.get(name); - - if argument.is_none() { - return Err(format!( - "No such argument '{}' exists on this command", - name - )); - } - - let result = argument.unwrap().result(); - Ok(result) - } -} diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs deleted file mode 100644 index ba25849c..00000000 --- a/azalea-brigadier/src/context/command_context_builder.rs +++ /dev/null @@ -1,176 +0,0 @@ -use crate::{ - arguments::argument_type::ArgumentType, command::Command, - command_dispatcher::CommandDispatcher, redirect_modifier::RedirectModifier, - tree::command_node::CommandNodeTrait, -}; -use std::fmt::Debug; -use std::{any::Any, collections::HashMap}; - -use super::{ - command_context::CommandContext, parsed_argument::ParsedArgument, - parsed_command_node::ParsedCommandNode, string_range::StringRange, - suggestion_context::SuggestionContext, -}; - -// public class CommandContextBuilder<S> { -// private final Map<String, ParsedArgument<S, ?>> arguments = new LinkedHashMap<>(); -// private final CommandNode<S> rootNode; -// private final List<ParsedCommandNode<S>> nodes = new ArrayList<>(); -// private final CommandDispatcher<S> dispatcher; -// private S source; -// private Command<S> command; -// private CommandContextBuilder<S> child; -// private StringRange range; -// private RedirectModifier<S> modifier = null; -// private boolean forks; - -#[derive(Clone)] -pub struct CommandContextBuilder<'a, S> { - arguments: HashMap<String, ParsedArgument<Box<dyn Any>>>, - root_node: &'a dyn CommandNodeTrait<S>, - nodes: Vec<ParsedCommandNode<S>>, - dispatcher: CommandDispatcher<'a, S>, - source: S, - command: Box<dyn Command<S>>, - child: Box<Option<CommandContextBuilder<'a, S>>>, - range: StringRange, - modifier: Option<Box<dyn RedirectModifier<S>>>, - forks: bool, -} - -// public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source, final CommandNode<S> rootNode, final int start) { -// this.rootNode = rootNode; -// this.dispatcher = dispatcher; -// this.source = source; -// this.range = StringRange.at(start); -// } - -impl<S> CommandContextBuilder<'_, S> { - pub fn new( - dispatcher: CommandDispatcher<S>, - source: S, - root_node: &dyn CommandNodeTrait<S>, - start: usize, - ) -> Self { - Self { - root_node: &root_node, - dispatcher, - source, - range: StringRange::at(start), - ..Default::default() - } - } - - pub fn with_source(mut self, source: S) -> Self { - self.source = source; - self - } - - pub fn source(&self) -> &S { - &self.source - } - - pub fn root_node(&self) -> &dyn CommandNodeTrait<S> { - &self.root_node - } - - pub fn with_argument(mut self, name: String, argument: ParsedArgument<Box<dyn Any>>) -> Self { - self.arguments.insert(name, argument); - self - } - - pub fn arguments(&self) -> &HashMap<String, ParsedArgument<Box<dyn Any>>> { - &self.arguments - } - - pub fn with_command(mut self, command: &dyn Command<S>) -> Self { - self.command = command; - self - } - - pub fn with_node(mut self, node: dyn CommandNodeTrait<S>, range: StringRange) -> Self { - self.nodes.push(ParsedCommandNode::new(node, range)); - self.range = StringRange::encompassing(&self.range, &range); - self.modifier = node.redirect_modifier(); - self.forks = node.is_fork(); - self - } - - pub fn with_child(mut self, child: CommandContextBuilder<S>) -> Self { - self.child = Some(child); - self - } - - pub fn child(&self) -> Option<&CommandContextBuilder<S>> { - self.child.as_ref() - } - - pub fn last_child(&self) -> Option<&CommandContextBuilder<S>> { - let mut result = self; - while let Some(child) = result.child() { - result = child; - } - Some(result) - } - - pub fn command(&self) -> &dyn Command<S> { - &*self.command - } - - pub fn nodes(&self) -> &Vec<ParsedCommandNode<S>> { - &self.nodes - } - - pub fn build(self, input: &str) -> CommandContext<S> { - CommandContext { - source: self.source, - input, - arguments: self.arguments, - command: self.command, - root_node: self.root_node, - nodes: self.nodes, - range: self.range, - child: self.child.map(|child| child.build(input)), - modifier: self.modifier, - forks: self.forks, - } - } - - pub fn dispatcher(&self) -> &CommandDispatcher<S> { - &self.dispatcher - } - - pub fn range(&self) -> &StringRange { - &self.range - } - - pub fn find_suggestion_context(&self, cursor: i32) -> Result<SuggestionContext<S>, String> { - if self.range.start() <= cursor { - if self.range.end() < cursor { - if let Some(child) = self.child() { - child.find_suggestion_context(cursor); - } else if !self.nodes.is_empty() { - let last = self.nodes.last().unwrap(); - let end = last.range().end() + 1; - return SuggestionContext::new(last.node(), end); - } else { - return SuggestionContext::new(self.root_node, self.range.start()); - } - } else { - let prev = self.root_node; - for node in &self.nodes { - let node_range = node.range(); - if node_range.start() <= cursor && cursor <= node_range.end() { - return SuggestionContext::new(prev, node_range.start()); - } - prev = node.node(); - } - if prev.is_none() { - return Err(String::from("Can't find node before cursor")); - } - return SuggestionContext::new(prev.unwrap(), self.range.start()); - } - } - Err(String::from("Can't find node before cursor")) - } -} diff --git a/azalea-brigadier/src/context/mod.rs b/azalea-brigadier/src/context/mod.rs deleted file mode 100644 index 196d7c5b..00000000 --- a/azalea-brigadier/src/context/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod command_context; -pub mod command_context_builder; -pub mod parsed_argument; -pub mod parsed_command_node; -pub mod string_range; -pub mod suggestion_context; diff --git a/azalea-brigadier/src/context/parsed_argument.rs b/azalea-brigadier/src/context/parsed_argument.rs deleted file mode 100644 index e0bdf97b..00000000 --- a/azalea-brigadier/src/context/parsed_argument.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::string_range::StringRange; - -#[derive(PartialEq, Eq, Hash, Clone)] -pub struct ParsedArgument<T> { - range: StringRange, - result: T, -} - -impl<T> ParsedArgument<T> { - fn new(start: usize, end: usize, result: &T) -> Self { - Self { - range: StringRange::between(start, end), - result, - } - } - - fn range(&self) -> &StringRange { - &self.range - } - - fn result(&self) -> &T { - &self.result - } -} diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs deleted file mode 100644 index 21d1b2e9..00000000 --- a/azalea-brigadier/src/context/parsed_command_node.rs +++ /dev/null @@ -1,30 +0,0 @@ -use super::string_range::StringRange; -use crate::tree::command_node::CommandNodeTrait; - -pub struct ParsedCommandNode<S> { - node: Box<dyn CommandNodeTrait<S>>, - range: StringRange, -} - -impl<S> ParsedCommandNode<S> { - fn new(node: dyn CommandNodeTrait<S>, range: StringRange) -> Self { - Self { node, range } - } - - fn node(&self) -> &dyn CommandNodeTrait<S> { - &self.node - } - - fn range(&self) -> &StringRange { - &self.range - } -} - -impl<S> Clone for ParsedCommandNode<S> { - fn clone_from(&mut self, source: &Self) { - Self { - node: self.node.clone(), - range: self.range.clone(), - } - } -} diff --git a/azalea-brigadier/src/context/suggestion_context.rs b/azalea-brigadier/src/context/suggestion_context.rs deleted file mode 100644 index 51a053c1..00000000 --- a/azalea-brigadier/src/context/suggestion_context.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::tree::command_node::CommandNodeTrait; - -pub struct SuggestionContext<'a, S> { - parent: &'a dyn CommandNodeTrait<S>, - start_pos: usize, -} diff --git a/azalea-brigadier/src/dispatcher.rs b/azalea-brigadier/src/dispatcher.rs new file mode 100644 index 00000000..65fe5b0a --- /dev/null +++ b/azalea-brigadier/src/dispatcher.rs @@ -0,0 +1,242 @@ +use crate::{ + builder::argument_builder::ArgumentBuilder, + context::{CommandContext, CommandContextBuilder}, + exceptions::{ + builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, + }, + parse_results::ParseResults, + string_range::StringRange, + string_reader::StringReader, + tree::CommandNode, +}; +use std::{ + any::Any, cell::RefCell, cmp::Ordering, collections::HashMap, marker::PhantomData, mem, rc::Rc, +}; + +#[derive(Default)] +pub struct CommandDispatcher<S: Any + Clone> { + root: Rc<RefCell<CommandNode<S>>>, + _marker: PhantomData<S>, +} + +impl<S: Any + Clone> CommandDispatcher<S> { + pub fn new() -> Self { + Self { + root: Rc::new(RefCell::new(CommandNode::default())), + _marker: PhantomData, + } + } + + pub fn register(&mut self, node: ArgumentBuilder<S>) { + println!("register {:#?}", node); + let build = Rc::new(RefCell::new(node.build())); + self.root.borrow_mut().add_child(&build); + // println!("build: {:#?}", build); + } + + pub fn parse(&self, command: StringReader, source: S) -> ParseResults<S> { + let context = CommandContextBuilder::new( + Rc::new(self.clone()), + Rc::new(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() { + if 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.clone(), + redirect.clone(), + reader.cursor, + ); + let parse = self + .parse_nodes(redirect, &reader, child_context) + .expect("Parsing nodes failed"); + context.with_child(Rc::new(parse.context)); + } 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.len() > 0 { + 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(); + println!("chosen {:#?}", best_potential); + return Ok(best_potential); + } + + Ok(ParseResults { + context: context_so_far, + reader: original_reader.clone(), + exceptions: errors, + }) + } + + /// Executes a given pre-parsed command. + pub fn execute(parse: ParseResults<S>) -> Result<i32, CommandSyntaxException> { + if parse.reader.can_read() { + println!("can read from reader {}", parse.reader.cursor); + 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) + ); + } + println!("a"); + 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.len() > 0 { + for context in contexts.iter() { + let child = &context.child; + if let Some(child) = child { + forked |= child.forks; + if child.has_nodes() { + found_command = true; + let modifier = &context.modifier; + if let Some(modifier) = modifier { + let results = modifier.apply(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) + ); + } + + Ok(if forked { successful_forks } else { result }) + } +} + +impl<S: Any + Clone> Clone for CommandDispatcher<S> { + fn clone(&self) -> Self { + Self { + root: self.root.clone(), + _marker: PhantomData, + } + } +} diff --git a/azalea-brigadier/src/exceptions/builtin_exception_provider.rs b/azalea-brigadier/src/exceptions/builtin_exception_provider.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/exceptions/builtin_exception_provider.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/exceptions/builtin_exceptions.rs b/azalea-brigadier/src/exceptions/builtin_exceptions.rs index 1533364b..5f2e1605 100644 --- a/azalea-brigadier/src/exceptions/builtin_exceptions.rs +++ b/azalea-brigadier/src/exceptions/builtin_exceptions.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{immutable_string_reader::ImmutableStringReader, message::Message}; +use crate::{message::Message, string_reader::StringReader}; use super::command_syntax_exception::CommandSyntaxException; @@ -35,9 +35,9 @@ pub enum BuiltInExceptions { ReaderExpectedBool, ReaderExpectedSymbol { symbol: char }, - ReaderUnknownCommand, - ReaderUnknownArgument, - DusoatcgerExpectedArgumentSeparator, + DispatcherUnknownCommand, + DispatcherUnknownArgument, + DispatcherExpectedArgumentSeparator, DispatcherParseException { message: String }, } @@ -127,13 +127,13 @@ impl fmt::Debug for BuiltInExceptions { write!(f, "Expected '{}'", symbol) } - BuiltInExceptions::ReaderUnknownCommand => { + BuiltInExceptions::DispatcherUnknownCommand => { write!(f, "Unknown command") } - BuiltInExceptions::ReaderUnknownArgument => { + BuiltInExceptions::DispatcherUnknownArgument => { write!(f, "Incorrect argument for command") } - BuiltInExceptions::DusoatcgerExpectedArgumentSeparator => { + BuiltInExceptions::DispatcherExpectedArgumentSeparator => { write!( f, "Expected whitespace to end one argument, but found trailing data" @@ -152,7 +152,7 @@ impl BuiltInExceptions { CommandSyntaxException::create(self, message) } - pub fn create_with_context(self, reader: &dyn ImmutableStringReader) -> CommandSyntaxException { + 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 index 38aa1c3a..6cd4e53d 100644 --- a/azalea-brigadier/src/exceptions/command_syntax_exception.rs +++ b/azalea-brigadier/src/exceptions/command_syntax_exception.rs @@ -3,6 +3,7 @@ use std::{cmp, fmt, rc::Rc}; use super::builtin_exceptions::BuiltInExceptions; use crate::message::Message; +#[derive(Clone)] pub struct CommandSyntaxException { type_: BuiltInExceptions, message: Message, @@ -59,7 +60,10 @@ impl CommandSyntaxException { builder.push_str("..."); } - builder.push_str(&input[cmp::max(0, cursor - CONTEXT_AMOUNT)..cursor]); + builder.push_str( + &input + [(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor], + ); builder.push_str("<--[HERE]"); return Some(builder); diff --git a/azalea-brigadier/src/exceptions/mod.rs b/azalea-brigadier/src/exceptions/mod.rs index 4a82b01e..0bca556e 100644 --- a/azalea-brigadier/src/exceptions/mod.rs +++ b/azalea-brigadier/src/exceptions/mod.rs @@ -1,3 +1,2 @@ -pub mod builtin_exception_provider; pub mod builtin_exceptions; pub mod command_syntax_exception; diff --git a/azalea-brigadier/src/immutable_string_reader.rs b/azalea-brigadier/src/immutable_string_reader.rs deleted file mode 100644 index 53531c64..00000000 --- a/azalea-brigadier/src/immutable_string_reader.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub trait ImmutableStringReader { - fn string(&self) -> &str; - fn remaining_length(&self) -> usize; - fn total_length(&self) -> usize; - fn cursor(&self) -> usize; - fn get_read(&self) -> &str; - fn remaining(&self) -> &str; - fn can_read_length(&self, length: usize) -> bool; - fn can_read(&self) -> bool; - fn peek(&self) -> char; - fn peek_offset(&self, offset: usize) -> char; -} diff --git a/azalea-brigadier/src/lib.rs b/azalea-brigadier/src/lib.rs index a1ac267c..476764d0 100644 --- a/azalea-brigadier/src/lib.rs +++ b/azalea-brigadier/src/lib.rs @@ -1,32 +1,55 @@ -#[macro_use] -extern crate lazy_static; - -#[macro_use] -extern crate enum_dispatch; - -mod ambiguity_consumer; -mod arguments; -mod builder; -mod command; -mod command_dispatcher; -mod context; -mod exceptions; -mod immutable_string_reader; -mod literal_message; -mod message; -mod parse_results; -mod redirect_modifier; -mod result_consumer; -mod single_redirect_modifier; -mod string_reader; -mod suggestion; -mod tree; +pub mod builder; +pub mod context; +pub mod dispatcher; +pub mod exceptions; +pub mod message; +pub mod modifier; +pub mod parse_results; +pub mod parsers; +pub mod string_range; +pub mod string_reader; +pub mod tree; #[cfg(test)] mod tests { + + use std::rc::Rc; + + use crate::{ + builder::{literal_argument_builder::literal, required_argument_builder::argument}, + dispatcher::CommandDispatcher, + parsers::integer, + }; + + struct CommandSourceStack { + player: String, + } + #[test] fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); + let mut dispatcher = CommandDispatcher::<Rc<CommandSourceStack>>::new(); + + let source = Rc::new(CommandSourceStack { + player: "player".to_string(), + }); + + dispatcher.register( + literal("foo") + .then(argument("bar", integer()).executes(|c| { + // println!("Bar is {}", get_integer(c, "bar")); + 2 + })) + .executes(|c| { + println!("Called foo with no arguments"); + 1 + }), + ); + + let parse = dispatcher.parse("foo 123".to_string().into(), source); + println!( + "{}", + CommandDispatcher::<Rc<CommandSourceStack>>::execute(parse).unwrap() + ); + // assert_eq!(dispatcher.execute("foo bar", source), 2); } } diff --git a/azalea-brigadier/src/literal_message.rs b/azalea-brigadier/src/literal_message.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/literal_message.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/main.rs b/azalea-brigadier/src/main.rs new file mode 100644 index 00000000..53f2efa8 --- /dev/null +++ b/azalea-brigadier/src/main.rs @@ -0,0 +1,38 @@ +use std::rc::Rc; + +use rust_command_parser::{ + builder::{literal_argument_builder::literal, required_argument_builder::argument}, + dispatcher::CommandDispatcher, + parsers::integer, +}; + +struct CommandSourceStack { + player: String, +} + +pub fn main() { + let mut dispatcher = CommandDispatcher::<Rc<CommandSourceStack>>::new(); + + let source = Rc::new(CommandSourceStack { + player: "player".to_string(), + }); + + dispatcher.register( + literal("foo") + .then(argument("bar", integer()).executes(|c| { + // println!("Bar is {}", get_integer(c, "bar")); + 2 + })) + .executes(|c| { + println!("Called foo with no arguments"); + 1 + }), + ); + + let parse = dispatcher.parse("foo 123".to_string().into(), source); + println!("{:?}", parse); + println!( + "{}", + CommandDispatcher::<Rc<CommandSourceStack>>::execute(parse).unwrap() + ); +} diff --git a/azalea-brigadier/src/message.rs b/azalea-brigadier/src/message.rs index 42894d0e..9d133c7e 100644 --- a/azalea-brigadier/src/message.rs +++ b/azalea-brigadier/src/message.rs @@ -1,7 +1,7 @@ use std::rc::Rc; #[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct Message(Rc<String>); +pub struct Message(String); impl Message { pub fn string(&self) -> String { @@ -11,6 +11,6 @@ impl Message { impl From<String> for Message { fn from(s: String) -> Self { - Self(Rc::new(s)) + Self(s) } } diff --git a/azalea-brigadier/src/modifier.rs b/azalea-brigadier/src/modifier.rs new file mode 100644 index 00000000..84528696 --- /dev/null +++ b/azalea-brigadier/src/modifier.rs @@ -0,0 +1,9 @@ +use std::{any::Any, rc::Rc}; + +use crate::{ + context::CommandContext, exceptions::command_syntax_exception::CommandSyntaxException, +}; + +pub trait RedirectModifier<S: Any + Clone> { + fn apply(&self, context: &CommandContext<S>) -> Result<Vec<Rc<S>>, CommandSyntaxException>; +} diff --git a/azalea-brigadier/src/parse_results.rs b/azalea-brigadier/src/parse_results.rs index 8b137891..fb862dec 100644 --- a/azalea-brigadier/src/parse_results.rs +++ b/azalea-brigadier/src/parse_results.rs @@ -1 +1,21 @@ +use crate::{ + context::CommandContextBuilder, exceptions::command_syntax_exception::CommandSyntaxException, + string_reader::StringReader, tree::CommandNode, +}; +use std::{any::Any, collections::HashMap, fmt::Debug, rc::Rc}; +pub struct ParseResults<S: Any + Clone> { + pub context: CommandContextBuilder<S>, + pub reader: StringReader, + pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxException>, +} + +impl<S: Any + Clone> 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/parsers.rs b/azalea-brigadier/src/parsers.rs new file mode 100644 index 00000000..a497e0d1 --- /dev/null +++ b/azalea-brigadier/src/parsers.rs @@ -0,0 +1,21 @@ +use std::{any::Any, marker::PhantomData, rc::Rc}; + +use crate::string_reader::StringReader; + +pub trait Parser { + fn parse(&self, reader: &mut StringReader) -> Option<Rc<dyn Any>>; +} + +struct Integer {} +impl Parser for Integer { + fn parse(&self, reader: &mut StringReader) -> Option<Rc<dyn Any>> { + let start = reader.cursor; + let result = reader.read_int(); + // TODO: check min and max + Some(Rc::new(result)) + } +} + +pub fn integer() -> impl Parser { + Integer {} +} diff --git a/azalea-brigadier/src/redirect_modifier.rs b/azalea-brigadier/src/redirect_modifier.rs deleted file mode 100644 index 7a6d4db5..00000000 --- a/azalea-brigadier/src/redirect_modifier.rs +++ /dev/null @@ -1,11 +0,0 @@ -use dyn_clonable::*; - -use crate::{ - context::command_context::CommandContext, - exceptions::command_syntax_exception::CommandSyntaxException, -}; - -#[clonable] -pub trait RedirectModifier<S>: Clone { - fn apply(&self, context: CommandContext<S>) -> Result<Vec<S>, CommandSyntaxException>; -} diff --git a/azalea-brigadier/src/result_consumer.rs b/azalea-brigadier/src/result_consumer.rs deleted file mode 100644 index 8b137891..00000000 --- a/azalea-brigadier/src/result_consumer.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/azalea-brigadier/src/single_redirect_modifier.rs b/azalea-brigadier/src/single_redirect_modifier.rs deleted file mode 100644 index dd63244d..00000000 --- a/azalea-brigadier/src/single_redirect_modifier.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::{ - context::command_context::CommandContext, - exceptions::command_syntax_exception::CommandSyntaxException, -}; - -pub trait SingleRedirectModifier<S> { - fn apply(&self, context: CommandContext<S>) -> Result<S, CommandSyntaxException>; -} diff --git a/azalea-brigadier/src/context/string_range.rs b/azalea-brigadier/src/string_range.rs index 87098a1a..8ca88624 100644 --- a/azalea-brigadier/src/context/string_range.rs +++ b/azalea-brigadier/src/string_range.rs @@ -1,6 +1,6 @@ use std::cmp; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct StringRange { start: usize, end: usize, @@ -31,7 +31,7 @@ impl StringRange { self.end } - pub fn get(&self, reader: &str) -> &str { + pub fn get<'a>(&self, reader: &'a str) -> &'a str { &reader[self.start..self.end] } diff --git a/azalea-brigadier/src/string_reader.rs b/azalea-brigadier/src/string_reader.rs index 694edccb..4b390155 100644 --- a/azalea-brigadier/src/string_reader.rs +++ b/azalea-brigadier/src/string_reader.rs @@ -1,14 +1,11 @@ -use crate::{ - exceptions::{ - builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, - }, - immutable_string_reader::ImmutableStringReader, +use crate::exceptions::{ + builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, }; -use std::str::FromStr; +use std::{rc::Rc, str::FromStr}; #[derive(Clone)] -pub struct StringReader<'a> { - string: &'a str, +pub struct StringReader { + string: String, pub cursor: usize, } @@ -16,63 +13,53 @@ const SYNTAX_ESCAPE: char = '\\'; const SYNTAX_DOUBLE_QUOTE: char = '"'; const SYNTAX_SINGLE_QUOTE: char = '\''; -// impl<'a> From<&'a str> for &StringReader<'a> {} - -// impl StringReader<'_> { -// fn from(string: &str) -> StringReader { -// StringReader { string, cursor: 0 } -// } -// } - -impl<'a> From<&'a str> for StringReader<'a> { - fn from(string: &'a str) -> Self { +impl From<String> for StringReader { + fn from(string: String) -> Self { Self { string, cursor: 0 } } } -impl ImmutableStringReader for StringReader<'_> { - fn string(&self) -> &str { - self.string +impl StringReader { + pub fn string(&self) -> &str { + &self.string } - fn remaining_length(&self) -> usize { + pub fn remaining_length(&self) -> usize { self.string.len() - self.cursor } - fn total_length(&self) -> usize { + pub fn total_length(&self) -> usize { self.string.len() } - fn get_read(&self) -> &str { + pub fn get_read(&self) -> &str { &self.string[..self.cursor] } - fn remaining(&self) -> &str { + pub fn remaining(&self) -> &str { &self.string[self.cursor..] } - fn can_read_length(&self, length: usize) -> bool { + pub fn can_read_length(&self, length: usize) -> bool { self.cursor + length <= self.string.len() } - fn can_read(&self) -> bool { + pub fn can_read(&self) -> bool { self.can_read_length(1) } - fn peek(&self) -> char { + pub fn peek(&self) -> char { self.string.chars().nth(self.cursor).unwrap() } - fn peek_offset(&self, offset: usize) -> char { + pub fn peek_offset(&self, offset: usize) -> char { self.string.chars().nth(self.cursor + offset).unwrap() } - fn cursor(&self) -> usize { + pub fn cursor(&self) -> usize { self.cursor } -} -impl StringReader<'_> { pub fn read(&mut self) -> char { let c = self.peek(); self.cursor += 1; @@ -99,7 +86,7 @@ impl StringReader<'_> { pub fn read_int(&mut self) -> Result<i32, CommandSyntaxException> { let start = self.cursor; - while self.can_read() && StringReader::<'_>::is_allowed_number(self.peek()) { + while self.can_read() && StringReader::is_allowed_number(self.peek()) { self.skip(); } let number = &self.string[start..self.cursor]; @@ -120,7 +107,7 @@ impl StringReader<'_> { pub fn read_long(&mut self) -> Result<i64, CommandSyntaxException> { let start = self.cursor; - while self.can_read() && StringReader::<'_>::is_allowed_number(self.peek()) { + while self.can_read() && StringReader::is_allowed_number(self.peek()) { self.skip(); } let number = &self.string[start..self.cursor]; @@ -162,7 +149,7 @@ impl StringReader<'_> { pub fn read_float(&mut self) -> Result<f32, CommandSyntaxException> { let start = self.cursor; - while self.can_read() && StringReader::<'_>::is_allowed_number(self.peek()) { + while self.can_read() && StringReader::is_allowed_number(self.peek()) { self.skip(); } let number = &self.string[start..self.cursor]; @@ -204,7 +191,7 @@ impl StringReader<'_> { return Ok(String::new()); } let next = self.peek(); - if !StringReader::<'_>::is_quoted_string_start(next) { + if !StringReader::is_quoted_string_start(next) { return Err(BuiltInExceptions::ReaderExpectedStartOfQuote.create_with_context(self)); } self.skip(); @@ -245,7 +232,7 @@ impl StringReader<'_> { return Ok(String::new()); } let next = self.peek(); - if StringReader::<'_>::is_quoted_string_start(next) { + if StringReader::is_quoted_string_start(next) { self.skip(); return self.read_string_until(next); } @@ -286,7 +273,7 @@ mod test { #[test] fn can_read() { - let mut reader = StringReader::from("abc"); + let mut reader = StringReader::from("abc".to_string()); assert_eq!(reader.can_read(), true); reader.skip(); // 'a' assert_eq!(reader.can_read(), true); @@ -298,7 +285,7 @@ mod test { #[test] fn get_remaining_length() { - let mut reader = StringReader::from("abc"); + let mut reader = StringReader::from("abc".to_string()); assert_eq!(reader.remaining_length(), 3); reader.cursor = 1; assert_eq!(reader.remaining_length(), 2); @@ -310,7 +297,7 @@ mod test { #[test] fn can_read_length() { - let reader = StringReader::from("abc"); + let reader = StringReader::from("abc".to_string()); assert_eq!(reader.can_read_length(1), true); assert_eq!(reader.can_read_length(2), true); assert_eq!(reader.can_read_length(3), true); @@ -320,7 +307,7 @@ mod test { #[test] fn peek() { - let mut reader = StringReader::from("abc"); + let mut reader = StringReader::from("abc".to_string()); assert_eq!(reader.peek(), 'a'); assert_eq!(reader.cursor(), 0); reader.cursor = 2; @@ -330,7 +317,7 @@ mod test { #[test] fn peek_length() { - let mut reader = StringReader::from("abc"); + let mut reader = StringReader::from("abc".to_string()); assert_eq!(reader.peek_offset(0), 'a'); assert_eq!(reader.peek_offset(2), 'c'); assert_eq!(reader.cursor(), 0); @@ -341,7 +328,7 @@ mod test { #[test] fn read() { - let mut reader = StringReader::from("abc"); + let mut reader = StringReader::from("abc".to_string()); assert_eq!(reader.read(), 'a'); assert_eq!(reader.read(), 'b'); assert_eq!(reader.read(), 'c'); @@ -350,14 +337,14 @@ mod test { #[test] fn skip() { - let mut reader = StringReader::from("abc"); + let mut reader = StringReader::from("abc".to_string()); reader.skip(); assert_eq!(reader.cursor(), 1); } #[test] fn get_remaining() { - let mut reader = StringReader::from("Hello!"); + let mut reader = StringReader::from("Hello!".to_string()); assert_eq!(reader.remaining(), "Hello!"); reader.cursor = 3; assert_eq!(reader.remaining(), "lo!"); @@ -367,7 +354,7 @@ mod test { #[test] fn get_read() { - let mut reader = StringReader::from("Hello!"); + let mut reader = StringReader::from("Hello!".to_string()); assert_eq!(reader.get_read(), ""); reader.cursor = 3; assert_eq!(reader.get_read(), "Hel"); @@ -377,28 +364,28 @@ mod test { #[test] fn skip_whitespace_none() { - let mut reader = StringReader::from("Hello!"); + let mut reader = StringReader::from("Hello!".to_string()); reader.skip_whitespace(); assert_eq!(reader.cursor(), 0); } #[test] fn skip_whitespace_mixed() { - let mut reader = StringReader::from(" \t \t\nHello!"); + let mut reader = StringReader::from(" \t \t\nHello!".to_string()); reader.skip_whitespace(); assert_eq!(reader.cursor(), 5); } #[test] fn skip_whitespace_empty() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); reader.skip_whitespace(); assert_eq!(reader.cursor(), 0); } #[test] fn read_unquoted_string() { - let mut reader = StringReader::from("hello world"); + let mut reader = StringReader::from("hello world".to_string()); assert_eq!(reader.read_unquoted_string(), "hello"); assert_eq!(reader.get_read(), "hello"); assert_eq!(reader.remaining(), " world"); @@ -406,7 +393,7 @@ mod test { #[test] fn read_unquoted_string_empty() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); assert_eq!(reader.read_unquoted_string(), ""); assert_eq!(reader.get_read(), ""); assert_eq!(reader.remaining(), ""); @@ -414,7 +401,7 @@ mod test { #[test] fn read_unquoted_string_empty_with_remaining() { - let mut reader = StringReader::from(" hello world"); + let mut reader = StringReader::from(" hello world".to_string()); assert_eq!(reader.read_unquoted_string(), ""); assert_eq!(reader.get_read(), ""); assert_eq!(reader.remaining(), " hello world"); @@ -422,7 +409,7 @@ mod test { #[test] fn read_quoted_string() { - let mut reader = StringReader::from("\"hello world\""); + let mut reader = StringReader::from("\"hello world\"".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), "hello world"); assert_eq!(reader.get_read(), "\"hello world\""); assert_eq!(reader.remaining(), ""); @@ -430,7 +417,7 @@ mod test { #[test] fn read_single_quoted_string() { - let mut reader = StringReader::from("'hello world'"); + let mut reader = StringReader::from("'hello world'".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), "hello world"); assert_eq!(reader.get_read(), "'hello world'"); assert_eq!(reader.remaining(), ""); @@ -438,7 +425,7 @@ mod test { #[test] fn read_mixed_quoted_string_double_inside_single() { - let mut reader = StringReader::from("'hello \"world\"'"); + let mut reader = StringReader::from("'hello \"world\"'".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), "hello \"world\""); assert_eq!(reader.get_read(), "'hello \"world\"'"); assert_eq!(reader.remaining(), ""); @@ -446,7 +433,7 @@ mod test { #[test] fn read_mixed_quoted_string_single_inside_double() { - let mut reader = StringReader::from("\"hello 'world'\""); + let mut reader = StringReader::from("\"hello 'world'\"".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), "hello 'world'"); assert_eq!(reader.get_read(), "\"hello 'world'\""); assert_eq!(reader.remaining(), ""); @@ -454,7 +441,7 @@ mod test { #[test] fn read_quoted_string_empty_quoted() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), ""); assert_eq!(reader.get_read(), ""); assert_eq!(reader.remaining(), ""); @@ -462,7 +449,7 @@ mod test { #[test] fn read_quoted_string_empty_quoted_with_remaining() { - let mut reader = StringReader::from("\"\" hello world"); + let mut reader = StringReader::from("\"\" hello world".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), ""); assert_eq!(reader.get_read(), "\"\""); assert_eq!(reader.remaining(), " hello world"); @@ -470,7 +457,7 @@ mod test { #[test] fn read_quoted_string_with_escaped_quote() { - let mut reader = StringReader::from("\"hello \\\"world\\\"\""); + let mut reader = StringReader::from("\"hello \\\"world\\\"\"".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), "hello \"world\""); assert_eq!(reader.get_read(), "\"hello \\\"world\\\"\""); assert_eq!(reader.remaining(), ""); @@ -478,7 +465,7 @@ mod test { #[test] fn read_quoted_string_with_escaped_escapes() { - let mut reader = StringReader::from("\"\\\\o/\""); + let mut reader = StringReader::from("\"\\\\o/\"".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), "\\o/"); assert_eq!(reader.get_read(), "\"\\\\o/\""); assert_eq!(reader.remaining(), ""); @@ -486,7 +473,7 @@ mod test { #[test] fn read_quoted_string_with_remaining() { - let mut reader = StringReader::from("\"hello world\" foo bar"); + let mut reader = StringReader::from("\"hello world\" foo bar".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), "hello world"); assert_eq!(reader.get_read(), "\"hello world\""); assert_eq!(reader.remaining(), " foo bar"); @@ -494,7 +481,7 @@ mod test { #[test] fn read_quoted_string_with_immediate_remaining() { - let mut reader = StringReader::from("\"hello world\"foo bar"); + let mut reader = StringReader::from("\"hello world\"foo bar".to_string()); assert_eq!(reader.read_quoted_string().unwrap(), "hello world"); assert_eq!(reader.get_read(), "\"hello world\""); assert_eq!(reader.remaining(), "foo bar"); @@ -502,7 +489,7 @@ mod test { #[test] fn read_quoted_string_no_open() { - let mut reader = StringReader::from("hello world\""); + let mut reader = StringReader::from("hello world\"".to_string()); let result = reader.read_quoted_string(); assert!(result.is_err()); if let Err(e) = result { @@ -513,7 +500,7 @@ mod test { #[test] fn read_quoted_string_no_close() { - let mut reader = StringReader::from("\"hello world"); + let mut reader = StringReader::from("\"hello world".to_string()); let result = reader.read_quoted_string(); assert!(result.is_err()); if let Err(e) = result { @@ -524,7 +511,7 @@ mod test { #[test] fn read_quoted_string_invalid_escape() { - let mut reader = StringReader::from("\"hello\\nworld\""); + let mut reader = StringReader::from("\"hello\\nworld\"".to_string()); let result = reader.read_quoted_string(); assert!(result.is_err()); if let Err(e) = result { @@ -538,7 +525,7 @@ mod test { #[test] fn read_quoted_string_invalid_quote_escape() { - let mut reader = StringReader::from("'hello\\\"\'world"); + let mut reader = StringReader::from("'hello\\\"\'world".to_string()); let result = reader.read_quoted_string(); assert!(result.is_err()); if let Err(e) = result { @@ -552,7 +539,7 @@ mod test { #[test] fn read_string_no_quotes() { - let mut reader = StringReader::from("hello world"); + let mut reader = StringReader::from("hello world".to_string()); assert_eq!(reader.read_string().unwrap(), "hello"); assert_eq!(reader.get_read(), "hello"); assert_eq!(reader.remaining(), " world"); @@ -560,7 +547,7 @@ mod test { #[test] fn read_string_single_quotes() { - let mut reader = StringReader::from("'hello world'"); + let mut reader = StringReader::from("'hello world'".to_string()); assert_eq!(reader.read_string().unwrap(), "hello world"); assert_eq!(reader.get_read(), "'hello world'"); assert_eq!(reader.remaining(), ""); @@ -568,7 +555,7 @@ mod test { #[test] fn read_string_double_quotes() { - let mut reader = StringReader::from("\"hello world\""); + let mut reader = StringReader::from("\"hello world\"".to_string()); assert_eq!(reader.read_string().unwrap(), "hello world"); assert_eq!(reader.get_read(), "\"hello world\""); assert_eq!(reader.remaining(), ""); @@ -576,7 +563,7 @@ mod test { #[test] fn read_int() { - let mut reader = StringReader::from("1234567890"); + let mut reader = StringReader::from("1234567890".to_string()); assert_eq!(reader.read_int().unwrap(), 1234567890); assert_eq!(reader.get_read(), "1234567890"); assert_eq!(reader.remaining(), ""); @@ -584,7 +571,7 @@ mod test { #[test] fn read_int_negative() { - let mut reader = StringReader::from("-1234567890"); + let mut reader = StringReader::from("-1234567890".to_string()); assert_eq!(reader.read_int().unwrap(), -1234567890); assert_eq!(reader.get_read(), "-1234567890"); assert_eq!(reader.remaining(), ""); @@ -592,7 +579,7 @@ mod test { #[test] fn read_int_invalid() { - let mut reader = StringReader::from("12.34"); + let mut reader = StringReader::from("12.34".to_string()); let result = reader.read_int(); assert!(result.is_err()); if let Err(e) = result { @@ -608,7 +595,7 @@ mod test { #[test] fn read_int_none() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); let result = reader.read_int(); assert!(result.is_err()); if let Err(e) = result { @@ -619,7 +606,7 @@ mod test { #[test] fn read_int_with_remaining() { - let mut reader = StringReader::from("1234567890 foo bar"); + let mut reader = StringReader::from("1234567890 foo bar".to_string()); assert_eq!(reader.read_int().unwrap(), 1234567890); assert_eq!(reader.get_read(), "1234567890"); assert_eq!(reader.remaining(), " foo bar"); @@ -627,7 +614,7 @@ mod test { #[test] fn read_int_with_remaining_immediate() { - let mut reader = StringReader::from("1234567890foo bar"); + let mut reader = StringReader::from("1234567890foo bar".to_string()); assert_eq!(reader.read_int().unwrap(), 1234567890); assert_eq!(reader.get_read(), "1234567890"); assert_eq!(reader.remaining(), "foo bar"); @@ -635,7 +622,7 @@ mod test { #[test] fn read_long() { - let mut reader = StringReader::from("1234567890"); + let mut reader = StringReader::from("1234567890".to_string()); assert_eq!(reader.read_long().unwrap(), 1234567890); assert_eq!(reader.get_read(), "1234567890"); assert_eq!(reader.remaining(), ""); @@ -643,7 +630,7 @@ mod test { #[test] fn read_long_negative() { - let mut reader = StringReader::from("-1234567890"); + let mut reader = StringReader::from("-1234567890".to_string()); assert_eq!(reader.read_long().unwrap(), -1234567890); assert_eq!(reader.get_read(), "-1234567890"); assert_eq!(reader.remaining(), ""); @@ -651,7 +638,7 @@ mod test { #[test] fn read_long_invalid() { - let mut reader = StringReader::from("12.34"); + let mut reader = StringReader::from("12.34".to_string()); let result = reader.read_long(); assert!(result.is_err()); if let Err(e) = result { @@ -667,7 +654,7 @@ mod test { #[test] fn read_long_none() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); let result = reader.read_long(); assert!(result.is_err()); if let Err(e) = result { @@ -678,7 +665,7 @@ mod test { #[test] fn read_long_with_remaining() { - let mut reader = StringReader::from("1234567890 foo bar"); + let mut reader = StringReader::from("1234567890 foo bar".to_string()); assert_eq!(reader.read_long().unwrap(), 1234567890); assert_eq!(reader.get_read(), "1234567890"); assert_eq!(reader.remaining(), " foo bar"); @@ -686,7 +673,7 @@ mod test { #[test] fn read_long_with_remaining_immediate() { - let mut reader = StringReader::from("1234567890foo bar"); + let mut reader = StringReader::from("1234567890foo bar".to_string()); assert_eq!(reader.read_long().unwrap(), 1234567890); assert_eq!(reader.get_read(), "1234567890"); assert_eq!(reader.remaining(), "foo bar"); @@ -694,7 +681,7 @@ mod test { #[test] fn read_double() { - let mut reader = StringReader::from("123"); + let mut reader = StringReader::from("123".to_string()); assert_eq!(reader.read_double().unwrap(), 123.0); assert_eq!(reader.get_read(), "123"); assert_eq!(reader.remaining(), ""); @@ -702,7 +689,7 @@ mod test { #[test] fn read_double_with_decimal() { - let mut reader = StringReader::from("12.34"); + let mut reader = StringReader::from("12.34".to_string()); assert_eq!(reader.read_double().unwrap(), 12.34); assert_eq!(reader.get_read(), "12.34"); assert_eq!(reader.remaining(), ""); @@ -710,7 +697,7 @@ mod test { #[test] fn read_double_negative() { - let mut reader = StringReader::from("-123"); + let mut reader = StringReader::from("-123".to_string()); assert_eq!(reader.read_double().unwrap(), -123.0); assert_eq!(reader.get_read(), "-123"); assert_eq!(reader.remaining(), ""); @@ -718,7 +705,7 @@ mod test { #[test] fn read_double_invalid() { - let mut reader = StringReader::from("12.34.56"); + let mut reader = StringReader::from("12.34.56".to_string()); let result = reader.read_double(); assert!(result.is_err()); if let Err(e) = result { @@ -734,7 +721,7 @@ mod test { #[test] fn read_double_none() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); let result = reader.read_double(); assert!(result.is_err()); if let Err(e) = result { @@ -745,7 +732,7 @@ mod test { #[test] fn read_double_with_remaining() { - let mut reader = StringReader::from("12.34 foo bar"); + let mut reader = StringReader::from("12.34 foo bar".to_string()); assert_eq!(reader.read_double().unwrap(), 12.34); assert_eq!(reader.get_read(), "12.34"); assert_eq!(reader.remaining(), " foo bar"); @@ -753,7 +740,7 @@ mod test { #[test] fn read_double_with_remaining_immediate() { - let mut reader = StringReader::from("12.34foo bar"); + let mut reader = StringReader::from("12.34foo bar".to_string()); assert_eq!(reader.read_double().unwrap(), 12.34); assert_eq!(reader.get_read(), "12.34"); assert_eq!(reader.remaining(), "foo bar"); @@ -761,7 +748,7 @@ mod test { #[test] fn read_float() { - let mut reader = StringReader::from("123"); + let mut reader = StringReader::from("123".to_string()); assert_eq!(reader.read_float().unwrap(), 123.0f32); assert_eq!(reader.get_read(), "123"); assert_eq!(reader.remaining(), ""); @@ -769,7 +756,7 @@ mod test { #[test] fn read_float_with_decimal() { - let mut reader = StringReader::from("12.34"); + let mut reader = StringReader::from("12.34".to_string()); assert_eq!(reader.read_float().unwrap(), 12.34f32); assert_eq!(reader.get_read(), "12.34"); assert_eq!(reader.remaining(), ""); @@ -777,7 +764,7 @@ mod test { #[test] fn read_float_negative() { - let mut reader = StringReader::from("-123"); + let mut reader = StringReader::from("-123".to_string()); assert_eq!(reader.read_float().unwrap(), -123.0f32); assert_eq!(reader.get_read(), "-123"); assert_eq!(reader.remaining(), ""); @@ -785,7 +772,7 @@ mod test { #[test] fn read_float_invalid() { - let mut reader = StringReader::from("12.34.56"); + let mut reader = StringReader::from("12.34.56".to_string()); let result = reader.read_float(); assert!(result.is_err()); if let Err(e) = result { @@ -801,7 +788,7 @@ mod test { #[test] fn read_float_none() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); let result = reader.read_float(); assert!(result.is_err()); if let Err(e) = result { @@ -812,7 +799,7 @@ mod test { #[test] fn read_float_with_remaining() { - let mut reader = StringReader::from("12.34 foo bar"); + let mut reader = StringReader::from("12.34 foo bar".to_string()); assert_eq!(reader.read_float().unwrap(), 12.34f32); assert_eq!(reader.get_read(), "12.34"); assert_eq!(reader.remaining(), " foo bar"); @@ -820,7 +807,7 @@ mod test { #[test] fn read_float_with_remaining_immediate() { - let mut reader = StringReader::from("12.34foo bar"); + let mut reader = StringReader::from("12.34foo bar".to_string()); assert_eq!(reader.read_float().unwrap(), 12.34f32); assert_eq!(reader.get_read(), "12.34"); assert_eq!(reader.remaining(), "foo bar"); @@ -828,14 +815,14 @@ mod test { #[test] fn expect_correct() { - let mut reader = StringReader::from("abc"); + let mut reader = StringReader::from("abc".to_string()); reader.expect('a'); assert_eq!(reader.cursor(), 1); } #[test] fn expect_incorrect() { - let mut reader = StringReader::from("bcd"); + let mut reader = StringReader::from("bcd".to_string()); let result = reader.expect('a'); assert!(result.is_err()); if let Err(e) = result { @@ -849,7 +836,7 @@ mod test { #[test] fn expect_none() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); let result = reader.expect('a'); assert!(result.is_err()); if let Err(e) = result { @@ -863,14 +850,14 @@ mod test { #[test] fn read_boolean_correct() { - let mut reader = StringReader::from("true"); + let mut reader = StringReader::from("true".to_string()); assert_eq!(reader.read_boolean().unwrap(), true); assert_eq!(reader.get_read(), "true"); } #[test] fn read_boolean_incorrect() { - let mut reader = StringReader::from("tuesday"); + let mut reader = StringReader::from("tuesday".to_string()); let result = reader.read_boolean(); assert!(result.is_err()); if let Err(e) = result { @@ -886,7 +873,7 @@ mod test { #[test] fn read_boolean_none() { - let mut reader = StringReader::from(""); + let mut reader = StringReader::from("".to_string()); let result = reader.read_boolean(); assert!(result.is_err()); if let Err(e) = result { diff --git a/azalea-brigadier/src/suggestion/integer_suggestion.rs b/azalea-brigadier/src/suggestion/integer_suggestion.rs deleted file mode 100644 index acee2329..00000000 --- a/azalea-brigadier/src/suggestion/integer_suggestion.rs +++ /dev/null @@ -1 +0,0 @@ -pub struct IntegerSuggestion {} diff --git a/azalea-brigadier/src/suggestion/mod.rs b/azalea-brigadier/src/suggestion/mod.rs deleted file mode 100644 index 050bae6c..00000000 --- a/azalea-brigadier/src/suggestion/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod integer_suggestion; -pub mod suggestion; -pub mod suggestion_provider; -pub mod suggestions; -pub mod suggestions_builder; diff --git a/azalea-brigadier/src/suggestion/suggestion.rs b/azalea-brigadier/src/suggestion/suggestion.rs deleted file mode 100644 index 4cbed7be..00000000 --- a/azalea-brigadier/src/suggestion/suggestion.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::cmp; - -use crate::{context::string_range::StringRange, message::Message}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Suggestion { - range: StringRange, - text: String, - tooltip: Option<Message>, -} - -impl Suggestion { - pub fn new(range: StringRange, text: String) -> Suggestion { - Suggestion { - range, - text, - tooltip: None, - } - } - - pub fn new_with_tooltip(range: StringRange, text: String, tooltip: Message) -> Suggestion { - Suggestion { - range, - text, - tooltip: Some(tooltip), - } - } - - pub fn range(&self) -> &StringRange { - &self.range - } - - pub fn text(&self) -> &String { - &self.text - } - - pub fn tooltip(&self) -> Option<&Message> { - self.tooltip.as_ref() - } - - pub fn apply(&self, input: &str) -> String { - if self.range.start() == 0 && self.range.end() == input.len() { - return self.text.clone(); - } - let mut result = String::new(); - if self.range.start() > 0 { - result.push_str(&input[0..self.range.start()]); - } - result.push_str(&self.text); - if self.range.end() < input.len() { - result.push_str(&input[self.range.end()..]); - } - result - } - - pub fn expand(&self, command: &str, range: StringRange) -> Suggestion { - if range == self.range { - return self.clone(); - } - let mut result = String::new(); - if range.start() < self.range.start() { - result.push_str(&command[range.start()..self.range.start()]); - } - result.push_str(&self.text); - if range.end() > self.range.end() { - result.push_str(&command[self.range.end()..range.end()]); - } - Suggestion { - range, - text: result, - tooltip: self.tooltip.clone(), - } - } - - pub fn compare_ignore_case(&self, b: &Suggestion) -> cmp::Ordering { - self.text.to_lowercase().cmp(&b.text.to_lowercase()) - } -} - -impl PartialOrd for Suggestion { - fn partial_cmp(&self, other: &Suggestion) -> Option<cmp::Ordering> { - Some(self.text.cmp(&other.text)) - } -} - -impl Ord for Suggestion { - fn cmp(&self, other: &Suggestion) -> cmp::Ordering { - self.text.cmp(&other.text) - } -} diff --git a/azalea-brigadier/src/suggestion/suggestion_provider.rs b/azalea-brigadier/src/suggestion/suggestion_provider.rs deleted file mode 100644 index 3027d460..00000000 --- a/azalea-brigadier/src/suggestion/suggestion_provider.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::{ - context::command_context::CommandContext, - exceptions::command_syntax_exception::CommandSyntaxException, -}; - -use super::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}; - -pub trait SuggestionProvider<S> { - fn suggestions( - &self, - context: &CommandContext<S>, - builder: &SuggestionsBuilder, - ) -> Result<Suggestions, CommandSyntaxException>; -} diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs deleted file mode 100644 index 9f0ee06d..00000000 --- a/azalea-brigadier/src/suggestion/suggestions.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::{cmp, collections::HashSet}; - -use crate::{context::string_range::StringRange, message::Message}; - -use super::suggestion::Suggestion; - -#[derive(PartialEq, Eq, Hash)] -pub struct Suggestions { - range: StringRange, - suggestions: Vec<Suggestions>, -} - -impl Suggestions { - fn range(&self) -> &StringRange { - &self.range - } - - fn list(&self) -> &Vec<Suggestions> { - &self.suggestions - } - - fn is_empty(&self) -> bool { - self.suggestions.is_empty() - } - - fn merge(command: &str, input: &Vec<Suggestions>) { - if input.is_empty() { - return Self::default(); - } else if input.len() == 1 { - return input.iter().next(); - } - let texts = HashSet::new(); - for suggestions in input { - texts.extend(suggestions.list()) - } - Self::new(command, texts) - } - - // public static Suggestions create(final String command, final Collection<Suggestion> suggestions) { - // if (suggestions.isEmpty()) { - // return EMPTY; - // } - // int start = Integer.MAX_VALUE; - // int end = Integer.MIN_VALUE; - // for (final Suggestion suggestion : suggestions) { - // start = Math.min(suggestion.getRange().getStart(), start); - // end = Math.max(suggestion.getRange().getEnd(), end); - // } - // final StringRange range = new StringRange(start, end); - // final Set<Suggestion> texts = new HashSet<>(); - // for (final Suggestion suggestion : suggestions) { - // texts.add(suggestion.expand(command, range)); - // } - // final List<Suggestion> sorted = new ArrayList<>(texts); - // sorted.sort((a, b) -> a.compareToIgnoreCase(b)); - // return new Suggestions(range, sorted); - pub fn new(command: String, suggestions: Vec<Suggestion>) -> Self { - if suggestions.is_empty() { - return Self::default(); - } - let mut start = usize::MAX; - let mut end = usize::MIN; - for suggestion in suggestions { - let start = cmp::min(suggestion.range().start(), start); - let end = cmp::max(suggestion.range().end(), end); - } - let range = StringRange::new(start, end); - let texts = HashSet::new(); - for suggestion in suggestions { - texts.insert(suggestion.expand(command, range)); - } - let sorted = texts.sort_by(|a, b| a.compare_ignore_case(b)); - Suggestions { - range, - suggestions: sorted, - } - } -} - -impl Default for Suggestions { - fn default() -> Self { - Self { - range: StringRange::at(0), - suggestions: vec![], - } - } -} - -// #[cfg(test)] -// mod tests { -// use crate::suggestion::suggestion::Suggestion; - -// use super::*; - -// #[test] -// fn merge_empty() { -// let merged = Suggestions::merge("foo b", vec![]); -// assert_eq!(merged.is_empty(), true); -// } - -// #[test] -// fn merge_single() { -// let suggestions = Suggestions::new(StringRange::at(5), "ar".to_string()); -// let merged = Suggestions::merge("foo b", vec![suggestions]); -// assert_eq!(merged, suggestions); -// } - -// #[test] -// fn merge_multiple() { -// let a = Suggestions::new( -// StringRange::at(5), -// vec![ -// Suggestion::new(StringRange::at(5), "ar".to_string()), -// Suggestion::new(StringRange::at(5), "az".to_string()), -// Suggestion::new(StringRange::at(5), "Az".to_string()), -// ], -// ); -// let b = Suggestions::new( -// StringRange::between(4, 5), -// vec![ -// Suggestion::new(StringRange::between(4, 5), "foo".to_string()), -// Suggestion::new(StringRange::between(4, 5), "qux".to_string()), -// Suggestion::new(StringRange::between(4, 5), "apple".to_string()), -// Suggestion::new(StringRange::between(4, 5), "Bar".to_string()), -// ], -// ); -// let merged = Suggestions::merge("foo b", vec![a, b]); -// assert_eq!( -// merged.get_list(), -// vec![ -// Suggestion::new(StringRange::between(4, 5), "apple".to_string()), -// Suggestion::new(StringRange::between(4, 5), "bar".to_string()), -// Suggestion::new(StringRange::between(4, 5), "Bar".to_string()), -// Suggestion::new(StringRange::between(4, 5), "baz".to_string()), -// Suggestion::new(StringRange::between(4, 5), "bAz".to_string()), -// Suggestion::new(StringRange::between(4, 5), "foo".to_string()), -// Suggestion::new(StringRange::between(4, 5), "qux".to_string()), -// ] -// ); -// } -// } diff --git a/azalea-brigadier/src/suggestion/suggestions_builder.rs b/azalea-brigadier/src/suggestion/suggestions_builder.rs deleted file mode 100644 index bc8f6f5d..00000000 --- a/azalea-brigadier/src/suggestion/suggestions_builder.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::context::string_range::StringRange; - -use super::{ - integer_suggestion::IntegerSuggestion, suggestion::Suggestion, suggestions::Suggestions, -}; - -pub struct SuggestionsBuilder { - input: String, - input_lowercase: String, - start: usize, - remaining: String, - remaining_lowercase: String, - result: Vec<Suggestion>, -} - -impl SuggestionsBuilder { - pub fn new_with_lowercase( - input: String, - input_lowercase: String, - start: usize, - ) -> SuggestionsBuilder { - SuggestionsBuilder { - input, - input_lowercase, - start, - remaining: input.get(start..).unwrap().to_string(), - remaining_lowercase: input_lowercase.get(start..).unwrap().to_string(), - result: Vec::new(), - } - } - - pub fn new(input: String, start: usize) -> SuggestionsBuilder { - SuggestionsBuilder::new_with_lowercase(input, input.to_lowercase(), start) - } - - pub fn input(&self) -> &str { - &self.input - } - - pub fn start(&self) -> usize { - self.start - } - - pub fn remaining(&self) -> &str { - &self.remaining - } - - pub fn remaining_lowercase(&self) -> &str { - &self.remaining_lowercase - } - - pub fn build(&self) -> Suggestions { - Suggestions::create(self.input(), self.result) - } - - pub fn suggest(&mut self, text: &str) -> &mut SuggestionsBuilder { - if text == self.remaining { - return self; - } - self.result.push(Suggestion::new( - StringRange::between(self.start, self.input.len()), - text, - )); - self - } - - pub fn suggest_with_tooltip(&mut self, text: &str, tooltip: &str) -> &mut SuggestionsBuilder { - if text == self.remaining { - return self; - } - self.result.push(Suggestion::new_with_tooltip( - StringRange::between(self.start, self.input.len()), - text, - tooltip, - )); - self - } - - pub fn suggest_with_value(&mut self, value: i32) -> &mut SuggestionsBuilder { - self.result.push(IntegerSuggestion::new( - StringRange::between(self.start, self.input.len()), - value, - )); - self - } - - pub fn suggest_with_value_and_tooltip( - &mut self, - value: i32, - tooltip: &str, - ) -> &mut SuggestionsBuilder { - self.result.push(IntegerSuggestion::new_with_tooltip( - StringRange::between(self.start, self.input.len()), - value, - tooltip, - )); - self - } - - pub fn add(&mut self, other: &SuggestionsBuilder) -> &mut SuggestionsBuilder { - self.result.extend(other.result.iter().cloned()); - self - } - - pub fn create_offset(&self, start: usize) -> SuggestionsBuilder { - SuggestionsBuilder::new_with_lowercase( - self.input.clone(), - self.input_lowercase.clone(), - start, - ) - } - - pub fn restart(&self) -> SuggestionsBuilder { - self.create_offset(self.start) - } -} diff --git a/azalea-brigadier/src/tree.rs b/azalea-brigadier/src/tree.rs new file mode 100644 index 00000000..2f023697 --- /dev/null +++ b/azalea-brigadier/src/tree.rs @@ -0,0 +1,269 @@ +use crate::{ + builder::{ + argument_builder::ArgumentBuilderType, literal_argument_builder::Literal, + required_argument_builder::Argument, + }, + context::{CommandContext, CommandContextBuilder, ParsedArgument}, + exceptions::{ + builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, + }, + modifier::RedirectModifier, + string_range::StringRange, + string_reader::StringReader, +}; +use std::{ + any::Any, + cell::RefCell, + collections::{BTreeMap, HashMap}, + fmt::Debug, + hash::Hash, + ptr, + rc::Rc, +}; + +/// An ArgumentBuilder that has been built. +#[derive(Clone)] +#[non_exhaustive] +pub struct CommandNode<S: Any + Clone> { + pub value: ArgumentBuilderType, + + // we use BTreeMap instead of HashMap because it can be hashed + pub children: BTreeMap<String, Rc<RefCell<CommandNode<S>>>>, + pub literals: BTreeMap<String, Rc<RefCell<CommandNode<S>>>>, + pub arguments: BTreeMap<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<dyn RedirectModifier<S>>>, +} + +impl<S: Any + Clone> CommandNode<S> { + // pub fn new() + // TODO: precalculate `literals` and `arguments` and include them in CommandNode + fn literals(&self) -> &BTreeMap<String, Rc<RefCell<CommandNode<S>>>> { + &self.literals + } + fn arguments(&self) -> &BTreeMap<String, Rc<RefCell<CommandNode<S>>>> { + &self.arguments + } + + /// 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(); + + println!("get relevant nodes {:?} literals={:?}", self, literals); + + if literals.len() > 0 { + 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() + .map(|argument| argument.clone()) + .collect(); + } + } else { + return self + .arguments() + .values() + .map(|argument| argument.clone()) + .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 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(); + // TODO: handle this better + let result = argument + .parse(reader) + .expect("Couldn't get result for some reason"); + let parsed = ParsedArgument { + range: StringRange::between(start, reader.cursor()), + result: result, + }; + + context_builder.with_argument(&argument.name, parsed.clone()); + context_builder.with_node(Rc::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(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(ref 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: Any + Clone> 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: Any + Clone> Default for CommandNode<S> { + fn default() -> Self { + println!("making default node"); + Self { + value: ArgumentBuilderType::Literal(Literal::default()), + + children: BTreeMap::new(), + literals: BTreeMap::new(), + arguments: BTreeMap::new(), + + command: None, + requirement: Rc::new(|_| true), + redirect: None, + forks: false, + modifier: None, + } + } +} + +impl<S: Any + Clone> 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: Any + Clone> PartialEq for CommandNode<S> { + fn eq(&self, other: &Self) -> bool { + if self.children != other.children { + return false; + } + if let Some(selfexecutes) = &self.command { + if let Some(otherexecutes) = &other.command { + if !Rc::ptr_eq(selfexecutes, otherexecutes) { + return false; + } + } else { + return false; + } + } + true + } +} +impl<S: Any + Clone> Eq for CommandNode<S> {} diff --git a/azalea-brigadier/src/tree/argument_command_node.rs b/azalea-brigadier/src/tree/argument_command_node.rs deleted file mode 100644 index 9d2af14e..00000000 --- a/azalea-brigadier/src/tree/argument_command_node.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::{ - any::Any, - collections::HashMap, - fmt::{Debug, Display, Formatter}, -}; - -use crate::{ - arguments::argument_type::ArgumentType, - builder::required_argument_builder::RequiredArgumentBuilder, - command::Command, - context::{ - command_context::CommandContext, command_context_builder::CommandContextBuilder, - parsed_argument::ParsedArgument, - }, - exceptions::command_syntax_exception::CommandSyntaxException, - immutable_string_reader::ImmutableStringReader, - redirect_modifier::RedirectModifier, - string_reader::StringReader, - suggestion::{ - suggestion_provider::SuggestionProvider, suggestions::Suggestions, - suggestions_builder::SuggestionsBuilder, - }, -}; - -use super::{ - command_node::{BaseCommandNode, CommandNodeTrait}, - literal_command_node::LiteralCommandNode, - root_command_node::RootCommandNode, -}; - -const USAGE_ARGUMENT_OPEN: &str = "<"; -const USAGE_ARGUMENT_CLOSE: &str = ">"; - -pub struct ArgumentCommandNode<S> { - name: String, - type_: Box<dyn ArgumentType<Into = dyn Any>>, - custom_suggestions: Option<Box<dyn SuggestionProvider<S>>>, - - children: HashMap<String, Box<dyn CommandNodeTrait<S>>>, - literals: HashMap<String, LiteralCommandNode<S>>, - arguments: HashMap<String, ArgumentCommandNode<S>>, - pub requirement: Box<dyn Fn(&S) -> bool>, - redirect: Option<Box<dyn CommandNodeTrait<S>>>, - modifier: Option<Box<dyn RedirectModifier<S>>>, - forks: bool, - pub command: Option<Box<dyn Command<S>>>, -} - -impl<S> ArgumentCommandNode<S> { - fn get_type(&self) -> &dyn ArgumentType<Into = dyn Any> { - &*self.type_ - } - - fn custom_suggestions(&self) -> &Option<Box<dyn SuggestionProvider<S>>> { - &self.custom_suggestions - } -} - -impl<S> CommandNodeTrait<S> for ArgumentCommandNode<S> { - fn name(&self) -> &str { - &self.name - } - - fn parse( - &self, - reader: &mut StringReader, - context_builder: CommandContextBuilder<S>, - ) -> Result<(), CommandSyntaxException> { - // final int start = reader.getCursor(); - // final T result = type.parse(reader); - // final ParsedArgument<S> parsed = new ParsedArgument<>(start, reader.getCursor(), result); - - // contextBuilder.withArgument(name, parsed); - // contextBuilder.withNode(this, parsed.getRange()); - - let start = reader.cursor(); - let result = self.get_type().parse(reader)?; - let parsed = ParsedArgument::new(start, reader.get_cursor(), result); - - context_builder.with_argument(&self.name, parsed); - context_builder.with_node(self, parsed.get_range()); - - Ok(()) - } - - fn list_suggestions( - &self, - context: CommandContext<S>, - builder: &SuggestionsBuilder, - ) -> Result<Suggestions, CommandSyntaxException> { - if self.custom_suggestions.is_none() { - self.get_type().list_suggestions(context, builder) - } else { - self.custom_suggestions.get_suggestions(context, builder) - } - } - - fn is_valid_input(&self, input: &str) -> bool { - let reader = StringReader::new(input); - let result = self.get_type().parse(reader); - if result.is_ok() { - return !reader.can_read() || reader.peek() == ' '; - } else { - return false; - } - } - - fn usage_text(&self) -> &str { - USAGE_ARGUMENT_OPEN + self.name + USAGE_ARGUMENT_CLOSE - } - - fn create_builder(&self) -> RequiredArgumentBuilder<S> { - let builder = RequiredArgumentBuilder::argument(&self.name, &self.type_); - builder.requires(self.base.get_requirement()); - builder.forward( - self.base.get_redirect(), - self.base.get_redirect_modifier(), - self.base.is_fork(), - ); - builder.suggests(self.custom_suggestions()); - if self.base.get_command() != None { - builder.executes(self.base.get_command().unwrap()); - } - builder - } - - fn get_examples(&self) -> Vec<String> { - self.type_.get_examples() - } - - fn redirect_modifier(&self) -> Option<&dyn RedirectModifier<S>> { - self.modifier.as_ref().map(|modifier| modifier.as_ref()) - } - - fn can_use(&self, source: S) -> bool { - (self.requirement)(&source) - } - - fn add_child(&self, node: &Box<dyn CommandNodeTrait<S>>) -> Result<(), String> { - let dynamic_node = node as &dyn Any; - if dynamic_node.is::<RootCommandNode<S>>() { - return Err(String::from( - "Cannot add a RootCommandNode as a child to any other CommandNode", - )); - } - - let mut child = self.children.get(node.name()); - if let Some(child) = child { - // We've found something to merge onto - if let Some(command) = node.base().command() { - child.base_mut().command = Some(*command); - } - for grandchild in node.base().children().values() { - child.base_mut().add_child(&*grandchild)?; - } - Ok(()) - } else { - self.children.insert(node.name().to_string(), *node); - - if let Some(dynamic_node) = dynamic_node.downcast_ref::<LiteralCommandNode<S>>() { - self.literals.insert(node.name().to_string(), *dynamic_node); - } else if let Some(dynamic_node) = dynamic_node.downcast_ref::<ArgumentCommandNode<S>>() - { - self.arguments - .insert(node.name().to_string(), *dynamic_node); - } - Ok(()) - } - } -} - -impl<S> Display for ArgumentCommandNode<S> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "<argument {}: {}>", self.name, self.type_) - } -} diff --git a/azalea-brigadier/src/tree/command_node.rs b/azalea-brigadier/src/tree/command_node.rs deleted file mode 100644 index 207e114e..00000000 --- a/azalea-brigadier/src/tree/command_node.rs +++ /dev/null @@ -1,143 +0,0 @@ -use super::{ - argument_command_node::ArgumentCommandNode, literal_command_node::LiteralCommandNode, - root_command_node::RootCommandNode, -}; -use crate::{ - arguments::argument_type::ArgumentType, - builder::argument_builder::ArgumentBuilder, - command::Command, - context::{command_context::CommandContext, command_context_builder::CommandContextBuilder}, - exceptions::command_syntax_exception::CommandSyntaxException, - redirect_modifier::RedirectModifier, - string_reader::StringReader, - suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}, -}; -use std::ops::Deref; -use std::{any::Any, collections::HashMap, fmt::Debug}; - -#[enum_dispatch(CommandNodeTrait)] -enum CommandNodeEnum<S> { - Literal(LiteralCommandNode<S>), - Argument(ArgumentCommandNode<S>), - Root(RootCommandNode<S>), -} - -impl<S> CommandNodeEnum<S> { - fn redirect_modifier(&self) -> Option<&dyn RedirectModifier<S>> { - (*self).modifier.as_ref().map(|modifier| modifier.as_ref()) - } - - fn can_use(&self, source: S) -> bool { - (self.requirement)(&source) - } - - fn add_child(&self, node: &Box<dyn CommandNodeTrait<S>>) -> Result<(), String> { - let dynamic_node = node as &dyn Any; - if dynamic_node.is::<RootCommandNode<S>>() { - return Err(String::from( - "Cannot add a RootCommandNode as a child to any other CommandNode", - )); - } - - let mut child = self.children.get(node.name()); - if let Some(child) = child { - // We've found something to merge onto - if let Some(command) = node.base().command() { - child.base_mut().command = Some(*command); - } - for grandchild in node.base().children().values() { - child.base_mut().add_child(&*grandchild)?; - } - Ok(()) - } else { - self.children.insert(node.name().to_string(), *node); - - if let Some(dynamic_node) = dynamic_node.downcast_ref::<LiteralCommandNode<S>>() { - self.literals.insert(node.name().to_string(), *dynamic_node); - } else if let Some(dynamic_node) = dynamic_node.downcast_ref::<ArgumentCommandNode<S>>() - { - self.arguments - .insert(node.name().to_string(), *dynamic_node); - } - Ok(()) - } - } -} -pub struct BaseCommandNode<S> { - children: HashMap<String, Box<dyn CommandNodeTrait<S>>>, - literals: HashMap<String, LiteralCommandNode<S>>, - arguments: HashMap<String, ArgumentCommandNode<S>>, - pub requirement: Box<dyn Fn(&S) -> bool>, - redirect: Option<Box<dyn CommandNodeTrait<S>>>, - modifier: Option<Box<dyn RedirectModifier<S>>>, - forks: bool, - pub command: Option<Box<dyn Command<S>>>, -} - -// impl<S> Clone for BaseCommandNode<'_, S> { -// fn clone(&self) -> Self { -// Self { -// children: self.children.clone(), -// literals: self.literals.clone(), -// arguments: self.arguments.clone(), -// requirement: self.requirement.clone(), -// redirect: self.redirect.clone(), -// modifier: self.modifier.clone(), -// forks: self.forks.clone(), -// command: self.command.clone(), -// } -// } -// } - -impl<S> Debug for BaseCommandNode<S> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("BaseCommandNode") - .field("children", &self.children) - .field("literals", &self.literals) - .field("arguments", &self.arguments) - .field("requirement", &self.requirement) - .field("redirect", &self.redirect) - .field("modifier", &self.modifier) - .field("forks", &self.forks) - .field("command", &self.command) - .finish() - } -} - -impl<S> Default for BaseCommandNode<S> { - fn default() -> Self { - Self { - children: HashMap::new(), - literals: HashMap::new(), - arguments: HashMap::new(), - requirement: Box::new(|_| true), - redirect: None, - modifier: None, - forks: false, - command: None, - } - } -} - -#[enum_dispatch] -pub trait CommandNodeTrait<S> { - fn name(&self) -> &str; - fn usage_text(&self) -> &str; - fn parse( - &self, - reader: &mut StringReader, - context_builder: CommandContextBuilder<S>, - ) -> Result<(), CommandSyntaxException>; - fn list_suggestions( - &self, - context: CommandContext<S>, - builder: &SuggestionsBuilder, - ) -> Result<Suggestions, CommandSyntaxException>; - fn is_valid_input(&self, input: &str) -> bool; - fn create_builder(&self) -> Box<dyn ArgumentBuilder<S>>; - fn get_examples(&self) -> Vec<String>; - - fn redirect_modifier(&self) -> Option<&dyn RedirectModifier<S>>; - fn can_use(&self, source: S) -> bool; - fn add_child(&self, node: &Box<dyn CommandNodeTrait<S>>) -> Result<(), String>; -} diff --git a/azalea-brigadier/src/tree/literal_command_node.rs b/azalea-brigadier/src/tree/literal_command_node.rs deleted file mode 100644 index 2db31d97..00000000 --- a/azalea-brigadier/src/tree/literal_command_node.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::{ - arguments::argument_type::ArgumentType, - builder::{ - argument_builder::ArgumentBuilder, literal_argument_builder::LiteralArgumentBuilder, - }, - command::Command, - context::{command_context::CommandContext, command_context_builder::CommandContextBuilder}, - exceptions::{ - builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, - }, - immutable_string_reader::ImmutableStringReader, - redirect_modifier::RedirectModifier, - string_reader::StringReader, - suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}, -}; -use std::{collections::HashMap, fmt::Debug}; - -use super::{ - argument_command_node::ArgumentCommandNode, - command_node::{BaseCommandNode, CommandNodeTrait}, -}; - -#[derive(Debug, Clone)] -pub struct LiteralCommandNode<S> { - literal: String, - literal_lowercase: String, - - children: HashMap<String, Box<dyn CommandNodeTrait<S>>>, - literals: HashMap<String, LiteralCommandNode<S>>, - arguments: HashMap<String, ArgumentCommandNode<S>>, - pub requirement: Box<dyn Fn(&S) -> bool>, - redirect: Option<Box<dyn CommandNodeTrait<S>>>, - modifier: Option<Box<dyn RedirectModifier<S>>>, - forks: bool, - pub command: Option<Box<dyn Command<S>>>, -} - -impl<S> LiteralCommandNode<S> { - pub fn new(literal: String) -> Self { - let literal_lowercase = literal.to_lowercase(); - Self { - literal, - literal_lowercase, - ..Default::default() - } - } - - pub fn literal(&self) -> &String { - &self.literal - } - - pub fn parse(&self, reader: &mut StringReader) -> i32 { - let start = reader.cursor(); - if reader.can_read_length(self.literal.len()) { - let end = start + self.literal.len(); - if reader.string()[start..end].eq(&self.literal) { - reader.cursor = end; - if !reader.can_read() || reader.peek() == ' ' { - return end as i32; - } else { - reader.cursor = start; - } - } - } - -1 - } -} - -impl<S> CommandNodeTrait<S> for LiteralCommandNode<S> { - fn name(&self) -> &str { - &self.literal - } - - fn parse( - &self, - reader: &mut StringReader<'_>, - context_builder: CommandContextBuilder<S>, - ) -> Result<(), CommandSyntaxException> { - let start = reader.cursor(); - let end = self.parse(reader); - if end > -1 { - return Ok(()); - } - - Err(BuiltInExceptions::LiteralIncorrect { - expected: self.literal().to_string(), - } - .create_with_context(reader)) - } - - fn list_suggestions( - &self, - context: CommandContext<S>, - builder: &SuggestionsBuilder, - ) -> Result<Suggestions, CommandSyntaxException> { - if self - .literal_lowercase - .starts_with(&builder.remaining_lowercase()) - { - Ok(builder.suggest(self.literal()).build()) - } else { - Ok(Suggestions::default()) - } - } - - fn is_valid_input(&self, input: &str) -> bool { - self.parse(&mut StringReader::from(input)) > -1 - } - - fn usage_text(&self) -> &str { - &self.literal - } - - fn create_builder(&self) -> Box<dyn ArgumentBuilder<S>> { - let mut builder = LiteralArgumentBuilder::literal(self.literal().to_string()); - builder.base.requires(&self.base().requirement); - builder.base.forward( - self.base.redirect(), - self.base.redirect_modifier(), - self.base.is_fork(), - ); - if self.command().is_some() { - builder.executes(self.command().unwrap()); - } - builder - } - - fn get_examples(&self) -> Vec<String> { - todo!() - } -} diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs deleted file mode 100644 index 3dc22583..00000000 --- a/azalea-brigadier/src/tree/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod argument_command_node; -pub mod command_node; -pub mod literal_command_node; -pub mod root_command_node; diff --git a/azalea-brigadier/src/tree/root_command_node.rs b/azalea-brigadier/src/tree/root_command_node.rs deleted file mode 100644 index 6b8bc157..00000000 --- a/azalea-brigadier/src/tree/root_command_node.rs +++ /dev/null @@ -1,79 +0,0 @@ -use super::{ - argument_command_node::ArgumentCommandNode, - command_node::{BaseCommandNode, CommandNodeTrait}, - literal_command_node::LiteralCommandNode, -}; -use crate::{ - arguments::argument_type::ArgumentType, - builder::argument_builder::ArgumentBuilder, - command::Command, - context::{command_context::CommandContext, command_context_builder::CommandContextBuilder}, - exceptions::{ - builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, - }, - redirect_modifier::RedirectModifier, - string_reader::StringReader, - suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}, -}; -use std::{ - any::Any, - collections::HashMap, - fmt::{Debug, Display, Formatter}, -}; - -#[derive(Default)] -pub struct RootCommandNode<S> { - // Since Rust doesn't have extending, we put the struct this is extending as the "base" field - children: HashMap<String, Box<dyn CommandNodeTrait<S>>>, - literals: HashMap<String, LiteralCommandNode<S>>, - arguments: HashMap<String, ArgumentCommandNode<S>>, - pub requirement: Box<dyn Fn(&S) -> bool>, - redirect: Option<Box<dyn CommandNodeTrait<S>>>, - modifier: Option<Box<dyn RedirectModifier<S>>>, - forks: bool, - pub command: Option<Box<dyn Command<S>>>, -} - -impl<S> CommandNodeTrait<S> for RootCommandNode<S> { - fn name(&self) -> &str { - "" - } - - fn parse( - &self, - reader: &mut StringReader<'_>, - context_builder: CommandContextBuilder<S>, - ) -> Result<(), CommandSyntaxException> { - Ok(()) - } - - fn list_suggestions( - &self, - context: CommandContext<S>, - builder: &SuggestionsBuilder, - ) -> Result<Suggestions, CommandSyntaxException> { - Ok(Suggestions::default()) - } - - fn is_valid_input(&self, input: &str) -> bool { - false - } - - fn usage_text(&self) -> &str { - "" - } - - fn create_builder(&self) -> Box<dyn ArgumentBuilder<S>> { - panic!("Cannot convert root into a builder"); - } - - fn get_examples(&self) -> Vec<String> { - vec![] - } -} - -impl<S> Display for RootCommandNode<S> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "<root>") - } -} |
