bind/parser/
bind_rules.rs

1// Copyright 2019 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::common::{
6    compound_identifier, condition_value, many_until_eof, map_err, skip_ws, using_list, ws,
7    BindParserError, CompoundIdentifier, Include, NomSpan, Span, Value,
8};
9use nom::branch::alt;
10use nom::bytes::complete::tag;
11use nom::combinator::{opt, value};
12use nom::multi::{many1, separated_list1};
13use nom::sequence::{delimited, preceded, terminated};
14use nom::{IResult, Parser};
15
16#[derive(Debug, Clone, PartialEq)]
17pub struct Ast<'a> {
18    pub using: Vec<Include>,
19    pub statements: StatementBlock<'a>,
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub enum ConditionOp {
24    Equals,
25    NotEquals,
26}
27
28#[derive(Debug, Clone, PartialEq)]
29pub struct Condition<'a> {
30    pub span: Span<'a>,
31    pub lhs: CompoundIdentifier,
32    pub op: ConditionOp,
33    pub rhs: Value,
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub enum Statement<'a> {
38    ConditionStatement {
39        span: Span<'a>,
40        condition: Condition<'a>,
41    },
42    Accept {
43        span: Span<'a>,
44        identifier: CompoundIdentifier,
45        values: Vec<Value>,
46    },
47    If {
48        span: Span<'a>,
49        blocks: Vec<(Condition<'a>, StatementBlock<'a>)>,
50        else_block: StatementBlock<'a>,
51    },
52    False {
53        span: Span<'a>,
54    },
55    True {
56        span: Span<'a>,
57    },
58}
59
60pub type StatementBlock<'a> = Vec<Statement<'a>>;
61
62impl<'a> Statement<'a> {
63    pub fn get_span(&'a self) -> &'a Span<'a> {
64        match self {
65            Statement::ConditionStatement { span, .. } => span,
66            Statement::Accept { span, .. } => span,
67            Statement::If { span, .. } => span,
68            Statement::False { span } => span,
69            Statement::True { span } => span,
70        }
71    }
72}
73
74// TODO(https://fxbug.dev/42110532): Improve error reporting here.
75impl<'a> TryFrom<&'a str> for Ast<'a> {
76    type Error = BindParserError;
77
78    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
79        match rules(NomSpan::new(input)) {
80            Ok((_, ast)) => Ok(ast),
81            Err(nom::Err::Error(e)) => Err(e),
82            Err(nom::Err::Failure(e)) => Err(e),
83            Err(nom::Err::Incomplete(_)) => {
84                unreachable!("Parser should never generate Incomplete errors")
85            }
86        }
87    }
88}
89
90fn condition_op(input: NomSpan) -> IResult<NomSpan, ConditionOp, BindParserError> {
91    let equals = value(ConditionOp::Equals, tag("=="));
92    let not_equals = value(ConditionOp::NotEquals, tag("!="));
93    map_err(alt((equals, not_equals)), BindParserError::ConditionOp).parse(input)
94}
95
96fn condition(input: NomSpan) -> IResult<NomSpan, Condition, BindParserError> {
97    let from = skip_ws(input)?;
98    let (input, lhs) = ws(compound_identifier).parse(from)?;
99    let (input, op) = ws(condition_op).parse(input)?;
100    let (to, rhs) = ws(condition_value).parse(input)?;
101
102    let span = Span::from_to(&from, &to);
103    Ok((to, Condition { span, lhs, op, rhs }))
104}
105
106fn condition_statement(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
107    let from = skip_ws(input)?;
108    let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
109    let (to, condition) = terminated(condition, terminator).parse(from)?;
110
111    let span = Span::from_to(&from, &to);
112    Ok((to, Statement::ConditionStatement { span, condition }))
113}
114
115fn keyword_if(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
116    ws(map_err(tag("if"), BindParserError::IfKeyword)).parse(input)
117}
118
119fn keyword_else(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
120    ws(map_err(tag("else"), BindParserError::ElseKeyword)).parse(input)
121}
122
123fn if_statement(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
124    let from = skip_ws(input)?;
125
126    let if_block = (preceded(keyword_if, condition), statement_block);
127    let mut if_blocks = separated_list1(keyword_else, if_block);
128
129    let mut else_block = preceded(keyword_else, statement_block);
130
131    let (input, blocks) = if_blocks.parse(from)?;
132    let (to, else_block) = else_block.parse(input)?;
133
134    let span = Span::from_to(&from, &to);
135    Ok((to, Statement::If { span, blocks, else_block }))
136}
137
138pub fn statement_block(input: NomSpan) -> IResult<NomSpan, Vec<Statement>, BindParserError> {
139    let block_start = ws(map_err(tag("{"), BindParserError::IfBlockStart));
140    let block_end = ws(map_err(tag("}"), BindParserError::IfBlockEnd));
141    delimited(block_start, many1(ws(statement)), block_end).parse(input)
142}
143
144fn keyword_accept(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
145    ws(map_err(tag("accept"), BindParserError::AcceptKeyword)).parse(input)
146}
147
148fn accept(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
149    let from = skip_ws(input)?;
150
151    let list_start = ws(map_err(tag("{"), BindParserError::ListStart));
152    let list_end = ws(map_err(tag("}"), BindParserError::ListEnd));
153    let separator = || ws(map_err(tag(","), BindParserError::ListSeparator));
154
155    let values = separated_list1(separator(), ws(condition_value));
156    // Lists may optionally be terminated by an additional trailing separator.
157    let values = terminated(values, opt(separator()));
158    let value_list = delimited(list_start, values, list_end);
159
160    let (to, (identifier, values)) =
161        preceded(keyword_accept, (ws(compound_identifier), value_list)).parse(from)?;
162
163    let span = Span::from_to(&from, &to);
164    Ok((to, Statement::Accept { span, identifier, values }))
165}
166
167fn keyword_false(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
168    let from = skip_ws(input)?;
169    let keyword = ws(map_err(tag("false"), BindParserError::FalseKeyword));
170    let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
171    let (to, _) = terminated(keyword, terminator).parse(from)?;
172
173    let span = Span::from_to(&from, &to);
174    Ok((to, Statement::False { span }))
175}
176
177fn keyword_true(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
178    let from = skip_ws(input)?;
179    let keyword = ws(map_err(tag("true"), BindParserError::TrueKeyword));
180    let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
181    let (to, _) = terminated(keyword, terminator).parse(from)?;
182
183    let span = Span::from_to(&from, &to);
184    Ok((to, Statement::True { span }))
185}
186
187fn statement(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
188    alt((condition_statement, if_statement, accept, keyword_false, keyword_true)).parse(input)
189}
190
191fn rules(input: NomSpan) -> IResult<NomSpan, Ast, BindParserError> {
192    let (input, using) = ws(using_list).parse(input)?;
193    let (input, statements) = many_until_eof(ws(statement)).parse(input)?;
194    if statements.is_empty() {
195        return Err(nom::Err::Error(BindParserError::NoStatements(input.to_string())));
196    }
197    Ok((input, Ast { using, statements }))
198}
199
200#[cfg(test)]
201mod test {
202    use super::*;
203    use crate::make_identifier;
204    use crate::parser::common::test::check_result;
205
206    mod condition_ops {
207        use super::*;
208
209        #[test]
210        fn equality() {
211            check_result(condition_op(NomSpan::new("==")), "", ConditionOp::Equals);
212        }
213
214        #[test]
215        fn inequality() {
216            check_result(condition_op(NomSpan::new("!=")), "", ConditionOp::NotEquals);
217        }
218
219        #[test]
220        fn invalid() {
221            assert_eq!(
222                condition_op(NomSpan::new(">=")),
223                Err(nom::Err::Error(BindParserError::ConditionOp(">=".to_string())))
224            );
225        }
226
227        #[test]
228        fn empty() {
229            assert_eq!(
230                condition_op(NomSpan::new("")),
231                Err(nom::Err::Error(BindParserError::ConditionOp("".to_string())))
232            );
233        }
234    }
235
236    mod conditions {
237        use super::*;
238
239        #[test]
240        fn equality_condition() {
241            check_result(
242                condition(NomSpan::new("abc == true")),
243                "",
244                Condition {
245                    span: Span { offset: 0, line: 1, fragment: "abc == true" },
246                    lhs: make_identifier!["abc"],
247                    op: ConditionOp::Equals,
248                    rhs: Value::BoolLiteral(true),
249                },
250            );
251        }
252
253        #[test]
254        fn empty() {
255            assert_eq!(
256                condition(NomSpan::new("")),
257                Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
258            );
259        }
260
261        #[test]
262        fn span() {
263            // Span doesn't contain leading or trailing whitespace, and offset and line number
264            // are correct.
265            check_result(
266                condition(NomSpan::new(" \n\t\r\nabc \n\t\r\n== \n\t\r\ntrue \n\t\r\n")),
267                " \n\t\r\n",
268                Condition {
269                    span: Span { offset: 5, line: 3, fragment: "abc \n\t\r\n== \n\t\r\ntrue" },
270                    lhs: make_identifier!["abc"],
271                    op: ConditionOp::Equals,
272                    rhs: Value::BoolLiteral(true),
273                },
274            );
275        }
276    }
277
278    mod if_statements {
279        use super::*;
280
281        #[test]
282        fn simple() {
283            check_result(
284                if_statement(NomSpan::new("if a == b { c == 1; } else { d == 2; }")),
285                "",
286                Statement::If {
287                    span: Span {
288                        offset: 0,
289                        line: 1,
290                        fragment: "if a == b { c == 1; } else { d == 2; }",
291                    },
292                    blocks: vec![(
293                        Condition {
294                            span: Span { offset: 3, line: 1, fragment: "a == b" },
295                            lhs: make_identifier!["a"],
296                            op: ConditionOp::Equals,
297                            rhs: Value::Identifier(make_identifier!["b"]),
298                        },
299                        vec![Statement::ConditionStatement {
300                            span: Span { offset: 12, line: 1, fragment: "c == 1;" },
301                            condition: Condition {
302                                span: Span { offset: 12, line: 1, fragment: "c == 1" },
303                                lhs: make_identifier!["c"],
304                                op: ConditionOp::Equals,
305                                rhs: Value::NumericLiteral(1),
306                            },
307                        }],
308                    )],
309                    else_block: vec![Statement::ConditionStatement {
310                        span: Span { offset: 29, line: 1, fragment: "d == 2;" },
311                        condition: Condition {
312                            span: Span { offset: 29, line: 1, fragment: "d == 2" },
313                            lhs: make_identifier!["d"],
314                            op: ConditionOp::Equals,
315                            rhs: Value::NumericLiteral(2),
316                        },
317                    }],
318                },
319            );
320        }
321
322        #[test]
323        fn else_if() {
324            check_result(
325                if_statement(NomSpan::new(
326                    "if a == b { c == 1; } else if e == 3 { d == 2; } else { f != 4; }",
327                )),
328                "",
329                Statement::If {
330                    span: Span {
331                        offset: 0,
332                        line: 1,
333                        fragment:
334                            "if a == b { c == 1; } else if e == 3 { d == 2; } else { f != 4; }",
335                    },
336                    blocks: vec![
337                        (
338                            Condition {
339                                span: Span { offset: 3, line: 1, fragment: "a == b" },
340                                lhs: make_identifier!["a"],
341                                op: ConditionOp::Equals,
342                                rhs: Value::Identifier(make_identifier!["b"]),
343                            },
344                            vec![Statement::ConditionStatement {
345                                span: Span { offset: 12, line: 1, fragment: "c == 1;" },
346                                condition: Condition {
347                                    span: Span { offset: 12, line: 1, fragment: "c == 1" },
348                                    lhs: make_identifier!["c"],
349                                    op: ConditionOp::Equals,
350                                    rhs: Value::NumericLiteral(1),
351                                },
352                            }],
353                        ),
354                        (
355                            Condition {
356                                span: Span { offset: 30, line: 1, fragment: "e == 3" },
357                                lhs: make_identifier!["e"],
358                                op: ConditionOp::Equals,
359                                rhs: Value::NumericLiteral(3),
360                            },
361                            vec![Statement::ConditionStatement {
362                                span: Span { offset: 39, line: 1, fragment: "d == 2;" },
363                                condition: Condition {
364                                    span: Span { offset: 39, line: 1, fragment: "d == 2" },
365                                    lhs: make_identifier!["d"],
366                                    op: ConditionOp::Equals,
367                                    rhs: Value::NumericLiteral(2),
368                                },
369                            }],
370                        ),
371                    ],
372                    else_block: vec![Statement::ConditionStatement {
373                        span: Span { offset: 56, line: 1, fragment: "f != 4;" },
374                        condition: Condition {
375                            span: Span { offset: 56, line: 1, fragment: "f != 4" },
376                            lhs: make_identifier!["f"],
377                            op: ConditionOp::NotEquals,
378                            rhs: Value::NumericLiteral(4),
379                        },
380                    }],
381                },
382            );
383        }
384
385        #[test]
386        fn nested() {
387            check_result(
388                if_statement(NomSpan::new(
389                    "if a == 1 { if b == 2 { c != 3; } else { c == 3; } } else { d == 2; }",
390                )),
391                "",
392                Statement::If {
393                    span: Span {
394                        offset: 0,
395                        line: 1,
396                        fragment:
397                            "if a == 1 { if b == 2 { c != 3; } else { c == 3; } } else { d == 2; }",
398                    },
399                    blocks: vec![(
400                        Condition {
401                            span: Span { offset: 3, line: 1, fragment: "a == 1" },
402                            lhs: make_identifier!["a"],
403                            op: ConditionOp::Equals,
404                            rhs: Value::NumericLiteral(1),
405                        },
406                        vec![Statement::If {
407                            span: Span {
408                                offset: 12,
409                                line: 1,
410                                fragment: "if b == 2 { c != 3; } else { c == 3; }",
411                            },
412                            blocks: vec![(
413                                Condition {
414                                    span: Span { offset: 15, line: 1, fragment: "b == 2" },
415                                    lhs: make_identifier!["b"],
416                                    op: ConditionOp::Equals,
417                                    rhs: Value::NumericLiteral(2),
418                                },
419                                vec![Statement::ConditionStatement {
420                                    span: Span { offset: 24, line: 1, fragment: "c != 3;" },
421                                    condition: Condition {
422                                        span: Span { offset: 24, line: 1, fragment: "c != 3" },
423                                        lhs: make_identifier!["c"],
424                                        op: ConditionOp::NotEquals,
425                                        rhs: Value::NumericLiteral(3),
426                                    },
427                                }],
428                            )],
429                            else_block: vec![Statement::ConditionStatement {
430                                span: Span { offset: 41, line: 1, fragment: "c == 3;" },
431                                condition: Condition {
432                                    span: Span { offset: 41, line: 1, fragment: "c == 3" },
433                                    lhs: make_identifier!["c"],
434                                    op: ConditionOp::Equals,
435                                    rhs: Value::NumericLiteral(3),
436                                },
437                            }],
438                        }],
439                    )],
440                    else_block: vec![Statement::ConditionStatement {
441                        span: Span { offset: 60, line: 1, fragment: "d == 2;" },
442                        condition: Condition {
443                            span: Span { offset: 60, line: 1, fragment: "d == 2" },
444                            lhs: make_identifier!["d"],
445                            op: ConditionOp::Equals,
446                            rhs: Value::NumericLiteral(2),
447                        },
448                    }],
449                },
450            );
451        }
452
453        #[test]
454        fn invalid() {
455            // Must have 'if' keyword.
456            assert_eq!(
457                if_statement(NomSpan::new("a == b { c == 1; }")),
458                Err(nom::Err::Error(BindParserError::IfKeyword("a == b { c == 1; }".to_string())))
459            );
460
461            // Must have condition.
462            assert_eq!(
463                if_statement(NomSpan::new("if { c == 1; }")),
464                Err(nom::Err::Error(BindParserError::Identifier("{ c == 1; }".to_string())))
465            );
466
467            // Must have else block.
468            assert_eq!(
469                if_statement(NomSpan::new("if a == b { c == 1; }")),
470                Err(nom::Err::Error(BindParserError::ElseKeyword("".to_string())))
471            );
472            assert_eq!(
473                if_statement(NomSpan::new("if a == b { c == 1; } else if e == 3 { d == 2; }")),
474                Err(nom::Err::Error(BindParserError::ElseKeyword("".to_string())))
475            );
476
477            // Must delimit blocks with {}s.
478            assert_eq!(
479                if_statement(NomSpan::new("if a == b c == 1; }")),
480                Err(nom::Err::Error(BindParserError::IfBlockStart("c == 1; }".to_string())))
481            );
482            assert_eq!(
483                if_statement(NomSpan::new("if a == b { c == 1;")),
484                Err(nom::Err::Error(BindParserError::IfBlockEnd("".to_string())))
485            );
486
487            // Blocks must not be empty.
488            // TODO(https://fxbug.dev/42110532): Improve this error message, it currently reports a failure to parse
489            // an accept statement due to the way the combinator works for the statement parser.
490            assert!(if_statement(NomSpan::new("if a == b { } else { c == 1; }")).is_err());
491            assert!(if_statement(NomSpan::new("if a == b { c == 1; } else { }")).is_err());
492        }
493
494        #[test]
495        fn empty() {
496            assert_eq!(
497                if_statement(NomSpan::new("")),
498                Err(nom::Err::Error(BindParserError::IfKeyword("".to_string())))
499            );
500        }
501    }
502
503    mod accepts {
504        use super::*;
505
506        #[test]
507        fn simple() {
508            check_result(
509                accept(NomSpan::new("accept a { 1 }")),
510                "",
511                Statement::Accept {
512                    span: Span { offset: 0, line: 1, fragment: "accept a { 1 }" },
513                    identifier: make_identifier!["a"],
514                    values: vec![Value::NumericLiteral(1)],
515                },
516            );
517        }
518
519        #[test]
520        fn multiple_values() {
521            check_result(
522                accept(NomSpan::new("accept a { 1, 2 }")),
523                "",
524                Statement::Accept {
525                    span: Span { offset: 0, line: 1, fragment: "accept a { 1, 2 }" },
526                    identifier: make_identifier!["a"],
527                    values: vec![Value::NumericLiteral(1), Value::NumericLiteral(2)],
528                },
529            );
530        }
531
532        #[test]
533        fn trailing_comma() {
534            check_result(
535                accept(NomSpan::new("accept a { 1, 2, }")),
536                "",
537                Statement::Accept {
538                    span: Span { offset: 0, line: 1, fragment: "accept a { 1, 2, }" },
539                    identifier: make_identifier!["a"],
540                    values: vec![Value::NumericLiteral(1), Value::NumericLiteral(2)],
541                },
542            );
543        }
544
545        #[test]
546        fn invalid() {
547            // Must have accept keyword.
548            assert_eq!(
549                accept(NomSpan::new("a { 1 }")),
550                Err(nom::Err::Error(BindParserError::AcceptKeyword("a { 1 }".to_string())))
551            );
552
553            // Must have identifier.
554            assert_eq!(
555                accept(NomSpan::new("accept { 1 }")),
556                Err(nom::Err::Error(BindParserError::Identifier("{ 1 }".to_string())))
557            );
558
559            // Must have at least one value.
560            assert_eq!(
561                accept(NomSpan::new("accept a { }")),
562                Err(nom::Err::Error(BindParserError::ConditionValue("}".to_string())))
563            );
564
565            // Must delimit blocks with {}s.
566            assert_eq!(
567                accept(NomSpan::new("accept a 1 }")),
568                Err(nom::Err::Error(BindParserError::ListStart("1 }".to_string())))
569            );
570            assert_eq!(
571                accept(NomSpan::new("accept a { 1")),
572                Err(nom::Err::Error(BindParserError::ListEnd("".to_string())))
573            );
574        }
575
576        #[test]
577        fn empty() {
578            assert_eq!(
579                accept(NomSpan::new("")),
580                Err(nom::Err::Error(BindParserError::AcceptKeyword("".to_string())))
581            );
582        }
583
584        #[test]
585        fn span() {
586            // Span doesn't contain leading or trailing whitespace, and line number is correct.
587            check_result(
588                accept(NomSpan::new(" \n\t\r\naccept a \n\t\r\n{ 1 } \n\t\r\n")),
589                " \n\t\r\n",
590                Statement::Accept {
591                    span: Span { offset: 5, line: 3, fragment: "accept a \n\t\r\n{ 1 }" },
592                    identifier: make_identifier!["a"],
593                    values: vec![Value::NumericLiteral(1)],
594                },
595            );
596        }
597    }
598
599    mod false_statement {
600        use super::*;
601
602        #[test]
603        fn simple() {
604            check_result(
605                keyword_false(NomSpan::new("false;")),
606                "",
607                Statement::False { span: Span { offset: 0, line: 1, fragment: "false;" } },
608            );
609        }
610
611        #[test]
612        fn invalid() {
613            // Must have false keyword.
614            assert_eq!(
615                keyword_false(NomSpan::new("a;")),
616                Err(nom::Err::Error(BindParserError::FalseKeyword("a;".to_string())))
617            );
618            assert_eq!(
619                keyword_false(NomSpan::new(";")),
620                Err(nom::Err::Error(BindParserError::FalseKeyword(";".to_string())))
621            );
622
623            // Must have semicolon.
624            assert_eq!(
625                keyword_false(NomSpan::new("false")),
626                Err(nom::Err::Error(BindParserError::Semicolon("".to_string())))
627            );
628        }
629
630        #[test]
631        fn empty() {
632            assert_eq!(
633                keyword_false(NomSpan::new("")),
634                Err(nom::Err::Error(BindParserError::FalseKeyword("".to_string())))
635            );
636        }
637
638        #[test]
639        fn span() {
640            // Span doesn't contain leading or trailing whitespace, and line number is correct.
641            check_result(
642                keyword_false(NomSpan::new(" \n\t\r\nfalse; \n\t\r\n")),
643                " \n\t\r\n",
644                Statement::False { span: Span { offset: 5, line: 3, fragment: "false;" } },
645            );
646        }
647    }
648
649    mod true_statement {
650        use super::*;
651
652        #[test]
653        fn simple() {
654            check_result(
655                keyword_true(NomSpan::new("true;")),
656                "",
657                Statement::True { span: Span { offset: 0, line: 1, fragment: "true;" } },
658            );
659        }
660
661        #[test]
662        fn invalid() {
663            // Must have true keyword.
664            assert_eq!(
665                keyword_true(NomSpan::new("p;")),
666                Err(nom::Err::Error(BindParserError::TrueKeyword("p;".to_string())))
667            );
668            assert_eq!(
669                keyword_true(NomSpan::new(";")),
670                Err(nom::Err::Error(BindParserError::TrueKeyword(";".to_string())))
671            );
672
673            // Must have semicolon.
674            assert_eq!(
675                keyword_true(NomSpan::new("true")),
676                Err(nom::Err::Error(BindParserError::Semicolon("".to_string())))
677            );
678        }
679
680        #[test]
681        fn empty() {
682            assert_eq!(
683                keyword_true(NomSpan::new("")),
684                Err(nom::Err::Error(BindParserError::TrueKeyword("".to_string())))
685            );
686        }
687
688        #[test]
689        fn span() {
690            // Span doesn't contain leading or trailing whitespace, and line number is correct.
691            check_result(
692                keyword_true(NomSpan::new(" \n\t\r\ntrue; \n\t\r\n")),
693                " \n\t\r\n",
694                Statement::True { span: Span { offset: 5, line: 3, fragment: "true;" } },
695            );
696        }
697    }
698
699    mod rules {
700        use super::*;
701
702        #[test]
703        fn simple() {
704            check_result(
705                rules(NomSpan::new("using a; x == 1;")),
706                "",
707                Ast {
708                    using: vec![Include { name: make_identifier!["a"], alias: None }],
709                    statements: vec![Statement::ConditionStatement {
710                        span: Span { offset: 9, line: 1, fragment: "x == 1;" },
711                        condition: Condition {
712                            span: Span { offset: 9, line: 1, fragment: "x == 1" },
713                            lhs: make_identifier!["x"],
714                            op: ConditionOp::Equals,
715                            rhs: Value::NumericLiteral(1),
716                        },
717                    }],
718                },
719            );
720        }
721
722        #[test]
723        fn empty() {
724            assert_eq!(
725                rules(NomSpan::new("")),
726                Err(nom::Err::Error(BindParserError::NoStatements("".to_string())))
727            );
728        }
729
730        #[test]
731        fn requires_statement() {
732            // Must have a statement.
733            assert_eq!(
734                rules(NomSpan::new("using a;")),
735                Err(nom::Err::Error(BindParserError::NoStatements("".to_string())))
736            );
737        }
738
739        #[test]
740        fn using_list_optional() {
741            check_result(
742                rules(NomSpan::new("x == 1;")),
743                "",
744                Ast {
745                    using: vec![],
746                    statements: vec![Statement::ConditionStatement {
747                        span: Span { offset: 0, line: 1, fragment: "x == 1;" },
748                        condition: Condition {
749                            span: Span { offset: 0, line: 1, fragment: "x == 1" },
750                            lhs: make_identifier!["x"],
751                            op: ConditionOp::Equals,
752                            rhs: Value::NumericLiteral(1),
753                        },
754                    }],
755                },
756            );
757        }
758
759        #[test]
760        fn requires_semicolons() {
761            // TODO(https://fxbug.dev/42110532): Improve the error type that is returned here.
762            assert_eq!(
763                rules(NomSpan::new("x == 1")),
764                Err(nom::Err::Error(BindParserError::TrueKeyword("x == 1".to_string())))
765            );
766        }
767
768        #[test]
769        fn multiple_statements() {
770            check_result(
771                rules(NomSpan::new(
772                    "x == 1; accept y { true } false; if z == 2 { a != 3; } else { a == 3; }",
773                )),
774                "",
775                Ast {
776                    using: vec![],
777                    statements: vec![
778                        Statement::ConditionStatement {
779                            span: Span { offset: 0, line: 1, fragment: "x == 1;" },
780                            condition: Condition {
781                                span: Span { offset: 0, line: 1, fragment: "x == 1" },
782                                lhs: make_identifier!["x"],
783                                op: ConditionOp::Equals,
784                                rhs: Value::NumericLiteral(1),
785                            },
786                        },
787                        Statement::Accept {
788                            span: Span { offset: 8, line: 1, fragment: "accept y { true }" },
789                            identifier: make_identifier!["y"],
790                            values: vec![Value::BoolLiteral(true)],
791                        },
792                        Statement::False { span: Span { offset: 26, line: 1, fragment: "false;" } },
793                        Statement::If {
794                            span: Span {
795                                offset: 33,
796                                line: 1,
797                                fragment: "if z == 2 { a != 3; } else { a == 3; }",
798                            },
799                            blocks: vec![(
800                                Condition {
801                                    span: Span { offset: 36, line: 1, fragment: "z == 2" },
802                                    lhs: make_identifier!["z"],
803                                    op: ConditionOp::Equals,
804                                    rhs: Value::NumericLiteral(2),
805                                },
806                                vec![Statement::ConditionStatement {
807                                    span: Span { offset: 45, line: 1, fragment: "a != 3;" },
808                                    condition: Condition {
809                                        span: Span { offset: 45, line: 1, fragment: "a != 3" },
810                                        lhs: make_identifier!["a"],
811                                        op: ConditionOp::NotEquals,
812                                        rhs: Value::NumericLiteral(3),
813                                    },
814                                }],
815                            )],
816                            else_block: vec![Statement::ConditionStatement {
817                                span: Span { offset: 62, line: 1, fragment: "a == 3;" },
818                                condition: Condition {
819                                    span: Span { offset: 62, line: 1, fragment: "a == 3" },
820                                    lhs: make_identifier!["a"],
821                                    op: ConditionOp::Equals,
822                                    rhs: Value::NumericLiteral(3),
823                                },
824                            }],
825                        },
826                    ],
827                },
828            );
829        }
830    }
831}