use nom::branch::alt; use nom::bytes::complete::tag; use nom::bytes::complete::take_till1; use nom::character::complete::multispace0; use nom::character::complete::multispace1; use nom::combinator::not; use nom::combinator::peek; use nom::multi::separated_list1; use nom::sequence::delimited; use super::error::Res; #[derive(Debug)] pub enum Token<'s> { Atom(&'s str), List(Vec>), } #[tracing::instrument(ret, level = "debug")] pub fn sexp<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { let (remaining, _) = multispace0(input)?; let (remaining, tkn) = token(remaining)?; let (remaining, _) = multispace0(remaining)?; Ok((remaining, tkn)) } #[tracing::instrument(ret, level = "debug")] fn token<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { alt((list, atom))(input) } #[tracing::instrument(ret, level = "debug")] fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { let (remaining, opening_paren) = tag("(")(input)?; let (remaining, children) = delimited(multispace0, separated_list1(multispace1, token), multispace0)(remaining)?; let (remaining, closing_paren) = tag(")")(remaining)?; Ok((remaining, Token::List(children))) } #[tracing::instrument(ret, level = "debug")] fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { not(peek(tag(")")))(input)?; unquoted_atom(input) } #[tracing::instrument(ret, level = "debug")] fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { let (remaining, body) = take_till1(|c| match c { ' ' | '\t' | '\r' | '\n' => true, _ => false, })(input)?; Ok((remaining, Token::Atom(body))) } #[cfg(test)] mod tests { use super::*; #[test] fn simple() { let input = " (foo bar baz ) "; let (remaining, parsed) = sexp(input).expect("Parse the input"); assert_eq!(remaining, ""); assert!(match parsed { Token::Atom(_) => false, Token::List(_) => true, }); } }