mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
Merge branch 'main' into auth
This commit is contained in:
commit
3e507f0db4
134 changed files with 5062 additions and 1003 deletions
1
.gitignore
vendored
Normal file → Executable file
1
.gitignore
vendored
Normal file → Executable file
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
/doc
|
||||||
|
|
0
.gitpod.yml
Normal file → Executable file
0
.gitpod.yml
Normal file → Executable file
0
.vscode/settings.json
vendored
Normal file → Executable file
0
.vscode/settings.json
vendored
Normal file → Executable file
21
Cargo.lock
generated
Normal file → Executable file
21
Cargo.lock
generated
Normal file → Executable file
|
@ -70,6 +70,10 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "azalea-brigadier"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "azalea-chat"
|
name = "azalea-chat"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -116,11 +120,15 @@ dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"azalea-auth",
|
"azalea-auth",
|
||||||
|
"azalea-brigadier",
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
"azalea-nbt",
|
"azalea-nbt",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"packet-macros",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -748,6 +756,15 @@ version = "11.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packet-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
|
@ -827,9 +844,9 @@ checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.32"
|
version = "1.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
1
Cargo.toml
Normal file → Executable file
1
Cargo.toml
Normal file → Executable file
|
@ -8,4 +8,5 @@ members = [
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
"azalea-auth",
|
"azalea-auth",
|
||||||
"azalea-nbt",
|
"azalea-nbt",
|
||||||
|
"azalea-brigadier",
|
||||||
]
|
]
|
||||||
|
|
6
README.md
Normal file → Executable file
6
README.md
Normal file → Executable file
|
@ -6,8 +6,8 @@ I named this Azalea because it sounds like a cool word and this is a cool librar
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- Bypass most anticheats
|
|
||||||
- Only support the latest Minecraft version
|
|
||||||
- Do everything a vanilla client can do
|
- Do everything a vanilla client can do
|
||||||
- Be fast
|
|
||||||
- Be easy to use
|
- Be easy to use
|
||||||
|
- Bypass most/all anticheats
|
||||||
|
- Support the latest Minecraft version
|
||||||
|
- Be fast
|
||||||
|
|
0
azalea-auth/Cargo.toml
Normal file → Executable file
0
azalea-auth/Cargo.toml
Normal file → Executable file
0
azalea-auth/src/game_profile.rs
Normal file → Executable file
0
azalea-auth/src/game_profile.rs
Normal file → Executable file
0
azalea-auth/src/lib.rs
Normal file → Executable file
0
azalea-auth/src/lib.rs
Normal file → Executable file
8
azalea-brigadier/Cargo.toml
Executable file
8
azalea-brigadier/Cargo.toml
Executable 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]
|
3
azalea-brigadier/README.md
Executable file
3
azalea-brigadier/README.md
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
# Azalea Brigadier
|
||||||
|
|
||||||
|
A Rust port of Mojang's [Brigadier](https://github.com/Mojang/brigadier) command parsing and dispatching library.
|
7
azalea-brigadier/src/arguments/argument_type.rs
Executable file
7
azalea-brigadier/src/arguments/argument_type.rs
Executable 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>;
|
||||||
|
}
|
54
azalea-brigadier/src/arguments/integer_argument_type.rs
Executable file
54
azalea-brigadier/src/arguments/integer_argument_type.rs
Executable 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()
|
||||||
|
}
|
4
azalea-brigadier/src/arguments/mod.rs
Executable file
4
azalea-brigadier/src/arguments/mod.rs
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
mod argument_type;
|
||||||
|
pub mod integer_argument_type;
|
||||||
|
|
||||||
|
pub use argument_type::ArgumentType;
|
137
azalea-brigadier/src/builder/argument_builder.rs
Executable file
137
azalea-brigadier/src/builder/argument_builder.rs
Executable 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()
|
||||||
|
}
|
||||||
|
}
|
24
azalea-brigadier/src/builder/literal_argument_builder.rs
Executable file
24
azalea-brigadier/src/builder/literal_argument_builder.rs
Executable 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)))
|
||||||
|
}
|
3
azalea-brigadier/src/builder/mod.rs
Executable file
3
azalea-brigadier/src/builder/mod.rs
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod argument_builder;
|
||||||
|
pub mod literal_argument_builder;
|
||||||
|
pub mod required_argument_builder;
|
45
azalea-brigadier/src/builder/required_argument_builder.rs
Executable file
45
azalea-brigadier/src/builder/required_argument_builder.rs
Executable 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())
|
||||||
|
}
|
298
azalea-brigadier/src/command_dispatcher.rs
Executable file
298
azalea-brigadier/src/command_dispatcher.rs
Executable 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
azalea-brigadier/src/context/command_context.rs
Executable file
80
azalea-brigadier/src/context/command_context.rs
Executable 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
Executable file
116
azalea-brigadier/src/context/command_context_builder.rs
Executable 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
Executable file
11
azalea-brigadier/src/context/mod.rs
Executable 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
Executable file
8
azalea-brigadier/src/context/parsed_argument.rs
Executable 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
Executable file
18
azalea-brigadier/src/context/parsed_command_node.rs
Executable 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
Executable file
45
azalea-brigadier/src/context/string_range.rs
Executable 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
|
||||||
|
}
|
||||||
|
}
|
159
azalea-brigadier/src/exceptions/builtin_exceptions.rs
Executable file
159
azalea-brigadier/src/exceptions/builtin_exceptions.rs
Executable 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())
|
||||||
|
}
|
||||||
|
}
|
91
azalea-brigadier/src/exceptions/command_syntax_exception.rs
Executable file
91
azalea-brigadier/src/exceptions/command_syntax_exception.rs
Executable 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())
|
||||||
|
}
|
||||||
|
}
|
5
azalea-brigadier/src/exceptions/mod.rs
Executable file
5
azalea-brigadier/src/exceptions/mod.rs
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
mod builtin_exceptions;
|
||||||
|
mod command_syntax_exception;
|
||||||
|
|
||||||
|
pub use builtin_exceptions::BuiltInExceptions;
|
||||||
|
pub use command_syntax_exception::CommandSyntaxException;
|
10
azalea-brigadier/src/lib.rs
Executable file
10
azalea-brigadier/src/lib.rs
Executable 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;
|
14
azalea-brigadier/src/message.rs
Executable file
14
azalea-brigadier/src/message.rs
Executable 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)
|
||||||
|
}
|
||||||
|
}
|
6
azalea-brigadier/src/modifier.rs
Executable file
6
azalea-brigadier/src/modifier.rs
Executable 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>;
|
21
azalea-brigadier/src/parse_results.rs
Executable file
21
azalea-brigadier/src/parse_results.rs
Executable 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()
|
||||||
|
}
|
||||||
|
}
|
274
azalea-brigadier/src/string_reader.rs
Executable file
274
azalea-brigadier/src/string_reader.rs
Executable 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(())
|
||||||
|
}
|
||||||
|
}
|
259
azalea-brigadier/src/tree/mod.rs
Executable file
259
azalea-brigadier/src/tree/mod.rs
Executable 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> {}
|
0
azalea-brigadier/tests/arguments/bool_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/bool_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/double_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/double_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/float_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/float_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/integer_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/integer_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/long_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/long_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/string_argument_type_test.rs
Executable file
0
azalea-brigadier/tests/arguments/string_argument_type_test.rs
Executable file
75
azalea-brigadier/tests/builder/argument_builder_test.rs
Executable file
75
azalea-brigadier/tests/builder/argument_builder_test.rs
Executable 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;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
0
azalea-brigadier/tests/builder/literal_argument_builder_test.rs
Executable file
0
azalea-brigadier/tests/builder/literal_argument_builder_test.rs
Executable file
0
azalea-brigadier/tests/builder/required_argument_builder_test.rs
Executable file
0
azalea-brigadier/tests/builder/required_argument_builder_test.rs
Executable file
410
azalea-brigadier/tests/command_dispatcher_test.rs
Executable file
410
azalea-brigadier/tests/command_dispatcher_test.rs
Executable 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)
|
||||||
|
}
|
1
azalea-brigadier/tests/command_dispatcher_usages_test.rs
Executable file
1
azalea-brigadier/tests/command_dispatcher_usages_test.rs
Executable file
|
@ -0,0 +1 @@
|
||||||
|
|
1
azalea-brigadier/tests/command_suggestions_test.rs
Executable file
1
azalea-brigadier/tests/command_suggestions_test.rs
Executable file
|
@ -0,0 +1 @@
|
||||||
|
|
0
azalea-brigadier/tests/context/command_context_test.rs
Executable file
0
azalea-brigadier/tests/context/command_context_test.rs
Executable file
0
azalea-brigadier/tests/context/parsed_argument_test.rs
Executable file
0
azalea-brigadier/tests/context/parsed_argument_test.rs
Executable file
612
azalea-brigadier/tests/string_reader_test.rs
Executable file
612
azalea-brigadier/tests/string_reader_test.rs
Executable 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));
|
||||||
|
}
|
||||||
|
}
|
0
azalea-brigadier/tests/suggestion/suggestion_test.rs
Executable file
0
azalea-brigadier/tests/suggestion/suggestion_test.rs
Executable file
0
azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
Executable file
0
azalea-brigadier/tests/suggestion/suggestions_builder_test.rs
Executable file
0
azalea-brigadier/tests/suggestion/suggestions_test.rs
Executable file
0
azalea-brigadier/tests/suggestion/suggestions_test.rs
Executable file
0
azalea-brigadier/tests/tree/abstract_command_node_test.rs
Executable file
0
azalea-brigadier/tests/tree/abstract_command_node_test.rs
Executable file
0
azalea-brigadier/tests/tree/argument_command_node_test.rs
Executable file
0
azalea-brigadier/tests/tree/argument_command_node_test.rs
Executable file
0
azalea-brigadier/tests/tree/literal_command_node_test.rs
Executable file
0
azalea-brigadier/tests/tree/literal_command_node_test.rs
Executable file
0
azalea-brigadier/tests/tree/root_command_node_test.rs
Executable file
0
azalea-brigadier/tests/tree/root_command_node_test.rs
Executable file
0
azalea-chat/Cargo.toml
Normal file → Executable file
0
azalea-chat/Cargo.toml
Normal file → Executable file
3
azalea-chat/README.md
Executable file
3
azalea-chat/README.md
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
# Azalea Chat
|
||||||
|
|
||||||
|
Parse Minecraft chat messages.
|
0
azalea-chat/src/base_component.rs
Normal file → Executable file
0
azalea-chat/src/base_component.rs
Normal file → Executable file
0
azalea-chat/src/component.rs
Normal file → Executable file
0
azalea-chat/src/component.rs
Normal file → Executable file
0
azalea-chat/src/events.rs
Normal file → Executable file
0
azalea-chat/src/events.rs
Normal file → Executable file
0
azalea-chat/src/lib.rs
Normal file → Executable file
0
azalea-chat/src/lib.rs
Normal file → Executable file
0
azalea-chat/src/style.rs
Normal file → Executable file
0
azalea-chat/src/style.rs
Normal file → Executable file
0
azalea-chat/src/text_component.rs
Normal file → Executable file
0
azalea-chat/src/text_component.rs
Normal file → Executable file
0
azalea-chat/src/translatable_component.rs
Normal file → Executable file
0
azalea-chat/src/translatable_component.rs
Normal file → Executable file
0
azalea-chat/tests/integration_test.rs
Normal file → Executable file
0
azalea-chat/tests/integration_test.rs
Normal file → Executable file
0
azalea-client/Cargo.toml
Normal file → Executable file
0
azalea-client/Cargo.toml
Normal file → Executable file
3
azalea-client/README.md
Executable file
3
azalea-client/README.md
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
# Azalea Client
|
||||||
|
|
||||||
|
A lower level Minecraft bot library.
|
43
azalea-client/src/connect.rs
Normal file → Executable file
43
azalea-client/src/connect.rs
Normal file → Executable file
|
@ -49,10 +49,18 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> {
|
||||||
println!("Got profile {:?}", p.game_profile);
|
println!("Got profile {:?}", p.game_profile);
|
||||||
break conn.game();
|
break conn.game();
|
||||||
}
|
}
|
||||||
_ => panic!("unhandled packet"),
|
LoginPacket::ServerboundHelloPacket(p) => {
|
||||||
|
println!("Got hello {:?}", p);
|
||||||
|
}
|
||||||
|
LoginPacket::ClientboundLoginDisconnectPacket(p) => {
|
||||||
|
println!("Got disconnect {:?}", p);
|
||||||
|
}
|
||||||
|
LoginPacket::ClientboundCustomQueryPacket(p) => {
|
||||||
|
println!("Got custom query {:?}", p);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error: {:?}", e);
|
panic!("Error: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -65,13 +73,30 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> {
|
||||||
GamePacket::ClientboundLoginPacket(p) => {
|
GamePacket::ClientboundLoginPacket(p) => {
|
||||||
println!("Got login packet {:?}", p);
|
println!("Got login packet {:?}", p);
|
||||||
}
|
}
|
||||||
// GamePacket::ClientboundKeepAlivePacket(p) => {
|
GamePacket::ClientboundUpdateViewDistancePacket(p) => {
|
||||||
// println!("Got keep alive packet {:?}", p.keep_alive_id);
|
println!("Got view distance packet {:?}", p);
|
||||||
// }
|
}
|
||||||
// GamePacket::ClientboundChatMessagePacket(p) => {
|
GamePacket::ClientboundCustomPayloadPacket(p) => {
|
||||||
// println!("Got chat message packet {:?}", p.message);
|
println!("Got custom payload packet {:?}", p);
|
||||||
// }
|
}
|
||||||
_ => panic!("unhandled packet"),
|
GamePacket::ClientboundChangeDifficultyPacket(p) => {
|
||||||
|
println!("Got difficulty packet {:?}", p);
|
||||||
|
}
|
||||||
|
GamePacket::ClientboundDeclareCommandsPacket(p) => {
|
||||||
|
println!("Got declare commands packet {:?}", p);
|
||||||
|
}
|
||||||
|
GamePacket::ClientboundPlayerAbilitiesPacket(p) => {
|
||||||
|
println!("Got player abilities packet {:?}", p);
|
||||||
|
}
|
||||||
|
GamePacket::ClientboundSetCarriedItemPacket(p) => {
|
||||||
|
println!("Got set carried item packet {:?}", p);
|
||||||
|
}
|
||||||
|
GamePacket::ClientboundUpdateTagsPacket(p) => {
|
||||||
|
println!("Got update tags packet {:?}", p);
|
||||||
|
}
|
||||||
|
GamePacket::ClientboundDisconnectPacket(p) => {
|
||||||
|
println!("Got login disconnect packet {:?}", p);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
panic!("Error: {:?}", e);
|
panic!("Error: {:?}", e);
|
||||||
|
|
0
azalea-client/src/crypt.rs
Normal file → Executable file
0
azalea-client/src/crypt.rs
Normal file → Executable file
0
azalea-client/src/lib.rs
Normal file → Executable file
0
azalea-client/src/lib.rs
Normal file → Executable file
2
azalea-client/src/ping.rs
Normal file → Executable file
2
azalea-client/src/ping.rs
Normal file → Executable file
|
@ -38,7 +38,7 @@ pub async fn ping_server(
|
||||||
let packet = conn.read().await.unwrap();
|
let packet = conn.read().await.unwrap();
|
||||||
|
|
||||||
match packet {
|
match packet {
|
||||||
StatusPacket::ClientboundStatusResponsePacket(p) => Ok(*p),
|
StatusPacket::ClientboundStatusResponsePacket(p) => Ok(p),
|
||||||
_ => Err("Invalid packet type".to_string()),
|
_ => Err("Invalid packet type".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
0
azalea-core/Cargo.toml
Normal file → Executable file
0
azalea-core/Cargo.toml
Normal file → Executable file
96
azalea-core/src/difficulty.rs
Executable file
96
azalea-core/src/difficulty.rs
Executable file
|
@ -0,0 +1,96 @@
|
||||||
|
use std::fmt::{Debug, Error, Formatter};
|
||||||
|
|
||||||
|
#[derive(Hash, Clone, Debug, PartialEq)]
|
||||||
|
pub enum Difficulty {
|
||||||
|
PEACEFUL,
|
||||||
|
EASY,
|
||||||
|
NORMAL,
|
||||||
|
HARD,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Err {
|
||||||
|
InvalidDifficulty(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Err {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Err::InvalidDifficulty(s) => write!(f, "Invalid difficulty: {}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Difficulty {
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Difficulty::PEACEFUL => "peaceful",
|
||||||
|
Difficulty::EASY => "easy",
|
||||||
|
Difficulty::NORMAL => "normal",
|
||||||
|
Difficulty::HARD => "hard",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_name(name: &str) -> Result<Difficulty, Err> {
|
||||||
|
match name {
|
||||||
|
"peaceful" => Ok(Difficulty::PEACEFUL),
|
||||||
|
"easy" => Ok(Difficulty::EASY),
|
||||||
|
"normal" => Ok(Difficulty::NORMAL),
|
||||||
|
"hard" => Ok(Difficulty::HARD),
|
||||||
|
_ => Err(Err::InvalidDifficulty(name.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn by_id(id: u8) -> Difficulty {
|
||||||
|
match id % 4 {
|
||||||
|
0 => Difficulty::PEACEFUL,
|
||||||
|
1 => Difficulty::EASY,
|
||||||
|
2 => Difficulty::NORMAL,
|
||||||
|
3 => Difficulty::HARD,
|
||||||
|
// this shouldn't be possible because of the modulo, so panicking is fine
|
||||||
|
_ => panic!("Unknown difficulty id: {}", id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Difficulty::PEACEFUL => 0,
|
||||||
|
Difficulty::EASY => 1,
|
||||||
|
Difficulty::NORMAL => 2,
|
||||||
|
Difficulty::HARD => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_difficulty_from_name() {
|
||||||
|
assert_eq!(
|
||||||
|
Difficulty::PEACEFUL,
|
||||||
|
Difficulty::from_name("peaceful").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(Difficulty::EASY, Difficulty::from_name("easy").unwrap());
|
||||||
|
assert_eq!(Difficulty::NORMAL, Difficulty::from_name("normal").unwrap());
|
||||||
|
assert_eq!(Difficulty::HARD, Difficulty::from_name("hard").unwrap());
|
||||||
|
assert!(Difficulty::from_name("invalid").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_difficulty_id() {
|
||||||
|
assert_eq!(0, Difficulty::PEACEFUL.id());
|
||||||
|
assert_eq!(1, Difficulty::EASY.id());
|
||||||
|
assert_eq!(2, Difficulty::NORMAL.id());
|
||||||
|
assert_eq!(3, Difficulty::HARD.id());
|
||||||
|
assert_eq!(4, Difficulty::PEACEFUL.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_difficulty_name() {
|
||||||
|
assert_eq!("peaceful", Difficulty::PEACEFUL.name());
|
||||||
|
assert_eq!("easy", Difficulty::EASY.name());
|
||||||
|
assert_eq!("normal", Difficulty::NORMAL.name());
|
||||||
|
assert_eq!("hard", Difficulty::HARD.name());
|
||||||
|
}
|
||||||
|
}
|
2
azalea-core/src/game_type.rs
Normal file → Executable file
2
azalea-core/src/game_type.rs
Normal file → Executable file
|
@ -1,5 +1,3 @@
|
||||||
use azalea_chat;
|
|
||||||
|
|
||||||
#[derive(Hash, Clone, Debug)]
|
#[derive(Hash, Clone, Debug)]
|
||||||
pub enum GameType {
|
pub enum GameType {
|
||||||
SURVIVAL,
|
SURVIVAL,
|
||||||
|
|
10
azalea-core/src/lib.rs
Normal file → Executable file
10
azalea-core/src/lib.rs
Normal file → Executable file
|
@ -1,14 +1,6 @@
|
||||||
//! Random miscellaneous things like UUIDs that don't deserve their own crate.
|
//! Random miscellaneous things like UUIDs that don't deserve their own crate.
|
||||||
|
|
||||||
|
pub mod difficulty;
|
||||||
pub mod game_type;
|
pub mod game_type;
|
||||||
pub mod resource_location;
|
pub mod resource_location;
|
||||||
pub mod serializable_uuid;
|
pub mod serializable_uuid;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let result = 2 + 2;
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
0
azalea-core/src/resource_location.rs
Normal file → Executable file
0
azalea-core/src/resource_location.rs
Normal file → Executable file
0
azalea-core/src/serializable_uuid.rs
Normal file → Executable file
0
azalea-core/src/serializable_uuid.rs
Normal file → Executable file
5
azalea-nbt/Cargo.toml
Normal file → Executable file
5
azalea-nbt/Cargo.toml
Normal file → Executable file
|
@ -16,11 +16,14 @@ tokio = "^1.15.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = {version = "^0.3.5", features = ["html_reports", "async_tokio"]}
|
criterion = {version = "^0.3.5", features = ["html_reports", "async_tokio"]}
|
||||||
tokio = {version = "^1.15.0", features = ["fs"]}
|
tokio = {version = "^1.15.0", features = ["fs", "io-util", "macros", "rt", "rt-multi-thread"]}
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
debug = true
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
harness = false
|
harness = false
|
||||||
name = "my_benchmark"
|
name = "my_benchmark"
|
||||||
|
|
5
azalea-nbt/README.md
Normal file → Executable file
5
azalea-nbt/README.md
Normal file → Executable file
|
@ -1,6 +1,3 @@
|
||||||
# Azalea NBT
|
# Azalea NBT
|
||||||
|
|
||||||
Deserialize Minecraft NBT. This is somewhat based on [Hermatite NBT](https://github.com/PistonDevelopers/hematite_nbt).
|
A fast NBT serializer and deserializer.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
13
azalea-nbt/benches/my_benchmark.rs
Normal file → Executable file
13
azalea-nbt/benches/my_benchmark.rs
Normal file → Executable file
|
@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Criterion, Throughput};
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, Cursor, Read, Seek, SeekFrom},
|
io::{self, Read, Seek, SeekFrom},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn bench_serialize(filename: &str, c: &mut Criterion) {
|
fn bench_serialize(filename: &str, c: &mut Criterion) {
|
||||||
|
@ -28,12 +28,13 @@ fn bench_serialize(filename: &str, c: &mut Criterion) {
|
||||||
|
|
||||||
group.throughput(Throughput::Bytes(decoded_src.len() as u64));
|
group.throughput(Throughput::Bytes(decoded_src.len() as u64));
|
||||||
|
|
||||||
// idk if this is criterion's fault or rust's fault but the async benchmark doesn't compile
|
|
||||||
// group.bench_function("Decode", |b| {
|
// group.bench_function("Decode", |b| {
|
||||||
// b.to_async(tokio::runtime::Runtime::new().unwrap();).iter(|| async {
|
// b.to_async(tokio::runtime::Runtime::new().unwrap())
|
||||||
// decoded_src_stream.seek(SeekFrom::Start(0)).unwrap();
|
// .iter(|| async {
|
||||||
// Tag::read(&mut decoded_src_stream).await.unwrap();
|
// let mut owned_decoded_src_stream = decoded_src_stream.clone();
|
||||||
// })
|
// owned_decoded_src_stream.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
// Tag::read(&mut owned_decoded_src_stream).await.unwrap();
|
||||||
|
// })
|
||||||
// });
|
// });
|
||||||
|
|
||||||
group.bench_function("Encode", |b| {
|
group.bench_function("Encode", |b| {
|
||||||
|
|
35
azalea-nbt/src/decode.rs
Normal file → Executable file
35
azalea-nbt/src/decode.rs
Normal file → Executable file
|
@ -11,16 +11,17 @@ async fn read_string<R>(stream: &mut R) -> Result<String, Error>
|
||||||
where
|
where
|
||||||
R: AsyncRead + std::marker::Unpin,
|
R: AsyncRead + std::marker::Unpin,
|
||||||
{
|
{
|
||||||
let length = stream.read_u16().await.map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_u16().await?;
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(length as usize);
|
let mut buf = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
buf.push(stream.read_u8().await.map_err(|_| Error::InvalidTag)?);
|
buf.push(stream.read_u8().await?);
|
||||||
}
|
}
|
||||||
String::from_utf8(buf).map_err(|_| Error::InvalidTag)
|
Ok(String::from_utf8(buf)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
|
#[inline]
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
async fn read_known<R>(stream: &mut R, id: u8) -> Result<Tag, Error>
|
async fn read_known<R>(stream: &mut R, id: u8) -> Result<Tag, Error>
|
||||||
where
|
where
|
||||||
|
@ -31,26 +32,26 @@ impl Tag {
|
||||||
// a TAG_Compound, and is not named despite being in a TAG_Compound
|
// a TAG_Compound, and is not named despite being in a TAG_Compound
|
||||||
0 => Tag::End,
|
0 => Tag::End,
|
||||||
// A single signed byte
|
// A single signed byte
|
||||||
1 => Tag::Byte(stream.read_i8().await.map_err(|_| Error::InvalidTag)?),
|
1 => Tag::Byte(stream.read_i8().await?),
|
||||||
// A single signed, big endian 16 bit integer
|
// A single signed, big endian 16 bit integer
|
||||||
2 => Tag::Short(stream.read_i16().await.map_err(|_| Error::InvalidTag)?),
|
2 => Tag::Short(stream.read_i16().await?),
|
||||||
// A single signed, big endian 32 bit integer
|
// A single signed, big endian 32 bit integer
|
||||||
3 => Tag::Int(stream.read_i32().await.map_err(|_| Error::InvalidTag)?),
|
3 => Tag::Int(stream.read_i32().await?),
|
||||||
// A single signed, big endian 64 bit integer
|
// A single signed, big endian 64 bit integer
|
||||||
4 => Tag::Long(stream.read_i64().await.map_err(|_| Error::InvalidTag)?),
|
4 => Tag::Long(stream.read_i64().await?),
|
||||||
// A single, big endian IEEE-754 single-precision floating point
|
// A single, big endian IEEE-754 single-precision floating point
|
||||||
// number (NaN possible)
|
// number (NaN possible)
|
||||||
5 => Tag::Float(stream.read_f32().await.map_err(|_| Error::InvalidTag)?),
|
5 => Tag::Float(stream.read_f32().await?),
|
||||||
// A single, big endian IEEE-754 double-precision floating point
|
// A single, big endian IEEE-754 double-precision floating point
|
||||||
// number (NaN possible)
|
// number (NaN possible)
|
||||||
6 => Tag::Double(stream.read_f64().await.map_err(|_| Error::InvalidTag)?),
|
6 => Tag::Double(stream.read_f64().await?),
|
||||||
// A length-prefixed array of signed bytes. The prefix is a signed
|
// A length-prefixed array of signed bytes. The prefix is a signed
|
||||||
// integer (thus 4 bytes)
|
// integer (thus 4 bytes)
|
||||||
7 => {
|
7 => {
|
||||||
let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_i32().await?;
|
||||||
let mut bytes = Vec::with_capacity(length as usize);
|
let mut bytes = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
bytes.push(stream.read_i8().await.map_err(|_| Error::InvalidTag)?);
|
bytes.push(stream.read_i8().await?);
|
||||||
}
|
}
|
||||||
Tag::ByteArray(bytes)
|
Tag::ByteArray(bytes)
|
||||||
}
|
}
|
||||||
|
@ -67,8 +68,8 @@ impl Tag {
|
||||||
// another reference implementation by Mojang uses 1 instead;
|
// another reference implementation by Mojang uses 1 instead;
|
||||||
// parsers should accept any type if the length is <= 0).
|
// parsers should accept any type if the length is <= 0).
|
||||||
9 => {
|
9 => {
|
||||||
let type_id = stream.read_u8().await.map_err(|_| Error::InvalidTag)?;
|
let type_id = stream.read_u8().await?;
|
||||||
let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_i32().await?;
|
||||||
let mut list = Vec::with_capacity(length as usize);
|
let mut list = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
list.push(Tag::read_known(stream, type_id).await?);
|
list.push(Tag::read_known(stream, type_id).await?);
|
||||||
|
@ -94,20 +95,20 @@ impl Tag {
|
||||||
// signed integer (thus 4 bytes) and indicates the number of 4 byte
|
// signed integer (thus 4 bytes) and indicates the number of 4 byte
|
||||||
// integers.
|
// integers.
|
||||||
11 => {
|
11 => {
|
||||||
let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_i32().await?;
|
||||||
let mut ints = Vec::with_capacity(length as usize);
|
let mut ints = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
ints.push(stream.read_i32().await.map_err(|_| Error::InvalidTag)?);
|
ints.push(stream.read_i32().await?);
|
||||||
}
|
}
|
||||||
Tag::IntArray(ints)
|
Tag::IntArray(ints)
|
||||||
}
|
}
|
||||||
// A length-prefixed array of signed longs. The prefix is a signed
|
// A length-prefixed array of signed longs. The prefix is a signed
|
||||||
// integer (thus 4 bytes) and indicates the number of 8 byte longs.
|
// integer (thus 4 bytes) and indicates the number of 8 byte longs.
|
||||||
12 => {
|
12 => {
|
||||||
let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_i32().await?;
|
||||||
let mut longs = Vec::with_capacity(length as usize);
|
let mut longs = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
longs.push(stream.read_i64().await.map_err(|_| Error::InvalidTag)?);
|
longs.push(stream.read_i64().await?);
|
||||||
}
|
}
|
||||||
Tag::LongArray(longs)
|
Tag::LongArray(longs)
|
||||||
}
|
}
|
||||||
|
|
231
azalea-nbt/src/encode.rs
Normal file → Executable file
231
azalea-nbt/src/encode.rs
Normal file → Executable file
|
@ -2,96 +2,197 @@ use crate::Error;
|
||||||
use crate::Tag;
|
use crate::Tag;
|
||||||
use byteorder::{WriteBytesExt, BE};
|
use byteorder::{WriteBytesExt, BE};
|
||||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
// who needs friends when you've got code that runs in nanoseconds?
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write_string(writer: &mut dyn Write, string: &str) -> Result<(), Error> {
|
fn write_string(writer: &mut dyn Write, string: &str) -> Result<(), Error> {
|
||||||
writer
|
writer.write_i16::<BE>(string.len() as i16)?;
|
||||||
.write_i16::<BE>(string.len() as i16)
|
writer.write_all(string.as_bytes())?;
|
||||||
.map_err(|_| Error::WriteError)?;
|
|
||||||
writer
|
|
||||||
.write_all(string.as_bytes())
|
|
||||||
.map_err(|_| Error::WriteError)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
#[inline]
|
||||||
pub fn write_without_end(&self, writer: &mut dyn Write) -> Result<(), Error> {
|
fn write_compound(
|
||||||
match self {
|
writer: &mut dyn Write,
|
||||||
|
value: &HashMap<String, Tag>,
|
||||||
|
end_tag: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
for (key, tag) in value {
|
||||||
|
match tag {
|
||||||
Tag::End => {}
|
Tag::End => {}
|
||||||
Tag::Byte(value) => writer.write_i8(*value).map_err(|_| Error::WriteError)?,
|
Tag::Byte(value) => {
|
||||||
Tag::Short(value) => writer
|
writer.write_u8(1)?;
|
||||||
.write_i16::<BE>(*value)
|
write_string(writer, key)?;
|
||||||
.map_err(|_| Error::WriteError)?,
|
writer.write_i8(*value)?
|
||||||
Tag::Int(value) => writer
|
}
|
||||||
.write_i32::<BE>(*value)
|
Tag::Short(value) => {
|
||||||
.map_err(|_| Error::WriteError)?,
|
writer.write_u8(2)?;
|
||||||
Tag::Long(value) => writer
|
write_string(writer, key)?;
|
||||||
.write_i64::<BE>(*value)
|
writer.write_i16::<BE>(*value)?
|
||||||
.map_err(|_| Error::WriteError)?,
|
}
|
||||||
Tag::Float(value) => writer
|
Tag::Int(value) => {
|
||||||
.write_f32::<BE>(*value)
|
writer.write_u8(3)?;
|
||||||
.map_err(|_| Error::WriteError)?,
|
write_string(writer, key)?;
|
||||||
Tag::Double(value) => writer
|
writer.write_i32::<BE>(*value)?
|
||||||
.write_f64::<BE>(*value)
|
}
|
||||||
.map_err(|_| Error::WriteError)?,
|
Tag::Long(value) => {
|
||||||
|
writer.write_u8(4)?;
|
||||||
|
write_string(writer, key)?;
|
||||||
|
writer.write_i64::<BE>(*value)?
|
||||||
|
}
|
||||||
|
Tag::Float(value) => {
|
||||||
|
writer.write_u8(5)?;
|
||||||
|
write_string(writer, key)?;
|
||||||
|
writer.write_f32::<BE>(*value)?
|
||||||
|
}
|
||||||
|
Tag::Double(value) => {
|
||||||
|
writer.write_u8(6)?;
|
||||||
|
write_string(writer, key)?;
|
||||||
|
writer.write_f64::<BE>(*value)?
|
||||||
|
}
|
||||||
Tag::ByteArray(value) => {
|
Tag::ByteArray(value) => {
|
||||||
writer
|
writer.write_u8(7)?;
|
||||||
.write_i32::<BE>(value.len() as i32)
|
write_string(writer, key)?;
|
||||||
.map_err(|_| Error::WriteError)?;
|
writer.write_i32::<BE>(value.len() as i32)?;
|
||||||
for &byte in value {
|
for &byte in value {
|
||||||
writer.write_i8(byte).map_err(|_| Error::WriteError)?;
|
writer.write_i8(byte)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tag::String(value) => {
|
Tag::String(value) => {
|
||||||
write_string(writer, value)?;
|
writer.write_u8(8)?;
|
||||||
|
write_string(writer, key)?;
|
||||||
|
write_string(writer, value)?
|
||||||
}
|
}
|
||||||
Tag::List(value) => {
|
Tag::List(value) => {
|
||||||
// we just get the type from the first item, or default the type to END
|
writer.write_u8(9)?;
|
||||||
if value.is_empty() {
|
write_string(writer, key)?;
|
||||||
writer.write_i8(0).map_err(|_| Error::WriteError)?;
|
write_list(writer, value)?
|
||||||
writer.write_i32::<BE>(0).map_err(|_| Error::WriteError)?;
|
|
||||||
} else {
|
|
||||||
let type_id = value[0].id();
|
|
||||||
writer.write_u8(type_id).map_err(|_| Error::WriteError)?;
|
|
||||||
writer
|
|
||||||
.write_i32::<BE>(value.len() as i32)
|
|
||||||
.map_err(|_| Error::WriteError)?;
|
|
||||||
for tag in value {
|
|
||||||
tag.write_without_end(writer)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Tag::Compound(value) => {
|
Tag::Compound(value) => {
|
||||||
for (key, tag) in value {
|
writer.write_u8(10)?;
|
||||||
writer.write_u8(tag.id()).map_err(|_| Error::WriteError)?;
|
write_string(writer, key)?;
|
||||||
write_string(writer, key)?;
|
write_compound(writer, value, true)?
|
||||||
tag.write_without_end(writer)?;
|
|
||||||
}
|
|
||||||
writer
|
|
||||||
.write_u8(Tag::End.id())
|
|
||||||
.map_err(|_| Error::WriteError)?;
|
|
||||||
}
|
}
|
||||||
Tag::IntArray(value) => {
|
Tag::IntArray(value) => {
|
||||||
writer
|
writer.write_u8(11)?;
|
||||||
.write_i32::<BE>(value.len() as i32)
|
write_string(writer, key)?;
|
||||||
.map_err(|_| Error::WriteError)?;
|
writer.write_i32::<BE>(value.len() as i32)?;
|
||||||
for &int in value {
|
for &int in value {
|
||||||
writer.write_i32::<BE>(int).map_err(|_| Error::WriteError)?;
|
writer.write_i32::<BE>(int)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tag::LongArray(value) => {
|
Tag::LongArray(value) => {
|
||||||
writer
|
writer.write_u8(12)?;
|
||||||
.write_i32::<BE>(value.len() as i32)
|
write_string(writer, key)?;
|
||||||
.map_err(|_| Error::WriteError)?;
|
writer.write_i32::<BE>(value.len() as i32)?;
|
||||||
for &long in value {
|
for &long in value {
|
||||||
writer
|
writer.write_i64::<BE>(long)?;
|
||||||
.write_i64::<BE>(long)
|
|
||||||
.map_err(|_| Error::WriteError)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if end_tag {
|
||||||
|
writer.write_u8(Tag::End.id())?;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn write_list(writer: &mut dyn Write, value: &[Tag]) -> Result<(), Error> {
|
||||||
|
// we just get the type from the first item, or default the type to END
|
||||||
|
if value.is_empty() {
|
||||||
|
writer.write_all(&[0; 5])?;
|
||||||
|
} else {
|
||||||
|
let first_tag = &value[0];
|
||||||
|
writer.write_u8(first_tag.id())?;
|
||||||
|
writer.write_i32::<BE>(value.len() as i32)?;
|
||||||
|
match first_tag {
|
||||||
|
Tag::Int(_) => {
|
||||||
|
for tag in value {
|
||||||
|
writer.write_i32::<BE>(
|
||||||
|
*tag.as_int().expect("List of Int should only contains Int"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag::String(_) => {
|
||||||
|
for tag in value {
|
||||||
|
write_string(
|
||||||
|
writer,
|
||||||
|
tag.as_string()
|
||||||
|
.expect("List of String should only contain String"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag::Compound(_) => {
|
||||||
|
for tag in value {
|
||||||
|
write_compound(
|
||||||
|
writer,
|
||||||
|
tag.as_compound()
|
||||||
|
.expect("List of Compound should only contain Compound"),
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
for tag in value {
|
||||||
|
tag.write_without_end(writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn write_bytearray(writer: &mut dyn Write, value: &Vec<i8>) -> Result<(), Error> {
|
||||||
|
writer.write_i32::<BE>(value.len() as i32)?;
|
||||||
|
for &byte in value {
|
||||||
|
writer.write_i8(byte)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn write_intarray(writer: &mut dyn Write, value: &Vec<i32>) -> Result<(), Error> {
|
||||||
|
writer.write_i32::<BE>(value.len() as i32)?;
|
||||||
|
for &int in value {
|
||||||
|
writer.write_i32::<BE>(int)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn write_longarray(writer: &mut dyn Write, value: &Vec<i64>) -> Result<(), Error> {
|
||||||
|
writer.write_i32::<BE>(value.len() as i32)?;
|
||||||
|
for &long in value {
|
||||||
|
writer.write_i64::<BE>(long)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
#[inline]
|
||||||
|
pub fn write_without_end(&self, writer: &mut dyn Write) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Tag::End => {}
|
||||||
|
Tag::Byte(value) => writer.write_i8(*value)?,
|
||||||
|
Tag::Short(value) => writer.write_i16::<BE>(*value)?,
|
||||||
|
Tag::Int(value) => writer.write_i32::<BE>(*value)?,
|
||||||
|
Tag::Long(value) => writer.write_i64::<BE>(*value)?,
|
||||||
|
Tag::Float(value) => writer.write_f32::<BE>(*value)?,
|
||||||
|
Tag::Double(value) => writer.write_f64::<BE>(*value)?,
|
||||||
|
Tag::ByteArray(value) => write_bytearray(writer, value)?,
|
||||||
|
Tag::String(value) => write_string(writer, value)?,
|
||||||
|
Tag::List(value) => write_list(writer, value)?,
|
||||||
|
Tag::Compound(value) => write_compound(writer, value, true)?,
|
||||||
|
Tag::IntArray(value) => write_intarray(writer, value)?,
|
||||||
|
Tag::LongArray(value) => write_longarray(writer, value)?,
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -99,11 +200,7 @@ impl Tag {
|
||||||
pub fn write(&self, writer: &mut impl Write) -> Result<(), Error> {
|
pub fn write(&self, writer: &mut impl Write) -> Result<(), Error> {
|
||||||
match self {
|
match self {
|
||||||
Tag::Compound(value) => {
|
Tag::Compound(value) => {
|
||||||
for (key, tag) in value {
|
write_compound(writer, value, false)?;
|
||||||
writer.write_u8(tag.id()).map_err(|_| Error::WriteError)?;
|
|
||||||
write_string(writer, key)?;
|
|
||||||
tag.write_without_end(writer)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err(Error::InvalidTag),
|
_ => Err(Error::InvalidTag),
|
||||||
|
|
11
azalea-nbt/src/error.rs
Normal file → Executable file
11
azalea-nbt/src/error.rs
Normal file → Executable file
|
@ -14,3 +14,14 @@ impl std::fmt::Display for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(_: std::io::Error) -> Self {
|
||||||
|
Error::WriteError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<std::string::FromUtf8Error> for Error {
|
||||||
|
fn from(_: std::string::FromUtf8Error) -> Self {
|
||||||
|
Error::WriteError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
0
azalea-nbt/src/lib.rs
Normal file → Executable file
0
azalea-nbt/src/lib.rs
Normal file → Executable file
108
azalea-nbt/src/tag.rs
Normal file → Executable file
108
azalea-nbt/src/tag.rs
Normal file → Executable file
|
@ -36,4 +36,112 @@ impl Tag {
|
||||||
Tag::LongArray(_) => 12,
|
Tag::LongArray(_) => 12,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_byte(&self) -> Option<&i8> {
|
||||||
|
if let Tag::Byte(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_short(&self) -> Option<&i16> {
|
||||||
|
if let Tag::Short(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_int(&self) -> Option<&i32> {
|
||||||
|
if let Tag::Int(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_long(&self) -> Option<&i64> {
|
||||||
|
if let Tag::Long(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_float(&self) -> Option<&f32> {
|
||||||
|
if let Tag::Float(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_double(&self) -> Option<&f64> {
|
||||||
|
if let Tag::Double(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_string(&self) -> Option<&str> {
|
||||||
|
if let Tag::String(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_compound(&self) -> Option<&HashMap<String, Tag>> {
|
||||||
|
if let Tag::Compound(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_bytearray(&self) -> Option<&Vec<i8>> {
|
||||||
|
if let Tag::ByteArray(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_intarray(&self) -> Option<&Vec<i32>> {
|
||||||
|
if let Tag::IntArray(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_longarray(&self) -> Option<&Vec<i64>> {
|
||||||
|
if let Tag::LongArray(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_list(&self) -> Option<&[Tag]> {
|
||||||
|
if let Tag::List(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
0
azalea-nbt/tests/bigtest.nbt
Normal file → Executable file
0
azalea-nbt/tests/bigtest.nbt
Normal file → Executable file
0
azalea-nbt/tests/complex_player.dat
Normal file → Executable file
0
azalea-nbt/tests/complex_player.dat
Normal file → Executable file
0
azalea-nbt/tests/hello_world.nbt
Normal file → Executable file
0
azalea-nbt/tests/hello_world.nbt
Normal file → Executable file
0
azalea-nbt/tests/inttest.nbt
Normal file → Executable file
0
azalea-nbt/tests/inttest.nbt
Normal file → Executable file
0
azalea-nbt/tests/level.dat
Normal file → Executable file
0
azalea-nbt/tests/level.dat
Normal file → Executable file
0
azalea-nbt/tests/simple_player.dat
Normal file → Executable file
0
azalea-nbt/tests/simple_player.dat
Normal file → Executable file
0
azalea-nbt/tests/stringtest.nbt
Normal file → Executable file
0
azalea-nbt/tests/stringtest.nbt
Normal file → Executable file
0
azalea-nbt/tests/tests.rs
Normal file → Executable file
0
azalea-nbt/tests/tests.rs
Normal file → Executable file
4
azalea-protocol/Cargo.toml
Normal file → Executable file
4
azalea-protocol/Cargo.toml
Normal file → Executable file
|
@ -10,11 +10,15 @@ async-compression = {version = "^0.3.8", features = ["tokio", "zlib"]}
|
||||||
async-recursion = "^0.3.2"
|
async-recursion = "^0.3.2"
|
||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
azalea-auth = {path = "../azalea-auth"}
|
azalea-auth = {path = "../azalea-auth"}
|
||||||
|
azalea-brigadier = {path = "../azalea-brigadier"}
|
||||||
azalea-chat = {path = "../azalea-chat"}
|
azalea-chat = {path = "../azalea-chat"}
|
||||||
azalea-core = {path = "../azalea-core"}
|
azalea-core = {path = "../azalea-core"}
|
||||||
azalea-nbt = {path = "../azalea-nbt"}
|
azalea-nbt = {path = "../azalea-nbt"}
|
||||||
byteorder = "^1.4.3"
|
byteorder = "^1.4.3"
|
||||||
bytes = "^1.1.0"
|
bytes = "^1.1.0"
|
||||||
|
num-derive = "^0.3.3"
|
||||||
|
num-traits = "^0.2.14"
|
||||||
|
packet-macros = {path = "./packet-macros"}
|
||||||
serde = {version = "1.0.130", features = ["serde_derive"]}
|
serde = {version = "1.0.130", features = ["serde_derive"]}
|
||||||
serde_json = "^1.0.72"
|
serde_json = "^1.0.72"
|
||||||
thiserror = "^1.0.30"
|
thiserror = "^1.0.30"
|
||||||
|
|
3
azalea-protocol/README.md
Executable file
3
azalea-protocol/README.md
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
# Azalea Protocol
|
||||||
|
|
||||||
|
Sent and receive Minecraft packets.
|
13
azalea-protocol/packet-macros/Cargo.toml
Executable file
13
azalea-protocol/packet-macros/Cargo.toml
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
edition = "2021"
|
||||||
|
name = "packet-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "^1.0.36"
|
||||||
|
quote = "^1.0.10"
|
||||||
|
syn = "^1.0.82"
|
300
azalea-protocol/packet-macros/src/lib.rs
Executable file
300
azalea-protocol/packet-macros/src/lib.rs
Executable file
|
@ -0,0 +1,300 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::{
|
||||||
|
self, braced,
|
||||||
|
parse::{Parse, ParseStream, Result},
|
||||||
|
parse_macro_input, DeriveInput, FieldsNamed, Ident, LitInt, Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream {
|
||||||
|
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
|
||||||
|
|
||||||
|
let fields = match data {
|
||||||
|
syn::Data::Struct(syn::DataStruct { fields, .. }) => fields,
|
||||||
|
_ => panic!("#[derive(*Packet)] can only be used on structs"),
|
||||||
|
};
|
||||||
|
let FieldsNamed { named, .. } = match fields {
|
||||||
|
syn::Fields::Named(f) => f,
|
||||||
|
_ => panic!("#[derive(*Packet)] can only be used on structs with named fields"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let write_fields = named
|
||||||
|
.iter()
|
||||||
|
.map(|f| {
|
||||||
|
let field_name = &f.ident;
|
||||||
|
let field_type = &f.ty;
|
||||||
|
// do a different buf.write_* for each field depending on the type
|
||||||
|
// if it's a string, use buf.write_string
|
||||||
|
match field_type {
|
||||||
|
syn::Type::Path(_) => {
|
||||||
|
if f.attrs.iter().any(|attr| attr.path.is_ident("varint")) {
|
||||||
|
quote! {
|
||||||
|
crate::mc_buf::McBufVarintWritable::varint_write_into(&self.#field_name, buf)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
crate::mc_buf::McBufWritable::write_into(&self.#field_name, buf)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!(
|
||||||
|
"Error writing field {}: {}",
|
||||||
|
field_name.clone().unwrap(),
|
||||||
|
field_type.to_token_stream()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let read_fields = named
|
||||||
|
.iter()
|
||||||
|
.map(|f| {
|
||||||
|
let field_name = &f.ident;
|
||||||
|
let field_type = &f.ty;
|
||||||
|
// do a different buf.write_* for each field depending on the type
|
||||||
|
// if it's a string, use buf.write_string
|
||||||
|
match field_type {
|
||||||
|
syn::Type::Path(_) => {
|
||||||
|
if f.attrs.iter().any(|a| a.path.is_ident("varint")) {
|
||||||
|
quote! {
|
||||||
|
let #field_name = crate::mc_buf::McBufVarintReadable::varint_read_into(buf).await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
let #field_name = crate::mc_buf::McBufReadable::read_into(buf).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!(
|
||||||
|
"Error reading field {}: {}",
|
||||||
|
field_name.clone().unwrap(),
|
||||||
|
field_type.to_token_stream()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let read_field_names = named.iter().map(|f| &f.ident).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl #ident {
|
||||||
|
pub fn get(self) -> #state {
|
||||||
|
#state::#ident(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
|
||||||
|
#(#write_fields)*
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
|
||||||
|
buf: &mut T,
|
||||||
|
) -> Result<#state, String> {
|
||||||
|
#(#read_fields)*
|
||||||
|
Ok(#ident {
|
||||||
|
#(#read_field_names: #read_field_names),*
|
||||||
|
}.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(GamePacket, attributes(varint))]
|
||||||
|
pub fn derive_game_packet(input: TokenStream) -> TokenStream {
|
||||||
|
as_packet_derive(input, quote! {crate::packets::game::GamePacket})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(HandshakePacket, attributes(varint))]
|
||||||
|
pub fn derive_handshake_packet(input: TokenStream) -> TokenStream {
|
||||||
|
as_packet_derive(input, quote! {crate::packets::handshake::HandshakePacket})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(LoginPacket, attributes(varint))]
|
||||||
|
pub fn derive_login_packet(input: TokenStream) -> TokenStream {
|
||||||
|
as_packet_derive(input, quote! {crate::packets::login::LoginPacket})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(StatusPacket, attributes(varint))]
|
||||||
|
pub fn derive_status_packet(input: TokenStream) -> TokenStream {
|
||||||
|
as_packet_derive(input, quote! {crate::packets::status::StatusPacket})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PacketIdPair {
|
||||||
|
id: u32,
|
||||||
|
module: Ident,
|
||||||
|
name: Ident,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PacketIdMap {
|
||||||
|
packets: Vec<PacketIdPair>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for PacketIdMap {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let mut packets = vec![];
|
||||||
|
loop {
|
||||||
|
// 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
|
||||||
|
// 0x0e
|
||||||
|
let packet_id: LitInt = match input.parse() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
let packet_id = packet_id.base10_parse::<u32>()?;
|
||||||
|
// :
|
||||||
|
input.parse::<Token![:]>()?;
|
||||||
|
// clientbound_change_difficulty_packet
|
||||||
|
let module: Ident = input.parse()?;
|
||||||
|
// ::
|
||||||
|
input.parse::<Token![::]>()?;
|
||||||
|
// ClientboundChangeDifficultyPacket
|
||||||
|
let name: Ident = input.parse()?;
|
||||||
|
|
||||||
|
packets.push(PacketIdPair {
|
||||||
|
id: packet_id,
|
||||||
|
module,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if input.parse::<Token![,]>().is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PacketIdMap { packets })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DeclareStatePackets {
|
||||||
|
name: Ident,
|
||||||
|
serverbound: PacketIdMap,
|
||||||
|
clientbound: PacketIdMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for DeclareStatePackets {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let name = input.parse()?;
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
|
||||||
|
let serverbound_token: Ident = input.parse()?;
|
||||||
|
if serverbound_token != "Serverbound" {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
serverbound_token.span(),
|
||||||
|
"Expected `Serverbound`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
input.parse::<Token![=>]>()?;
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
let serverbound = content.parse()?;
|
||||||
|
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
|
||||||
|
let clientbound_token: Ident = input.parse()?;
|
||||||
|
if clientbound_token != "Clientbound" {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
clientbound_token.span(),
|
||||||
|
"Expected `Clientbound`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
input.parse::<Token![=>]>()?;
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
let clientbound = content.parse()?;
|
||||||
|
|
||||||
|
Ok(DeclareStatePackets {
|
||||||
|
name,
|
||||||
|
serverbound,
|
||||||
|
clientbound,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn declare_state_packets(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeclareStatePackets);
|
||||||
|
|
||||||
|
let state_name = input.name;
|
||||||
|
let state_name_litstr = syn::LitStr::new(&state_name.to_string(), state_name.span());
|
||||||
|
|
||||||
|
let mut enum_contents = quote!();
|
||||||
|
let mut id_match_contents = quote!();
|
||||||
|
let mut write_match_contents = quote!();
|
||||||
|
let mut serverbound_read_match_contents = quote!();
|
||||||
|
let mut clientbound_read_match_contents = quote!();
|
||||||
|
for PacketIdPair { id, module, name } in input.serverbound.packets {
|
||||||
|
enum_contents.extend(quote! {
|
||||||
|
#name(#module::#name),
|
||||||
|
});
|
||||||
|
id_match_contents.extend(quote! {
|
||||||
|
#state_name::#name(_packet) => #id,
|
||||||
|
});
|
||||||
|
write_match_contents.extend(quote! {
|
||||||
|
#state_name::#name(packet) => packet.write(buf),
|
||||||
|
});
|
||||||
|
serverbound_read_match_contents.extend(quote! {
|
||||||
|
#id => #module::#name::read(buf).await?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for PacketIdPair { id, module, name } in input.clientbound.packets {
|
||||||
|
enum_contents.extend(quote! {
|
||||||
|
#name(#module::#name),
|
||||||
|
});
|
||||||
|
id_match_contents.extend(quote! {
|
||||||
|
#state_name::#name(_packet) => #id,
|
||||||
|
});
|
||||||
|
write_match_contents.extend(quote! {
|
||||||
|
#state_name::#name(packet) => packet.write(buf),
|
||||||
|
});
|
||||||
|
clientbound_read_match_contents.extend(quote! {
|
||||||
|
#id => #module::#name::read(buf).await?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum #state_name
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
#enum_contents
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl crate::packets::ProtocolPacket for #state_name {
|
||||||
|
fn id(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
#id_match_contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
|
||||||
|
match self {
|
||||||
|
#write_match_contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a packet by its id, ConnectionProtocol, and flow
|
||||||
|
async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
|
||||||
|
id: u32,
|
||||||
|
flow: &crate::connect::PacketFlow,
|
||||||
|
buf: &mut T,
|
||||||
|
) -> Result<#state_name, String>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Ok(match flow {
|
||||||
|
crate::connect::PacketFlow::ServerToClient => match id {
|
||||||
|
#clientbound_read_match_contents
|
||||||
|
_ => panic!("Unknown ServerToClient {} packet id: {}", #state_name_litstr, id),
|
||||||
|
},
|
||||||
|
crate::connect::PacketFlow::ClientToServer => match id {
|
||||||
|
#serverbound_read_match_contents
|
||||||
|
_ => return Err(format!("Unknown ClientToServer {} packet id: {}", #state_name_litstr, id)),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue