kamo 0.9.4

A library to assist in the creation of an interpreter or compiler and its associated runtime.
Documentation
use crate::parser::{code, Input, ParseResult, Parser, Span};

use super::SequenceError;

/// Creates a parser that parses `element` followed by `postfix`. The parser
/// will return the result of `element`.
///
/// # Examples
///
/// ```rust
/// # use kamo::{Position, parser::{
/// #     prelude::*, CharacterError, SequenceError, code, Input, Span
/// # }};
/// let mut parser = terminated(tag("let"), char(')'));
///
/// assert_eq!(parser(Input::new("let)")), Ok(("let", Input::new(""))));
/// assert_eq!(parser(Input::new("abc)")), Err(ParseError::new(
///     Position::new(0, 1, 1),
///     code::ERR_TAG,
///     CharacterError::Tag("let")
/// )));
/// assert_eq!(parser(Input::new("let(")), Err(ParseError::new(
///     Position::new(3, 1, 4),
///     code::ERR_TERMINATED,
///     SequenceError::Terminated
/// )));
/// 
/// let error = parser(Input::new("let")).expect_err("error output");
/// 
/// assert!(error.is_eof());
/// assert_eq!(error, ParseError::new(
///     Position::new(3, 1, 4),
///     code::ERR_TERMINATED,
///     SequenceError::Terminated
/// ));
/// 
/// let error = parser(Input::new("")).expect_err("error output");
/// 
/// assert!(error.is_eof());
/// assert_eq!(error, ParseError::new(
///     Position::new(0, 1, 1),
///     code::ERR_TAG,
///     CharacterError::Tag("let")
/// ));
/// ```
pub fn terminated<'a, 'b, F1, F2, O1, O2>(
    mut element: F1,
    mut postfix: F2,
) -> impl FnMut(Input<'a>) -> ParseResult<'a, O1>
where
    O1: 'b,
    O2: 'b,
    F1: Parser<'a, 'b, O1> + 'b,
    F2: Parser<'a, 'b, O2> + 'b,
{
    move |input| {
        let (output, cursor) = element.parse(input)?;
        let (_, cursor) = postfix.parse(cursor).map_err(|mut err| {
            err.push(
                Span::new(cursor.position(), err.span().end()),
                code::ERR_TERMINATED,
                SequenceError::Terminated,
            );
            err
        })?;

        Ok((output, cursor))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::{
        parser::{character::CharacterError, prelude::*, ParseError},
        Position,
    };

    #[test]
    fn terminated_success() {
        let (output, input) =
            terminated(tag("let"), char(')'))(Input::new("let)")).expect("valid output");

        assert_eq!(output, "let");
        assert_eq!(input, Input::from(""));
        assert_eq!(input.current(), None);
        assert_eq!(input.position(), Position::new(4, 1, 5));
    }

    #[test]
    fn terminated_failure() {
        let error =
            terminated(tag("let"), char(')'))(Input::new("abc)")).expect_err("invalid output");

        assert_eq!(
            error,
            ParseError::new(
                Position::new(0, 1, 1),
                code::ERR_TAG,
                CharacterError::Tag("let")
            )
        );

        let error =
            terminated(tag("let"), char(')'))(Input::new("let(")).expect_err("invalid output");

        assert_eq!(
            error,
            ParseError::new(
                Position::new(3, 1, 4),
                code::ERR_TERMINATED,
                SequenceError::Terminated
            )
        );

        let error =
            terminated(tag("let"), char(')'))(Input::new("let")).expect_err("invalid output");

        assert!(error.is_eof());
        assert_eq!(
            error,
            ParseError::eof(Position::new(3, 1, 4)).and(
                Position::new(3, 1, 4),
                code::ERR_TERMINATED,
                SequenceError::Terminated
            )
        );
    }
}