use logos::Logos;
use crate::lexer::LexIt;
use crate::error::ParseError;
use crate::error::ParseError::{ReachedEOF, UnreachedEOF};
use crate::step::Step;
use crate::step::Step::{Error, Fail, Success};
pub struct ParseIt<'a, T> where T: Logos<'a, Source=str>, {
lexer: LexIt<'a, T>,
}
impl<'a, Token> ParseIt<'a, Token>
where Token: Logos<'a, Source=str> + PartialEq,
{
pub fn new(src: &'a str) -> Result<Self, ParseError<'a>>
where Token::Extras: Default
{
Ok(ParseIt {
lexer: LexIt::new(src)?,
})
}
pub fn token(&self, pos: usize) -> Result<(&Token, usize), ParseError<'a>> {
self.lexer.token(pos)
}
pub fn one_or_more<T, Then>(&self, pos: usize, then: Then) -> Step<'a, Vec<T>>
where
Then: FnOnce(usize) -> Step<'a, T> + Copy,
{
match self.zero_or_more(pos, then) {
Success(vals, _) if vals.is_empty() => Fail(pos),
other => other,
}
}
pub fn zero_or_more<T, Then>(&self, pos: usize, then: Then) -> Step<'a, Vec<T>>
where
Then: FnOnce(usize) -> Step<'a, T> + Copy,
{
match then(pos).then_multi_zip(then).merge() {
Fail(_) => Success(vec![], pos),
Error(ReachedEOF(_)) => Success(vec![], pos),
success => success,
}
}
pub fn validate_eof<T>(&self, res: Step<'a, T>) -> Step<'a, T> {
match res {
Success(_, pos) if self.lexer.len() != pos => Error(UnreachedEOF(pos)),
other => other,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct EmptyToken {}
#[macro_export]
macro_rules! token {
($obj:expr => $($matcher:pat $(if $pred:expr)* => $result:expr),*) => {
match $obj {
Ok((t,p)) => match t {
$($matcher $(if $pred)* => Step::Success($result, p + 1)),*,
_ => Step::Fail(p)
}
Err(e) => Step::Error(e)
}
};
($obj:expr => $($matcher:pat $(if $pred:expr)*),*) => {
match $obj {
Ok((t,p)) => match t {
$($matcher $(if $pred)* => Step::Success(EmptyToken{}, p + 1)),*,
_ => Step::Fail(p)
}
Err(e) => Step::Error(e)
}
}
}
#[macro_export]
macro_rules! wrap {
($pos:literal => $left:ident; $internal:ident; $right:ident ) => {
$left($pos).then($internal).then_zip($right).take_left()
} ;
($pos:literal => $left:ident; $internal:ident ?; $right:ident ) => {
$left($pos).then_or_none($internal).then_zip($right).take_left()
};
($pos:literal => $left:ident; $internal:ident or $default:ident; $right:ident ) => {
$left($pos).then_or_val($internal,$default).then_zip($right).take_left()
};
($pos:literal => $left:ident; $internal:ident or $default:literal; $right:ident ) => {
$left($pos).then_or_val($internal,$default).then_zip($right).take_left()
};
($pos:ident => $left:ident; $internal:ident; $right:ident ) => {
$left($pos).then($internal).then_zip($right).take_left()
} ;
($pos:ident => $left:ident; $internal:ident ?; $right:ident ) => {
$left($pos).then_or_none($internal).then_zip($right).take_left()
};
($pos:ident => $left:ident; $internal:ident or $default:ident; $right:ident ) => {
$left($pos).then_or_val($internal,$default).then_zip($right).take_left()
};
($pos:ident => $left:ident; $internal:ident or $default:literal; $right:ident ) => {
$left($pos).then_or_val($internal,$default).then_zip($right).take_left()
}
}
#[macro_export]
macro_rules! seq {
($pos:ident => $elem:ident, $sep:ident ) => {
$elem($pos)
.then_multi_zip(|p| $sep(p).then($elem))
.merge()
};
($pos:ident => $elem:ident,$sep:ident, ) => {
$elem($pos)
.then_multi_zip(|p:usize| {
$sep(p).then($elem).then_or_none_zip(|p| $sep(p).or_none()).take_left()
})
.merge()
};
($pos:literal => $elem:ident, $sep:ident ) => {
$elem($pos)
.then_multi_zip(|p| $sep(p).then($elem))
.merge()
};
($pos:ident => $elem:ident,$sep:ident, ) => {
$elem($pos)
.then_multi_zip(|p:usize| {
$sep(p).then($elem).then_or_none_zip(|p| $sep(p).or_none()).take_left()
})
.merge()
};
}
#[cfg(test)]
mod tests {
use logos::Logos;
use crate::parser::ParseIt;
use crate::parser::Step;
use crate::parser::EmptyToken;
#[derive(Logos, PartialEq)]
pub enum TFQ {
#[token("(")]
L,
#[token(")")]
R,
#[token(",")]
C,
#[token("word")]
Word,
#[token("none")]
None,
#[error]
Error,
}
#[test]
fn test() {
let p: ParseIt<TFQ> = ParseIt::new("word,word,word").unwrap();
let comma = |pos: usize| { token!(p.token(pos) => TFQ::C) };
let word = |pos: usize| { token!(p.token(pos) => TFQ::Word) };
let pos = 0;
seq!(pos => word, comma );
}
}