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