mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
Rewrite brigadier
This commit is contained in:
parent
4ff67d4917
commit
a72a47ced7
51 changed files with 1127 additions and 1849 deletions
|
@ -1 +0,0 @@
|
|||
|
|
@ -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>;
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -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;
|
||||
use crate::{context::CommandContext, modifier::RedirectModifier, tree::CommandNode};
|
||||
|
||||
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>>>,
|
||||
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),
|
||||
}
|
||||
|
||||
/// 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>>>,
|
||||
}
|
||||
|
||||
pub trait ArgumentBuilder<S> {
|
||||
fn build(self) -> Box<dyn CommandNodeTrait<S>>;
|
||||
}
|
||||
// todo: maybe remake this to be based on a CommandNode like vanilla does?
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn arguments(&self) -> &Vec<&dyn CommandNodeTrait<S>> {
|
||||
&self.arguments.get_children()
|
||||
}
|
||||
|
||||
pub fn executes(&mut self, command: Box<dyn Command<S>>) -> &mut Self {
|
||||
self.command = Some(command);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn command(&self) -> Option<Box<dyn Command<S>>> {
|
||||
self.command
|
||||
}
|
||||
|
||||
pub fn requires(&mut self, requirement: Box<dyn Fn(&S) -> bool>) -> &mut Self {
|
||||
self.requirement = requirement;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn requirement(&self) -> Box<dyn Fn(&S) -> bool> {
|
||||
self.requirement
|
||||
}
|
||||
|
||||
pub fn redirect(&mut self, target: Box<dyn CommandNodeTrait<S>>) -> &mut Self {
|
||||
self.forward(target, None, false)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn fork(
|
||||
&mut self,
|
||||
target: &dyn CommandNodeTrait<S>,
|
||||
modifier: &dyn RedirectModifier<S>,
|
||||
) -> &mut Self {
|
||||
self.forward(target, Some(modifier), true)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn get_redirect(&self) -> Option<&dyn CommandNodeTrait<S>> {
|
||||
self.target.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_redirect_modifier(&self) -> Option<&dyn RedirectModifier<S>> {
|
||||
self.modifier.as_ref()
|
||||
}
|
||||
|
||||
pub fn is_fork(&self) -> bool {
|
||||
self.forks
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
arguments: Default::default(),
|
||||
children: Default::default(),
|
||||
literals: Default::default(),
|
||||
};
|
||||
|
||||
for argument in self.arguments() {
|
||||
result.add_child(argument);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Default for BaseArgumentBuilder<'_, S> {
|
||||
fn default() -> Self {
|
||||
/// A node that isn't yet built.
|
||||
impl<S: Any + Clone> ArgumentBuilder<S> {
|
||||
pub fn new(value: ArgumentBuilderType) -> Self {
|
||||
Self {
|
||||
arguments: Default::default(),
|
||||
command: Default::default(),
|
||||
requirement: Default::default(),
|
||||
target: Default::default(),
|
||||
modifier: Default::default(),
|
||||
forks: Default::default(),
|
||||
value,
|
||||
children: BTreeMap::new(),
|
||||
literals: BTreeMap::new(),
|
||||
arguments: BTreeMap::new(),
|
||||
executes: None,
|
||||
requirement: Rc::new(|_| true),
|
||||
forks: false,
|
||||
modifier: None,
|
||||
}
|
||||
}
|
||||
|
||||
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 executes<F>(&mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&CommandContext<S>) -> i32 + 'static,
|
||||
{
|
||||
self.executes = Some(Rc::new(f));
|
||||
self.clone()
|
||||
}
|
||||
|
||||
pub fn build(self) -> CommandNode<S> {
|
||||
println!("building {:?}", self);
|
||||
CommandNode {
|
||||
value: self.value,
|
||||
|
||||
children: self.children,
|
||||
literals: self.literals,
|
||||
arguments: self.arguments,
|
||||
|
||||
command: self.executes.clone(),
|
||||
requirement: self.requirement.clone(),
|
||||
redirect: None,
|
||||
forks: self.forks,
|
||||
modifier: self.modifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
builder::{literal_argument_builder::literal, required_argument_builder::argument},
|
||||
parsers::integer,
|
||||
};
|
||||
|
||||
use super::ArgumentBuilder;
|
||||
|
||||
// public class ArgumentBuilderTest {
|
||||
// private TestableArgumentBuilder<Object> builder;
|
||||
|
||||
// @Before
|
||||
// public void setUp() throws Exception {
|
||||
// builder = new TestableArgumentBuilder<>();
|
||||
// }
|
||||
|
||||
// @Test
|
||||
// public void testArguments() throws Exception {
|
||||
// final RequiredArgumentBuilder<Object, ?> argument = argument("bar", integer());
|
||||
|
||||
// builder.then(argument);
|
||||
|
||||
// assertThat(builder.getArguments(), hasSize(1));
|
||||
// assertThat(builder.getArguments(), hasItem((CommandNode<Object>) argument.build()));
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_arguments() {
|
||||
let mut builder: ArgumentBuilder<()> = literal("foo");
|
||||
|
||||
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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Literal {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl<S> LiteralArgumentBuilder<S> {
|
||||
pub fn new(literal: String) -> Self {
|
||||
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<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)
|
||||
impl From<Literal> for ArgumentBuilderType {
|
||||
fn from(literal: Literal) -> Self {
|
||||
Self::Literal(literal)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortcut for creating a new literal builder node.
|
||||
pub fn literal<S: Any + Clone>(value: &str) -> ArgumentBuilder<S> {
|
||||
ArgumentBuilder::new(ArgumentBuilderType::Literal(Literal::new(value)))
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
|
||||
use crate::{parsers::Parser, string_reader::StringReader};
|
||||
use std::{any::Any, fmt::Debug, rc::Rc};
|
||||
|
||||
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>>>,
|
||||
/// 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 get_type(&self) -> Box<dyn ArgumentType<Into = dyn Any>> {
|
||||
self.type_
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
// 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
|
||||
pub fn parse(&self, reader: &mut StringReader) -> Option<Rc<dyn Any>> {
|
||||
self.parser.parse(reader)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Argument> for ArgumentBuilderType {
|
||||
fn from(argument: Argument) -> Self {
|
||||
Self::Argument(argument)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Argument {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Argument")
|
||||
.field("name", &self.name)
|
||||
// .field("parser", &self.parser)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortcut for creating a new argument builder node.
|
||||
pub fn argument<'a, S: Any + Clone>(
|
||||
name: &'a str,
|
||||
parser: impl Parser + 'static,
|
||||
) -> ArgumentBuilder<S> {
|
||||
ArgumentBuilder::new(Argument::new(name, Rc::new(parser)).into())
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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 }
|
||||
}
|
||||
}
|
145
azalea-brigadier/src/context.rs
Normal file
145
azalea-brigadier/src/context.rs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
use crate::tree::command_node::CommandNodeTrait;
|
||||
|
||||
pub struct SuggestionContext<'a, S> {
|
||||
parent: &'a dyn CommandNodeTrait<S>,
|
||||
start_pos: usize,
|
||||
}
|
242
azalea-brigadier/src/dispatcher.rs
Normal file
242
azalea-brigadier/src/dispatcher.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
pub mod builtin_exception_provider;
|
||||
pub mod builtin_exceptions;
|
||||
pub mod command_syntax_exception;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
38
azalea-brigadier/src/main.rs
Normal file
38
azalea-brigadier/src/main.rs
Normal file
|
@ -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()
|
||||
);
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
9
azalea-brigadier/src/modifier.rs
Normal file
9
azalea-brigadier/src/modifier.rs
Normal file
|
@ -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>;
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
21
azalea-brigadier/src/parsers.rs
Normal file
21
azalea-brigadier/src/parsers.rs
Normal file
|
@ -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 {}
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -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>;
|
||||
}
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub struct IntegerSuggestion {}
|
|
@ -1,5 +0,0 @@
|
|||
pub mod integer_suggestion;
|
||||
pub mod suggestion;
|
||||
pub mod suggestion_provider;
|
||||
pub mod suggestions;
|
||||
pub mod suggestions_builder;
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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()),
|
||||
// ]
|
||||
// );
|
||||
// }
|
||||
// }
|
|
@ -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)
|
||||
}
|
||||
}
|
269
azalea-brigadier/src/tree.rs
Normal file
269
azalea-brigadier/src/tree.rs
Normal file
|
@ -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> {}
|
|
@ -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_)
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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!()
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
pub mod argument_command_node;
|
||||
pub mod command_node;
|
||||
pub mod literal_command_node;
|
||||
pub mod root_command_node;
|
|
@ -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>")
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue