1
2
Fork 0
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:
mat 2022-04-17 14:02:13 -05:00
parent 4ff67d4917
commit a72a47ced7
51 changed files with 1127 additions and 1849 deletions

View file

@ -1 +0,0 @@

View file

@ -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>;
}

View file

@ -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)
}
}

View file

@ -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;

View file

@ -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;
// }
// }
// }
}

View file

@ -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)))
}

View file

@ -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())
}

View file

@ -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>;
}

View file

@ -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 }
}
}

View 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();
}
}

View file

@ -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)
}
}

View file

@ -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"))
}
}

View file

@ -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;

View file

@ -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
}
}

View file

@ -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(),
}
}
}

View file

@ -1,6 +0,0 @@
use crate::tree::command_node::CommandNodeTrait;
pub struct SuggestionContext<'a, S> {
parent: &'a dyn CommandNodeTrait<S>,
start_pos: usize,
}

View 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,
}
}
}

View file

@ -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())
}

View file

@ -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);

View file

@ -1,3 +1,2 @@
pub mod builtin_exception_provider;
pub mod builtin_exceptions;
pub mod command_syntax_exception;

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -1 +0,0 @@

View 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()
);
}

View file

@ -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)
}
}

View 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>;
}

View file

@ -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()
}
}

View 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 {}
}

View file

@ -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>;
}

View file

@ -1 +0,0 @@

View file

@ -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>;
}

View file

@ -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]
}

View file

@ -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 {

View file

@ -1 +0,0 @@
pub struct IntegerSuggestion {}

View file

@ -1,5 +0,0 @@
pub mod integer_suggestion;
pub mod suggestion;
pub mod suggestion_provider;
pub mod suggestions;
pub mod suggestions_builder;

View file

@ -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)
}
}

View file

@ -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>;
}

View file

@ -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()),
// ]
// );
// }
// }

View file

@ -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)
}
}

View 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> {}

View file

@ -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_)
}
}

View file

@ -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>;
}

View file

@ -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!()
}
}

View file

@ -1,4 +0,0 @@
pub mod argument_command_node;
pub mod command_node;
pub mod literal_command_node;
pub mod root_command_node;

View file

@ -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>")
}
}