1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

Merge pull request #1 from mat-1/brigadier

azalea-brigadier
This commit is contained in:
mat 2022-04-20 01:34:12 +00:00 committed by GitHub
commit 5fd87615cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 2841 additions and 0 deletions

4
Cargo.lock generated
View file

@ -67,6 +67,10 @@ dependencies = [
"uuid",
]
[[package]]
name = "azalea-brigadier"
version = "0.1.0"
[[package]]
name = "azalea-chat"
version = "0.1.0"

View file

@ -8,4 +8,5 @@ members = [
"azalea-core",
"azalea-auth",
"azalea-nbt",
"azalea-brigadier",
]

View file

@ -0,0 +1,8 @@
[package]
edition = "2021"
name = "azalea-brigadier"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,3 @@
# Azalea Brigadier
A Rust port of Mojang's [Brigadier](https://github.com/Mojang/brigadier) command parsing and dispatching library.

View file

@ -0,0 +1,7 @@
use std::{any::Any, rc::Rc};
use crate::{exceptions::CommandSyntaxException, string_reader::StringReader};
pub trait ArgumentType {
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException>;
}

View file

@ -0,0 +1,54 @@
use std::{any::Any, rc::Rc};
use crate::{
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
string_reader::StringReader,
};
use super::ArgumentType;
#[derive(Default)]
struct Integer {
pub minimum: Option<i32>,
pub maximum: Option<i32>,
}
impl ArgumentType for Integer {
fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
let start = reader.cursor;
let result = reader.read_int()?;
if let Some(minimum) = self.minimum {
if result < minimum {
reader.cursor = start;
return Err(BuiltInExceptions::IntegerTooSmall {
found: result,
min: minimum,
}
.create_with_context(reader));
}
}
if let Some(maximum) = self.maximum {
if result > maximum {
reader.cursor = start;
return Err(BuiltInExceptions::IntegerTooBig {
found: result,
max: maximum,
}
.create_with_context(reader));
}
}
Ok(Rc::new(result))
}
}
pub fn integer() -> impl ArgumentType {
Integer::default()
}
pub fn get_integer<S>(context: &CommandContext<S>, name: &str) -> Option<i32> {
context
.argument(name)
.unwrap()
.downcast_ref::<i32>()
.copied()
}

View file

@ -0,0 +1,4 @@
mod argument_type;
pub mod integer_argument_type;
pub use argument_type::ArgumentType;

View file

