mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
change a BTreeMap to a HashMap
This commit is contained in:
parent
b3864af9c4
commit
8d71fbf813
8 changed files with 1248 additions and 0 deletions
711
azalea-brigadier/src/command_dispatcher.rs
Normal file
711
azalea-brigadier/src/command_dispatcher.rs
Normal file
|
@ -0,0 +1,711 @@
|
||||||
|
use crate::{
|
||||||
|
builder::argument_builder::ArgumentBuilder,
|
||||||
|
context::{CommandContext, CommandContextBuilder},
|
||||||
|
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||||
|
parse_results::ParseResults,
|
||||||
|
string_reader::StringReader,
|
||||||
|
tree::CommandNode,
|
||||||
|
};
|
||||||
|
use std::{cell::RefCell, cmp::Ordering, collections::HashMap, marker::PhantomData, mem, rc::Rc};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CommandDispatcher<S> {
|
||||||
|
root: Rc<RefCell<CommandNode<S>>>,
|
||||||
|
_marker: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> CommandDispatcher<S> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
root: Rc::new(RefCell::new(CommandNode::default())),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(&mut self, node: ArgumentBuilder<S>) -> Rc<RefCell<CommandNode<S>>> {
|
||||||
|
let build = Rc::new(RefCell::new(node.build()));
|
||||||
|
self.root.borrow_mut().add_child(&build);
|
||||||
|
build
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(&self, command: StringReader, source: Rc<S>) -> ParseResults<S> {
|
||||||
|
let context = CommandContextBuilder::new(
|
||||||
|
Rc::new(self.clone()),
|
||||||
|
source,
|
||||||
|
self.root.clone(),
|
||||||
|
command.cursor(),
|
||||||
|
);
|
||||||
|
self.parse_nodes(&self.root, &command, context).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_nodes(
|
||||||
|
&self,
|
||||||
|
node: &Rc<RefCell<CommandNode<S>>>,
|
||||||
|
original_reader: &StringReader,
|
||||||
|
context_so_far: CommandContextBuilder<S>,
|
||||||
|
) -> Result<ParseResults<S>, CommandSyntaxException> {
|
||||||
|
let source = context_so_far.source.clone();
|
||||||
|
let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxException>::new();
|
||||||
|
let mut potentials: Vec<ParseResults<S>> = vec![];
|
||||||
|
let cursor = original_reader.cursor();
|
||||||
|
|
||||||
|
for child in node
|
||||||
|
.borrow()
|
||||||
|
.get_relevant_nodes(&mut original_reader.clone())
|
||||||
|
{
|
||||||
|
if !child.borrow().can_use(source.clone()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut context = context_so_far.clone();
|
||||||
|
let mut reader = original_reader.clone();
|
||||||
|
|
||||||
|
let parse_with_context_result =
|
||||||
|
child.borrow().parse_with_context(&mut reader, &mut context);
|
||||||
|
if let Err(ex) = parse_with_context_result {
|
||||||
|
errors.insert(
|
||||||
|
Rc::new((*child.borrow()).clone()),
|
||||||
|
BuiltInExceptions::DispatcherParseException {
|
||||||
|
message: ex.message(),
|
||||||
|
}
|
||||||
|
.create_with_context(&reader),
|
||||||
|
);
|
||||||
|
reader.cursor = cursor;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if reader.can_read() && reader.peek() != ' ' {
|
||||||
|
errors.insert(
|
||||||
|
Rc::new((*child.borrow()).clone()),
|
||||||
|
BuiltInExceptions::DispatcherExpectedArgumentSeparator
|
||||||
|
.create_with_context(&reader),
|
||||||
|
);
|
||||||
|
reader.cursor = cursor;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.with_command(&child.borrow().command);
|
||||||
|
if reader.can_read_length(if child.borrow().redirect.is_none() {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}) {
|
||||||
|
reader.skip();
|
||||||
|
if let Some(redirect) = &child.borrow().redirect {
|
||||||
|
let child_context = CommandContextBuilder::new(
|
||||||
|
Rc::new(self.clone()),
|
||||||
|
source,
|
||||||
|
redirect.clone(),
|
||||||
|
reader.cursor,
|
||||||
|
);
|
||||||
|
let parse = self
|
||||||
|
.parse_nodes(redirect, &reader, child_context)
|
||||||
|
.expect("Parsing nodes failed");
|
||||||
|
context.with_child(Rc::new(parse.context));
|
||||||
|
return Ok(ParseResults {
|
||||||
|
context,
|
||||||
|
reader: parse.reader,
|
||||||
|
exceptions: parse.exceptions,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let parse = self
|
||||||
|
.parse_nodes(&child, &reader, context)
|
||||||
|
.expect("Parsing nodes failed");
|
||||||
|
potentials.push(parse);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
potentials.push(ParseResults {
|
||||||
|
context,
|
||||||
|
reader,
|
||||||
|
exceptions: HashMap::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !potentials.is_empty() {
|
||||||
|
if potentials.len() > 1 {
|
||||||
|
potentials.sort_by(|a, b| {
|
||||||
|
if !a.reader.can_read() && b.reader.can_read() {
|
||||||
|
return Ordering::Less;
|
||||||
|
};
|
||||||
|
if a.reader.can_read() && !b.reader.can_read() {
|
||||||
|
return Ordering::Greater;
|
||||||
|
};
|
||||||
|
if a.exceptions.is_empty() && !b.exceptions.is_empty() {
|
||||||
|
return Ordering::Less;
|
||||||
|
};
|
||||||
|
if !a.exceptions.is_empty() && b.exceptions.is_empty() {
|
||||||
|
return Ordering::Greater;
|
||||||
|
};
|
||||||
|
Ordering::Equal
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let best_potential = potentials.into_iter().next().unwrap();
|
||||||
|
return Ok(best_potential);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ParseResults {
|
||||||
|
context: context_so_far,
|
||||||
|
reader: original_reader.clone(),
|
||||||
|
exceptions: errors,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(
|
||||||
|
&self,
|
||||||
|
input: StringReader,
|
||||||
|
source: Rc<S>,
|
||||||
|
) -> Result<i32, CommandSyntaxException> {
|
||||||
|
let parse = self.parse(input, source);
|
||||||
|
Self::execute_parsed(parse)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_paths(
|
||||||
|
&self,
|
||||||
|
node: Rc<RefCell<CommandNode<S>>>,
|
||||||
|
result: &mut Vec<Vec<Rc<RefCell<CommandNode<S>>>>>,
|
||||||
|
parents: Vec<Rc<RefCell<CommandNode<S>>>>,
|
||||||
|
) {
|
||||||
|
let mut current = parents;
|
||||||
|
current.push(node.clone());
|
||||||
|
result.push(current.clone());
|
||||||
|
|
||||||
|
for child in node.borrow().children.values() {
|
||||||
|
self.add_paths(child.clone(), result, current.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_path(&self, target: CommandNode<S>) -> Vec<String> {
|
||||||
|
let rc_target = Rc::new(RefCell::new(target));
|
||||||
|
let mut nodes: Vec<Vec<Rc<RefCell<CommandNode<S>>>>> = Vec::new();
|
||||||
|
self.add_paths(self.root.clone(), &mut nodes, vec![]);
|
||||||
|
|
||||||
|
for list in nodes {
|
||||||
|
if *list.last().expect("Nothing in list").borrow() == *rc_target.borrow() {
|
||||||
|
let mut result: Vec<String> = Vec::with_capacity(list.len());
|
||||||
|
for node in list {
|
||||||
|
if node != self.root {
|
||||||
|
result.push(node.borrow().name().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_node(&self, path: &[&str]) -> Option<Rc<RefCell<CommandNode<S>>>> {
|
||||||
|
let mut node = self.root.clone();
|
||||||
|
for name in path {
|
||||||
|
if let Some(child) = node.clone().borrow().child(name) {
|
||||||
|
node = child
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a given pre-parsed command.
|
||||||
|
pub fn execute_parsed(parse: ParseResults<S>) -> Result<i32, CommandSyntaxException> {
|
||||||
|
if parse.reader.can_read() {
|
||||||
|
if parse.exceptions.len() == 1 {
|
||||||
|
return Err(parse.exceptions.values().next().unwrap().clone());
|
||||||
|
}
|
||||||
|
if parse.context.range.is_empty() {
|
||||||
|
return Err(
|
||||||
|
BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Err(
|
||||||
|
BuiltInExceptions::DispatcherUnknownArgument.create_with_context(&parse.reader)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut result = 0i32;
|
||||||
|
let mut successful_forks = 0;
|
||||||
|
let mut forked = false;
|
||||||
|
let mut found_command = false;
|
||||||
|
let command = parse.reader.string();
|
||||||
|
let original = parse.context.build(command);
|
||||||
|
let mut contexts = vec![original];
|
||||||
|
let mut next: Vec<CommandContext<S>> = vec![];
|
||||||
|
|
||||||
|
while !contexts.is_empty() {
|
||||||
|
for context in contexts.iter() {
|
||||||
|
let child = &context.child;
|
||||||
|
if let Some(child) = child {
|
||||||
|
println!("aaaaaaa {:?}", child);
|
||||||
|
forked |= child.forks;
|
||||||
|
if child.has_nodes() {
|
||||||
|
found_command = true;
|
||||||
|
let modifier = &context.modifier;
|
||||||
|
if let Some(modifier) = modifier {
|
||||||
|
let results = modifier(context);
|
||||||
|
if let Ok(results) = results {
|
||||||
|
if !results.is_empty() {
|
||||||
|
next.extend(results.iter().map(|s| child.copy_for(s.clone())));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
// self.consumer.on_command_complete(context, false, 0);
|
||||||
|
if !forked {
|
||||||
|
return Err(results.err().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next.push(child.copy_for(context.source.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(context_command) = &context.command {
|
||||||
|
found_command = true;
|
||||||
|
|
||||||
|
let value = context_command(context);
|
||||||
|
result += value;
|
||||||
|
// consumer.on_command_complete(context, true, value);
|
||||||
|
successful_forks += 1;
|
||||||
|
|
||||||
|
// TODO: allow context_command to error and handle those errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move next into contexts and clear next
|
||||||
|
mem::swap(&mut contexts, &mut next);
|
||||||
|
next.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_command {
|
||||||
|
// consumer.on_command_complete(original, false, 0);
|
||||||
|
return Err(
|
||||||
|
BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is not how vanilla does it but it works
|
||||||
|
Ok(if successful_forks >= 2 {
|
||||||
|
successful_forks
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
})
|
||||||
|
// Ok(if forked { successful_forks } else { result })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for CommandDispatcher<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
root: self.root.clone(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
builder::{literal_argument_builder::literal, required_argument_builder::argument},
|
||||||
|
parsers::integer,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct CommandSource {}
|
||||||
|
|
||||||
|
fn input_with_offset(input: &str, offset: usize) -> StringReader {
|
||||||
|
let mut result: StringReader = input.into();
|
||||||
|
result.cursor = offset;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_and_execute_command() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("foo").executes(|_| 42));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
subject
|
||||||
|
.execute("foo".into(), Rc::new(CommandSource {}))
|
||||||
|
.unwrap(),
|
||||||
|
42
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_and_execute_offset_command() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("foo").executes(|_| 42));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
subject
|
||||||
|
.execute(input_with_offset("/foo", 1), Rc::new(CommandSource {}))
|
||||||
|
.unwrap(),
|
||||||
|
42
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_and_merge_commands() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("base").then(literal("foo").executes(|_| 42)));
|
||||||
|
subject.register(literal("base").then(literal("bar").executes(|_| 42)));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
subject
|
||||||
|
.execute("base foo".into(), Rc::new(CommandSource {}))
|
||||||
|
.unwrap(),
|
||||||
|
42
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
subject
|
||||||
|
.execute("base bar".into(), Rc::new(CommandSource {}))
|
||||||
|
.unwrap(),
|
||||||
|
42
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_unknown_command() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("bar"));
|
||||||
|
subject.register(literal("baz"));
|
||||||
|
|
||||||
|
let execute_result = subject.execute("foo".into(), Rc::new(CommandSource {}));
|
||||||
|
|
||||||
|
let err = execute_result.err().unwrap();
|
||||||
|
match err.type_ {
|
||||||
|
BuiltInExceptions::DispatcherUnknownCommand => {}
|
||||||
|
_ => panic!("Unexpected error"),
|
||||||
|
}
|
||||||
|
assert_eq!(err.cursor().unwrap(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_impermissible_command() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("foo").requires(|_| false));
|
||||||
|
|
||||||
|
let execute_result = subject.execute("foo".into(), Rc::new(CommandSource {}));
|
||||||
|
|
||||||
|
let err = execute_result.err().unwrap();
|
||||||
|
match err.type_ {
|
||||||
|
BuiltInExceptions::DispatcherUnknownCommand => {}
|
||||||
|
_ => panic!("Unexpected error"),
|
||||||
|
}
|
||||||
|
assert_eq!(err.cursor().unwrap(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_empty_command() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal(""));
|
||||||
|
|
||||||
|
let execute_result = subject.execute("".into(), Rc::new(CommandSource {}));
|
||||||
|
|
||||||
|
let err = execute_result.err().unwrap();
|
||||||
|
match err.type_ {
|
||||||
|
BuiltInExceptions::DispatcherUnknownCommand => {}
|
||||||
|
_ => panic!("Unexpected error"),
|
||||||
|
}
|
||||||
|
assert_eq!(err.cursor().unwrap(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_unknown_subcommand() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("foo").executes(|_| 42));
|
||||||
|
|
||||||
|
let execute_result = subject.execute("foo bar".into(), Rc::new(CommandSource {}));
|
||||||
|
|
||||||
|
let err = execute_result.err().unwrap();
|
||||||
|
match err.type_ {
|
||||||
|
BuiltInExceptions::DispatcherUnknownArgument => {}
|
||||||
|
_ => panic!("Unexpected error"),
|
||||||
|
}
|
||||||
|
assert_eq!(err.cursor().unwrap(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_incorrect_literal() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("foo").executes(|_| 42).then(literal("bar")));
|
||||||
|
|
||||||
|
let execute_result = subject.execute("foo baz".into(), Rc::new(CommandSource {}));
|
||||||
|
|
||||||
|
let err = execute_result.err().unwrap();
|
||||||
|
match err.type_ {
|
||||||
|
BuiltInExceptions::DispatcherUnknownArgument => {}
|
||||||
|
_ => panic!("Unexpected error"),
|
||||||
|
}
|
||||||
|
assert_eq!(err.cursor().unwrap(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_ambiguous_incorrect_argument() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(
|
||||||
|
literal("foo")
|
||||||
|
.executes(|_| 42)
|
||||||
|
.then(literal("bar"))
|
||||||
|
.then(literal("baz")),
|
||||||
|
);
|
||||||
|
|
||||||
|
let execute_result = subject.execute("foo unknown".into(), Rc::new(CommandSource {}));
|
||||||
|
|
||||||
|
let err = execute_result.err().unwrap();
|
||||||
|
match err.type_ {
|
||||||
|
BuiltInExceptions::DispatcherUnknownArgument => {}
|
||||||
|
_ => panic!("Unexpected error"),
|
||||||
|
}
|
||||||
|
assert_eq!(err.cursor().unwrap(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_subcommand() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
subject.register(
|
||||||
|
literal("foo")
|
||||||
|
.then(literal("a"))
|
||||||
|
.then(literal("=").executes(|_| 100))
|
||||||
|
.then(literal("c"))
|
||||||
|
.executes(|_| 42),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
subject
|
||||||
|
.execute("foo =".into(), Rc::new(CommandSource {}))
|
||||||
|
.unwrap(),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_incomplete_literal() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("foo").then(literal("bar").executes(|_| 42)));
|
||||||
|
|
||||||
|
let parse = subject.parse("foo ".into(), Rc::new(CommandSource {}));
|
||||||
|
assert_eq!(parse.reader.remaining(), " ");
|
||||||
|
assert_eq!(parse.context.nodes.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_incomplete_argument() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
subject.register(literal("foo").then(argument("bar", integer()).executes(|_| 42)));
|
||||||
|
|
||||||
|
let parse = subject.parse("foo ".into(), Rc::new(CommandSource {}));
|
||||||
|
assert_eq!(parse.reader.remaining(), " ");
|
||||||
|
assert_eq!(parse.context.nodes.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_ambiguious_parent_subcommand() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
subject.register(
|
||||||
|
literal("test")
|
||||||
|
.then(argument("incorrect", integer()).executes(|_| 42))
|
||||||
|
.then(
|
||||||
|
argument("right", integer()).then(argument("sub", integer()).executes(|_| 100)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
subject
|
||||||
|
.execute("test 1 2".into(), Rc::new(CommandSource {}))
|
||||||
|
.unwrap(),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_ambiguious_parent_subcommand_via_redirect() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
let real = subject.register(
|
||||||
|
literal("test")
|
||||||
|
.then(argument("incorrect", integer()).executes(|_| 42))
|
||||||
|
.then(
|
||||||
|
argument("right", integer()).then(argument("sub", integer()).executes(|_| 100)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
subject.register(literal("redirect").redirect(real));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
subject
|
||||||
|
.execute("redirect 1 2".into(), Rc::new(CommandSource {}))
|
||||||
|
.unwrap(),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_redirected_multiple_times() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
let concrete_node = subject.register(literal("actual").executes(|_| 42));
|
||||||
|
let root = subject.root.clone();
|
||||||
|
let redirect_node = subject.register(literal("redirected").redirect(root.clone()));
|
||||||
|
|
||||||
|
let input = "redirected redirected actual";
|
||||||
|
|
||||||
|
let parse = subject.parse(input.into(), Rc::new(CommandSource {}));
|
||||||
|
assert_eq!(parse.context.range.get(input), "redirected");
|
||||||
|
assert_eq!(parse.context.nodes.len(), 1);
|
||||||
|
assert_eq!(parse.context.root, root);
|
||||||
|
assert_eq!(parse.context.nodes[0].range, parse.context.range);
|
||||||
|
assert_eq!(parse.context.nodes[0].node, redirect_node);
|
||||||
|
|
||||||
|
let child1 = parse.context.child.clone();
|
||||||
|
assert!(child1.is_some());
|
||||||
|
assert_eq!(child1.clone().unwrap().range.get(input), "redirected");
|
||||||
|
assert_eq!(child1.clone().unwrap().nodes.len(), 1);
|
||||||
|
assert_eq!(child1.clone().unwrap().root, root);
|
||||||
|
assert_eq!(
|
||||||
|
child1.clone().unwrap().nodes[0].range,
|
||||||
|
child1.clone().unwrap().range
|
||||||
|
);
|
||||||
|
assert_eq!(child1.clone().unwrap().nodes[0].node, redirect_node);
|
||||||
|
|
||||||
|
let child2 = child1.unwrap().child.clone();
|
||||||
|
assert!(child2.is_some());
|
||||||
|
assert_eq!(child2.clone().unwrap().range.get(input), "actual");
|
||||||
|
assert_eq!(child2.clone().unwrap().nodes.len(), 1);
|
||||||
|
assert_eq!(child2.clone().unwrap().root, root);
|
||||||
|
assert_eq!(
|
||||||
|
child2.clone().unwrap().nodes[0].range,
|
||||||
|
child2.clone().unwrap().range
|
||||||
|
);
|
||||||
|
assert_eq!(child2.clone().unwrap().nodes[0].node, concrete_node);
|
||||||
|
|
||||||
|
assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_redirected() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
let source1 = Rc::new(CommandSource {});
|
||||||
|
let source2 = Rc::new(CommandSource {});
|
||||||
|
|
||||||
|
let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Rc<CommandSource>>, CommandSyntaxException> {
|
||||||
|
Ok(vec![source1.clone(), source2.clone()])
|
||||||
|
};
|
||||||
|
|
||||||
|
let concrete_node = subject.register(literal("actual").executes(|_| 42));
|
||||||
|
let redirect_node =
|
||||||
|
subject.register(literal("redirected").fork(subject.root.clone(), Rc::new(modifier)));
|
||||||
|
|
||||||
|
let input = "redirected actual";
|
||||||
|
let parse = subject.parse(input.into(), Rc::new(CommandSource {}));
|
||||||
|
assert_eq!(parse.context.range.get(input), "redirected");
|
||||||
|
assert_eq!(parse.context.nodes.len(), 1);
|
||||||
|
assert_eq!(parse.context.root, subject.root);
|
||||||
|
assert_eq!(parse.context.nodes[0].range, parse.context.range);
|
||||||
|
assert_eq!(parse.context.nodes[0].node, redirect_node);
|
||||||
|
|
||||||
|
let parent = parse.context.child.clone();
|
||||||
|
assert!(parent.is_some());
|
||||||
|
let parent = parent.unwrap();
|
||||||
|
assert_eq!(parent.range.get(input), "actual");
|
||||||
|
assert_eq!(parent.nodes.len(), 1);
|
||||||
|
assert_eq!(parse.context.root, subject.root);
|
||||||
|
assert_eq!(parent.nodes[0].range, parent.range);
|
||||||
|
assert_eq!(parent.nodes[0].node, concrete_node);
|
||||||
|
assert_eq!(parent.source, Rc::new(CommandSource {}));
|
||||||
|
|
||||||
|
assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_orphaned_subcommand() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
subject.register(
|
||||||
|
literal("foo")
|
||||||
|
.then(argument("bar", integer()))
|
||||||
|
.executes(|_| 42),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = subject.execute("foo 5".into(), Rc::new(CommandSource {}));
|
||||||
|
assert!(result.is_err());
|
||||||
|
let result = result.unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
*result.get_type(),
|
||||||
|
BuiltInExceptions::DispatcherUnknownCommand
|
||||||
|
);
|
||||||
|
assert_eq!(result.cursor(), Some(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_invalid_other() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
subject.register(literal("w").executes(|_| panic!("This should not run")));
|
||||||
|
subject.register(literal("world").executes(|_| 42));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
subject
|
||||||
|
.execute("world".into(), Rc::new(CommandSource {}))
|
||||||
|
.unwrap(),
|
||||||
|
42
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_no_space_separator() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
subject.register(
|
||||||
|
literal("foo")
|
||||||
|
.then(argument("bar", integer()))
|
||||||
|
.executes(|_| 42),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = subject.execute("foo$".into(), Rc::new(CommandSource {}));
|
||||||
|
assert!(result.is_err());
|
||||||
|
let result = result.unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
*result.get_type(),
|
||||||
|
BuiltInExceptions::DispatcherUnknownCommand
|
||||||
|
);
|
||||||
|
assert_eq!(result.cursor(), Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_invalid_subcommand() {
|
||||||
|
let mut subject = CommandDispatcher::new();
|
||||||
|
|
||||||
|
subject.register(
|
||||||
|
literal("foo")
|
||||||
|
.then(argument("bar", integer()))
|
||||||
|
.executes(|_| 42),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = subject.execute("foo bar".into(), Rc::new(CommandSource {}));
|
||||||
|
assert!(result.is_err());
|
||||||
|
let result = result.unwrap_err();
|
||||||
|
// this fails for some reason, i blame mojang
|
||||||
|
// assert_eq!(*result.get_type(), BuiltInExceptions::ReaderExpectedInt);
|
||||||
|
assert_eq!(result.cursor(), Some(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_path() {
|
||||||
|
let mut subject = CommandDispatcher::<()>::new();
|
||||||
|
|
||||||
|
let bar = literal("bar").build();
|
||||||
|
subject.register(literal("foo").then_built(bar.clone()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
subject.get_path(bar),
|
||||||
|
vec!["foo".to_string(), "bar".to_string()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_node_doesnt_exist() {
|
||||||
|
let subject = CommandDispatcher::<()>::new();
|
||||||
|
|
||||||
|
assert_eq!(subject.find_node(&vec!["foo", "bar"]), None)
|
||||||
|
}
|
||||||
|
}
|
80
azalea-brigadier/src/context/command_context.rs
Normal file
80
azalea-brigadier/src/context/command_context.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use super::{parsed_command_node::ParsedCommandNode, string_range::StringRange, ParsedArgument};
|
||||||
|
use crate::{modifier::RedirectModifier, tree::CommandNode};
|
||||||
|
use std::{any::Any, cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
|
||||||
|
|
||||||
|
/// A built `CommandContextBuilder`.
|
||||||
|
pub struct CommandContext<S> {
|
||||||
|
pub source: Rc<S>,
|
||||||
|
pub input: String,
|
||||||
|
pub arguments: HashMap<String, ParsedArgument>,
|
||||||
|
pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
|
||||||
|
pub root_node: Rc<RefCell<CommandNode<S>>>,
|
||||||
|
pub nodes: Vec<ParsedCommandNode<S>>,
|
||||||
|
pub range: StringRange,
|
||||||
|
pub child: Option<Rc<CommandContext<S>>>,
|
||||||
|
pub modifier: Option<Rc<RedirectModifier<S>>>,
|
||||||
|
pub forks: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for CommandContext<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
source: self.source.clone(),
|
||||||
|
input: self.input.clone(),
|
||||||
|
arguments: self.arguments.clone(),
|
||||||
|
command: self.command.clone(),
|
||||||
|
root_node: self.root_node.clone(),
|
||||||
|
nodes: self.nodes.clone(),
|
||||||
|
range: self.range.clone(),
|
||||||
|
child: self.child.clone(),
|
||||||
|
modifier: self.modifier.clone(),
|
||||||
|
forks: self.forks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Debug for CommandContext<S> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("CommandContext")
|
||||||
|
// .field("source", &self.source)
|
||||||
|
.field("input", &self.input)
|
||||||
|
// .field("arguments", &self.arguments)
|
||||||
|
// .field("command", &self.command)
|
||||||
|
// .field("root_node", &self.root_node)
|
||||||
|
// .field("nodes", &self.nodes)
|
||||||
|
.field("range", &self.range)
|
||||||
|
.field("child", &self.child)
|
||||||
|
// .field("modifier", &self.modifier)
|
||||||
|
.field("forks", &self.forks)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> CommandContext<S> {
|
||||||
|
pub fn copy_for(&self, source: Rc<S>) -> Self {
|
||||||
|
if Rc::ptr_eq(&source, &self.source) {
|
||||||
|
return self.clone();
|
||||||
|
}
|
||||||
|
CommandContext {
|
||||||
|
source,
|
||||||
|
input: self.input.clone(),
|
||||||
|
arguments: self.arguments.clone(),
|
||||||
|
command: self.command.clone(),
|
||||||
|
root_node: self.root_node.clone(),
|
||||||
|
nodes: self.nodes.clone(),
|
||||||
|
range: self.range.clone(),
|
||||||
|
child: self.child.clone(),
|
||||||
|
modifier: self.modifier.clone(),
|
||||||
|
forks: self.forks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_nodes(&self) -> bool {
|
||||||
|
!self.nodes.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn argument(&self, name: &str) -> Option<Rc<dyn Any>> {
|
||||||
|
let argument = self.arguments.get(name);
|
||||||
|
argument.map(|a| a.result.clone())
|
||||||
|
}
|
||||||
|
}
|
116
azalea-brigadier/src/context/command_context_builder.rs
Normal file
116
azalea-brigadier/src/context/command_context_builder.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use super::{
|
||||||
|
command_context::CommandContext, parsed_command_node::ParsedCommandNode,
|
||||||
|
string_range::StringRange, ParsedArgument,
|
||||||
|
};
|
||||||
|
use crate::{command_dispatcher::CommandDispatcher, modifier::RedirectModifier, tree::CommandNode};
|
||||||
|
use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
|
||||||
|
|
||||||
|
pub struct CommandContextBuilder<S> {
|
||||||
|
pub arguments: HashMap<String, ParsedArgument>,
|
||||||
|
pub root: Rc<RefCell<CommandNode<S>>>,
|
||||||
|
pub nodes: Vec<ParsedCommandNode<S>>,
|
||||||
|
pub dispatcher: Rc<CommandDispatcher<S>>,
|
||||||
|
pub source: Rc<S>,
|
||||||
|
pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
|
||||||
|
pub child: Option<Rc<CommandContextBuilder<S>>>,
|
||||||
|
pub range: StringRange,
|
||||||
|
pub modifier: Option<Rc<RedirectModifier<S>>>,
|
||||||
|
pub forks: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for CommandContextBuilder<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
arguments: self.arguments.clone(),
|
||||||
|
root: self.root.clone(),
|
||||||
|
nodes: self.nodes.clone(),
|
||||||
|
dispatcher: self.dispatcher.clone(),
|
||||||
|
source: self.source.clone(),
|
||||||
|
command: self.command.clone(),
|
||||||
|
child: self.child.clone(),
|
||||||
|
range: self.range.clone(),
|
||||||
|
modifier: self.modifier.clone(),
|
||||||
|
forks: self.forks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> CommandContextBuilder<S> {
|
||||||
|
pub fn new(
|
||||||
|
dispatcher: Rc<CommandDispatcher<S>>,
|
||||||
|
source: Rc<S>,
|
||||||
|
root_node: Rc<RefCell<CommandNode<S>>>,
|
||||||
|
start: usize,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
arguments: HashMap::new(),
|
||||||
|
root: root_node,
|
||||||
|
source,
|
||||||
|
range: StringRange::at(start),
|
||||||
|
command: None,
|
||||||
|
dispatcher,
|
||||||
|
nodes: vec![],
|
||||||
|
child: None,
|
||||||
|
modifier: None,
|
||||||
|
forks: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_command(
|
||||||
|
&mut self,
|
||||||
|
command: &Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
|
||||||
|
) -> &Self {
|
||||||
|
self.command = command.clone();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn with_child(&mut self, child: Rc<CommandContextBuilder<S>>) -> &Self {
|
||||||
|
self.child = Some(child);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn with_argument(&mut self, name: &str, argument: ParsedArgument) -> &Self {
|
||||||
|
self.arguments.insert(name.to_string(), argument);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn with_node(&mut self, node: Rc<RefCell<CommandNode<S>>>, range: StringRange) -> &Self {
|
||||||
|
self.nodes.push(ParsedCommandNode {
|
||||||
|
node: node.clone(),
|
||||||
|
range: range.clone(),
|
||||||
|
});
|
||||||
|
self.range = StringRange::encompassing(&self.range, &range);
|
||||||
|
self.modifier = node.borrow().modifier.clone();
|
||||||
|
self.forks = node.borrow().forks;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self, input: &str) -> CommandContext<S> {
|
||||||
|
CommandContext {
|
||||||
|
arguments: self.arguments.clone(),
|
||||||
|
root_node: self.root.clone(),
|
||||||
|
nodes: self.nodes.clone(),
|
||||||
|
source: self.source.clone(),
|
||||||
|
command: self.command.clone(),
|
||||||
|
child: self.child.clone().map(|c| Rc::new(c.build(input))),
|
||||||
|
range: self.range.clone(),
|
||||||
|
forks: self.forks,
|
||||||
|
modifier: self.modifier.clone(),
|
||||||
|
input: input.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Debug for CommandContextBuilder<S> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("CommandContextBuilder")
|
||||||
|
// .field("arguments", &self.arguments)
|
||||||
|
.field("root", &self.root)
|
||||||
|
// .field("nodes", &self.nodes)
|
||||||
|
// .field("dispatcher", &self.dispatcher)
|
||||||
|
// .field("source", &self.source)
|
||||||
|
// .field("command", &self.command)
|
||||||
|
.field("child", &self.child)
|
||||||
|
.field("range", &self.range)
|
||||||
|
// .field("modifier", &self.modifier)
|
||||||
|
.field("forks", &self.forks)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
11
azalea-brigadier/src/context/mod.rs
Normal file
11
azalea-brigadier/src/context/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
mod command_context;
|
||||||
|
mod command_context_builder;
|
||||||
|
mod parsed_argument;
|
||||||
|
mod parsed_command_node;
|
||||||
|
mod string_range;
|
||||||
|
|
||||||
|
pub use command_context::CommandContext;
|
||||||
|
pub use command_context_builder::CommandContextBuilder;
|
||||||
|
pub use parsed_argument::ParsedArgument;
|
||||||
|
pub use parsed_command_node::ParsedCommandNode;
|
||||||
|
pub use string_range::StringRange;
|
8
azalea-brigadier/src/context/parsed_argument.rs
Normal file
8
azalea-brigadier/src/context/parsed_argument.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use super::string_range::StringRange;
|
||||||
|
use std::{any::Any, rc::Rc};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ParsedArgument {
|
||||||
|
pub range: StringRange,
|
||||||
|
pub result: Rc<dyn Any>,
|
||||||
|
}
|
18
azalea-brigadier/src/context/parsed_command_node.rs
Normal file
18
azalea-brigadier/src/context/parsed_command_node.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use super::string_range::StringRange;
|
||||||
|
use crate::tree::CommandNode;
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParsedCommandNode<S> {
|
||||||
|
pub node: Rc<RefCell<CommandNode<S>>>,
|
||||||
|
pub range: StringRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for ParsedCommandNode<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
node: self.node.clone(),
|
||||||
|
range: self.range.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
azalea-brigadier/src/context/string_range.rs
Normal file
45
azalea-brigadier/src/context/string_range.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||||
|
pub struct StringRange {
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StringRange {
|
||||||
|
pub fn new(start: usize, end: usize) -> Self {
|
||||||
|
Self { start, end }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn at(pos: usize) -> Self {
|
||||||
|
Self::new(pos, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn between(start: usize, end: usize) -> Self {
|
||||||
|
Self::new(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encompassing(a: &Self, b: &Self) -> Self {
|
||||||
|
Self::new(cmp::min(a.start, b.start), cmp::max(a.end, b.end))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&self) -> usize {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end(&self) -> usize {
|
||||||
|
self.end
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<'a>(&self, reader: &'a str) -> &'a str {
|
||||||
|
&reader[self.start..self.end]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.start == self.end
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn length(&self) -> usize {
|
||||||
|
self.end - self.start
|
||||||
|
}
|
||||||
|
}
|
259
azalea-brigadier/src/tree/mod.rs
Normal file
259
azalea-brigadier/src/tree/mod.rs
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
use crate::{
|
||||||
|
builder::{
|
||||||
|
argument_builder::ArgumentBuilderType, literal_argument_builder::Literal,
|
||||||
|
required_argument_builder::Argument,
|
||||||
|
},
|
||||||
|
context::{CommandContext, CommandContextBuilder, ParsedArgument, StringRange},
|
||||||
|
exceptions::{BuiltInExceptions, CommandSyntaxException},
|
||||||
|
modifier::RedirectModifier,
|
||||||
|
string_reader::StringReader,
|
||||||
|
};
|
||||||
|
use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, ptr, rc::Rc};
|
||||||
|
|
||||||
|
/// An ArgumentBuilder that has been built.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct CommandNode<S> {
|
||||||
|
pub value: ArgumentBuilderType,
|
||||||
|
|
||||||
|
pub children: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
|
||||||
|
pub literals: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
|
||||||
|
pub arguments: HashMap<String, Rc<RefCell<CommandNode<S>>>>,
|
||||||
|
|
||||||
|
pub command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
|
||||||
|
pub requirement: Rc<dyn Fn(Rc<S>) -> bool>,
|
||||||
|
pub redirect: Option<Rc<RefCell<CommandNode<S>>>>,
|
||||||
|
pub forks: bool,
|
||||||
|
pub modifier: Option<Rc<RedirectModifier<S>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for CommandNode<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
value: self.value.clone(),
|
||||||
|
children: self.children.clone(),
|
||||||
|
literals: self.literals.clone(),
|
||||||
|
arguments: self.arguments.clone(),
|
||||||
|
command: self.command.clone(),
|
||||||
|
requirement: self.requirement.clone(),
|
||||||
|
redirect: self.redirect.clone(),
|
||||||
|
forks: self.forks,
|
||||||
|
modifier: self.modifier.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> CommandNode<S> {
|
||||||
|
/// Gets the literal, or panics. You should use match if you're not certain about the type.
|
||||||
|
pub fn literal(&self) -> &Literal {
|
||||||
|
match self.value {
|
||||||
|
ArgumentBuilderType::Literal(ref literal) => literal,
|
||||||
|
_ => panic!("CommandNode::literal() called on non-literal node"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Gets the argument, or panics. You should use match if you're not certain about the type.
|
||||||
|
pub fn argument(&self) -> &Argument {
|
||||||
|
match self.value {
|
||||||
|
ArgumentBuilderType::Argument(ref argument) => argument,
|
||||||
|
_ => panic!("CommandNode::argument() called on non-argument node"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_relevant_nodes(&self, input: &mut StringReader) -> Vec<Rc<RefCell<CommandNode<S>>>> {
|
||||||
|
let literals = &self.literals;
|
||||||
|
|
||||||
|
if !literals.is_empty() {
|
||||||
|
let cursor = input.cursor();
|
||||||
|
while input.can_read() && input.peek() != ' ' {
|
||||||
|
input.skip();
|
||||||
|
}
|
||||||
|
let text: String = input
|
||||||
|
.string()
|
||||||
|
.chars()
|
||||||
|
.skip(cursor)
|
||||||
|
.take(input.cursor() - cursor)
|
||||||
|
.collect();
|
||||||
|
input.cursor = cursor;
|
||||||
|
let literal = literals.get(&text);
|
||||||
|
if let Some(literal) = literal {
|
||||||
|
return vec![literal.clone()];
|
||||||
|
} else {
|
||||||
|
return self.arguments.values().cloned().collect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.arguments.values().cloned().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_use(&self, source: Rc<S>) -> bool {
|
||||||
|
(self.requirement)(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_child(&mut self, node: &Rc<RefCell<CommandNode<S>>>) {
|
||||||
|
let child = self.children.get(node.borrow().name());
|
||||||
|
if let Some(child) = child {
|
||||||
|
// We've found something to merge onto
|
||||||
|
if let Some(command) = &node.borrow().command {
|
||||||
|
child.borrow_mut().command = Some(command.clone());
|
||||||
|
}
|
||||||
|
for grandchild in node.borrow().children.values() {
|
||||||
|
child.borrow_mut().add_child(grandchild);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.children
|
||||||
|
.insert(node.borrow().name().to_string(), node.clone());
|
||||||
|
match &node.borrow().value {
|
||||||
|
ArgumentBuilderType::Literal(literal) => {
|
||||||
|
self.literals.insert(literal.value.clone(), node.clone());
|
||||||
|
}
|
||||||
|
ArgumentBuilderType::Argument(argument) => {
|
||||||
|
self.arguments.insert(argument.name.clone(), node.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
match &self.value {
|
||||||
|
ArgumentBuilderType::Argument(argument) => &argument.name,
|
||||||
|
ArgumentBuilderType::Literal(literal) => &literal.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn child(&self, name: &str) -> Option<Rc<RefCell<CommandNode<S>>>> {
|
||||||
|
self.children.get(name).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_with_context(
|
||||||
|
&self,
|
||||||
|
reader: &mut StringReader,
|
||||||
|
context_builder: &mut CommandContextBuilder<S>,
|
||||||
|
) -> Result<(), CommandSyntaxException> {
|
||||||
|
match self.value {
|
||||||
|
ArgumentBuilderType::Argument(ref argument) => {
|
||||||
|
let start = reader.cursor();
|
||||||
|
let result = argument.parse(reader)?;
|
||||||
|
let parsed = ParsedArgument {
|
||||||
|
range: StringRange::between(start, reader.cursor()),
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
|
||||||
|
context_builder.with_argument(&argument.name, parsed.clone());
|
||||||
|
context_builder.with_node(Rc::new(RefCell::new(self.clone())), parsed.range);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ArgumentBuilderType::Literal(ref literal) => {
|
||||||
|
let start = reader.cursor();
|
||||||
|
let end = self.parse(reader);
|
||||||
|
|
||||||
|
if let Some(end) = end {
|
||||||
|
context_builder.with_node(
|
||||||
|
Rc::new(RefCell::new(self.clone())),
|
||||||
|
StringRange::between(start, end),
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(BuiltInExceptions::LiteralIncorrect {
|
||||||
|
expected: literal.value.clone(),
|
||||||
|
}
|
||||||
|
.create_with_context(reader))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&self, reader: &mut StringReader) -> Option<usize> {
|
||||||
|
match self.value {
|
||||||
|
ArgumentBuilderType::Argument(_) => {
|
||||||
|
panic!("Can't parse argument.")
|
||||||
|
}
|
||||||
|
ArgumentBuilderType::Literal(ref literal) => {
|
||||||
|
let start = reader.cursor();
|
||||||
|
if reader.can_read_length(literal.value.len()) {
|
||||||
|
let end = start + literal.value.len();
|
||||||
|
if reader
|
||||||
|
.string()
|
||||||
|
.get(start..end)
|
||||||
|
.expect("Couldn't slice reader correctly?")
|
||||||
|
== literal.value
|
||||||
|
{
|
||||||
|
reader.cursor = end;
|
||||||
|
if !reader.can_read() || reader.peek() == ' ' {
|
||||||
|
return Some(end);
|
||||||
|
} else {
|
||||||
|
reader.cursor = start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Debug for CommandNode<S> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("CommandNode")
|
||||||
|
.field("value", &self.value)
|
||||||
|
.field("children", &self.children)
|
||||||
|
.field("command", &self.command.is_some())
|
||||||
|
// .field("requirement", &self.requirement)
|
||||||
|
.field("redirect", &self.redirect)
|
||||||
|
.field("forks", &self.forks)
|
||||||
|
// .field("modifier", &self.modifier)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Default for CommandNode<S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
value: ArgumentBuilderType::Literal(Literal::default()),
|
||||||
|
|
||||||
|
children: HashMap::new(),
|
||||||
|
literals: HashMap::new(),
|
||||||
|
arguments: HashMap::new(),
|
||||||
|
|
||||||
|
command: None,
|
||||||
|
requirement: Rc::new(|_| true),
|
||||||
|
redirect: None,
|
||||||
|
forks: false,
|
||||||
|
modifier: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Hash for CommandNode<S> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
// hash the children
|
||||||
|
for (k, v) in &self.children {
|
||||||
|
k.hash(state);
|
||||||
|
v.borrow().hash(state);
|
||||||
|
}
|
||||||
|
// i hope this works because if doesn't then that'll be a problem
|
||||||
|
ptr::hash(&self.command, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> PartialEq for CommandNode<S> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if self.children != other.children {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(selfexecutes) = &self.command {
|
||||||
|
// idk how to do this better since we can't compare `dyn Fn`s
|
||||||
|
if let Some(otherexecutes) = &other.command {
|
||||||
|
#[allow(clippy::vtable_address_comparisons)]
|
||||||
|
if !Rc::ptr_eq(selfexecutes, otherexecutes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if other.command.is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<S> Eq for CommandNode<S> {}
|
Loading…
Add table
Reference in a new issue