pddl/parsers/
typed_list.rs

1//! Provides the [`typed_list`] parser combinator.
2
3use nom::character::complete::char;
4use nom::combinator::map;
5use nom::multi::many0;
6use nom::sequence::{preceded, tuple};
7
8use crate::parsers::{
9    parse_type, space_separated_list0, space_separated_list1, ws, ParseResult, Span,
10};
11use crate::types::{Typed, TypedList};
12
13/// Parser combinator that parses a typed list, i.e. `x* | x⁺ - <type> <typed-list (x)>.
14///
15/// ## Example
16/// ```
17/// # use nom::character::complete::alpha1;
18/// # use pddl::parsers::{parse_name, typed_list, preamble::*};
19/// # use pddl::{Name, PrimitiveType, ToTyped, Type, Typed, TypedList};
20/// // Single implicitly typed element.
21/// assert!(typed_list(parse_name)(Span::new("abc")).is_value(TypedList::from_iter([
22///     Name::new("abc").to_typed(Type::OBJECT)
23/// ])));
24///
25/// // Multiple implicitly typed elements.
26/// assert!(typed_list(parse_name)(Span::new("abc def\nghi")).is_value(TypedList::from_iter([
27///     Name::new("abc").to_typed(Type::OBJECT),
28///     Name::new("def").to_typed(Type::OBJECT),
29///     Name::new("ghi").to_typed(Type::OBJECT)
30/// ])));
31///
32/// // Multiple explicitly typed elements.
33/// assert!(typed_list(parse_name)(Span::new("abc def - word kitchen - room")).is_value(TypedList::from_iter([
34///     Name::new("abc").to_typed("word"),
35///     Name::new("def").to_typed("word"),
36///     Name::new("kitchen").to_typed("room"),
37/// ])));
38///
39/// // Mixed
40/// assert!(typed_list(parse_name)(Span::new("abc def - word\ngeorgia - (either state country)\nuvw xyz")).is_value(TypedList::from_iter([
41///     Name::new("abc").to_typed("word"),
42///     Name::new("def").to_typed("word"),
43///     Name::new("georgia").to_typed_either(["state", "country"]),
44///     Name::new("uvw").to_typed(Type::OBJECT),
45///     Name::new("xyz").to_typed(Type::OBJECT)
46/// ])));
47/// ```
48pub fn typed_list<'a, F, O>(inner: F) -> impl FnMut(Span<'a>) -> ParseResult<'a, TypedList<O>>
49where
50    F: Clone + FnMut(Span<'a>) -> ParseResult<'a, O>,
51{
52    // `x*`
53    let implicitly_typed = map(inner.clone(), |o| Typed::new_object(o));
54    let implicitly_typed_list = space_separated_list0(implicitly_typed);
55
56    // `x⁺ - <type>`
57    let explicitly_typed = map(
58        tuple((
59            space_separated_list1(inner.clone()),
60            preceded(ws(char('-')), parse_type),
61        )),
62        |(os, t)| {
63            os.into_iter()
64                .map(move |o| Typed::new(o, t.clone()))
65                .collect::<Vec<_>>()
66        },
67    );
68
69    let typed_list_choice = tuple((
70        map(many0(explicitly_typed), |vec| {
71            vec.into_iter().flatten().collect::<Vec<_>>()
72        }),
73        implicitly_typed_list,
74    ));
75
76    map(typed_list_choice, |(mut explicit, mut implicit)| {
77        explicit.append(&mut implicit);
78        TypedList::new(explicit)
79    })
80}
81
82#[cfg(test)]
83mod tests {
84    use crate::parsers::preamble::*;
85    use crate::parsers::{parse_name, typed_list};
86    use crate::{Name, ToTyped, Type, TypedList};
87
88    #[test]
89    fn test_parse() {
90        // Single implicitly typed element.
91        assert!(
92            typed_list(parse_name)(Span::new("abc"))
93                .is_value(TypedList::from_iter([
94                    Name::new("abc").to_typed(Type::OBJECT)
95                ]))
96        );
97
98        // Multiple implicitly typed elements.
99        assert!(
100            typed_list(parse_name)(Span::new("abc def\nghi")).is_value(TypedList::from_iter([
101                Name::new("abc").to_typed(Type::OBJECT),
102                Name::new("def").to_typed(Type::OBJECT),
103                Name::new("ghi").to_typed(Type::OBJECT)
104            ]))
105        );
106
107        // Multiple explicitly typed elements.
108        assert!(
109            typed_list(parse_name)(Span::new("abc def - word kitchen - room")).is_value(
110                TypedList::from_iter([
111                    Name::new("abc").to_typed("word"),
112                    Name::new("def").to_typed("word"),
113                    Name::new("kitchen").to_typed("room"),
114                ])
115            )
116        );
117
118        // Mixed
119        assert!(typed_list(parse_name)(Span::new(
120            "abc def - word\ngeorgia - (either state country)\nuvw xyz"
121        ))
122        .is_value(TypedList::from_iter([
123            Name::new("abc").to_typed("word"),
124            Name::new("def").to_typed("word"),
125            Name::new("georgia").to_typed_either(["state", "country"]),
126            Name::new("uvw").to_typed(Type::OBJECT),
127            Name::new("xyz").to_typed(Type::OBJECT)
128        ])));
129    }
130}