@ -0,0 +1,137 @@
use crate::{context::CommandContext, modifier::RedirectModifier, tree::CommandNode};
use super::{literal_argument_builder::Literal, required_argument_builder::Argument};
use std::{cell::RefCell, fmt::Debug, rc::Rc};
#[derive(Debug, Clone)]
pub enum ArgumentBuilderType {
Literal(Literal),
Argument(Argument),
}
/// A node that hasn't yet been built.
pub struct ArgumentBuilder<S> {
arguments: CommandNode<S>,
command: Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>,
requirement: Rc<dyn Fn(Rc<S>) -> bool>,
target: Option<Rc<RefCell<CommandNode<S>>>>,
forks: bool,
modifier: Option<Rc<RedirectModifier<S>>>,
}
impl<S> Clone for ArgumentBuilder<S> {
fn clone(&self) -> Self {
Self {
arguments: self.arguments.clone(),
command: self.command.clone(),
requirement: self.requirement.clone(),
target: self.target.clone(),
forks: self.forks,
modifier: self.modifier.clone(),
}
}
}
/// A node that isn't yet built.
impl<S> ArgumentBuilder<S> {
pub fn new(value: ArgumentBuilderType) -> Self {
Self {
arguments: CommandNode {
value,
..Default::default()
},
command: None,
requirement: Rc::new(|_| true),
forks: false,
modifier: None,
target: None,
}
}
pub fn then(&mut self, argument: ArgumentBuilder<S>) -> Self {
self.then_built(argument.build())
}
pub fn then_built(&mut self, argument: CommandNode<S>) -> Self {
self.arguments.add_child(&Rc::new(RefCell::new(argument)));
self.clone()
}
pub fn executes<F>(&mut self, f: F) -> Self
where
F: Fn(&CommandContext<S>) -> i32 + 'static,
{
self.command = Some(Rc::new(f));
self.clone()
}
pub fn requires<F>(&mut self, requirement: F) -> Self
where
F: Fn(Rc<S>) -> bool + 'static,
{
self.requirement = Rc::new(requirement);
self.clone()
}
pub fn redirect(&mut self, target: Rc<RefCell<CommandNode<S>>>) -> Self {
self.forward(target, None, false)
}
pub fn fork(
&mut self,
target: Rc<RefCell<CommandNode<S>>>,
modifier: Rc<RedirectModifier<S>>,
) -> Self {
self.forward(target, Some(modifier), true)
}
pub fn forward(
&mut self,
target: Rc<RefCell<CommandNode<S>>>,
modifier: Option<Rc<RedirectModifier<S>>>,
fork: bool,
) -> Self {
if !self.arguments.children.is_empty() {
panic!("Cannot forward a node with children");
}
self.target = Some(target);
self.modifier = modifier;
self.forks = fork;
self.clone()
}
pub fn build(self) -> CommandNode<S> {
let mut result = CommandNode {
value: self.arguments.value,
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.children.values() {
result.add_child(argument);
}
result
}
}
impl<S> Debug for ArgumentBuilder<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArgumentBuilder")
.field("arguments", &self.arguments)
// .field("command", &self.command)
// .field("requirement", &self.requirement)
.field("target", &self.target)
.field("forks", &self.forks)
// .field("modifier", &self.modifier)
.finish()
}
}

View file

@ -0,0 +1,24 @@
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
#[derive(Debug, Clone, Default)]
pub struct Literal {
pub value: String,
}
impl Literal {
pub fn new(value: &str) -> Self {
Self {
value: value.to_string(),
}
}
}
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>(value: &str) -> ArgumentBuilder<S> {
ArgumentBuilder::new(ArgumentBuilderType::Literal(Literal::new(value)))
}

View file

@ -0,0 +1,3 @@
pub mod argument_builder;
pub mod literal_argument_builder;
pub mod required_argument_builder;

View file

@ -0,0 +1,45 @@
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
use crate::{
arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader,
};
use std::{any::Any, fmt::Debug, rc::Rc};
/// An argument node type. The `T` type parameter is the type of the argument,
/// which can be anything.
#[derive(Clone)]
pub struct Argument {
pub name: String,
parser: Rc<dyn ArgumentType>,
}
impl Argument {
pub fn new(name: &str, parser: Rc<dyn ArgumentType>) -> Self {
Self {
name: name.to_string(),
parser,
}
}
pub fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> {
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<S>(name: &str, parser: impl ArgumentType + 'static) -> ArgumentBuilder<S> {
ArgumentBuilder::new(Argument::new(name, Rc::new(parser)).into())
}

View file

@ -0,0 +1,298 @@
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> {
pub 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,
}
}
}

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

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

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

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

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

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

View file

