diff --git a/azalea-brigadier/README.md b/azalea-brigadier/README.md new file mode 100644 index 00000000..92c0d27e --- /dev/null +++ b/azalea-brigadier/README.md @@ -0,0 +1,3 @@ +# Azalea Brigadier + +A Rustier port of Mojang's [Brigadier](https://github.com/Mojang/brigadier) command parsing and dispatching library. diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs index 4dc97ee0..4c48d6bb 100644 --- a/azalea-brigadier/src/arguments/argument_type.rs +++ b/azalea-brigadier/src/arguments/argument_type.rs @@ -1,3 +1,10 @@ +use crate::{ + context::command_context::CommandContext, + exceptions::command_syntax_exception::CommandSyntaxException, + string_reader::StringReader, + suggestion::{suggestions::Suggestions, suggestions_builder::SuggestionsBuilder}, +}; + pub trait ArgumentType { // T parse(StringReader reader) throws CommandSyntaxException; @@ -9,12 +16,12 @@ pub trait ArgumentType { // return Collections.emptyList(); // } - fn parse(reader: &mut StringReader) -> Result; + fn parse(reader: &mut StringReader) -> Result; fn list_suggestions( context: &CommandContext, builder: &mut SuggestionsBuilder, - ) -> Result; + ) -> Result; fn get_examples() -> Vec; } diff --git a/azalea-brigadier/src/arguments/mod.rs b/azalea-brigadier/src/arguments/mod.rs index 18d01d88..50b0f09b 100644 --- a/azalea-brigadier/src/arguments/mod.rs +++ b/azalea-brigadier/src/arguments/mod.rs @@ -1 +1 @@ -mod argument_type; +pub mod argument_type; diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs index e69de29b..ddbb447e 100644 --- a/azalea-brigadier/src/context/command_context.rs +++ b/azalea-brigadier/src/context/command_context.rs @@ -0,0 +1,3 @@ +pub struct CommandContext { + source: S, +} diff --git a/azalea-brigadier/src/context/mod.rs b/azalea-brigadier/src/context/mod.rs index e69de29b..196d7c5b 100644 --- a/azalea-brigadier/src/context/mod.rs +++ b/azalea-brigadier/src/context/mod.rs @@ -0,0 +1,6 @@ +pub mod command_context; +pub mod command_context_builder; +pub mod parsed_argument; +pub mod parsed_command_node; +pub mod string_range; +pub mod suggestion_context; diff --git a/azalea-brigadier/src/exceptions/builtin_exceptions.rs b/azalea-brigadier/src/exceptions/builtin_exceptions.rs index e69de29b..fcca49cd 100644 --- a/azalea-brigadier/src/exceptions/builtin_exceptions.rs +++ b/azalea-brigadier/src/exceptions/builtin_exceptions.rs @@ -0,0 +1,158 @@ +use std::fmt; + +use crate::{immutable_string_reader::ImmutableStringReader, message::Message}; + +use super::command_syntax_exception::CommandSyntaxException; + +pub enum BuiltInExceptions { + DoubleTooSmall { found: usize, min: usize }, + DoubleTooBig { found: usize, max: usize }, + + FloatTooSmall { found: usize, min: usize }, + FloatTooBig { found: usize, max: usize }, + + IntegerTooSmall { found: usize, min: usize }, + IntegerTooBig { found: usize, max: usize }, + + LONGTooSmall { found: usize, min: usize }, + LONGTooBig { found: usize, max: usize }, + + 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 }, + + ReaderUnknownCommand, + ReaderUnknownArgument, + DusoatcgerExpectedArgumentSeparator, + 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::ReaderUnknownCommand => { + write!(f, "Unknown command") + } + BuiltInExceptions::ReaderUnknownArgument => { + write!(f, "Incorrect argument for command") + } + BuiltInExceptions::DusoatcgerExpectedArgumentSeparator => { + 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: &dyn ImmutableStringReader) -> CommandSyntaxException { + let message = Message::from(format!("{:?}", self)); + CommandSyntaxException::new(self, message, reader.string(), reader.cursor()) + } +} diff --git a/azalea-brigadier/src/exceptions/command_exception_type.rs b/azalea-brigadier/src/exceptions/command_exception_type.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/azalea-brigadier/src/exceptions/command_syntax_exception.rs b/azalea-brigadier/src/exceptions/command_syntax_exception.rs index e69de29b..b9fbea45 100644 --- a/azalea-brigadier/src/exceptions/command_syntax_exception.rs +++ b/azalea-brigadier/src/exceptions/command_syntax_exception.rs @@ -0,0 +1,82 @@ +use std::{cmp, rc::Rc}; + +use super::builtin_exceptions::BuiltInExceptions; +use crate::message::Message; + +pub struct CommandSyntaxException { + type_: BuiltInExceptions, + message: Message, + input: Option, + cursor: Option, +} + +const CONTEXT_AMOUNT: usize = 10; +const ENABLE_COMMAND_STACK_TRACES: bool = true; + +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 { + 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 - CONTEXT_AMOUNT)..cursor]); + builder.push_str("<--[HERE]"); + + return Some(builder); + } + } + None + } + + pub fn get_type(&self) -> &BuiltInExceptions { + &self.type_ + } + + pub fn input(&self) -> String { + self.input() + } + + pub fn cursor(&self) -> Option { + self.cursor + } +} diff --git a/azalea-brigadier/src/exceptions/dynamic2_command_exception_type.rs b/azalea-brigadier/src/exceptions/dynamic2_command_exception_type.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/azalea-brigadier/src/exceptions/dynamic3_command_exception_type.rs b/azalea-brigadier/src/exceptions/dynamic3_command_exception_type.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/azalea-brigadier/src/exceptions/dynamic4_command_exception_type.rs b/azalea-brigadier/src/exceptions/dynamic4_command_exception_type.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/azalea-brigadier/src/exceptions/dynamicN_command_exception_type.rs b/azalea-brigadier/src/exceptions/dynamicN_command_exception_type.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/azalea-brigadier/src/exceptions/dynamic_command_exception_type.rs b/azalea-brigadier/src/exceptions/dynamic_command_exception_type.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/azalea-brigadier/src/exceptions/mod.rs b/azalea-brigadier/src/exceptions/mod.rs index e69de29b..4a82b01e 100644 --- a/azalea-brigadier/src/exceptions/mod.rs +++ b/azalea-brigadier/src/exceptions/mod.rs @@ -0,0 +1,3 @@ +pub mod builtin_exception_provider; +pub mod builtin_exceptions; +pub mod command_syntax_exception; diff --git a/azalea-brigadier/src/exceptions/simple_command_exception_type.rs b/azalea-brigadier/src/exceptions/simple_command_exception_type.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/azalea-brigadier/src/immutable_string_reader.rs b/azalea-brigadier/src/immutable_string_reader.rs index 2e067ace..53531c64 100644 --- a/azalea-brigadier/src/immutable_string_reader.rs +++ b/azalea-brigadier/src/immutable_string_reader.rs @@ -1,6 +1,8 @@ pub trait ImmutableStringReader { + fn string(&self) -> &str; fn remaining_length(&self) -> usize; fn total_length(&self) -> usize; + fn cursor(&self) -> usize; fn get_read(&self) -> &str; fn remaining(&self) -> &str; fn can_read_length(&self, length: usize) -> bool; diff --git a/azalea-brigadier/src/message.rs b/azalea-brigadier/src/message.rs index 8b137891..71d0b178 100644 --- a/azalea-brigadier/src/message.rs +++ b/azalea-brigadier/src/message.rs @@ -1 +1,15 @@ +use std::rc::Rc; +pub struct Message(Rc); + +impl Message { + pub fn string(&self) -> String { + self.0.to_string() + } +} + +impl From for Message { + fn from(s: String) -> Self { + Self(Rc::new(s)) + } +} diff --git a/azalea-brigadier/src/string_reader.rs b/azalea-brigadier/src/string_reader.rs index 376cc711..1119403a 100644 --- a/azalea-brigadier/src/string_reader.rs +++ b/azalea-brigadier/src/string_reader.rs @@ -1,23 +1,34 @@ -use crate::immutable_string_reader::ImmutableStringReader; +use crate::{ + exceptions::{ + builtin_exceptions::BuiltInExceptions, command_syntax_exception::CommandSyntaxException, + }, + immutable_string_reader::ImmutableStringReader, +}; use std::str::FromStr; #[derive(Clone)] -struct StringReader<'a> { - pub string: &'a str, - pub cursor: usize, +pub struct StringReader<'a> { + string: &'a str, + cursor: usize, } const SYNTAX_ESCAPE: char = '\\'; const SYNTAX_DOUBLE_QUOTE: char = '"'; const SYNTAX_SINGLE_QUOTE: char = '\''; -impl<'a> From<&'a str> for &StringReader<'a> { - fn from(string: &'a str) -> &StringReader<'a> { - &StringReader { string, cursor: 0 } +// impl<'a> From<&'a str> for &StringReader<'a> {} + +impl StringReader<'_> { + fn from(string: &str) -> StringReader { + StringReader { string, cursor: 0 } } } impl ImmutableStringReader for StringReader<'_> { + fn string(&self) -> &str { + self.string + } + fn remaining_length(&self) -> usize { self.string.len() - self.cursor } @@ -27,7 +38,7 @@ impl ImmutableStringReader for StringReader<'_> { } fn get_read(&self) -> &str { - &self.string[self.cursor..] + &self.string[..self.cursor] } fn remaining(&self) -> &str { @@ -49,122 +60,122 @@ impl ImmutableStringReader for StringReader<'_> { fn peek_offset(&self, offset: usize) -> char { self.string.chars().nth(self.cursor + offset).unwrap() } + + fn cursor(&self) -> usize { + self.cursor + } } impl StringReader<'_> { - fn read(&mut self) -> char { + pub fn read(&mut self) -> char { let c = self.peek(); self.cursor += 1; c } - fn skip(&mut self) { + pub fn skip(&mut self) { self.cursor += 1; } - fn is_allowed_number(c: char) -> bool { + pub fn is_allowed_number(c: char) -> bool { c >= '0' && c <= '9' || c == '.' || c == '-' } - fn is_quoted_string_start(c: char) -> bool { + pub fn is_quoted_string_start(c: char) -> bool { c == SYNTAX_DOUBLE_QUOTE || c == SYNTAX_SINGLE_QUOTE } - fn skip_whitespace(&mut self) { + pub fn skip_whitespace(&mut self) { while self.can_read() && self.peek().is_whitespace() { self.skip(); } } - fn read_int(&self) -> Result<(), CommandSyntaxException> { + pub fn read_int(&mut self) -> Result<(), 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(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_expected_int() - .create_with_context(self)); + return Err(BuiltInExceptions::ReaderExpectedInt.create_with_context(self)); } let result = i32::from_str(number); if result.is_err() { self.cursor = start; - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_invalid_int() - .create_with_context(self, number)); + return Err(BuiltInExceptions::ReaderInvalidInt { + value: number.to_string(), + } + .create_with_context(self)); } Ok(()) } - fn read_long(&self) -> Result<(), CommandSyntaxException> { + pub fn read_long(&mut self) -> Result<(), 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(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_expected_long() - .create_with_context(self)); + return Err(BuiltInExceptions::ReaderExpectedLong.create_with_context(self)); } let result = i64::from_str(number); if result.is_err() { self.cursor = start; - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_invalid_long() - .create_with_context(self, number)); + return Err(BuiltInExceptions::ReaderInvalidLong { + value: number.to_string(), + } + .create_with_context(self)); } Ok(()) } - fn read_double(&self) -> Result<(), CommandSyntaxException> { + pub fn read_double(&mut self) -> Result<(), 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(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_expected_double() - .create_with_context(self)); + return Err(BuiltInExceptions::ReaderExpectedDouble.create_with_context(self)); } let result = f64::from_str(number); if result.is_err() { self.cursor = start; - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_invalid_double() - .create_with_context(self, number)); + return Err(BuiltInExceptions::ReaderInvalidDouble { + value: number.to_string(), + } + .create_with_context(self)); } Ok(()) } - fn read_float(&self) -> Result<(), CommandSyntaxException> { + pub fn read_float(&mut self) -> Result<(), 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(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_expected_float() - .create_with_context(self)); + return Err(BuiltInExceptions::ReaderExpectedFloat.create_with_context(self)); } let result = f32::from_str(number); if result.is_err() { self.cursor = start; - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_invalid_float() - .create_with_context(self, number)); + return Err(BuiltInExceptions::ReaderInvalidFloat { + value: number.to_string(), + } + .create_with_context(self)); } Ok(()) } - fn is_allowed_in_unquoted_string(c: char) -> bool { + pub fn is_allowed_in_unquoted_string(c: char) -> bool { c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' @@ -174,7 +185,7 @@ impl StringReader<'_> { || c == '+' } - fn read_unquoted_string(&self) -> &str { + 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(); @@ -182,22 +193,23 @@ impl StringReader<'_> { &self.string[start..self.cursor] } - fn read_quoted_string(&self) -> Result<&str, CommandSyntaxException> { + pub fn read_quoted_string(&mut self) -> Result { if !self.can_read() { - return ""; + return Ok(String::new()); } let next = self.peek(); if !StringReader::<'_>::is_quoted_string_start(next) { - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_expected_start_of_quote() - .create_with_context(self)); + return Err(BuiltInExceptions::ReaderExpectedStartOfQuote.create_with_context(self)); } self.skip(); self.read_string_until(next) } - fn read_string_until(&self, terminator: char) -> Result { - let result = String::new(); + pub fn read_string_until( + &mut self, + terminator: char, + ) -> Result { + let mut result = String::new(); let mut escaped = false; while self.can_read() { let c = self.read(); @@ -207,9 +219,8 @@ impl StringReader<'_> { escaped = false; } else { self.cursor -= 1; - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_invalid_escape() - .create_with_context(self, c)); + return Err(BuiltInExceptions::ReaderInvalidEscape { character: c } + .create_with_context(self)); } } else if c == SYNTAX_ESCAPE { escaped = true; @@ -220,21 +231,10 @@ impl StringReader<'_> { } } - Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_expected_end_of_quote() - .create_with_context(self)) + return Err(BuiltInExceptions::ReaderExpectedEndOfQuote.create_with_context(self)); } - fn read_string(&self) -> Result { - // if (!canRead()) { - // return ""; - // } - // final char next = peek(); - // if (isQuotedStringStart(next)) { - // skip(); - // return readStringUntil(next); - // } - // return readUnquotedString(); + pub fn read_string(&mut self) -> Result { if !self.can_read() { return Ok(String::new()); } @@ -246,32 +246,179 @@ impl StringReader<'_> { Ok(self.read_unquoted_string().to_string()) } - fn read_boolean(&self) -> Result { - let start = self.cursor; - let value = self.read_string()?; - if value.is_empty() { - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_expected_bool() - .create_with_context(self)); - } + pub fn read_boolean(&mut self) -> Result { + let start = self.cursor; + let value = self.read_string()?; + if value.is_empty() { + return Err(BuiltInExceptions::ReaderExpectedBool.create_with_context(self)); + } - if value == "true" { - return Ok(true); - } else if value == "false" { - return Ok(false); - } else { - self.cursor = start; - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_invalid_bool() - .create_with_context(self, value)); - } - } + if value == "true" { + return Ok(true); + } else if value == "false" { + return Ok(false); + } else { + self.cursor = start; + return Err(BuiltInExceptions::ReaderInvalidBool { value }.create_with_context(self)); + } + } - fn expect(&self, c: char) -> Result<(), CommandSyntaxException> { - if !self.can_read() || self.peek() != c { - return Err(CommandSyntaxException::BUILT_IN_EXCEPTIONS - .reader_expected_symbol() - .create_with_context(self, c)); - } - self.skip(); - } + 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(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn can_read() { + let mut reader = StringReader::from("abc"); + 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"); + 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"); + 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"); + 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"); + 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"); + 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"); + reader.skip(); + assert_eq!(reader.cursor(), 1); + } + + #[test] + fn get_remaining() { + let mut reader = StringReader::from("Hello!"); + 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!"); + 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!"); + reader.skip_whitespace(); + assert_eq!(reader.cursor(), 0); + } + + #[test] + fn skip_whitespace_mixed() { + let mut reader = StringReader::from(" \t \t\nHello!"); + reader.skip_whitespace(); + assert_eq!(reader.cursor(), 5); + } + + #[test] + fn skip_whitespace_empty() { + let mut reader = StringReader::from(""); + reader.skip_whitespace(); + assert_eq!(reader.cursor(), 0); + } + + #[test] + fn read_unquoted_string() { + let mut reader = StringReader::from("hello world"); + 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(""); + 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"); + 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\""); + assert_eq!(reader.read_unquoted_string(), "hello world"); + assert_eq!(reader.get_read(), "\"hello world\""); + assert_eq!(reader.remaining(), ""); + } +} diff --git a/azalea-brigadier/src/suggestion/mod.rs b/azalea-brigadier/src/suggestion/mod.rs index e69de29b..050bae6c 100644 --- a/azalea-brigadier/src/suggestion/mod.rs +++ b/azalea-brigadier/src/suggestion/mod.rs @@ -0,0 +1,5 @@ +pub mod integer_suggestion; +pub mod suggestion; +pub mod suggestion_provider; +pub mod suggestions; +pub mod suggestions_builder; diff --git a/azalea-brigadier/src/suggestion/suggestions.rs b/azalea-brigadier/src/suggestion/suggestions.rs index e69de29b..354fc418 100644 --- a/azalea-brigadier/src/suggestion/suggestions.rs +++ b/azalea-brigadier/src/suggestion/suggestions.rs @@ -0,0 +1 @@ +pub struct Suggestions {} diff --git a/azalea-brigadier/src/suggestion/suggestions_builder.rs b/azalea-brigadier/src/suggestion/suggestions_builder.rs index e69de29b..6960f52b 100644 --- a/azalea-brigadier/src/suggestion/suggestions_builder.rs +++ b/azalea-brigadier/src/suggestion/suggestions_builder.rs @@ -0,0 +1 @@ +pub struct SuggestionsBuilder {} diff --git a/azalea-brigadier/tests/string_reader_test.rs b/azalea-brigadier/tests/string_reader_test.rs deleted file mode 100644 index e69de29b..00000000