mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 14:42:32 +00:00
string reader
This commit is contained in:
parent
d959fb2d0c
commit
8331851d97
22 changed files with 528 additions and 96 deletions
3
azalea-brigadier/README.md
Normal file
3
azalea-brigadier/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Azalea Brigadier
|
||||
|
||||
A Rustier port of Mojang's [Brigadier](https://github.com/Mojang/brigadier) command parsing and dispatching library.
|
|
@ -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> {
|
||||
// T parse(StringReader reader) throws CommandSyntaxException;
|
||||
|
||||
|
@ -9,12 +16,12 @@ pub trait ArgumentType<T> {
|
|||
// return Collections.emptyList();
|
||||
// }
|
||||
|
||||
fn parse(reader: &mut StringReader) -> Result<T, CommandSyntaxError>;
|
||||
fn parse(reader: &mut StringReader) -> Result<T, CommandSyntaxException>;
|
||||
|
||||
fn list_suggestions<S>(
|
||||
context: &CommandContext<S>,
|
||||
builder: &mut SuggestionsBuilder,
|
||||
) -> Result<Suggestions, CommandSyntaxError>;
|
||||
) -> Result<Suggestions, CommandSyntaxException>;
|
||||
|
||||
fn get_examples() -> Vec<String>;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
mod argument_type;
|
||||
pub mod argument_type;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
pub struct CommandContext<S> {
|
||||
source: S,
|
||||
}
|
|
@ -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;
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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<String>,
|
||||
cursor: Option<usize>,
|
||||
}
|
||||
|
||||
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<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 - 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<usize> {
|
||||
self.cursor
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod builtin_exception_provider;
|
||||
pub mod builtin_exceptions;
|
||||
pub mod command_syntax_exception;
|
|
@ -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;
|
||||
|
|
|
@ -1 +1,15 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
pub struct Message(Rc<String>);
|
||||
|
||||
impl Message {
|
||||
pub fn string(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Message {
|
||||
fn from(s: String) -> Self {
|
||||
Self(Rc::new(s))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, CommandSyntaxException> {
|
||||
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<String, CommandSynatxException> {
|
||||
let result = String::new();
|
||||
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();
|
||||
|
@ -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<String, CommandSyntaxException> {
|
||||
// if (!canRead()) {
|
||||
// return "";
|
||||
// }
|
||||
// final char next = peek();
|
||||
// if (isQuotedStringStart(next)) {
|
||||
// skip();
|
||||
// return readStringUntil(next);
|
||||
// }
|
||||
// return readUnquotedString();
|
||||
pub fn read_string(&mut self) -> Result<String, CommandSyntaxException> {
|
||||
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<bool, CommandSyntaxException> {
|
||||
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<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" {
|
||||
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(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
pub mod integer_suggestion;
|
||||
pub mod suggestion;
|
||||
pub mod suggestion_provider;
|
||||
pub mod suggestions;
|
||||
pub mod suggestions_builder;
|
|
@ -0,0 +1 @@
|
|||
pub struct Suggestions {}
|
|
@ -0,0 +1 @@
|
|||
pub struct SuggestionsBuilder {}
|
Loading…
Reference in a new issue