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