use {crate::CowStr, alloc::borrow::Cow, descape::InvalidEscape, logos::Logos};
pub type Lexer<'a> = logos::Lexer<'a, Token<'a>>;
type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(Clone, Debug, PartialEq, Eq, Logos)]
#[logos(error = Error)]
#[logos(skip r"[ \t\f]+|[#;].*")]
pub enum Token<'a> {
#[regex("[0-9A-Za-z_-]+")]
UnquotedString(&'a str),
#[regex(r"'[^']*'", single_quoted)]
#[regex(r#""([^"\\]|\\.)*""#, double_quoted)]
QuotedString(CowStr<'a>),
#[token("\n")]
LineBreak,
}
impl<'a> Token<'a> {
pub fn into_cow_str(self) -> CowStr<'a> {
match self {
Self::UnquotedString(s) => Cow::Borrowed(s),
Self::QuotedString(cow) => cow,
Self::LineBreak => Cow::Borrowed("\n"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Error {
Escape(InvalidEscape),
UnexpectedEndOfPattern,
#[default]
InvalidToken,
}
fn single_quoted<'a>(lexer: &Lexer<'a>) -> Result<CowStr<'a>> {
get_without_surrounding_chars(lexer).map(Cow::Borrowed)
}
fn double_quoted<'a>(lexer: &Lexer<'a>) -> Result<CowStr<'a>> {
let slice = get_without_surrounding_chars(lexer)?;
descape::UnescapeExt::to_unescaped(slice).map_err(Error::Escape)
}
fn get_without_surrounding_chars<'a>(lexer: &Lexer<'a>) -> Result<&'a str> {
let slice = lexer.slice();
slice
.get(1..slice.len() - 1)
.ok_or(Error::UnexpectedEndOfPattern)
}
#[cfg(test)]
mod tests {
use super::*;
impl Token<'_> {
fn as_str(&self) -> &'_ str {
match self {
Token::UnquotedString(s) => s,
Token::QuotedString(cow) => cow,
Token::LineBreak => "\n",
}
}
}
#[test]
fn lex_unquoted() {
assert_eq!(lex_one_token("simple_ident").as_str(), "simple_ident");
assert_eq!(lex_one_token("133t").as_str(), "133t");
}
#[test]
fn lex_quoted() {
assert_eq!(lex_one_token("'single quoted'").as_str(), "single quoted");
assert_eq!(
lex_one_token(r#""doubly quoted""#).as_str(),
"doubly quoted"
);
}
#[test]
fn lex_quoted_escapes() {
assert_eq!(
lex_one_token(r#""string's cake""#).as_str(),
"string's cake"
);
}
#[test]
fn comments() {
let input = "#comment\n#comment";
let mut lexer = Token::lexer(input);
assert_eq!(lexer.next(), Some(Ok(Token::LineBreak)));
assert_eq!(lexer.next(), None);
}
#[track_caller]
fn lex_one_token(input: &str) -> Token<'_> {
let mut lexer = Token::lexer(input);
let token = lexer
.next()
.expect("expected at least one token")
.expect("expected valid parse");
assert_eq!(lexer.next(), None, "required at most one token");
token
}
}