//! Corresponds to `ast::basic`. use chumsky::prelude::*; use chumsky::text::Character; use crate::ast::{BoundedSeparated, Ident, Line, Separated, Space}; use crate::span::Span; pub type Error = Simple; pub type EParser = BoxedParser<'static, char, O, Error>; fn inline() -> impl Parser { filter(|c: &char| c.is_whitespace() && *c != '\n') .repeated() .to(()) } fn newline() -> impl Parser { just('\n').to(()) } fn line() -> impl Parser { let empty = newline().to(Line::Empty); let comment = just('#') .ignore_then(take_until(newline())) .map(|(s, _)| s) .collect::() .map(Line::Comment); empty.or(comment) } pub fn space() -> EParser { inline() .ignore_then(line()) .repeated() .then_ignore(inline()) .map_with_span(|lines, span| Space { lines, span }) .boxed() } pub fn ident() -> EParser { text::ident() .try_map(|name, span| { if matches!( &name as &str, "nil" | "true" | "false" | "not" | "and" | "or" | "local" | "function" | "module" ) { Err(Simple::custom(span, "identifier uses reserved name")) } else { Ok(Ident { name, span }) } }) .boxed() } pub fn local(space: EParser) -> EParser> { text::keyword("local").ignore_then(space).or_not().boxed() } // This function is more of a utility function. Because of this and to keep the // code nicer, I have decided that the rules specified in the `parser` module // don't apply to it. pub fn separated_by( elem: impl Parser + Clone + 'static, separator: impl Parser + 'static, trailing_separator: impl Parser + 'static, ) -> EParser> { elem.clone() .then(separator.then(elem).repeated()) .then(trailing_separator.or_not()) .or_not() .map_with_span(|s, span| match s { Some(((first_elem, last_elems), trailing)) => Separated::NonEmpty { first_elem, last_elems, trailing, span, }, None => Separated::Empty(span), }) .boxed() } pub fn bounded_separated( space: impl Parser + Clone + 'static, start: impl Parser + 'static, end: impl Parser + 'static, separator: impl Parser + 'static, elem: impl Parser + Clone + 'static, ) -> EParser> { start .ignore_then(space.clone()) .then( elem.clone() .then(space.clone()) .then_ignore(separator) .then(space.clone()) .repeated(), ) .then(elem.then(space).or_not()) .then_ignore(end) .map_with_span(|((s0, first_elems), last_elem), span| { let mut space_before_elem = s0; let mut elems = vec![]; for ((elem, s1), s2) in first_elems { elems.push((space_before_elem, elem, s1)); space_before_elem = s2; } let trailing = if let Some((elem, s1)) = last_elem { elems.push((space_before_elem, elem, s1)); None } else { Some(space_before_elem) }; BoundedSeparated { elems, trailing, span, } }) .boxed() }