@ -0,0 +1,159 @@
use std::fmt;
use crate::{message::Message, string_reader::StringReader};
use super::command_syntax_exception::CommandSyntaxException;
#[derive(Clone, PartialEq)]
pub enum BuiltInExceptions {
DoubleTooSmall { found: f64, min: f64 },
DoubleTooBig { found: f64, max: f64 },
FloatTooSmall { found: f32, min: f32 },
FloatTooBig { found: f32, max: f32 },
IntegerTooSmall { found: i32, min: i32 },
IntegerTooBig { found: i32, max: i32 },
LongTooSmall { found: i64, min: i64 },
LongTooBig { found: i64, max: i64 },
LiteralIncorrect { expected: String },
ReaderExpectedStartOfQuote,
ReaderExpectedEndOfQuote,
ReaderInvalidEscape { character: char },
ReaderInvalidBool { value: String },
ReaderInvalidInt { value: String },
ReaderExpectedInt,
ReaderInvalidLong { value: String },
ReaderExpectedLong,
ReaderInvalidDouble { value: String },
ReaderExpectedDouble,
ReaderInvalidFloat { value: String },
ReaderExpectedFloat,
ReaderExpectedBool,
ReaderExpectedSymbol { symbol: char },
DispatcherUnknownCommand,
DispatcherUnknownArgument,
DispatcherExpectedArgumentSeparator,
DispatcherParseException { message: String },
}
impl fmt::Debug for BuiltInExceptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuiltInExceptions::DoubleTooSmall { found, min } => {
write!(f, "Double must not be less than {}, found {}", min, found)
}
BuiltInExceptions::DoubleTooBig { found, max } => {
write!(f, "Double must not be more than {}, found {}", max, found)
}
BuiltInExceptions::FloatTooSmall { found, min } => {
write!(f, "Float must not be less than {}, found {}", min, found)
}
BuiltInExceptions::FloatTooBig { found, max } => {
write!(f, "Float must not be more than {}, found {}", max, found)
}
BuiltInExceptions::IntegerTooSmall { found, min } => {
write!(f, "Integer must not be less than {}, found {}", min, found)
}
BuiltInExceptions::IntegerTooBig { found, max } => {
write!(f, "Integer must not be more than {}, found {}", max, found)
}
BuiltInExceptions::LongTooSmall { found, min } => {
write!(f, "Long must not be less than {}, found {}", min, found)
}
BuiltInExceptions::LongTooBig { found, max } => {
write!(f, "Long must not be more than {}, found {}", max, found)
}
BuiltInExceptions::LiteralIncorrect { expected } => {
write!(f, "Expected literal {}", expected)
}
BuiltInExceptions::ReaderExpectedStartOfQuote => {
write!(f, "Expected quote to start a string")
}
BuiltInExceptions::ReaderExpectedEndOfQuote => {
write!(f, "Unclosed quoted string")
}
BuiltInExceptions::ReaderInvalidEscape { character } => {
write!(
f,
"Invalid escape sequence '{}' in quoted string",
character
)
}
BuiltInExceptions::ReaderInvalidBool { value } => {
write!(
f,
"Invalid bool, expected true or false but found '{}'",
value
)
}
BuiltInExceptions::ReaderInvalidInt { value } => {
write!(f, "Invalid Integer '{}'", value)
}
BuiltInExceptions::ReaderExpectedInt => {
write!(f, "Expected Integer")
}
BuiltInExceptions::ReaderInvalidLong { value } => {
write!(f, "Invalid long '{}'", value)
}
BuiltInExceptions::ReaderExpectedLong => {
write!(f, "Expected long")
}
BuiltInExceptions::ReaderInvalidDouble { value } => {
write!(f, "Invalid double '{}'", value)
}
BuiltInExceptions::ReaderExpectedDouble => {
write!(f, "Expected double")
}
BuiltInExceptions::ReaderInvalidFloat { value } => {
write!(f, "Invalid Float '{}'", value)
}
BuiltInExceptions::ReaderExpectedFloat => {
write!(f, "Expected Float")
}
BuiltInExceptions::ReaderExpectedBool => {
write!(f, "Expected bool")
}
BuiltInExceptions::ReaderExpectedSymbol { symbol } => {
write!(f, "Expected '{}'", symbol)
}
BuiltInExceptions::DispatcherUnknownCommand => {
write!(f, "Unknown command")
}
BuiltInExceptions::DispatcherUnknownArgument => {
write!(f, "Incorrect argument for command")
}
BuiltInExceptions::DispatcherExpectedArgumentSeparator => {
write!(
f,
"Expected whitespace to end one argument, but found trailing data"
)
}
BuiltInExceptions::DispatcherParseException { message } => {
write!(f, "Could not parse command: {}", message)
}
}
}
}
impl BuiltInExceptions {
pub fn create(self) -> CommandSyntaxException {
let message = Message::from(format!("{:?}", self));
CommandSyntaxException::create(self, message)
}
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

@ -0,0 +1,91 @@
use std::{cmp, fmt};
use super::builtin_exceptions::BuiltInExceptions;
use crate::message::Message;
#[derive(Clone, PartialEq)]
pub struct CommandSyntaxException {
pub type_: BuiltInExceptions,
message: Message,
input: Option<String>,
cursor: Option<usize>,
}
const CONTEXT_AMOUNT: usize = 10;
impl CommandSyntaxException {
pub fn new(type_: BuiltInExceptions, message: Message, input: &str, cursor: usize) -> Self {
Self {
type_,
message,
input: Some(input.to_string()),
cursor: Some(cursor),
}
}
pub fn create(type_: BuiltInExceptions, message: Message) -> Self {
Self {
type_,
message,
input: None,
cursor: None,
}
}
pub fn message(&self) -> String {
let mut message = self.message.string();
let context = self.context();
if let Some(context) = context {
message.push_str(&format!(
" at position {}: {}",
self.cursor.unwrap_or(usize::MAX),
context
));
}
message
}
pub fn raw_message(&self) -> &Message {
&self.message
}
pub fn context(&self) -> Option<String> {
if let Some(input) = &self.input {
if let Some(cursor) = self.cursor {
let mut builder = String::new();
let cursor = cmp::min(input.len(), cursor);
if cursor > CONTEXT_AMOUNT {
builder.push_str("...");
}
builder.push_str(
&input
[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
);
builder.push_str("<--[HERE]");
return Some(builder);
}
}
None
}
pub fn get_type(&self) -> &BuiltInExceptions {
&self.type_
}
pub fn input(&self) -> &Option<String> {
&self.input
}
pub fn cursor(&self) -> Option<usize> {
self.cursor
}
}
impl fmt::Debug for CommandSyntaxException {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message())
}
}

