mod error;
use crate::{
span::{Spannable, Spanned},
SourceCodeScanner,
};
pub use error::CharTokenError;
pub use error::ParseCharError;
pub use error::StringErrorList;
pub use error::StringTokenError;
impl<'src> SourceCodeScanner<'src> {
#[inline]
pub fn parse_string<E, F: Fn(&Self, &mut String, char) -> Result<(), E>>(
&self,
char_consumer: F,
) -> Spanned<Result<String, Vec<StringTokenError<E>>>> {
let Some(Spanned { span, data: delim }) = self.next_span() else {
panic!(
"parse_string should only be called if the source code iterator cannot be empty."
)
};
let mut result = String::new();
let mut errors = Vec::with_capacity(4);
loop {
if !self.has_next() {
errors.push(StringTokenError::NoClosingDelimiter);
break;
}
if self.peek_is(delim) {
self.skip();
break;
}
if let Err(e) = char_consumer(self, &mut result, delim) {
errors.push(StringTokenError::CharError(e));
};
}
span.up_to(&self.span()).wrap(if errors.is_empty() {
Ok(result)
} else {
Err(errors)
})
}
#[inline]
pub fn try_parse_simple_string(&self) -> Option<Spanned<Result<String, StringErrorList>>> {
if let Some('"' | '\'') = self.peek() {
Some(self.parse_string(parse_simple_character))
} else {
None
}
}
#[inline]
pub fn try_parse_strict_string(&self) -> Option<Spanned<Result<String, StringErrorList>>> {
self.peek()
.eq(&Some('"'))
.then(|| self.parse_string(parse_simple_character))
}
#[inline]
pub fn try_parse_character_token(&self) -> Option<Spanned<Result<char, CharTokenError>>> {
if self.peek() == Some('\'') {
let start_span = self.span();
self.skip();
let mut container = String::with_capacity(1);
if let Err(err) = parse_simple_character(self, &mut container, '\'') {
return Some(
Err(CharTokenError::CharError(err)).spanned(start_span.up_to(&self.span())),
);
}
match self.next() {
Some('\'') => Some(
Ok(unsafe { container.chars().next().unwrap_unchecked() })
.spanned(start_span.up_to(&self.span())),
),
Some(x) => Some(
Err(CharTokenError::UnclosedCharError(x))
.spanned(start_span.up_to(&self.span())),
),
None => Some(
Err(CharTokenError::UnclosedCharErrorEOF)
.spanned(start_span.up_to(&self.span())),
),
}
} else {
None
}
}
}
#[inline]
pub fn parse_simple_character(
context: &SourceCodeScanner,
accumulator: &mut String,
delimiter: char,
) -> Result<(), ParseCharError> {
let Some(Spanned {
data: next_char,
span,
}) = context.next_span()
else {
return Err(ParseCharError::NoCharFound);
};
if next_char != '\\' {
if next_char == delimiter {
return Err(ParseCharError::UnescapedDelimiter(span));
} else {
accumulator.push(next_char);
return Ok(());
}
}
let slash_span = context.span();
match context.next_span() {
Some(Spanned { data: 't', .. }) => accumulator.push('\t'),
Some(Spanned { data: 'n', .. }) => accumulator.push('\n'),
Some(Spanned { data: 'r', .. }) => accumulator.push('\r'),
Some(Spanned { data: '0', .. }) => accumulator.push('\0'),
Some(Spanned { data: '\\', .. }) => accumulator.push('\\'),
Some(Spanned { data: '\n', .. }) => (),
Some(Spanned { data, .. }) if data == delimiter => accumulator.push(delimiter),
Some(Spanned { data, span }) => {
return Err(ParseCharError::IllegalEscape(data, span));
}
None => {
return Err(ParseCharError::NoEscape(slash_span));
}
};
Ok(())
}
#[cfg(test)]
mod tests {
use crate::{
common::string::{CharTokenError, ParseCharError},
span::{Span, Spannable},
SourceCodeScanner,
};
fn ctx(code: &str) -> SourceCodeScanner {
SourceCodeScanner::new(code)
}
#[test]
pub fn parse_string() {
let code = ctx(r#"%Awesome!% %Meowmeow \n uwu%"#);
unsafe {
assert_eq!(
code.parse_string(super::parse_simple_character),
Ok(String::from("Awesome!")).spanned(Span::new(0, 10))
);
code.skip();
assert_eq!(
code.parse_string(super::parse_simple_character),
Ok(String::from("Meowmeow \n uwu")).spanned(Span::new(11, 17))
);
assert!(!code.has_next());
}
}
#[test]
pub fn try_parse_simple_string() {
let code = ctx(r#"'single quotes' "double quotes!""#);
unsafe {
assert_eq!(
code.try_parse_simple_string(),
Some(Ok(String::from("single quotes")).spanned(Span::new(0, 15)))
);
code.skip();
assert_eq!(
code.try_parse_simple_string(),
Some(Ok(String::from("double quotes!")).spanned(Span::new(16, 16)))
);
assert!(!code.has_next());
assert_eq!(code.try_parse_simple_string(), None);
}
}
#[test]
pub fn try_parse_strict_string() {
let code = ctx(r#"'h' "double quotes!""#);
assert_eq!(code.try_parse_strict_string(), None);
assert_eq!(code.next(), Some('\''));
assert_eq!(code.next(), Some('h'));
assert_eq!(code.next(), Some('\''));
code.skip();
unsafe {
assert_eq!(
code.try_parse_strict_string(),
Some(Ok(String::from("double quotes!")).spanned(Span::new(4, 16)))
);
}
assert!(!code.has_next());
assert_eq!(code.try_parse_strict_string(), None);
}
#[test]
pub fn try_parse_character_token() {
let code = ctx(r#"'p' 'q' '\t' '\'' ' ' ''"#);
unsafe {
assert_eq!(
code.try_parse_character_token(),
Some(Ok('p').spanned(Span::new(0, 3)))
);
code.skip();
assert_eq!(
code.try_parse_character_token(),
Some(Ok('q').spanned(Span::new(4, 3)))
);
code.skip();
assert_eq!(
code.try_parse_character_token(),
Some(Ok('\t').spanned(Span::new(8, 4)))
);
code.skip();
assert_eq!(
code.try_parse_character_token(),
Some(Ok('\'').spanned(Span::new(13, 4)))
);
code.skip();
assert_eq!(
code.try_parse_character_token(),
Some(Ok(' ').spanned(Span::new(18, 3)))
);
code.skip();
assert_eq!(
code.try_parse_character_token(),
Some(
Err(CharTokenError::CharError(
ParseCharError::UnescapedDelimiter(Span::new(23, 1))
))
.spanned(Span::new(22, 2))
)
);
assert!(!code.has_next());
}
}
#[test]
pub fn parse_simple_character() {
let mut code = ctx(r#"ab2\tx\\8*"'\""#);
let mut acc = String::new();
let mut next = || super::parse_simple_character(&mut code, &mut acc, '"');
assert_eq!(next(), Ok(()));
assert_eq!(next(), Ok(()));
assert_eq!(next(), Ok(()));
assert_eq!(next(), Ok(()));
assert_eq!(next(), Ok(()));
assert_eq!(next(), Ok(()));
assert_eq!(next(), Ok(()));
assert_eq!(next(), Ok(()));
unsafe {
assert_eq!(
next(),
Err(ParseCharError::UnescapedDelimiter(Span::new(10, 1)))
);
}
assert_eq!(next(), Ok(()));
assert_eq!(next(), Ok(()));
assert_eq!(&acc, "ab2\tx\\8*'\"");
assert!(!code.has_next());
}
}