Skip to main content

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, StatementBlock, statement_block};
6use crate::parser::common::{
7    BindParserError, CompoundIdentifier, Include, NomSpan, ParentType, compound_identifier,
8    many_until_eof, map_err, using_list, ws,
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 Parent<'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_parent: Parent<'a>,
29    pub additional_parents: Vec<Parent<'a>>,
30    pub optional_parents: Vec<Parent<'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_parent(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
53    let (input, kw) =
54        ws(map_err(alt((tag("node"), tag("parent"))), BindParserError::ParentKeyword))
55            .parse(input)?;
56    if kw.fragment() == &"node" {
57        return Err(nom::Err::Error(BindParserError::DeprecatedNodeKeyword(
58            kw.fragment().to_string(),
59        )));
60    }
61    Ok((input, kw))
62}
63
64fn keyword_primary(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
65    ws(map_err(tag("primary"), BindParserError::PrimaryOrOptionalKeyword)).parse(input)
66}
67
68fn keyword_optional(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
69    ws(map_err(tag("optional"), BindParserError::PrimaryOrOptionalKeyword)).parse(input)
70}
71
72fn parent_type(input: NomSpan) -> IResult<NomSpan, ParentType, BindParserError> {
73    let (input, keyword) = opt(alt((keyword_optional, keyword_primary))).parse(input)?;
74    match keyword {
75        Some(kw) => match kw.fragment() {
76            &"optional" => Ok((input, ParentType::Optional)),
77            &"primary" => Ok((input, ParentType::Primary)),
78            &&_ => Err(nom::Err::Error(BindParserError::PrimaryOrOptionalKeyword(
79                kw.fragment().to_string(),
80            ))),
81        },
82        None => Ok((input, ParentType::Additional)),
83    }
84}
85
86fn composite_name(input: NomSpan) -> IResult<NomSpan, CompoundIdentifier, BindParserError> {
87    let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
88    delimited(keyword_composite, ws(compound_identifier), terminator).parse(input)
89}
90
91fn parent_name(input: NomSpan) -> IResult<NomSpan, String, BindParserError> {
92    let escapable = escaped(is_not(r#"\""#), '\\', one_of(r#"\""#));
93    let literal = delimited(char('"'), escapable, char('"'));
94    map_err(map(literal, |s: NomSpan| s.fragment().to_string()), BindParserError::InvalidParentName)
95        .parse(input)
96}
97
98fn parent(
99    input: NomSpan,
100) -> IResult<NomSpan, (ParentType, String, Vec<Statement>), BindParserError> {
101    let (input, parent_type) = parent_type(input)?;
102    let (input, _parent) = keyword_parent(input)?;
103    let (input, parent_name) = ws(parent_name).parse(input)?;
104
105    let (input, statements) = statement_block(input)?;
106    return Ok((input, (parent_type, parent_name, statements)));
107}
108
109fn composite<'a>(input: NomSpan<'a>) -> IResult<NomSpan, Ast, BindParserError> {
110    let parents = |input: NomSpan<'a>| -> IResult<
111        NomSpan,
112        (Parent<'a>, Vec<Parent<'a>>, Vec<Parent<'a>>),
113        BindParserError,
114    > {
115        let (input, parents) = many_until_eof(ws(parent)).parse(input)?;
116        if parents.is_empty() {
117            return Err(nom::Err::Error(BindParserError::NoParents(input.to_string())));
118        }
119        let mut primary_parent = None;
120        let mut additional_parents = vec![];
121        let mut optional_parents = vec![];
122        let mut parent_names = HashSet::new();
123        for (parent_type, name, statements) in parents {
124            if parent_names.contains(&name) {
125                return Err(nom::Err::Error(BindParserError::DuplicateParentName(
126                    input.to_string(),
127                )));
128            }
129            parent_names.insert(name.clone());
130
131            match parent_type {
132                ParentType::Primary => {
133                    if primary_parent.is_some() {
134                        return Err(nom::Err::Error(BindParserError::OnePrimaryParent(
135                            input.to_string(),
136                        )));
137                    }
138                    primary_parent = Some(Parent { name: name, statements: statements });
139                }
140                ParentType::Additional => {
141                    additional_parents.push(Parent { name: name, statements: statements });
142                }
143                ParentType::Optional => {
144                    optional_parents.push(Parent { name: name, statements: statements });
145                }
146            }
147        }
148        if let Some(primary_parent) = primary_parent {
149            return Ok((input, (primary_parent, additional_parents, optional_parents)));
150        }
151        return Err(nom::Err::Error(BindParserError::OnePrimaryParent(input.to_string())));
152    };
153    map(
154        (ws(composite_name), ws(using_list), parents),
155        |(name, using, (primary_parent, additional_parents, optional_parents))| Ast {
156            name,
157            using,
158            primary_parent,
159            additional_parents,
160            optional_parents,
161        },
162    )
163    .parse(input)
164}
165
166#[cfg(test)]
167mod test {
168    use super::*;
169    use crate::make_identifier;
170    use crate::parser::bind_rules::{Condition, ConditionOp};
171    use crate::parser::common::test::check_result;
172    use crate::parser::common::{Span, Value};
173
174    mod composite_name {
175        use super::*;
176
177        #[test]
178        fn single_name() {
179            check_result(composite_name(NomSpan::new("composite a;")), "", make_identifier!["a"]);
180        }
181
182        #[test]
183        fn compound_name() {
184            check_result(
185                composite_name(NomSpan::new("composite a.b;")),
186                "",
187                make_identifier!["a", "b"],
188            );
189        }
190
191        #[test]
192        fn whitespace() {
193            check_result(
194                composite_name(NomSpan::new("composite \n\t a\n\t ;")),
195                "",
196                make_identifier!["a"],
197            );
198        }
199
200        #[test]
201        fn invalid() {
202            // Must have a name.
203            assert_eq!(
204                composite_name(NomSpan::new("composite ;")),
205                Err(nom::Err::Error(BindParserError::Identifier(";".to_string())))
206            );
207
208            // Must be terminated by ';'.
209            assert_eq!(
210                composite_name(NomSpan::new("composite a")),
211                Err(nom::Err::Error(BindParserError::Semicolon("".to_string())))
212            );
213        }
214
215        #[test]
216        fn empty() {
217            // Does not match empty string.
218            assert_eq!(
219                composite_name(NomSpan::new("")),
220                Err(nom::Err::Error(BindParserError::CompositeKeyword("".to_string())))
221            );
222        }
223    }
224
225    mod composites {
226        use super::*;
227
228        #[test]
229        fn empty() {
230            // Does not match empty string.
231            assert_eq!(
232                composite(NomSpan::new("")),
233                Err(nom::Err::Error(BindParserError::CompositeKeyword("".to_string())))
234            );
235        }
236
237        #[test]
238        fn node_keyword_error() {
239            assert_eq!(
240                composite(NomSpan::new("composite a; primary node \"bananaquit\" { true; }")),
241                Err(nom::Err::Error(BindParserError::DeprecatedNodeKeyword("node".to_string())))
242            );
243        }
244
245        #[test]
246        fn one_primary_parent() {
247            check_result(
248                composite(NomSpan::new("composite a; primary parent \"bananaquit\" { true; }")),
249                "",
250                Ast {
251                    name: make_identifier!["a"],
252                    using: vec![],
253                    primary_parent: Parent {
254                        name: "bananaquit".to_string(),
255                        statements: vec![Statement::True {
256                            span: Span { offset: 43, line: 1, fragment: "true;" },
257                        }],
258                    },
259                    additional_parents: vec![],
260                    optional_parents: vec![],
261                },
262            );
263        }
264
265        #[test]
266        fn one_primary_parent_keyword() {
267            check_result(
268                composite(NomSpan::new("composite a; primary parent \"pdev\" { true; }")),
269                "",
270                Ast {
271                    name: make_identifier!["a"],
272                    using: vec![],
273                    primary_parent: Parent {
274                        name: "pdev".to_string(),
275                        statements: vec![Statement::True {
276                            span: Span { offset: 37, line: 1, fragment: "true;" },
277                        }],
278                    },
279                    additional_parents: vec![],
280                    optional_parents: vec![],
281                },
282            );
283        }
284
285        #[test]
286        fn one_primary_parent_one_additional() {
287            check_result(
288                composite(NomSpan::new(
289                    "composite a; primary parent \"dipper\" { true; } parent \"streamcreeper\" { false; }",
290                )),
291                "",
292                Ast {
293                    name: make_identifier!["a"],
294                    using: vec![],
295                    primary_parent: Parent {
296                        name: "dipper".to_string(),
297                        statements: vec![Statement::True {
298                            span: Span { offset: 39, line: 1, fragment: "true;" },
299                        }],
300                    },
301                    additional_parents: vec![Parent {
302                        name: "streamcreeper".to_string(),
303                        statements: vec![Statement::False {
304                            span: Span { offset: 72, line: 1, fragment: "false;" },
305                        }],
306                    }],
307                    optional_parents: vec![],
308                },
309            );
310        }
311
312        #[test]
313        fn one_primary_parent_one_optional() {
314            check_result(
315                composite(NomSpan::new(
316                    "composite a; primary parent \"dipper\" { true; } optional parent \"oilbird\" { x == 1; }",
317                )),
318                "",
319                Ast {
320                    name: make_identifier!["a"],
321                    using: vec![],
322                    primary_parent: Parent {
323                        name: "dipper".to_string(),
324                        statements: vec![Statement::True {
325                            span: Span { offset: 39, line: 1, fragment: "true;" },
326                        }],
327                    },
328                    additional_parents: vec![],
329                    optional_parents: vec![Parent {
330                        name: "oilbird".to_string(),
331                        statements: vec![Statement::ConditionStatement {
332                            span: Span { offset: 75, line: 1, fragment: "x == 1;" },
333                            condition: Condition {
334                                span: Span { offset: 75, line: 1, fragment: "x == 1" },
335                                lhs: make_identifier!["x"],
336                                op: ConditionOp::Equals,
337                                rhs: Value::NumericLiteral(1),
338                            },
339                        }],
340                    }],
341                },
342            );
343        }
344
345        #[test]
346        fn one_primary_parent_one_additional_one_optional() {
347            check_result(
348                composite(NomSpan::new(
349                    "composite a; primary parent \"dipper\" { true; } parent \"streamcreeper\" { false; } optional parent \"oilbird\" { x == 1; }",
350                )),
351                "",
352                Ast {
353                    name: make_identifier!["a"],
354                    using: vec![],
355                    primary_parent: Parent {
356                        name: "dipper".to_string(),
357                        statements: vec![Statement::True {
358                            span: Span { offset: 39, line: 1, fragment: "true;" },
359                        }],
360                    },
361                    additional_parents: vec![Parent {
362                        name: "streamcreeper".to_string(),
363                        statements: vec![Statement::False {
364                            span: Span { offset: 72, line: 1, fragment: "false;" },
365                        }],
366                    }],
367                    optional_parents: vec![Parent {
368                        name: "oilbird".to_string(),
369                        statements: vec![Statement::ConditionStatement {
370                            span: Span { offset: 109, line: 1, fragment: "x == 1;" },
371                            condition: Condition {
372                                span: Span { offset: 109, line: 1, fragment: "x == 1" },
373                                lhs: make_identifier!["x"],
374                                op: ConditionOp::Equals,
375                                rhs: Value::NumericLiteral(1),
376                            },
377                        }],
378                    }],
379                },
380            );
381        }
382
383        #[test]
384        fn one_primary_parent_two_additional() {
385            check_result(
386                composite(NomSpan::new(
387                    "composite a; primary parent \"fireback\" { true; } parent \"ovenbird\" { false; } parent \"oilbird\" { x == 1; }",
388                )),
389                "",
390                Ast {
391                    name: make_identifier!["a"],
392                    using: vec![],
393                    primary_parent: Parent {
394                        name: "fireback".to_string(),
395                        statements: vec![Statement::True {
396                            span: Span { offset: 41, line: 1, fragment: "true;" },
397                        }],
398                    },
399                    additional_parents: vec![
400                        Parent {
401                            name: "ovenbird".to_string(),
402                            statements: vec![Statement::False {
403                                span: Span { offset: 69, line: 1, fragment: "false;" },
404                            }],
405                        },
406                        Parent {
407                            name: "oilbird".to_string(),
408                            statements: vec![Statement::ConditionStatement {
409                                span: Span { offset: 97, line: 1, fragment: "x == 1;" },
410                                condition: Condition {
411                                    span: Span { offset: 97, line: 1, fragment: "x == 1" },
412                                    lhs: make_identifier!["x"],
413                                    op: ConditionOp::Equals,
414                                    rhs: Value::NumericLiteral(1),
415                                },
416                            }],
417                        },
418                    ],
419                    optional_parents: vec![],
420                },
421            );
422        }
423
424        #[test]
425        fn using_list() {
426            check_result(
427                composite(NomSpan::new(
428                    "composite a; using x.y as z; primary parent \"oilbird\" { true; }",
429                )),
430                "",
431                Ast {
432                    name: make_identifier!["a"],
433                    using: vec![Include {
434                        name: make_identifier!["x", "y"],
435                        alias: Some("z".to_string()),
436                    }],
437                    primary_parent: Parent {
438                        name: "oilbird".to_string(),
439                        statements: vec![Statement::True {
440                            span: Span { offset: 56, line: 1, fragment: "true;" },
441                        }],
442                    },
443                    additional_parents: vec![],
444                    optional_parents: vec![],
445                },
446            );
447        }
448
449        #[test]
450        fn no_nodes() {
451            assert_eq!(
452                composite(NomSpan::new("composite a; using x.y as z;")),
453                Err(nom::Err::Error(BindParserError::NoParents("".to_string())))
454            );
455            assert_eq!(
456                composite(NomSpan::new("composite a;")),
457                Err(nom::Err::Error(BindParserError::NoParents("".to_string())))
458            );
459        }
460
461        #[test]
462        fn not_one_primary_parent() {
463            assert_eq!(
464                composite(NomSpan::new("composite a; parent \"chiffchaff\"{ true; }")),
465                Err(nom::Err::Error(BindParserError::OnePrimaryParent("".to_string())))
466            );
467            assert_eq!(
468                composite(NomSpan::new(
469                    "composite a; primary parent \"chiffchaff\" { true; } primary parent \"warbler\" { false; }"
470                )),
471                Err(nom::Err::Error(BindParserError::OnePrimaryParent("".to_string())))
472            );
473        }
474
475        #[test]
476        fn no_primary_parent_name() {
477            assert_eq!(
478                composite(NomSpan::new("composite a; primary parent { true; }")),
479                Err(nom::Err::Error(BindParserError::InvalidParentName("{ true; }".to_string())))
480            );
481            assert_eq!(
482                composite(NomSpan::new("composite a; primary parent chiffchaff { true; }")),
483                Err(nom::Err::Error(BindParserError::InvalidParentName(
484                    "chiffchaff { true; }".to_string()
485                )))
486            );
487
488            assert_eq!(
489                composite(NomSpan::new("composite a; primary parent chiffchaff\" { true; }")),
490                Err(nom::Err::Error(BindParserError::InvalidParentName(
491                    "chiffchaff\" { true; }".to_string()
492                )))
493            );
494        }
495
496        #[test]
497        fn no_parent_name() {
498            assert_eq!(
499                composite(NomSpan::new(
500                    "composite a; primary parent \"oilbird\" { true; } parent { x == 1; }"
501                )),
502                Err(nom::Err::Error(BindParserError::InvalidParentName("{ x == 1; }".to_string())))
503            );
504            assert_eq!(
505                composite(NomSpan::new(
506                    "composite a; primary parent \"oilbird\" { true; } parent \"warbler { x == 1; }"
507                )),
508                Err(nom::Err::Error(BindParserError::InvalidParentName("".to_string())))
509            );
510
511            assert_eq!(
512                composite(NomSpan::new(
513                    "composite a; primary parent \"oilbird\" { true; } parent warbler { x == 1; }"
514                )),
515                Err(nom::Err::Error(BindParserError::InvalidParentName(
516                    "warbler { x == 1; }".to_string()
517                )))
518            );
519        }
520
521        #[test]
522        fn duplicate_parent_names() {
523            assert_eq!(
524                composite(NomSpan::new(
525                    "composite a; primary parent \"bobolink\" { true; } parent \"bobolink\" { x == 1; }"
526                )),
527                Err(nom::Err::Error(BindParserError::DuplicateParentName("".to_string())))
528            );
529
530            assert_eq!(
531                composite(NomSpan::new(
532                    "composite a; primary parent \"bobolink\" { true; } parent \"cowbird\" { x == 1; } parent \"cowbird\" { false; }"
533                )),
534                Err(nom::Err::Error(BindParserError::DuplicateParentName("".to_string())))
535            );
536        }
537    }
538}