View file

@ -0,0 +1,5 @@
mod builtin_exceptions;
mod command_syntax_exception;
pub use builtin_exceptions::BuiltInExceptions;
pub use command_syntax_exception::CommandSyntaxException;

View file

@ -0,0 +1,10 @@
pub mod arguments;
pub mod builder;
pub mod command_dispatcher;
pub mod context;
pub mod exceptions;
pub mod message;
pub mod modifier;
pub mod parse_results;
pub mod string_reader;
pub mod tree;

View file

@ -0,0 +1,14 @@
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Message(String);
impl Message {
pub fn string(&self) -> String {
self.0.to_string()
}
}
impl From<String> for Message {
fn from(s: String) -> Self {
Self(s)
}
}

View file

@ -0,0 +1,6 @@
use std::rc::Rc;
use crate::{context::CommandContext, exceptions::CommandSyntaxException};
pub type RedirectModifier<S> =
dyn Fn(&CommandContext<S>) -> Result<Vec<Rc<S>>, CommandSyntaxException>;

View file

@ -0,0 +1,21 @@
use crate::{
context::CommandContextBuilder, exceptions::CommandSyntaxException,
string_reader::StringReader, tree::CommandNode,
};
use std::{collections::HashMap, fmt::Debug, rc::Rc};
pub struct ParseResults<S> {
pub context: CommandContextBuilder<S>,
pub reader: StringReader,
pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxException>,
}
impl<S> 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,274 @@
use crate::exceptions::{BuiltInExceptions, CommandSyntaxException};
use std::str::FromStr;
#[derive(Clone)]
pub struct StringReader {
string: String,
pub cursor: usize,
}
const SYNTAX_ESCAPE: char = '\\';
const SYNTAX_DOUBLE_QUOTE: char = '"';
const SYNTAX_SINGLE_QUOTE: char = '\'';
impl From<String> for StringReader {
fn from(string: String) -> Self {
Self { string, cursor: 0 }
}
}
impl From<&str> for StringReader {
fn from(string: &str) -> Self {
Self {
string: string.to_string(),
cursor: 0,
}
}
}
impl StringReader {
pub fn string(&self) -> &str {
&self.string
}
pub fn remaining_length(&self) -> usize {
self.string.len() - self.cursor
}
pub fn total_length(&self) -> usize {
self.string.len()
}
pub fn get_read(&self) -> &str {
&self.string[..self.cursor]
}
pub fn remaining(&self) -> &str {
&self.string[self.cursor..]
}
pub fn can_read_length(&self, length: usize) -> bool {
self.cursor + length <= self.string.len()
}
pub fn can_read(&self) -> bool {
self.can_read_length(1)
}
pub fn peek(&self) -> char {
self.string.chars().nth(self.cursor).unwrap()
}
pub fn peek_offset(&self, offset: usize) -> char {
self.string.chars().nth(self.cursor + offset).unwrap()
}
pub fn cursor(&self) -> usize {
self.cursor
}
pub fn read(&mut self) -> char {
let c = self.peek();
self.cursor += 1;
c
}
pub fn skip(&mut self) {
self.cursor += 1;
}
pub fn is_allowed_number(c: char) -> bool {
('0'..='9').contains(&c) || c == '.' || c == '-'
}
pub fn is_quoted_string_start(c: char) -> bool {
c == SYNTAX_DOUBLE_QUOTE || c == SYNTAX_SINGLE_QUOTE
}
pub fn skip_whitespace(&mut self) {
while self.can_read() && self.peek().is_whitespace() {
self.skip();
}
}
pub fn read_int(&mut self) -> Result<i32, CommandSyntaxException> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedInt.create_with_context(self));
}
let result = i32::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidInt {
value: number.to_string(),
}
.create_with_context(self));
}
Ok(result.unwrap())
}
pub fn read_long(&mut self) -> Result<i64, CommandSyntaxException> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedLong.create_with_context(self));
}
let result = i64::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidLong {
value: number.to_string(),
}
.create_with_context(self));
}
Ok(result.unwrap())
}
pub fn read_double(&mut self) -> Result<f64, CommandSyntaxException> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedDouble.create_with_context(self));
}
let result = f64::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidDouble {
value: number.to_string(),
}
.create_with_context(self));
}
Ok(result.unwrap())
}
pub fn read_float(&mut self) -> Result<f32, CommandSyntaxException> {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_number(self.peek()) {
self.skip();
}
let number = &self.string[start..self.cursor];
if number.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedFloat.create_with_context(self));
}
let result = f32::from_str(number);
if result.is_err() {
self.cursor = start;
return Err(BuiltInExceptions::ReaderInvalidFloat {
value: number.to_string(),
}
.create_with_context(self));
}
Ok(result.unwrap())
}
pub fn is_allowed_in_unquoted_string(c: char) -> bool {
('0'..='9').contains(&c)
|| ('A'..='Z').contains(&c)
|| ('a'..='z').contains(&c)
|| c == '_'
|| c == '-'
|| c == '.'
|| c == '+'
}
pub fn read_unquoted_string(&mut self) -> &str {
let start = self.cursor;
while self.can_read() && StringReader::is_allowed_in_unquoted_string(self.peek()) {
self.skip();
}
&self.string[start..self.cursor]
}
pub fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxException> {
if !self.can_read() {
return Ok(String::new());
}
let next = self.peek();
if !StringReader::is_quoted_string_start(next) {
return Err(BuiltInExceptions::ReaderExpectedStartOfQuote.create_with_context(self));
}
self.skip();
self.read_string_until(next)
}
pub fn read_string_until(
&mut self,
terminator: char,
) -> Result<String, CommandSyntaxException> {
let mut result = String::new();
let mut escaped = false;
while self.can_read() {
let c = self.read();
if escaped {
if c == terminator || c == SYNTAX_ESCAPE {
result.push(c);
escaped = false;
} else {
self.cursor -= 1;
return Err(BuiltInExceptions::ReaderInvalidEscape { character: c }
.create_with_context(self));
}
} else if c == SYNTAX_ESCAPE {
escaped = true;
} else if c == terminator {
return Ok(result);
} else {
result.push(c);
}
}
Err(BuiltInExceptions::ReaderExpectedEndOfQuote.create_with_context(self))
}
pub fn read_string(&mut self) -> Result<String, CommandSyntaxException> {
if !self.can_read() {
return Ok(String::new());
}
let next = self.peek();
if StringReader::is_quoted_string_start(next) {
self.skip();
return self.read_string_until(next);
}
Ok(self.read_unquoted_string().to_string())
}
pub fn read_boolean(&mut self) -> Result<bool, CommandSyntaxException> {
let start = self.cursor;
let value = self.read_string()?;
if value.is_empty() {
return Err(BuiltInExceptions::ReaderExpectedBool.create_with_context(self));
}
if value == "true" {
Ok(true)
} else if value == "false" {
Ok(false)
} else {
self.cursor = start;
Err(BuiltInExceptions::ReaderInvalidBool { value }.create_with_context(self))
}
}
pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxException> {
if !self.can_read() || self.peek() != c {
return Err(
BuiltInExceptions::ReaderExpectedSymbol { symbol: c }.create_with_context(self)
);
}
self.skip();
Ok(())
}
}

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

