pdl_compiler/
parser.rs

1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::ast;
16use codespan_reporting::diagnostic::Diagnostic;
17use codespan_reporting::files;
18use pest::iterators::{Pair, Pairs};
19use pest::{Parser, Token};
20use std::iter::{Filter, Peekable};
21
22// Generate the PDL parser.
23//
24// TODO:
25// - use #[grammar = "pdl.pest"]
26//   currently not possible because CARGO_MANIFEST_DIR is not set
27//   in soong environment.
28// - use silent atomic rules for keywords like
29//   ENUM = @{ "enum" ~ WHITESPACE }
30//   currently not implemented in pest:
31//   https://github.com/pest-parser/pest/issues/520
32#[derive(pest_derive::Parser)]
33#[grammar_inline = r#"
34WHITESPACE = _{ " " | "\n" | "\r" | "\t" }
35COMMENT = { block_comment | line_comment }
36
37block_comment = { "/*" ~ (!"*/" ~ ANY)* ~ "*/" }
38line_comment = { "//" ~ (!"\n" ~ ANY)* }
39
40alpha = { 'a'..'z' | 'A'..'Z' }
41digit = { '0'..'9' }
42hexdigit = { digit | 'a'..'f' | 'A'..'F' }
43alphanum = { alpha | digit | "_" }
44
45identifier = @{ alpha ~ alphanum* }
46payload_identifier = @{ "_payload_" }
47body_identifier = @{ "_body_" }
48intvalue = @{ digit+ }
49hexvalue = @{ ("0x"|"0X") ~ hexdigit+ }
50integer = @{ hexvalue | intvalue }
51string = @{ "\"" ~ (!"\"" ~ ANY)* ~ "\"" }
52size_modifier = @{ "+" ~ intvalue }
53
54ENUM = @{ "enum" ~ WHITESPACE }
55PACKET = @{ "packet" ~ WHITESPACE }
56STRUCT = @{ "struct" ~ WHITESPACE }
57GROUP = @{ "group" ~ WHITESPACE }
58CHECKSUM = @{ "checksum" ~ WHITESPACE }
59CUSTOM_FIELD = @{ "custom_field" ~ WHITESPACE }
60TEST = @{ "test" ~ WHITESPACE }
61
62endianness_declaration = ${ ("little_endian_packets" | "big_endian_packets") ~ WHITESPACE }
63
64enum_value = { identifier ~ "=" ~ integer }
65enum_value_list = { enum_value ~ ("," ~ enum_value)* ~ ","? }
66enum_range = {
67    identifier ~ "=" ~ integer ~ ".." ~ integer ~ ("{" ~
68        enum_value_list ~
69    "}")?
70}
71enum_other = { identifier ~ "=" ~ ".." }
72enum_tag = { enum_range | enum_value | enum_other }
73enum_tag_list = { enum_tag ~ ("," ~ enum_tag)* ~ ","? }
74enum_declaration = {
75    ENUM ~ identifier ~ ":" ~ integer ~ "{" ~
76        enum_tag_list ~
77    "}"
78}
79
80constraint = { identifier ~ "=" ~ (identifier|integer) }
81constraint_list = { constraint ~ ("," ~ constraint)* }
82
83checksum_field = { "_checksum_start_" ~ "(" ~ identifier ~ ")" }
84padding_field = { "_padding_" ~ "[" ~ integer ~ "]" }
85size_field = { "_size_" ~ "(" ~ (identifier|payload_identifier|body_identifier)  ~ ")" ~ ":" ~ integer }
86count_field = { "_count_" ~ "(" ~ identifier ~ ")" ~ ":" ~ integer }
87elementsize_field = { "_elementsize_" ~ "(" ~ identifier ~ ")" ~ ":" ~ integer }
88body_field = @{ "_body_" }
89payload_field = { "_payload_" ~ (":" ~ "[" ~ size_modifier ~ "]")? }
90fixed_field = { "_fixed_" ~ "=" ~ (
91    (integer ~ ":" ~ integer) |
92    (identifier ~ ":" ~ identifier)
93)}
94reserved_field = { "_reserved_" ~ ":" ~ integer }
95array_field = { identifier ~ ":" ~ (integer|identifier) ~
96    "[" ~ (size_modifier|integer)? ~ "]"
97}
98scalar_field = { identifier ~ ":" ~ integer }
99typedef_field = { identifier ~ ":" ~ identifier }
100group_field = { identifier ~ ("{" ~ constraint_list? ~ "}")? }
101
102field_desc = _{
103    checksum_field |
104    padding_field |
105    size_field |
106    count_field |
107    elementsize_field |
108    body_field |
109    payload_field |
110    fixed_field |
111    reserved_field |
112    array_field |
113    scalar_field |
114    typedef_field |
115    group_field
116}
117field = { field_desc ~ ("if" ~ constraint)? }
118field_list = { field ~ ("," ~ field)* ~ ","? }
119
120packet_declaration = {
121   PACKET ~ identifier ~
122        (":" ~ identifier)? ~
123           ("(" ~ constraint_list ~ ")")? ~
124    "{" ~
125        field_list? ~
126    "}"
127}
128
129struct_declaration = {
130    STRUCT ~ identifier ~
131        (":" ~ identifier)? ~
132           ("(" ~ constraint_list ~ ")")? ~
133    "{" ~
134        field_list? ~
135    "}"
136}
137
138group_declaration = {
139    GROUP ~ identifier ~ "{" ~ field_list ~ "}"
140}
141
142checksum_declaration = {
143    CHECKSUM ~ identifier ~ ":" ~ integer ~ string
144}
145
146custom_field_declaration = {
147    CUSTOM_FIELD ~ identifier ~ (":" ~ integer)? ~ string
148}
149
150test_case = { string }
151test_case_list = _{ test_case ~ ("," ~ test_case)* ~ ","? }
152test_declaration = {
153    TEST ~ identifier ~ "{" ~
154        test_case_list ~
155    "}"
156}
157
158declaration = _{
159    enum_declaration |
160    packet_declaration |
161    struct_declaration |
162    group_declaration |
163    checksum_declaration |
164    custom_field_declaration |
165    test_declaration
166}
167
168file = {
169    SOI ~
170    endianness_declaration ~
171    declaration* ~
172    EOI
173}
174"#]
175pub struct PDLParser;
176
177type Node<'i> = Pair<'i, Rule>;
178type NodeIterator<'i> = Peekable<Filter<Pairs<'i, Rule>, fn(&Node<'i>) -> bool>>;
179struct Context<'a> {
180    file: ast::FileId,
181    line_starts: &'a Vec<usize>,
182    key: std::cell::Cell<usize>,
183}
184
185trait Helpers<'i> {
186    fn children(self) -> NodeIterator<'i>;
187    fn as_loc(&self, context: &Context) -> ast::SourceRange;
188    fn as_string(&self) -> String;
189    fn as_usize(&self) -> Result<usize, String>;
190}
191
192impl Context<'_> {
193    fn field_key(&self) -> ast::FieldKey {
194        ast::FieldKey(self.key.replace(self.key.get() + 1))
195    }
196
197    fn decl_key(&self) -> ast::DeclKey {
198        ast::DeclKey(self.key.replace(self.key.get() + 1))
199    }
200}
201
202impl<'i> Helpers<'i> for Node<'i> {
203    fn children(self) -> NodeIterator<'i> {
204        self.into_inner().filter((|n| n.as_rule() != Rule::COMMENT) as fn(&Self) -> bool).peekable()
205    }
206
207    fn as_loc(&self, context: &Context) -> ast::SourceRange {
208        let span = self.as_span();
209        ast::SourceRange {
210            file: context.file,
211            start: ast::SourceLocation::new(span.start_pos().pos(), context.line_starts),
212            end: ast::SourceLocation::new(span.end_pos().pos(), context.line_starts),
213        }
214    }
215
216    fn as_string(&self) -> String {
217        self.as_str().to_owned()
218    }
219
220    fn as_usize(&self) -> Result<usize, String> {
221        let text = self.as_str();
222        if let Some(num) = text.strip_prefix("0x") {
223            usize::from_str_radix(num, 16)
224                .map_err(|_| format!("cannot convert '{}' to usize", self.as_str()))
225        } else {
226            #[allow(clippy::from_str_radix_10)]
227            usize::from_str_radix(text, 10)
228                .map_err(|_| format!("cannot convert '{}' to usize", self.as_str()))
229        }
230    }
231}
232
233fn err_unexpected_rule<T>(expected: Rule, found: Rule) -> Result<T, String> {
234    Err(format!("expected rule {:?}, got {:?}", expected, found))
235}
236
237fn err_missing_rule<T>(expected: Rule) -> Result<T, String> {
238    Err(format!("expected rule {:?}, got nothing", expected))
239}
240
241fn expect<'i>(iter: &mut impl Iterator<Item = Node<'i>>, rule: Rule) -> Result<Node<'i>, String> {
242    match iter.next() {
243        Some(node) if node.as_rule() == rule => Ok(node),
244        Some(node) => err_unexpected_rule(rule, node.as_rule()),
245        None => err_missing_rule(rule),
246    }
247}
248
249fn maybe<'i>(iter: &mut NodeIterator<'i>, rule: Rule) -> Option<Node<'i>> {
250    iter.next_if(|n| n.as_rule() == rule)
251}
252
253fn parse_identifier(iter: &mut NodeIterator<'_>) -> Result<String, String> {
254    expect(iter, Rule::identifier).map(|n| n.as_string())
255}
256
257fn parse_integer(iter: &mut NodeIterator<'_>) -> Result<usize, String> {
258    expect(iter, Rule::integer).and_then(|n| n.as_usize())
259}
260
261fn parse_identifier_opt(iter: &mut NodeIterator<'_>) -> Result<Option<String>, String> {
262    Ok(maybe(iter, Rule::identifier).map(|n| n.as_string()))
263}
264
265fn parse_integer_opt(iter: &mut NodeIterator<'_>) -> Result<Option<usize>, String> {
266    maybe(iter, Rule::integer).map(|n| n.as_usize()).transpose()
267}
268
269fn parse_identifier_or_integer(
270    iter: &mut NodeIterator<'_>,
271) -> Result<(Option<String>, Option<usize>), String> {
272    match iter.next() {
273        Some(n) if n.as_rule() == Rule::identifier => Ok((Some(n.as_string()), None)),
274        Some(n) if n.as_rule() == Rule::integer => Ok((None, Some(n.as_usize()?))),
275        Some(n) => Err(format!(
276            "expected rule {:?} or {:?}, got {:?}",
277            Rule::identifier,
278            Rule::integer,
279            n.as_rule()
280        )),
281        None => {
282            Err(format!("expected rule {:?} or {:?}, got nothing", Rule::identifier, Rule::integer))
283        }
284    }
285}
286
287fn parse_string<'i>(iter: &mut impl Iterator<Item = Node<'i>>) -> Result<String, String> {
288    expect(iter, Rule::string)
289        .map(|n| n.as_str())
290        .and_then(|s| s.strip_prefix('"').ok_or_else(|| "expected \" prefix".to_owned()))
291        .and_then(|s| s.strip_suffix('"').ok_or_else(|| "expected \" suffix".to_owned()))
292        .map(|s| s.to_owned())
293}
294
295fn parse_size_modifier_opt(iter: &mut NodeIterator<'_>) -> Option<String> {
296    maybe(iter, Rule::size_modifier).map(|n| n.as_string())
297}
298
299fn parse_endianness(node: Node<'_>, context: &Context) -> Result<ast::Endianness, String> {
300    if node.as_rule() != Rule::endianness_declaration {
301        err_unexpected_rule(Rule::endianness_declaration, node.as_rule())
302    } else {
303        Ok(ast::Endianness {
304            loc: node.as_loc(context),
305            value: match node.as_str().trim() {
306                "little_endian_packets" => ast::EndiannessValue::LittleEndian,
307                "big_endian_packets" => ast::EndiannessValue::BigEndian,
308                _ => unreachable!(),
309            },
310        })
311    }
312}
313
314fn parse_constraint(node: Node<'_>, context: &Context) -> Result<ast::Constraint, String> {
315    if node.as_rule() != Rule::constraint {
316        err_unexpected_rule(Rule::constraint, node.as_rule())
317    } else {
318        let loc = node.as_loc(context);
319        let mut children = node.children();
320        let id = parse_identifier(&mut children)?;
321        let (tag_id, value) = parse_identifier_or_integer(&mut children)?;
322        Ok(ast::Constraint { id, loc, value, tag_id })
323    }
324}
325
326fn parse_constraint_list_opt(
327    iter: &mut NodeIterator<'_>,
328    context: &Context,
329) -> Result<Vec<ast::Constraint>, String> {
330    maybe(iter, Rule::constraint_list)
331        .map_or(Ok(vec![]), |n| n.children().map(|n| parse_constraint(n, context)).collect())
332}
333
334fn parse_enum_value(node: Node<'_>, context: &Context) -> Result<ast::TagValue, String> {
335    if node.as_rule() != Rule::enum_value {
336        err_unexpected_rule(Rule::enum_value, node.as_rule())
337    } else {
338        let loc = node.as_loc(context);
339        let mut children = node.children();
340        let id = parse_identifier(&mut children)?;
341        let value = parse_integer(&mut children)?;
342        Ok(ast::TagValue { id, loc, value })
343    }
344}
345
346fn parse_enum_value_list_opt(
347    iter: &mut NodeIterator<'_>,
348    context: &Context,
349) -> Result<Vec<ast::TagValue>, String> {
350    maybe(iter, Rule::enum_value_list)
351        .map_or(Ok(vec![]), |n| n.children().map(|n| parse_enum_value(n, context)).collect())
352}
353
354fn parse_enum_range(node: Node<'_>, context: &Context) -> Result<ast::TagRange, String> {
355    if node.as_rule() != Rule::enum_range {
356        err_unexpected_rule(Rule::enum_range, node.as_rule())
357    } else {
358        let loc = node.as_loc(context);
359        let mut children = node.children();
360        let id = parse_identifier(&mut children)?;
361        let start = parse_integer(&mut children)?;
362        let end = parse_integer(&mut children)?;
363        let tags = parse_enum_value_list_opt(&mut children, context)?;
364        Ok(ast::TagRange { id, loc, range: start..=end, tags })
365    }
366}
367
368fn parse_enum_other(node: Node<'_>, context: &Context) -> Result<ast::TagOther, String> {
369    if node.as_rule() != Rule::enum_other {
370        err_unexpected_rule(Rule::enum_other, node.as_rule())
371    } else {
372        let loc = node.as_loc(context);
373        let mut children = node.children();
374        let id = parse_identifier(&mut children)?;
375        Ok(ast::TagOther { id, loc })
376    }
377}
378
379fn parse_enum_tag(node: Node<'_>, context: &Context) -> Result<ast::Tag, String> {
380    if node.as_rule() != Rule::enum_tag {
381        err_unexpected_rule(Rule::enum_tag, node.as_rule())
382    } else {
383        match node.children().next() {
384            Some(node) if node.as_rule() == Rule::enum_value => {
385                Ok(ast::Tag::Value(parse_enum_value(node, context)?))
386            }
387            Some(node) if node.as_rule() == Rule::enum_range => {
388                Ok(ast::Tag::Range(parse_enum_range(node, context)?))
389            }
390            Some(node) if node.as_rule() == Rule::enum_other => {
391                Ok(ast::Tag::Other(parse_enum_other(node, context)?))
392            }
393            Some(node) => Err(format!(
394                "expected rule {:?} or {:?}, got {:?}",
395                Rule::enum_value,
396                Rule::enum_range,
397                node.as_rule()
398            )),
399            None => Err(format!(
400                "expected rule {:?} or {:?}, got nothing",
401                Rule::enum_value,
402                Rule::enum_range
403            )),
404        }
405    }
406}
407
408fn parse_enum_tag_list(
409    iter: &mut NodeIterator<'_>,
410    context: &Context,
411) -> Result<Vec<ast::Tag>, String> {
412    expect(iter, Rule::enum_tag_list)
413        .and_then(|n| n.children().map(|n| parse_enum_tag(n, context)).collect())
414}
415
416fn parse_field(node: Node<'_>, context: &Context) -> Result<ast::Field, String> {
417    let loc = node.as_loc(context);
418    let mut children = node.children();
419    let desc = children.next().unwrap();
420    let cond = children.next();
421    let rule = desc.as_rule();
422    let mut children = desc.children();
423    Ok(ast::Field {
424        loc,
425        key: context.field_key(),
426        cond: cond.map(|constraint| parse_constraint(constraint, context)).transpose()?,
427        desc: match rule {
428            Rule::checksum_field => {
429                let field_id = parse_identifier(&mut children)?;
430                ast::FieldDesc::Checksum { field_id }
431            }
432            Rule::padding_field => {
433                let size = parse_integer(&mut children)?;
434                ast::FieldDesc::Padding { size }
435            }
436            Rule::size_field => {
437                let field_id = match children.next() {
438                    Some(n) if n.as_rule() == Rule::identifier => n.as_string(),
439                    Some(n) if n.as_rule() == Rule::payload_identifier => n.as_string(),
440                    Some(n) if n.as_rule() == Rule::body_identifier => n.as_string(),
441                    Some(n) => err_unexpected_rule(Rule::identifier, n.as_rule())?,
442                    None => err_missing_rule(Rule::identifier)?,
443                };
444                let width = parse_integer(&mut children)?;
445                ast::FieldDesc::Size { field_id, width }
446            }
447            Rule::count_field => {
448                let field_id = parse_identifier(&mut children)?;
449                let width = parse_integer(&mut children)?;
450                ast::FieldDesc::Count { field_id, width }
451            }
452            Rule::elementsize_field => {
453                let field_id = parse_identifier(&mut children)?;
454                let width = parse_integer(&mut children)?;
455                ast::FieldDesc::ElementSize { field_id, width }
456            }
457            Rule::body_field => ast::FieldDesc::Body,
458            Rule::payload_field => {
459                let size_modifier = parse_size_modifier_opt(&mut children);
460                ast::FieldDesc::Payload { size_modifier }
461            }
462            Rule::fixed_field => match children.next() {
463                Some(n) if n.as_rule() == Rule::integer => {
464                    let value = n.as_usize()?;
465                    let width = parse_integer(&mut children)?;
466                    ast::FieldDesc::FixedScalar { width, value }
467                }
468                Some(n) if n.as_rule() == Rule::identifier => {
469                    let tag_id = n.as_string();
470                    let enum_id = parse_identifier(&mut children)?;
471                    ast::FieldDesc::FixedEnum { enum_id, tag_id }
472                }
473                _ => unreachable!(),
474            },
475            Rule::reserved_field => {
476                let width = parse_integer(&mut children)?;
477                ast::FieldDesc::Reserved { width }
478            }
479            Rule::array_field => {
480                let id = parse_identifier(&mut children)?;
481                let (type_id, width) = parse_identifier_or_integer(&mut children)?;
482                let (size, size_modifier) = match children.next() {
483                    Some(n) if n.as_rule() == Rule::integer => (Some(n.as_usize()?), None),
484                    Some(n) if n.as_rule() == Rule::size_modifier => (None, Some(n.as_string())),
485                    Some(n) => {
486                        return Err(format!(
487                            "expected rule {:?} or {:?}, got {:?}",
488                            Rule::integer,
489                            Rule::size_modifier,
490                            n.as_rule()
491                        ))
492                    }
493                    None => (None, None),
494                };
495                ast::FieldDesc::Array { id, type_id, width, size, size_modifier }
496            }
497            Rule::scalar_field => {
498                let id = parse_identifier(&mut children)?;
499                let width = parse_integer(&mut children)?;
500                ast::FieldDesc::Scalar { id, width }
501            }
502            Rule::typedef_field => {
503                let id = parse_identifier(&mut children)?;
504                let type_id = parse_identifier(&mut children)?;
505                ast::FieldDesc::Typedef { id, type_id }
506            }
507            Rule::group_field => {
508                let group_id = parse_identifier(&mut children)?;
509                let constraints = parse_constraint_list_opt(&mut children, context)?;
510                ast::FieldDesc::Group { group_id, constraints }
511            }
512            _ => return Err(format!("expected rule *_field, got {:?}", rule)),
513        },
514    })
515}
516
517fn parse_field_list(iter: &mut NodeIterator, context: &Context) -> Result<Vec<ast::Field>, String> {
518    expect(iter, Rule::field_list)
519        .and_then(|n| n.children().map(|n| parse_field(n, context)).collect())
520}
521
522fn parse_field_list_opt(
523    iter: &mut NodeIterator,
524    context: &Context,
525) -> Result<Vec<ast::Field>, String> {
526    maybe(iter, Rule::field_list)
527        .map_or(Ok(vec![]), |n| n.children().map(|n| parse_field(n, context)).collect())
528}
529
530fn parse_toplevel(root: Node<'_>, context: &Context) -> Result<ast::File, String> {
531    let mut toplevel_comments = vec![];
532    let mut file = ast::File::new(context.file);
533
534    let mut comment_start = vec![];
535    for token in root.clone().tokens() {
536        match token {
537            Token::Start { rule: Rule::COMMENT, pos } => comment_start.push(pos),
538            Token::End { rule: Rule::COMMENT, pos } => {
539                let start_pos = comment_start.pop().unwrap();
540                file.comments.push(ast::Comment {
541                    loc: ast::SourceRange {
542                        file: context.file,
543                        start: ast::SourceLocation::new(start_pos.pos(), context.line_starts),
544                        end: ast::SourceLocation::new(pos.pos(), context.line_starts),
545                    },
546                    text: start_pos.span(&pos).as_str().to_owned(),
547                })
548            }
549            _ => (),
550        }
551    }
552
553    for node in root.children() {
554        let loc = node.as_loc(context);
555        let rule = node.as_rule();
556        match rule {
557            Rule::endianness_declaration => file.endianness = parse_endianness(node, context)?,
558            Rule::checksum_declaration => {
559                let mut children = node.children();
560                expect(&mut children, Rule::CHECKSUM)?;
561                let id = parse_identifier(&mut children)?;
562                let width = parse_integer(&mut children)?;
563                let function = parse_string(&mut children)?;
564                file.declarations.push(ast::Decl {
565                    loc,
566                    key: context.decl_key(),
567                    desc: ast::DeclDesc::Checksum { id, function, width },
568                })
569            }
570            Rule::custom_field_declaration => {
571                let mut children = node.children();
572                expect(&mut children, Rule::CUSTOM_FIELD)?;
573                let id = parse_identifier(&mut children)?;
574                let width = parse_integer_opt(&mut children)?;
575                let function = parse_string(&mut children)?;
576                file.declarations.push(ast::Decl {
577                    loc,
578                    key: context.decl_key(),
579                    desc: ast::DeclDesc::CustomField { id, function, width },
580                })
581            }
582            Rule::enum_declaration => {
583                let mut children = node.children();
584                expect(&mut children, Rule::ENUM)?;
585                let id = parse_identifier(&mut children)?;
586                let width = parse_integer(&mut children)?;
587                let tags = parse_enum_tag_list(&mut children, context)?;
588                file.declarations.push(ast::Decl {
589                    loc,
590                    key: context.decl_key(),
591                    desc: ast::DeclDesc::Enum { id, width, tags },
592                })
593            }
594            Rule::packet_declaration => {
595                let mut children = node.children();
596                expect(&mut children, Rule::PACKET)?;
597                let id = parse_identifier(&mut children)?;
598                let parent_id = parse_identifier_opt(&mut children)?;
599                let constraints = parse_constraint_list_opt(&mut children, context)?;
600                let fields = parse_field_list_opt(&mut children, context)?;
601                file.declarations.push(ast::Decl {
602                    loc,
603                    key: context.decl_key(),
604                    desc: ast::DeclDesc::Packet { id, parent_id, constraints, fields },
605                })
606            }
607            Rule::struct_declaration => {
608                let mut children = node.children();
609                expect(&mut children, Rule::STRUCT)?;
610                let id = parse_identifier(&mut children)?;
611                let parent_id = parse_identifier_opt(&mut children)?;
612                let constraints = parse_constraint_list_opt(&mut children, context)?;
613                let fields = parse_field_list_opt(&mut children, context)?;
614                file.declarations.push(ast::Decl {
615                    loc,
616                    key: context.decl_key(),
617                    desc: ast::DeclDesc::Struct { id, parent_id, constraints, fields },
618                })
619            }
620            Rule::group_declaration => {
621                let mut children = node.children();
622                expect(&mut children, Rule::GROUP)?;
623                let id = parse_identifier(&mut children)?;
624                let fields = parse_field_list(&mut children, context)?;
625                file.declarations.push(ast::Decl {
626                    loc,
627                    key: context.decl_key(),
628                    desc: ast::DeclDesc::Group { id, fields },
629                })
630            }
631            Rule::test_declaration => {}
632            Rule::EOI => (),
633            _ => unreachable!(),
634        }
635    }
636    file.comments.append(&mut toplevel_comments);
637    file.max_key = context.key.get();
638    Ok(file)
639}
640
641/// Parse PDL source code from a string.
642///
643/// The file is added to the compilation database under the provided
644/// name.
645pub fn parse_inline(
646    sources: &mut ast::SourceDatabase,
647    name: &str,
648    source: String,
649) -> Result<ast::File, Diagnostic<ast::FileId>> {
650    let root = PDLParser::parse(Rule::file, &source)
651        .map_err(|e| {
652            Diagnostic::error()
653                .with_message(format!("failed to parse input file '{}': {}", name, e))
654        })?
655        .next()
656        .unwrap();
657    let line_starts: Vec<_> = files::line_starts(&source).collect();
658    let file = sources.add(name.to_owned(), source.clone());
659    parse_toplevel(root, &Context { file, line_starts: &line_starts, key: std::cell::Cell::new(0) })
660        .map_err(|e| Diagnostic::error().with_message(e))
661}
662
663/// Parse a new source file.
664///
665/// The source file is fully read and added to the compilation
666/// database. Returns the constructed AST, or a descriptive error
667/// message in case of syntax error.
668pub fn parse_file(
669    sources: &mut ast::SourceDatabase,
670    name: &str,
671) -> Result<ast::File, Diagnostic<ast::FileId>> {
672    let source = std::fs::read_to_string(name).map_err(|e| {
673        Diagnostic::error().with_message(format!("failed to read input file '{}': {}", name, e))
674    })?;
675    parse_inline(sources, name, source)
676}
677
678#[cfg(test)]
679mod test {
680    use super::*;
681
682    #[test]
683    fn endianness_is_set() {
684        // The file starts out with a placeholder little-endian value.
685        // This tests that we update it while parsing.
686        let mut db = ast::SourceDatabase::new();
687        let file = parse_inline(&mut db, "stdin", String::from("  big_endian_packets  ")).unwrap();
688        assert_eq!(file.endianness.value, ast::EndiannessValue::BigEndian);
689        assert_ne!(file.endianness.loc, ast::SourceRange::default());
690    }
691
692    #[test]
693    fn test_parse_string_bare() {
694        let mut pairs = PDLParser::parse(Rule::string, r#""test""#).unwrap();
695
696        assert_eq!(parse_string(&mut pairs).as_deref(), Ok("test"));
697        assert_eq!(pairs.next(), None, "pairs is empty");
698    }
699
700    #[test]
701    fn test_parse_string_space() {
702        let mut pairs = PDLParser::parse(Rule::string, r#""test with space""#).unwrap();
703
704        assert_eq!(parse_string(&mut pairs).as_deref(), Ok("test with space"));
705        assert_eq!(pairs.next(), None, "pairs is empty");
706    }
707
708    #[test]
709    #[should_panic] /* This is not supported */
710    fn test_parse_string_escape() {
711        let mut pairs = PDLParser::parse(Rule::string, r#""\"test\"""#).unwrap();
712
713        assert_eq!(parse_string(&mut pairs).as_deref(), Ok(r#""test""#));
714        assert_eq!(pairs.next(), None, "pairs is empty");
715    }
716
717    #[test]
718    fn test_no_whitespace_between_keywords() {
719        // Validate that the parser rejects inputs where whitespaces
720        // are not applied between alphabetical keywords and identifiers.
721        let mut db = ast::SourceDatabase::new();
722        assert!(parse_inline(
723            &mut db,
724            "test",
725            r#"
726            little_endian_packetsstructx{foo:8}
727            "#
728            .to_owned()
729        )
730        .is_err());
731
732        let result = parse_inline(
733            &mut db,
734            "test",
735            r#"
736            little_endian_packets
737            struct x { foo:8 }
738            "#
739            .to_owned(),
740        );
741        println!("{:?}", result);
742        assert!(result.is_ok());
743    }
744}