shex_compact/
grammar.rs

1use crate::{shex_parser_error::ParseError as ShExParseError, IRes, Span};
2use colored::*;
3use nom::{
4    branch::alt,
5    bytes::complete::{is_not, tag, tag_no_case},
6    character::complete::multispace1,
7    combinator::value,
8    multi::many0,
9    sequence::{delimited, pair},
10    Err,
11};
12use std::fmt::Debug;
13
14// Create a [`Span`][nom_locate::LocatedSpan] over the input.
15/* fn span_from_str(input: &str) -> Span<'_> {
16    Span::new(input)
17}*/
18
19// The result of a parse
20// type ParseResult<'a, T> = Result<T, LocatedParseError>;
21
22/// A combinator that modifies the associated error.
23pub(crate) fn map_error<'a, T: 'a>(
24    mut parser: impl FnMut(Span<'a>) -> IRes<'a, T> + 'a,
25    mut error: impl FnMut() -> ShExParseError + 'a,
26) -> impl FnMut(Span<'a>) -> IRes<'a, T> + 'a {
27    move |input| {
28        parser(input).map_err(|e| match e {
29            Err::Incomplete(_) => e,
30            Err::Error(context) => {
31                let mut err = error().at(input);
32                err.append(context);
33                Err::Error(err)
34            }
35            Err::Failure(context) => {
36                let mut err = error().at(input);
37                err.append(context);
38                Err::Failure(err)
39            }
40        })
41    }
42}
43
44/// A combinator to add tracing to the parser.
45/// [fun] is an identifier for the parser and [parser] is the actual parser.
46#[inline(always)]
47pub(crate) fn traced<'a, T, P>(
48    fun: &'static str,
49    mut parser: P,
50) -> impl FnMut(Span<'a>) -> IRes<'a, T>
51where
52    T: Debug,
53    P: FnMut(Span<'a>) -> IRes<'a, T>,
54{
55    move |input| {
56        tracing::trace!(target: "parser", "{fun}({input:?})");
57        let result = parser(input);
58        match &result {
59            Ok(res) => {
60                tracing::trace!(target: "parser", "{}", format!("{fun}({input:?}) -> {res:?}").green());
61            }
62            Err(e) => {
63                tracing::trace!(target: "parser", "{}", format!("{fun}({input:?}) -> {e:?}").red());
64            }
65        }
66        result
67    }
68}
69
70/// A combinator that recognises a comment, starting at a `#`
71/// character and ending at the end of the line.
72fn comment(input: Span) -> IRes<()> {
73    alt((
74        value((), pair(tag("#"), is_not("\n\r"))),
75        // a comment that immediately precedes the end of the line –
76        // this must come after the normal line comment above
77        value((), tag("#")),
78        value((), multi_comment),
79    ))(input)
80}
81
82fn multi_comment(i: Span) -> IRes<()> {
83    value((), delimited(tag("/*"), is_not("*/"), tag("*/")))(i)
84}
85
86/// A combinator that recognises an arbitrary amount of whitespace and
87/// comments.
88pub(crate) fn tws0(input: Span) -> IRes<()> {
89    value((), many0(alt((value((), multispace1), comment))))(input)
90}
91
92/*
93/// A combinator that recognises any non-empty amount of whitespace
94/// and comments.
95pub(crate) fn tws1(input: Span) -> IRes<()> {
96    value((), many1(alt((value((), multispace1), comment))))(input)
97}
98*/
99
100/// A combinator that creates a parser for a specific token.
101pub(crate) fn token<'a>(token: &'a str) -> impl FnMut(Span<'a>) -> IRes<'a, Span<'a>> {
102    map_error(tag(token), || {
103        ShExParseError::ExpectedToken(token.to_string())
104    })
105}
106
107/// A combinator that creates a parser for a specific token,
108/// surrounded by trailing whitespace or comments.
109pub(crate) fn token_tws<'a>(token: &'a str) -> impl FnMut(Span<'a>) -> IRes<'a, Span<'a>> {
110    map_error(delimited(tws0, tag(token), tws0), || {
111        ShExParseError::ExpectedToken(token.to_string())
112    })
113}
114
115/// A combinator that creates a parser for a case insensitive tag,
116/// surrounded by trailing whitespace or comments.
117pub(crate) fn tag_no_case_tws<'a>(token: &'a str) -> impl FnMut(Span<'a>) -> IRes<'a, Span<'a>> {
118    map_error(delimited(tws0, tag_no_case(token), tws0), || {
119        ShExParseError::ExpectedToken(token.to_string())
120    })
121}