View file

@ -0,0 +1,75 @@
use std::rc::Rc;
use crate::{
arguments::integer_argument_type::integer,
builder::{literal_argument_builder::literal, required_argument_builder::argument},
};
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.arguments.children.len(), 1);
let built_argument = Rc::new(argument.build());
assert!(builder
.arguments
.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

@ -0,0 +1,410 @@
use std::rc::Rc;
use azalea_brigadier::{
arguments::integer_argument_type::integer,
builder::{literal_argument_builder::literal, required_argument_builder::argument},
command_dispatcher::CommandDispatcher,
context::CommandContext,
exceptions::{BuiltInExceptions, CommandSyntaxException},
string_reader::StringReader,
};
#[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)
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,612 @@
use azalea_brigadier::{exceptions::BuiltInExceptions, string_reader::StringReader};
#[test]
fn can_read() {
let mut reader = StringReader::from("abc".to_string());
assert_eq!(reader.can_read(), true);
reader.skip(); // 'a'
assert_eq!(reader.can_read(), true);
reader.skip(); // 'b'
assert_eq!(reader.can_read(), true);
reader.skip(); // 'c'
assert_eq!(reader.can_read(), false);
}
#[test]
fn get_remaining_length() {
let mut reader = StringReader::from("abc".to_string());
assert_eq!(reader.remaining_length(), 3);
reader.cursor = 1;
assert_eq!(reader.remaining_length(), 2);
reader.cursor = 2;
assert_eq!(reader.remaining_length(), 1);
reader.cursor = 3;
assert_eq!(reader.remaining_length(), 0);
}
#[test]
fn can_read_length() {
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);
assert_eq!(reader.can_read_length(4), false);
assert_eq!(reader.can_read_length(5), false);
}
#[test]
fn peek() {
let mut reader = StringReader::from("abc".to_string());
assert_eq!(reader.peek(), 'a');
assert_eq!(reader.cursor(), 0);
reader.cursor = 2;
assert_eq!(reader.peek(), 'c');
assert_eq!(reader.cursor(), 2);
}
#[test]
fn peek_length() {
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);
reader.cursor = 1;
assert_eq!(reader.peek_offset(1), 'c');
assert_eq!(reader.cursor(), 1);
}
#[test]
fn read() {
let mut reader = StringReader::from("abc".to_string());
assert_eq!(reader.read(), 'a');
assert_eq!(reader.read(), 'b');
assert_eq!(reader.read(), 'c');
assert_eq!(reader.cursor(), 3);
}
#[test]
fn skip() {
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!".to_string());
assert_eq!(reader.remaining(), "Hello!");
reader.cursor = 3;
assert_eq!(reader.remaining(), "lo!");
reader.cursor = 6;
assert_eq!(reader.remaining(), "");
}
#[test]
fn get_read() {
let mut reader = StringReader::from("Hello!".to_string());
assert_eq!(reader.get_read(), "");
reader.cursor = 3;
assert_eq!(reader.get_read(), "Hel");
reader.cursor = 6;
assert_eq!(reader.get_read(), "Hello!");
}
#[test]
fn skip_whitespace_none() {
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!".to_string());
reader.skip_whitespace();
assert_eq!(reader.cursor(), 5);
}
#[test]
fn skip_whitespace_empty() {
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".to_string());
assert_eq!(reader.read_unquoted_string(), "hello");
assert_eq!(reader.get_read(), "hello");
assert_eq!(reader.remaining(), " world");
}
#[test]
fn read_unquoted_string_empty() {
let mut reader = StringReader::from("".to_string());
assert_eq!(reader.read_unquoted_string(), "");
assert_eq!(reader.get_read(), "");
assert_eq!(reader.remaining(), "");
}
#[test]
fn read_unquoted_string_empty_with_remaining() {
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");
}
#[test]
fn read_quoted_string() {
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(), "");
}
#[test]
fn read_single_quoted_string() {
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(), "");
}
#[test]
fn read_mixed_quoted_string_double_inside_single() {
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(), "");
}
#[test]
fn read_mixed_quoted_string_single_inside_double() {
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(), "");
}
#[test]
fn read_quoted_string_empty_quoted() {
let mut reader = StringReader::from("".to_string());
assert_eq!(reader.read_quoted_string().unwrap(), "");
assert_eq!(reader.get_read(), "");
assert_eq!(reader.remaining(), "");
}
#[test]
fn read_quoted_string_empty_quoted_with_remaining() {
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");
}
#[test]
fn read_quoted_string_with_escaped_quote() {
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(), "");
}
#[test]
fn read_quoted_string_with_escaped_escapes() {
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(), "");
}
#[test]
fn read_quoted_string_with_remaining() {
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");
}
#[test]
fn read_quoted_string_with_immediate_remaining() {
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");
}
#[test]
fn read_quoted_string_no_open() {
let mut reader = StringReader::from("hello world\"".to_string());
let result = reader.read_quoted_string();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedStartOfQuote);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_quoted_string_no_close() {
let mut reader = StringReader::from("\"hello world".to_string());
let result = reader.read_quoted_string();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedEndOfQuote);
assert_eq!(e.cursor(), Some(12));
}
}
#[test]
fn read_quoted_string_invalid_escape() {
let mut reader = StringReader::from("\"hello\\nworld\"".to_string());
let result = reader.read_quoted_string();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidEscape { character: 'n' }
);
assert_eq!(e.cursor(), Some(7));
}
}
#[test]
fn read_quoted_string_invalid_quote_escape() {
let mut reader = StringReader::from("'hello\\\"\'world".to_string());
let result = reader.read_quoted_string();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidEscape { character: '"' }
);
assert_eq!(e.cursor(), Some(7));
}
}
#[test]
fn read_string_no_quotes() {
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");
}
#[test]
fn read_string_single_quotes() {
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(), "");
}
#[test]
fn read_string_double_quotes() {
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(), "");
}
#[test]
fn read_int() {
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(), "");
}
#[test]
fn read_int_negative() {
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(), "");
}
#[test]
fn read_int_invalid() {
let mut reader = StringReader::from("12.34".to_string());
let result = reader.read_int();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidInt {
value: "12.34".to_string()
}
);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_int_none() {
let mut reader = StringReader::from("".to_string());
let result = reader.read_int();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedInt);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_int_with_remaining() {
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");
}
#[test]
fn read_int_with_remaining_immediate() {
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");
}
#[test]
fn read_long() {
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(), "");
}
#[test]
fn read_long_negative() {
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(), "");
}
#[test]
fn read_long_invalid() {
let mut reader = StringReader::from("12.34".to_string());
let result = reader.read_long();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidLong {
value: "12.34".to_string()
}
);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_long_none() {
let mut reader = StringReader::from("".to_string());
let result = reader.read_long();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedLong);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_long_with_remaining() {
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");
}
#[test]
fn read_long_with_remaining_immediate() {
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");
}
#[test]
fn read_double() {
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(), "");
}
#[test]
fn read_double_with_decimal() {
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(), "");
}
#[test]
fn read_double_negative() {
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(), "");
}
#[test]
fn read_double_invalid() {
let mut reader = StringReader::from("12.34.56".to_string());
let result = reader.read_double();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidDouble {
value: "12.34.56".to_string()
}
);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_double_none() {
let mut reader = StringReader::from("".to_string());
let result = reader.read_double();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedDouble);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_double_with_remaining() {
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");
}
#[test]
fn read_double_with_remaining_immediate() {
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");
}
#[test]
fn read_float() {
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(), "");
}
#[test]
fn read_float_with_decimal() {
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(), "");
}
#[test]
fn read_float_negative() {
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(), "");
}
#[test]
fn read_float_invalid() {
let mut reader = StringReader::from("12.34.56".to_string());
let result = reader.read_float();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidFloat {
value: "12.34.56".to_string()
}
);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_float_none() {
let mut reader = StringReader::from("".to_string());
let result = reader.read_float();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedFloat);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_float_with_remaining() {
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");
}
#[test]
fn read_float_with_remaining_immediate() {
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");
}
#[test]
fn expect_correct() {
let mut reader = StringReader::from("abc".to_string());
reader.expect('a').unwrap();
assert_eq!(reader.cursor(), 1);
}
#[test]
fn expect_incorrect() {
let mut reader = StringReader::from("bcd".to_string());
let result = reader.expect('a');
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn expect_none() {
let mut reader = StringReader::from("".to_string());
let result = reader.expect('a');
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' }
);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_boolean_correct() {
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".to_string());
let result = reader.read_boolean();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.get_type(),
&BuiltInExceptions::ReaderInvalidBool {
value: "tuesday".to_string()
}
);
assert_eq!(e.cursor(), Some(0));
}
}
#[test]
fn read_boolean_none() {
let mut reader = StringReader::from("".to_string());
let result = reader.read_boolean();
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedBool);
assert_eq!(e.cursor(), Some(0));
}
}

View file

@ -0,0 +1,37 @@
// use std::hash::Hash;
// use crate::mc_buf::Readable;
// use super::LoginPacket;
// #[derive(Hash, Clone, Debug)]
// pub struct ClientboundDeclareCommandsPacket {
// pub root: RootCommandNode<SharedSuggestionProvider>,
// pub public_key: Vec<u8>,
// pub nonce: Vec<u8>,
// }
// impl ClientboundHelloPacket {
// pub fn get(self) -> LoginPacket {
// LoginPacket::ClientboundHelloPacket(self)
// }
// pub fn write(&self, _buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
// panic!("ClientboundHelloPacket::write not implemented")
// }
// pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
// buf: &mut T,
// ) -> Result<LoginPacket, String> {
// let server_id = buf.read_utf_with_len(20).await?;
// let public_key = buf.read_byte_array().await?;
// let nonce = buf.read_byte_array().await?;
// Ok(ClientboundHelloPacket {
// server_id,
// public_key,
// nonce,
// }
// .get())
// }
// }