Skip to main content

step_p21/parser/
combinator.rs

1//! Parser combinators extended for STEP exchange structure
2//!
3//! This is helper submodule for writting a parser like as WSN definitions.
4//!
5//! Token separators in exchange structure is one of
6//!
7//! - space
8//! - explicit print control directives (`\N\` and `\F\` )
9//! - comments
10//!
11//! and combinators in this submodule responsible for handling them.
12
13use nom::{
14    IResult, Parser,
15    branch::alt,
16    bytes::complete::tag,
17    character::complete::{char, multispace0, multispace1, none_of},
18    combinator::{not, opt, peek, value},
19    multi::{many0, many1},
20};
21use nom_language::error::VerboseError;
22
23/// Parse result
24pub type ParseResult<'a, X> = IResult<&'a str, X, VerboseError<&'a str>>;
25
26/// Alias of `nom::Parser`
27pub trait ExchangeParser<'a, X>:
28    Clone + nom::Parser<&'a str, Output = X, Error = VerboseError<&'a str>>
29{
30}
31
32impl<'a, X, T> ExchangeParser<'a, X> for T where
33    T: Clone + nom::Parser<&'a str, Output = X, Error = VerboseError<&'a str>>
34{
35}
36
37pub fn char_<'a>(c: char) -> impl ExchangeParser<'a, char> {
38    move |input| {
39        let (input, c) = nom::character::complete::char(c).parse(input)?;
40        Ok((input, c))
41    }
42}
43
44pub fn tag_<'a>(name: &'static str) -> impl ExchangeParser<'a, &'a str> {
45    move |input| {
46        let (input, c) = nom::bytes::complete::tag(name).parse(input)?;
47        Ok((input, c))
48    }
49}
50
51pub fn opt_<'a, O>(
52    f: impl ExchangeParser<'a, O>,
53) -> impl ExchangeParser<'a, Option<O>> {
54    move |input| {
55        let (input, c) = nom::combinator::opt(f.clone()).parse(input)?;
56        Ok((input, c))
57    }
58}
59
60/// Comment
61///
62/// A comment shall be encoded as a solidus asterisk `/*`
63/// followed by any number of characters from the basic alphabet,
64/// and terminated by an asterisk solidus `*/`
65///
66/// These comments are dropped while parsing. Do not passed to following convert
67/// step.
68pub fn comment(input: &str) -> ParseResult<'_, String> {
69    let internal = alt((
70        none_of("*"),
71        tuple((char('*'), peek(not(char('/'))))).map(|(star, _not_slash)| star),
72    ));
73    tuple((tag("/*"), many0(internal), tag("*/")))
74        .map(|(_start, c, _end)| c.into_iter().collect())
75        .parse(input)
76}
77
78/// Comments with front/back spaces, or multi-space at least 1 char
79///
80/// - This never matches to empty string.
81/// - Drop matched comments and spaces
82///
83/// FIXME
84/// ------
85/// - support explicit print control directives
86pub fn separator(input: &str) -> ParseResult<'_, ()> {
87    let comment = many1(tuple((multispace0, comment, multispace0))).map(|_| ());
88    alt((comment, value((), multispace1))).parse(input)
89}
90
91pub fn many0_<'a, O>(
92    f: impl ExchangeParser<'a, O>,
93) -> impl ExchangeParser<'a, Vec<O>> {
94    move |input| {
95        let (input, first) = opt(f.clone()).parse(input)?;
96        if first.is_none() {
97            return Ok((input, Vec::new()));
98        };
99        let (input, tail) =
100            many0(tuple((ignorable, f.clone())).map(|(_sep, v)| v))
101                .parse(input)?;
102        let first = vec![first.unwrap()];
103        let list = first.into_iter().chain(tail).collect();
104        Ok((input, list))
105    }
106}
107
108pub fn many1_<'a, O>(
109    f: impl ExchangeParser<'a, O>,
110) -> impl ExchangeParser<'a, Vec<O>> {
111    move |input| {
112        tuple((f.clone(), many0(tuple((ignorable, f.clone())))))
113            .map(|(first, tail)| {
114                let first = vec![first];
115                let tail = tail.into_iter().map(|(_sep, val)| val);
116                first.into_iter().chain(tail).collect()
117            })
118            .parse(input)
119    }
120}
121
122pub fn ignorable(input: &str) -> ParseResult<'_, ()> {
123    let comment = many1(tuple((multispace0, comment, multispace0))).map(|_| ());
124    alt((comment, value((), multispace0))).parse(input)
125}
126
127pub fn separated<'a, O>(
128    c: char,
129    f: impl ExchangeParser<'a, O>,
130) -> impl ExchangeParser<'a, Vec<O>> {
131    move |input| {
132        tuple((
133            f.clone(),
134            many0(
135                tuple((ignorable, char(c), ignorable, f.clone()))
136                    .map(|(_sep1, _char, _sep2, value)| value),
137            ),
138        ))
139        .map(|(first, mut tails)| {
140            let mut values = vec![first];
141            values.append(&mut tails);
142            values
143        })
144        .parse(input)
145    }
146}
147
148pub fn comma_separated<'a, O>(
149    f: impl ExchangeParser<'a, O>,
150) -> impl ExchangeParser<'a, Vec<O>> {
151    separated(',', f)
152}
153
154/// Sequence of separated tokens
155pub fn tuple_<'a, O, List: Tuple<'a, O>>(
156    mut l: List,
157) -> impl ExchangeParser<'a, O> {
158    move |input| l.parse(input)
159}
160
161/// helper for [tuple_]
162pub trait Tuple<'a, O>: Clone {
163    fn parse(&mut self, input: &'a str) -> ParseResult<'a, O>;
164}
165
166/// Expand `tuple_gen!(f1, f2, f3)` to `tuple((f1, ignorable, tuple((f2,
167/// ignorable, f3))))`
168macro_rules! tuple_gen {
169    ($head:ident, $($tail:ident),*) => {
170        tuple(($head.clone(), ignorable, tuple_gen!($($tail),*)))
171    };
172    ($head:ident) => {
173        $head.clone()
174    };
175}
176
177/// Expand `match_gen!(o1, o2, o3)` to `(o1, _, (o2, _, o3))`
178macro_rules! match_gen {
179    ($head:ident, $($tail:ident),*) => {
180        ($head, _, match_gen!($($tail),*))
181    };
182    ($head:ident) => {
183        $head
184    };
185}
186
187macro_rules! impl_tuple {
188    ($($F:ident),*; $($O:ident),*; $($f:ident),*; $($o:ident),*) => {
189        impl<'a, $($F),*, $($O),*> Tuple<'a, ($($O),*)> for ($($F),*)
190        where
191            $( $F: ExchangeParser<'a, $O> ),*
192        {
193            fn parse(&mut self, input: &'a str) -> ParseResult<'a, ($($O),*)> {
194                let ($($f),*) = self;
195                tuple_gen!($($f),*)
196                    .map(|match_gen!($($o),*)| ($($o),*))
197                    .parse(input)
198            }
199        }
200    };
201}
202
203impl_tuple!(
204    F1, F2;
205    O1, O2;
206    f1, f2;
207    o1, o2
208);
209impl_tuple!(
210    F1, F2, F3;
211    O1, O2, O3;
212    f1, f2, f3;
213    o1, o2, o3
214);
215impl_tuple!(
216    F1, F2, F3, F4;
217    O1, O2, O3, O4;
218    f1, f2, f3, f4;
219    o1, o2, o3, o4
220);
221impl_tuple!(
222    F1, F2, F3, F4, F5;
223    O1, O2, O3, O4, O5;
224    f1, f2, f3, f4, f5;
225    o1, o2, o3, o4, o5
226);
227impl_tuple!(
228    F1, F2, F3, F4, F5, F6;
229    O1, O2, O3, O4, O5, O6;
230    f1, f2, f3, f4, f5, f6;
231    o1, o2, o3, o4, o5, o6
232);
233impl_tuple!(
234    F1, F2, F3, F4, F5, F6, F7;
235    O1, O2, O3, O4, O5, O6, O7;
236    f1, f2, f3, f4, f5, f6, f7;
237    o1, o2, o3, o4, o5, o6, o7
238);
239impl_tuple!(
240    F1, F2, F3, F4, F5, F6, F7, F8;
241    O1, O2, O3, O4, O5, O6, O7, O8;
242    f1, f2, f3, f4, f5, f6, f7, f8;
243    o1, o2, o3, o4, o5, o6, o7, o8
244);
245impl_tuple!(
246    F1, F2, F3, F4, F5, F6, F7, F8, F9;
247    O1, O2, O3, O4, O5, O6, O7, O8, O9;
248    f1, f2, f3, f4, f5, f6, f7, f8, f9;
249    o1, o2, o3, o4, o5, o6, o7, o8, o9
250);
251
252/// Identity shim for nom 7's `sequence::tuple`, which nom 8 removed.
253///
254/// nom 8 implements `Parser` for tuples directly, so wrapping is no longer
255/// needed -- but keeping the call sites spelled `tuple((a, b, c))` states the
256/// intent, and matches how the WSN productions read in the standard.
257pub fn tuple<T>(parsers: T) -> T {
258    parsers
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264    use crate::parser::basic::digit;
265    use nom::Finish;
266
267    #[test]
268    fn comment() {
269        let (res, c) = super::comment("/*🦀*/").finish().unwrap();
270        assert_eq!(res, "");
271        assert_eq!(c, "🦀");
272
273        let (res, c) = super::comment("/* vim * vim */").finish().unwrap();
274        assert_eq!(res, "");
275        assert_eq!(c, " vim * vim ");
276    }
277
278    #[test]
279    fn separator() {
280        let (res, _sep) = super::separator("/* comment */").finish().unwrap();
281        assert_eq!(res, "");
282
283        let (res, _sep) = super::separator("/* comment1 */ /* comment2 */")
284            .finish()
285            .unwrap();
286        assert_eq!(res, "");
287
288        let (res, _sep) = super::separator(" ").finish().unwrap();
289        assert_eq!(res, "");
290
291        assert!(super::separator("").finish().is_err());
292    }
293
294    fn tuple_digit(input: &str) -> ParseResult<'_, (char, char)> {
295        tuple_((digit, digit)).parse(input)
296    }
297
298    #[test]
299    fn tuple() {
300        let (res, (a, b)) = tuple_digit("1 /* comment */ 2").finish().unwrap();
301        assert_eq!(res, "");
302        assert_eq!(a, '1');
303        assert_eq!(b, '2');
304    }
305
306    #[test]
307    fn tuple_trailing_space() {
308        // does not match to trailing space
309        let (res, (a, b)) = tuple_digit("1 /* comment */ 2 ").finish().unwrap();
310        assert_eq!(res, " ");
311        assert_eq!(a, '1');
312        assert_eq!(b, '2');
313    }
314
315    #[test]
316    fn tuple_head_space() {
317        // does not match to head space
318        assert!(tuple_digit(" 1 /* comment */ 2").finish().is_err());
319    }
320
321    fn many0_digit(input: &str) -> ParseResult<'_, Vec<char>> {
322        many0_(digit).parse(input)
323    }
324
325    #[test]
326    fn many0() {
327        let (res, digits) =
328            many0_digit("1 /* comment */ 2 3").finish().unwrap();
329        assert_eq!(res, "");
330        assert_eq!(digits, &['1', '2', '3']);
331
332        // match to empty
333        let (res, digits) = many0_digit("").finish().unwrap();
334        assert_eq!(res, "");
335        assert!(digits.is_empty());
336
337        let (res, digits) = many1_digit("1").finish().unwrap();
338        assert_eq!(res, "");
339        assert_eq!(digits, &['1']);
340
341        // does not match to trailing space
342        let (res, digits) = many0_digit("1 /* comment */ 2 ").finish().unwrap();
343        assert_eq!(res, " ");
344        assert_eq!(digits, &['1', '2']);
345
346        // does not match to head space
347        let (res, digits) = many0_digit(" 1 /* comment */ 2").finish().unwrap();
348        assert_eq!(res, " 1 /* comment */ 2"); // match to nothing
349        assert!(digits.is_empty());
350    }
351
352    fn many1_digit(input: &str) -> ParseResult<'_, Vec<char>> {
353        many1_(digit).parse(input)
354    }
355
356    #[test]
357    fn many1() {
358        let (res, digits) =
359            many1_digit("1 /* comment */ 2 3").finish().unwrap();
360        assert_eq!(res, "");
361        assert_eq!(digits, &['1', '2', '3']);
362
363        // does not match to empty
364        assert!(many1_digit("").finish().is_err());
365
366        let (res, digits) = many1_digit("1").finish().unwrap();
367        assert_eq!(res, "");
368        assert_eq!(digits, &['1']);
369
370        // does not match to trailing space
371        let (res, digits) = many1_digit("1 /* comment */ 2 ").finish().unwrap();
372        assert_eq!(res, " ");
373        assert_eq!(digits, &['1', '2']);
374
375        // does not match to head space
376        assert!(many1_digit(" 1 /* comment */ 2").finish().is_err());
377    }
378
379    #[test]
380    fn ignorable() {
381        let (res, _) = super::ignorable("").finish().unwrap();
382        assert_eq!(res, "");
383
384        let (res, _) = super::ignorable(" ").finish().unwrap();
385        assert_eq!(res, "");
386
387        let (res, _) = super::ignorable("  ").finish().unwrap();
388        assert_eq!(res, "");
389
390        let (res, _) = super::ignorable("/* comment */").finish().unwrap();
391        assert_eq!(res, "");
392
393        let (res, _) = super::ignorable("/* comment */ ").finish().unwrap();
394        assert_eq!(res, "");
395
396        let (res, _) = super::ignorable(" /* comment */ ").finish().unwrap();
397        assert_eq!(res, "");
398    }
399
400    fn comma_digit(input: &str) -> ParseResult<'_, Vec<char>> {
401        comma_separated(digit).parse(input)
402    }
403
404    #[test]
405    fn comma() {
406        let (res, digits) = comma_digit("1,2").finish().unwrap();
407        assert_eq!(res, "");
408        assert_eq!(digits, &['1', '2']);
409
410        let (res, digits) = comma_digit("1 ,2").finish().unwrap();
411        assert_eq!(res, "");
412        assert_eq!(digits, &['1', '2']);
413
414        let (res, digits) = comma_digit("1, 2").finish().unwrap();
415        assert_eq!(res, "");
416        assert_eq!(digits, &['1', '2']);
417
418        let (res, digits) = comma_digit("1 , 2").finish().unwrap();
419        assert_eq!(res, "");
420        assert_eq!(digits, &['1', '2']);
421    }
422}