bind/parser/
bind_composite.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::parser::bind_rules::{statement_block, Statement, StatementBlock};
6use crate::parser::common::{
7    compound_identifier, many_until_eof, map_err, using_list, ws, BindParserError,
8    CompoundIdentifier, Include, NodeType, NomSpan,
9};
10use nom::branch::alt;
11use nom::bytes::complete::{escaped, is_not, tag};
12use nom::character::complete::{char, one_of};
13use nom::combinator::{map, opt};
14use nom::sequence::delimited;
15use nom::{IResult, Parser};
16use std::collections::HashSet;
17
18#[derive(Debug, PartialEq)]
19pub struct Node<'a> {
20    pub name: String,
21    pub statements: StatementBlock<'a>,
22}
23
24#[derive(Debug, PartialEq)]
25pub struct Ast<'a> {
26    pub name: CompoundIdentifier,
27    pub using: Vec<Include>,
28    pub primary_node: Node<'a>,
29    pub additional_nodes: Vec<Node<'a>>,
30    pub optional_nodes: Vec<Node<'a>>,
31}
32
33impl<'a> TryFrom<&'a str> for Ast<'a> {
34    type Error = BindParserError;
35
36    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
37        match composite(NomSpan::new(input)) {
38            Ok((_, ast)) => Ok(ast),
39            Err(nom::Err::Error(e)) => Err(e),
40            Err(nom::Err::Failure(e)) => Err(e),
41            Err(nom::Err::Incomplete(_)) => {
42                unreachable!("Parser should never generate Incomplete errors")
43            }
44        }
45    }
46}
47
48fn keyword_composite(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
49    ws(map_err(tag("composite"), BindParserError::CompositeKeyword)).parse(input)
50}
51
52fn keyword_node(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
53    ws(map_err(tag("node"), BindParserError::NodeKeyword)).parse(input)
54}
55
56fn keyword_primary(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
57    ws(map_err(tag("primary"), BindParserError::PrimaryOrOptionalKeyword)).parse(input)
58}
59
60fn keyword_optional(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
61    ws(map_err(tag("optional"), BindParserError::PrimaryOrOptionalKeyword)).parse(input)
62}
63
64fn node_type(input: NomSpan) -> IResult<NomSpan, NodeType, BindParserError> {
65    let (input, keyword) = opt(alt((keyword_optional, keyword_primary))).parse(input)?;
66    match keyword {
67        Some(kw) => match kw.fragment() {
68            &"optional" => Ok((input, NodeType::Optional)),
69            &"primary" => Ok((input, NodeType::Primary)),
70            &&_ => Err(nom::Err::Error(BindParserError::PrimaryOrOptionalKeyword(
71                kw.fragment().to_string(),
72            ))),
73        },
74        None => Ok((input, NodeType::Additional)),
75    }
76}
77
78fn composite_name(input: NomSpan) -> IResult<NomSpan, CompoundIdentifier, BindParserError> {
79    let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
80    delimited(keyword_composite, ws(compound_identifier), terminator).parse(input)
81}
82
83fn node_name(input: NomSpan) -> IResult<NomSpan, String, BindParserError> {
84    let escapable = escaped(is_not(r#"\""#), '\\', one_of(r#"\""#));
85    let literal = delimited(char('"'), escapable, char('"'));
86    map_err(map(literal, |s: NomSpan| s.fragment().to_string()), BindParserError::InvalidNodeName)
87        .parse(input)
88}
89
90fn node(input: NomSpan) -> IResult<NomSpan, (NodeType, String, Vec<Statement>), BindParserError> {
91    let (input, node_type) = node_type(input)?;
92    let (input, _node) = keyword_node(input)?;
93    let (input, node_name) = ws(node_name).parse(input)?;
94
95    let (input, statements) = statement_block(input)?;
96    return Ok((input, (node_type, node_name, statements)));
97}
98
99fn composite<'a>(input: NomSpan<'a>) -> IResult<NomSpan, Ast, BindParserError> {
100    let nodes =
101        |input: NomSpan<'a>| -> IResult<NomSpan, (Node<'a>, Vec<Node<'a>>, Vec<Node<'a>>), BindParserError> {
102            let (input, nodes) = many_until_eof(ws(node)).parse(input)?;
103            if nodes.is_empty() {
104                return Err(nom::Err::Error(BindParserError::NoNodes(input.to_string())));
105            }
106            let mut primary_node = None;
107            let mut additional_nodes = vec![];
108            let mut optional_nodes = vec![];
109            let mut node_names = HashSet::new();
110            for (node_type, name, statements) in nodes {
111                if node_names.contains(&name) {
112                    return Err(nom::Err::Error(BindParserError::DuplicateNodeName(
113                        input.to_string(),
114                    )));
115                }
116                node_names.insert(name.clone());
117
118                match node_type {
119                    NodeType::Primary => {
120                        if primary_node.is_some() {
121                            return Err(nom::Err::Error(BindParserError::OnePrimaryNode(
122                                input.to_string(),
123                            )));
124                        }
125                        primary_node = Some(Node { name: name, statements: statements });
126                    }
127                    NodeType::Additional => {
128                        additional_nodes.push(Node { name: name, statements: statements });
129                    },
130                    NodeType::Optional => {
131                        optional_nodes.push(Node { name: name, statements: statements });
132                    },
133                }
134            }
135            if let Some(primary_node) = primary_node {
136                return Ok((input, (primary_node, additional_nodes, optional_nodes)));
137            }
138            return Err(nom::Err::Error(BindParserError::OnePrimaryNode(input.to_string())));
139        };
140    map(
141        (ws(composite_name), ws(using_list), nodes),
142        |(name, using, (primary_node, additional_nodes, optional_nodes))| Ast {
143            name,
144            using,
145            primary_node,
146            additional_nodes,
147            optional_nodes,
148        },
149    )
150    .parse(input)
151}
152
153#[cfg(test)]
154mod test {
155    use super::*;
156    use crate::make_identifier;
157    use crate::parser::bind_rules::{Condition, ConditionOp};
158    use crate::parser::common::test::check_result;
159    use crate::parser::common::{Span, Value};
160
161    mod composite_name {
162        use super::*;
163
164        #[test]
165        fn single_name() {
166            check_result(composite_name(NomSpan::new("composite a;")), "", make_identifier!["a"]);
167        }
168
169        #[test]
170        fn compound_name() {
171            check_result(
172                composite_name(NomSpan::new("composite a.b;")),
173                "",
174                make_identifier!["a", "b"],
175            );
176        }
177
178        #[test]
179        fn whitespace() {
180            check_result(
181                composite_name(NomSpan::new("composite \n\t a\n\t ;")),
182                "",
183                make_identifier!["a"],
184            );
185        }
186
187        #[test]
188        fn invalid() {
189            // Must have a name.
190            assert_eq!(
191                composite_name(NomSpan::new("composite ;")),
192                Err(nom::Err::Error(BindParserError::Identifier(";".to_string())))
193            );
194
195            // Must be terminated by ';'.
196            assert_eq!(
197                composite_name(NomSpan::new("composite a")),
198                Err(nom::Err::Error(BindParserError::Semicolon("".to_string())))
199            );
200        }
201
202        #[test]
203        fn empty() {
204            // Does not match empty string.
205            assert_eq!(
206                composite_name(NomSpan::new("")),
207                Err(nom::Err::Error(BindParserError::CompositeKeyword("".to_string())))
208            );
209        }
210    }
211
212    mod composites {
213        use super::*;
214
215        #[test]
216        fn empty() {
217            // Does not match empty string.
218            assert_eq!(
219                composite(NomSpan::new("")),
220                Err(nom::Err::Error(BindParserError::CompositeKeyword("".to_string())))
221            );
222        }
223
224        #[test]
225        fn one_primary_node() {
226            check_result(
227                composite(NomSpan::new("composite a; primary node \"bananaquit\" { true; }")),
228                "",
229                Ast {
230                    name: make_identifier!["a"],
231                    using: vec![],
232                    primary_node: Node {
233                        name: "bananaquit".to_string(),
234                        statements: vec![Statement::True {
235                            span: Span { offset: 41, line: 1, fragment: "true;" },
236                        }],
237                    },
238                    additional_nodes: vec![],
239                    optional_nodes: vec![],
240                },
241            );
242        }
243
244        #[test]
245        fn one_primary_node_one_additional() {
246            check_result(
247                composite(NomSpan::new("composite a; primary node \"dipper\" { true; } node \"streamcreeper\" { false; }")),
248                "",
249                Ast {
250                    name: make_identifier!["a"],
251                    using: vec![],
252                    primary_node: Node {
253                        name: "dipper".to_string(),
254                        statements: vec![Statement::True {
255                        span: Span { offset: 37, line: 1, fragment: "true;" },
256                    }]},
257                    additional_nodes: vec![Node {
258                        name: "streamcreeper".to_string(),
259                        statements: vec![Statement::False {
260                        span: Span { offset: 68, line: 1, fragment: "false;" },
261                    }]}],
262                    optional_nodes: vec![],
263                },
264            );
265        }
266
267        #[test]
268        fn one_primary_node_one_optional() {
269            check_result(
270                composite(NomSpan::new("composite a; primary node \"dipper\" { true; } optional node \"oilbird\" { x == 1; }")),
271                "",
272                Ast {
273                    name: make_identifier!["a"],
274                    using: vec![],
275                    primary_node: Node {
276                        name: "dipper".to_string(),
277                        statements: vec![Statement::True {
278                        span: Span { offset: 37, line: 1, fragment: "true;" },
279                    }]},
280                    additional_nodes: vec![],
281                    optional_nodes: vec![Node {
282                        name: "oilbird".to_string(),
283                        statements:
284                        vec![Statement::ConditionStatement {
285                            span: Span { offset: 71, line: 1, fragment: "x == 1;" },
286                            condition: Condition {
287                                span: Span { offset: 71, line: 1, fragment: "x == 1" },
288                                lhs: make_identifier!["x"],
289                                op: ConditionOp::Equals,
290                                rhs: Value::NumericLiteral(1),
291                            },
292                        }],
293                    }],
294                },
295            );
296        }
297
298        #[test]
299        fn one_primary_node_one_additional_one_optional() {
300            check_result(
301                composite(NomSpan::new("composite a; primary node \"dipper\" { true; } node \"streamcreeper\" { false; } optional node \"oilbird\" { x == 1; }")),
302                "",
303                Ast {
304                    name: make_identifier!["a"],
305                    using: vec![],
306                    primary_node: Node {
307                        name: "dipper".to_string(),
308                        statements: vec![Statement::True {
309                        span: Span { offset: 37, line: 1, fragment: "true;" },
310                    }]},
311                    additional_nodes: vec![Node {
312                        name: "streamcreeper".to_string(),
313                        statements: vec![Statement::False {
314                        span: Span { offset: 68, line: 1, fragment: "false;" },
315                    }]}],
316                    optional_nodes: vec![Node {
317                        name: "oilbird".to_string(),
318                        statements:
319                        vec![Statement::ConditionStatement {
320                            span: Span { offset: 103, line: 1, fragment: "x == 1;" },
321                            condition: Condition {
322                                span: Span { offset: 103, line: 1, fragment: "x == 1" },
323                                lhs: make_identifier!["x"],
324                                op: ConditionOp::Equals,
325                                rhs: Value::NumericLiteral(1),
326                            },
327                        }],
328                    }],
329                },
330            );
331        }
332
333        #[test]
334        fn one_primary_node_two_additional() {
335            check_result(
336                composite(NomSpan::new(
337                    "composite a; primary node \"fireback\" { true; } node \"ovenbird\" { false; } node \"oilbird\" { x == 1; }",
338                )),
339                "",
340                Ast {
341                    name: make_identifier!["a"],
342                    using: vec![],
343                    primary_node: Node {
344                        name: "fireback".to_string(),
345                        statements: vec![Statement::True {
346                        span: Span { offset: 39, line: 1, fragment: "true;" },
347                        }]
348                    },
349                    additional_nodes: vec![
350                        Node {
351                            name: "ovenbird".to_string(),
352                            statements: vec![Statement::False {
353                            span: Span { offset: 65, line: 1, fragment: "false;" },
354                        }]
355                    },
356                    Node {
357                        name: "oilbird".to_string(),
358                        statements:
359                        vec![Statement::ConditionStatement {
360                            span: Span { offset: 91, line: 1, fragment: "x == 1;" },
361                            condition: Condition {
362                                span: Span { offset: 91, line: 1, fragment: "x == 1" },
363                                lhs: make_identifier!["x"],
364                                op: ConditionOp::Equals,
365                                rhs: Value::NumericLiteral(1),
366                            },
367                        }],
368                    }
369                    ],
370                    optional_nodes: vec![],
371                },
372            );
373        }
374
375        #[test]
376        fn using_list() {
377            check_result(
378                composite(NomSpan::new(
379                    "composite a; using x.y as z; primary node \"oilbird\" { true; }",
380                )),
381                "",
382                Ast {
383                    name: make_identifier!["a"],
384                    using: vec![Include {
385                        name: make_identifier!["x", "y"],
386                        alias: Some("z".to_string()),
387                    }],
388                    primary_node: Node {
389                        name: "oilbird".to_string(),
390                        statements: vec![Statement::True {
391                            span: Span { offset: 54, line: 1, fragment: "true;" },
392                        }],
393                    },
394                    additional_nodes: vec![],
395                    optional_nodes: vec![],
396                },
397            );
398        }
399
400        #[test]
401        fn no_nodes() {
402            assert_eq!(
403                composite(NomSpan::new("composite a; using x.y as z;")),
404                Err(nom::Err::Error(BindParserError::NoNodes("".to_string())))
405            );
406            assert_eq!(
407                composite(NomSpan::new("composite a;")),
408                Err(nom::Err::Error(BindParserError::NoNodes("".to_string())))
409            );
410        }
411
412        #[test]
413        fn not_one_primary_node() {
414            assert_eq!(
415                composite(NomSpan::new("composite a; node \"chiffchaff\"{ true; }")),
416                Err(nom::Err::Error(BindParserError::OnePrimaryNode("".to_string())))
417            );
418            assert_eq!(
419                composite(NomSpan::new(
420                    "composite a; primary node \"chiffchaff\" { true; } primary node \"warbler\" { false; }"
421                )),
422                Err(nom::Err::Error(BindParserError::OnePrimaryNode("".to_string())))
423            );
424        }
425
426        #[test]
427        fn no_primary_node_name() {
428            assert_eq!(
429                composite(NomSpan::new("composite a; primary node { true; }")),
430                Err(nom::Err::Error(BindParserError::InvalidNodeName("{ true; }".to_string())))
431            );
432            assert_eq!(
433                composite(NomSpan::new("composite a; primary node chiffchaff { true; }")),
434                Err(nom::Err::Error(BindParserError::InvalidNodeName(
435                    "chiffchaff { true; }".to_string()
436                )))
437            );
438
439            assert_eq!(
440                composite(NomSpan::new("composite a; primary node chiffchaff\" { true; }")),
441                Err(nom::Err::Error(BindParserError::InvalidNodeName(
442                    "chiffchaff\" { true; }".to_string()
443                )))
444            );
445        }
446
447        #[test]
448        fn no_node_name() {
449            assert_eq!(
450                composite(NomSpan::new(
451                    "composite a; primary node \"oilbird\" { true; } node { x == 1; }"
452                )),
453                Err(nom::Err::Error(BindParserError::InvalidNodeName("{ x == 1; }".to_string())))
454            );
455            assert_eq!(
456                composite(NomSpan::new(
457                    "composite a; primary node \"oilbird\" { true; } node \"warbler { x == 1; }"
458                )),
459                Err(nom::Err::Error(BindParserError::InvalidNodeName("".to_string())))
460            );
461
462            assert_eq!(
463                composite(NomSpan::new(
464                    "composite a; primary node \"oilbird\" { true; } node warbler { x == 1; }"
465                )),
466                Err(nom::Err::Error(BindParserError::InvalidNodeName(
467                    "warbler { x == 1; }".to_string()
468                )))
469            );
470        }
471
472        #[test]
473        fn duplicate_node_names() {
474            assert_eq!(
475                composite(NomSpan::new(
476                    "composite a; primary node \"bobolink\" { true; } node \"bobolink\" { x == 1; }"
477                )),
478                Err(nom::Err::Error(BindParserError::DuplicateNodeName(
479                    "".to_string()
480                )))
481            );
482
483            assert_eq!(
484                composite(NomSpan::new(
485                    "composite a; primary node \"bobolink\" { true; } node \"cowbird\" { x == 1; } node \"cowbird\" { false; }"
486                )),
487                Err(nom::Err::Error(BindParserError::DuplicateNodeName(
488                    "".to_string()
489                )))
490            );
491        }
492    }
493}