Skip to main content

device_driver_parser/
lib.rs

1use std::{borrow::Cow, fmt::Display, num::NonZeroU32};
2
3use chumsky::{
4    IterParser, Parser,
5    error::Rich,
6    extra,
7    input::{Input, MappedInput},
8    prelude::{choice, just, recursive},
9    select,
10};
11use device_driver_common::{
12    span::{Span, SpanExt, Spanned},
13    specifiers::{Access, AddressMode, BaseType, ByteOrder, Integer},
14};
15use device_driver_diagnostics::{Diagnostics, errors::ParsingError};
16use device_driver_lexer::Token;
17
18use crate::parse_num::{ParseIntRadix, ParseIntRadixError, ParseIntRadixErrorKind, parse_num};
19
20#[cfg(feature = "gen-docs")]
21pub mod gen_docs;
22mod parse_num;
23
24pub fn parse<'src>(tokens: &[Spanned<Token<'src>>], diagnostics: &mut Diagnostics) -> Ast<'src> {
25    let (ast, parse_errs) = node()
26        .map_with(|ast, e| (ast, e.span()))
27        .parse(
28            tokens.map(
29                tokens
30                    .last()
31                    .map(|t| Span::from(t.span.end..t.span.end))
32                    .unwrap_or_default(),
33                |token| (&token.value, &token.span),
34            ),
35        )
36        .into_output_errors();
37
38    for error in parse_errs {
39        diagnostics.add(ParsingError {
40            reason: error.to_string(),
41            span: *error.span(),
42        });
43    }
44
45    ast.map(|(root_node, span)| Ast {
46        root_node: Some(root_node),
47        span,
48    })
49    .unwrap_or_default()
50}
51
52// Don't forget to update the book when parsers are added, changed or removed!
53#[derive(Debug, Default)]
54pub struct Ast<'src> {
55    pub root_node: Option<Node<'src>>,
56    pub span: Span,
57}
58
59#[derive(Debug, Clone)]
60pub struct Node<'src> {
61    pub doc_comments: Vec<Spanned<&'src str>>,
62    pub node_type: Ident<'src>,
63    pub name: Ident<'src>,
64    pub repeat: Option<Spanned<Repeat<'src>>>,
65    pub type_specifier: Option<Spanned<TypeSpecifier<'src>>>,
66    pub short_properties: Vec<Spanned<Expression<'src>>>,
67    pub properties: Vec<Spanned<Property<'src>>>,
68    pub sub_nodes: Vec<Node<'src>>,
69    pub span: Span,
70}
71
72impl<'src> Display for Node<'src> {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        let indentation_level = f.width().unwrap_or_default();
75        let indentation = format!("{:width$}", "", width = indentation_level * 4);
76
77        for doc_comment in &self.doc_comments {
78            writeln!(
79                f,
80                "{indentation}///{}{doc_comment}",
81                if doc_comment.starts_with(" ") {
82                    ""
83                } else {
84                    " "
85                }
86            )?;
87        }
88        write!(f, "{indentation}{} {}", self.node_type.val, self.name.val)?;
89
90        if let Some(repeat) = self.repeat {
91            write!(f, "[{} stride {}]", repeat.source, repeat.stride)?;
92        }
93
94        for expression in self.short_properties.iter() {
95            write!(f, " {}", expression.get_human_string())?;
96        }
97
98        if let Some(type_specifier) = self.type_specifier.as_ref() {
99            write!(f, " -> {}", type_specifier.base_type)?;
100
101            if let Some(conversion) = type_specifier.conversion.as_ref() {
102                write!(f, " as")?;
103                if type_specifier.use_try {
104                    write!(f, " try")?;
105                }
106
107                match conversion {
108                    TypeConversion::Reference(ident) => write!(f, " {}", ident.val)?,
109                    TypeConversion::Subnode(node) => {
110                        if node.doc_comments.is_empty() {
111                            for (i, line) in node.to_string().lines().enumerate() {
112                                if i == 0 {
113                                    write!(f, " {line}")?;
114                                } else {
115                                    write!(f, "\n{indentation}{line}")?;
116                                }
117                            }
118                        } else {
119                            write!(f, "\n{node:width$}", width = indentation_level + 1)?;
120                        }
121                    }
122                }
123            }
124        }
125
126        if !self.sub_nodes.is_empty() || !self.properties.is_empty() {
127            writeln!(f, " {{")?;
128
129            for property in self.properties.iter() {
130                for doc_comment in property.doc_comments.iter() {
131                    writeln!(
132                        f,
133                        "{indentation}    ///{}{}",
134                        if doc_comment.starts_with(" ") {
135                            ""
136                        } else {
137                            " "
138                        },
139                        doc_comment
140                    )?;
141                }
142
143                write!(f, "{indentation}    {}:", property.name.val)?;
144
145                let expression = property.expression.get_human_string();
146
147                if expression.starts_with("///") {
148                    for line in expression.lines() {
149                        write!(f, "\n{indentation}        {line}")?;
150                    }
151                } else {
152                    for (i, line) in expression.lines().enumerate() {
153                        if i == 0 {
154                            write!(f, " {line}")?;
155                        } else {
156                            write!(f, "\n{indentation}    {line}")?;
157                        }
158                    }
159                }
160
161                writeln!(f, ",")?;
162            }
163
164            if !self.properties.is_empty() && !self.sub_nodes.is_empty() {
165                writeln!(f, "{indentation}",)?;
166            }
167
168            for node in self.sub_nodes.iter() {
169                writeln!(f, "{node:width$},", width = indentation_level + 1)?;
170            }
171
172            write!(f, "{indentation}}}")?;
173        }
174
175        Ok(())
176    }
177}
178
179#[derive(Debug, Clone)]
180pub struct TypeSpecifier<'src> {
181    pub base_type: Spanned<BaseType>,
182    pub use_try: bool,
183    pub conversion: Option<TypeConversion<'src>>,
184}
185
186#[derive(Debug, Clone)]
187pub enum TypeConversion<'src> {
188    Reference(Ident<'src>),
189    Subnode(Box<Node<'src>>),
190}
191
192#[derive(Debug, Clone)]
193pub struct Property<'src> {
194    pub doc_comments: Vec<Spanned<&'src str>>,
195    pub name: Ident<'src>,
196    pub expression: Spanned<Expression<'src>>,
197}
198
199#[derive(Debug, Clone)]
200pub enum Expression<'src> {
201    AddressRange { end: i128, start: i128 },
202    ByteArray(Vec<u8>),
203    BaseType(BaseType),
204    Integer(Integer),
205    Allow,
206    Number(i128),
207    DefaultNumber(Option<i128>),
208    CatchAllNumber(Option<i128>),
209    String(&'src str),
210    Access(Access),
211    ByteOrder(ByteOrder),
212    TypeReference(Ident<'src>),
213    SubNode(Box<Node<'src>>),
214    Auto,
215    AddressMode(AddressMode),
216    Error,
217}
218
219impl<'src> Expression<'src> {
220    pub fn as_range(&self) -> Option<(i128, i128)> {
221        if let Self::AddressRange { end, start } = self {
222            Some((*end, *start))
223        } else {
224            None
225        }
226    }
227
228    pub fn as_byte_order(&self) -> Option<ByteOrder> {
229        if let Self::ByteOrder(v) = self {
230            Some(*v)
231        } else {
232            None
233        }
234    }
235
236    pub fn as_access(&self) -> Option<Access> {
237        if let Self::Access(v) = self {
238            Some(*v)
239        } else {
240            None
241        }
242    }
243
244    pub fn as_integer(&self) -> Option<Integer> {
245        if let Self::Integer(v) = self {
246            Some(*v)
247        } else {
248            None
249        }
250    }
251
252    pub fn as_unsigned_integer(&self) -> Option<Integer> {
253        if let Self::Integer(v) = self {
254            Some(*v)
255        } else {
256            None
257        }
258    }
259
260    pub fn as_number(&self) -> Option<i128> {
261        if let Self::Number(v) = self {
262            Some(*v)
263        } else {
264            None
265        }
266    }
267
268    pub fn as_string(&self) -> Option<&'src str> {
269        if let Self::String(v) = self {
270            Some(*v)
271        } else {
272            None
273        }
274    }
275
276    pub fn as_address_mode(&self) -> Option<AddressMode> {
277        if let Self::AddressMode(v) = self {
278            Some(*v)
279        } else {
280            None
281        }
282    }
283
284    pub fn get_human_string(&self) -> Cow<'static, str> {
285        match self {
286            Expression::AddressRange { end, start } => format!("{end}:{start}").into(),
287            Expression::ByteArray(items) => format!("{items:?}").into(),
288            Expression::BaseType(base_type) => base_type.to_string().into(),
289            Expression::Integer(integer) => integer.to_string().into(),
290            Expression::Allow => "allow".into(),
291            Expression::Number(num) => num.to_string().into(),
292            Expression::DefaultNumber(Some(num)) => format!("default {num}").into(),
293            Expression::DefaultNumber(None) => "default _".into(),
294            Expression::CatchAllNumber(Some(num)) => format!("catch-all {num}").into(),
295            Expression::CatchAllNumber(None) => "catch-all _".into(),
296            Expression::String(val) => format!("\"{val}\"").into(),
297            Expression::Access(val) => val.to_string().into(),
298            Expression::ByteOrder(val) => val.to_string().into(),
299            Expression::TypeReference(ident) => ident.val.to_string().into(),
300            Expression::SubNode(val) => val.to_string().into(),
301            Expression::Auto => "_".into(),
302            Expression::AddressMode(val) => val.to_string().into(),
303            Expression::Error => "ERROR".into(),
304        }
305    }
306}
307
308impl<'src> Display for Expression<'src> {
309    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310        match self {
311            Expression::AddressRange { .. } => write!(f, "range"),
312            Expression::ByteArray(_) => write!(f, "[bytes]"),
313            Expression::BaseType(_) => write!(f, "base type"),
314            Expression::Integer(_) => write!(f, "integer type"),
315            Expression::Allow => write!(f, "allow"),
316            Expression::Number(_) => write!(f, "number"),
317            Expression::DefaultNumber(None) => write!(f, "default auto"),
318            Expression::CatchAllNumber(None) => write!(f, "catch-all auto"),
319            Expression::DefaultNumber(Some(_)) => write!(f, "default number"),
320            Expression::CatchAllNumber(Some(_)) => write!(f, "catch-all number"),
321            Expression::String(_) => write!(f, "string"),
322            Expression::Access(_) => write!(f, "access specifier"),
323            Expression::ByteOrder(_) => write!(f, "byte order"),
324            Expression::TypeReference(_) => write!(f, "type reference"),
325            Expression::SubNode(_) => write!(f, "sub node"),
326            Expression::Auto => write!(f, "auto"),
327            Expression::AddressMode(_) => write!(f, "address mode"),
328            Expression::Error => write!(f, "error"),
329        }
330    }
331}
332
333#[derive(Debug, Clone, Copy, Default)]
334pub struct Repeat<'src> {
335    pub source: Spanned<RepeatSource<'src>>,
336    pub stride: Spanned<i32>,
337}
338
339#[derive(Debug, Clone, Copy)]
340pub enum RepeatSource<'src> {
341    Count(NonZeroU32),
342    Enum(Ident<'src>),
343}
344
345impl<'src> Default for RepeatSource<'src> {
346    fn default() -> Self {
347        Self::Count(1.try_into().unwrap())
348    }
349}
350
351impl Display for RepeatSource<'_> {
352    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353        match self {
354            RepeatSource::Count(non_zero) => write!(f, "{non_zero}"),
355            RepeatSource::Enum(ident) => write!(f, "{}", ident.val),
356        }
357    }
358}
359
360#[derive(Debug, Clone, Copy)]
361pub struct Ident<'src> {
362    pub val: &'src str,
363    pub span: Span,
364    is_auto: bool,
365}
366
367impl<'src> Ident<'src> {
368    pub const fn new(val: &'src str, span: Span) -> Self {
369        Self {
370            val,
371            span,
372            is_auto: false,
373        }
374    }
375
376    pub const fn new_no_span(val: &'src str) -> Self {
377        Self {
378            val,
379            span: Span::empty(),
380            is_auto: false,
381        }
382    }
383
384    pub const fn new_auto(span: Span) -> Self {
385        Self {
386            val: "_",
387            span,
388            is_auto: true,
389        }
390    }
391
392    /// Returns true if the identifier was specified using an underscore token
393    pub fn is_auto(&self) -> bool {
394        self.is_auto
395    }
396}
397
398fn try_num<'tokens, 'src: 'tokens, I: ParseIntRadix>(
399    num_str: &'src str,
400    span: Span,
401) -> Result<I, RichErr<'tokens, 'src>> {
402    match parse_num::<I>(num_str) {
403        Ok(num) => Ok(num),
404        Err(ParseIntRadixError {
405            source,
406            kind,
407            target_bits,
408            target_signed,
409        }) => match kind {
410            ParseIntRadixErrorKind::Overflow => Err(Rich::custom(
411                span,
412                format!(
413                    "number `{source}` is parsed as a {}{target_bits}, but overflows.",
414                    if target_signed { 'i' } else { 'u' }
415                ),
416            )),
417            ParseIntRadixErrorKind::Underflow => Err(Rich::custom(
418                span,
419                format!(
420                    "number `{source}` is parsed as a {}{target_bits}, but underflows.",
421                    if target_signed { 'i' } else { 'u' }
422                ),
423            )),
424            ParseIntRadixErrorKind::Empty => Err(Rich::custom(
425                span,
426                format!("could not parse `{source}` as a number because it contains no numbers"),
427            )),
428            ParseIntRadixErrorKind::Zero => {
429                Err(Rich::custom(span, "number can't be 0 in this position"))
430            }
431        },
432    }
433}
434
435pub type InputType<'tokens, 'src> =
436    MappedInput<'tokens, Token<'src>, Span, &'tokens [Spanned<Token<'src>>]>;
437pub type RichErr<'tokens, 'src> = Rich<'tokens, Token<'src>, Span>;
438pub type RichExtra<'tokens, 'src> = extra::Err<RichErr<'tokens, 'src>>;
439
440pub fn ident<'tokens, 'src: 'tokens>(
441    allow_auto: bool,
442) -> impl Parser<'tokens, InputType<'tokens, 'src>, Ident<'src>, RichExtra<'tokens, 'src>> + Clone {
443    select! {
444        Token::Ident(val) = e => Ident::new(val, e.span()),
445        Token::Underscore = e if allow_auto => Ident::new_auto(e.span()),
446    }
447    .labelled(format!(
448        "Ident{}",
449        if allow_auto { "|Underscore" } else { "" }
450    ))
451    .as_terminal()
452}
453
454pub fn doc_comment<'tokens, 'src: 'tokens>()
455-> impl Parser<'tokens, InputType<'tokens, 'src>, Spanned<&'src str>, RichExtra<'tokens, 'src>> + Copy
456{
457    select! {
458        Token::DocCommentLine(val) => val
459    }
460    .map_with(|line, extra| line.spanned(extra.span()))
461    .labelled("DocCommentLine")
462    .as_terminal()
463}
464
465pub fn num<'tokens, 'src: 'tokens, I: ParseIntRadix>()
466-> impl Parser<'tokens, InputType<'tokens, 'src>, I, RichExtra<'tokens, 'src>> + Clone {
467    select! {
468        Token::Num(num) => num
469    }
470    .try_map(try_num::<I>)
471    .labelled(format!(
472        "Num<{}>",
473        // Get the type name of the integer, excluding module path
474        std::any::type_name::<I>().split("::").last().unwrap()
475    ))
476    .as_terminal()
477}
478
479pub fn range<'tokens, 'src: 'tokens>()
480-> impl Parser<'tokens, InputType<'tokens, 'src>, Expression<'src>, RichExtra<'tokens, 'src>> + Clone
481{
482    num::<i128>()
483        .then_ignore(just(Token::Colon))
484        .then(num::<i128>())
485        .map(|(end, start)| Expression::AddressRange { end, start })
486        .labelled("range")
487}
488
489pub fn base_type<'tokens, 'src: 'tokens>()
490-> impl Parser<'tokens, InputType<'tokens, 'src>, BaseType, RichExtra<'tokens, 'src>> + Copy {
491    select! { Token::BaseType(bt) => bt }
492        .labelled("BaseType")
493        .as_terminal()
494}
495
496pub fn integer<'tokens, 'src: 'tokens>()
497-> impl Parser<'tokens, InputType<'tokens, 'src>, Integer, RichExtra<'tokens, 'src>> + Copy {
498    select! { Token::Integer(i) => i }
499        .labelled("Integer")
500        .as_terminal()
501}
502
503pub fn byte_array<'tokens, 'src: 'tokens>()
504-> impl Parser<'tokens, InputType<'tokens, 'src>, Expression<'src>, RichExtra<'tokens, 'src>> + Clone
505{
506    num::<u8>()
507        .separated_by(just(Token::Comma))
508        .collect::<Vec<_>>()
509        .map(Expression::ByteArray)
510        .then_ignore(just(Token::Comma).or_not())
511        .delimited_by(just(Token::BracketOpen), just(Token::BracketClose))
512        .labelled("byte-array")
513}
514
515/// Expression without type reference since that clashes with nodes
516pub fn simple_expression<'tokens, 'src: 'tokens>()
517-> impl Parser<'tokens, InputType<'tokens, 'src>, Spanned<Expression<'src>>, RichExtra<'tokens, 'src>>
518+ Clone {
519    choice((
520        range().labelled("range").as_non_terminal(),
521        base_type().map(Expression::BaseType),
522        integer().map(Expression::Integer),
523        num::<i128>().map(Expression::Number),
524        just(Token::Default)
525            .ignore_then(
526                num::<i128>()
527                    .map(Some)
528                    .or(just(Token::Underscore).map(|_| None)),
529            )
530            .map(Expression::DefaultNumber)
531            .labelled("default-number"),
532        just(Token::CatchAll)
533            .ignore_then(
534                num::<i128>()
535                    .map(Some)
536                    .or(just(Token::Underscore).map(|_| None)),
537            )
538            .map(Expression::CatchAllNumber)
539            .labelled("catch-all-number"),
540        byte_array().labelled("byte-array").as_non_terminal(),
541        just(Token::Allow).map(|_| Expression::Allow),
542        select! { Token::Access(val) => val }
543            .map(Expression::Access)
544            .labelled("Access")
545            .as_terminal(),
546        select! { Token::ByteOrder(val) => val }
547            .map(Expression::ByteOrder)
548            .labelled("ByteOrder")
549            .as_terminal(),
550        just(Token::Underscore).map(|_| Expression::Auto),
551        select! { Token::String(val) => val }
552            .map(Expression::String)
553            .labelled("String")
554            .as_terminal(),
555        select! { Token::AddressMode(val) => val }
556            .map(Expression::AddressMode)
557            .labelled("AddressMode")
558            .as_terminal(),
559    ))
560    .map_with(|expression, extra| expression.spanned(extra.span()))
561    .labelled("simple-expression")
562}
563
564pub fn repeat<'tokens, 'src: 'tokens>()
565-> impl Parser<'tokens, InputType<'tokens, 'src>, Spanned<Repeat<'src>>, RichExtra<'tokens, 'src>>
566+ Clone {
567    choice((
568        num::<NonZeroU32>().map(RepeatSource::Count),
569        ident(false).map(RepeatSource::Enum),
570    ))
571    .map_with(|repeat_source, extra| repeat_source.with_span(extra.span()))
572    .then(
573        just(Token::Stride)
574            .ignore_then(num::<i32>().map_with(|num, extra| num.with_span(extra.span()))),
575    )
576    .delimited_by(just(Token::BracketOpen), just(Token::BracketClose))
577    .map_with(|(source, stride), extra| Repeat { source, stride }.spanned(extra.span()))
578    .labelled("repeat")
579}
580
581pub fn property<'tokens, 'src: 'tokens, 'node>(
582    node: impl Parser<'tokens, InputType<'tokens, 'src>, Node<'src>, RichExtra<'tokens, 'src>> + Clone,
583) -> impl Parser<'tokens, InputType<'tokens, 'src>, Spanned<Property<'src>>, RichExtra<'tokens, 'src>>
584+ Clone {
585    doc_comment()
586        .repeated()
587        .collect()
588        .then(
589            ident(false)
590                .then(
591                    just(Token::Colon).ignore_then(choice((
592                        simple_expression()
593                            .labelled("simple-expression")
594                            .as_non_terminal(),
595                        node.clone()
596                            .map_with(|node, extra| {
597                                Expression::SubNode(Box::new(node)).spanned(extra.span())
598                            })
599                            .labelled("node")
600                            .as_non_terminal(),
601                        ident(false)
602                            .map(Expression::TypeReference)
603                            .map_with(|expression, extra| expression.spanned(extra.span())),
604                    ))),
605                )
606                .map_with(|(name, expression), extra| {
607                    Property {
608                        doc_comments: Vec::new(),
609                        name,
610                        expression,
611                    }
612                    .spanned(extra.span())
613                }),
614        )
615        .map(|(docs, mut prop)| {
616            prop.doc_comments = docs;
617            prop
618        })
619        .labelled("property")
620}
621
622pub fn type_specifier<'tokens, 'src: 'tokens>(
623    node: impl Parser<'tokens, InputType<'tokens, 'src>, Node<'src>, RichExtra<'tokens, 'src>> + Clone,
624) -> impl Parser<
625    'tokens,
626    InputType<'tokens, 'src>,
627    Spanned<TypeSpecifier<'src>>,
628    RichExtra<'tokens, 'src>,
629> + Clone {
630    let type_conversion = just(Token::As).ignore_then(just(Token::Try).or_not()).then(
631        node.labelled("node")
632            .as_non_terminal()
633            .map(|node| TypeConversion::Subnode(Box::new(node)))
634            .or(ident(false).map(TypeConversion::Reference)),
635    );
636    just(Token::Arrow)
637        .ignore_then(
638            choice((
639                base_type(),
640                integer().map(BaseType::FixedSize),
641                just(Token::Underscore).map(|_| BaseType::Unspecified),
642            ))
643            .map_with(|b, e| b.spanned(e.span())),
644        )
645        .then(type_conversion.or_not())
646        .map(|(base_type, conversion)| TypeSpecifier {
647            base_type,
648            use_try: conversion
649                .as_ref()
650                .map(|(try_token, _)| try_token.is_some())
651                .unwrap_or_default(),
652            conversion: conversion.map(|(_, conversion)| conversion),
653        })
654        .map_with(|ts, e| ts.spanned(e.span()))
655        .labelled("type-specifier")
656}
657
658pub fn node_body<'tokens, 'src: 'tokens>(
659    node: impl Parser<'tokens, InputType<'tokens, 'src>, Node<'src>, RichExtra<'tokens, 'src>> + Clone,
660) -> impl Parser<
661    'tokens,
662    InputType<'tokens, 'src>,
663    (Vec<Spanned<Property<'src>>>, Vec<Node<'src>>),
664    RichExtra<'tokens, 'src>,
665> + Clone {
666    let properties = property(node.clone())
667        .labelled("property")
668        .as_non_terminal()
669        .separated_by(just(Token::Comma))
670        .at_least(1)
671        .collect::<Vec<_>>();
672    let nodes = node
673        .labelled("node")
674        .as_non_terminal()
675        .separated_by(just(Token::Comma))
676        .at_least(1)
677        .collect::<Vec<_>>();
678
679    // Body with comma forced between properties and nodes
680    choice((
681        // Properties + comma + nodes
682        properties
683            .clone()
684            .then_ignore(just(Token::Comma))
685            .then(nodes.clone()),
686        // Properties + no comma + no nodes
687        properties
688            .clone()
689            .map(|properties| (properties, Vec::new())),
690        // No properties + no comma + nodes
691        nodes.map(|nodes| (Vec::new(), nodes)),
692    ))
693    .then_ignore(just(Token::Comma).or_not())
694    .or_not()
695    .map(|body| body.unwrap_or_default())
696    .delimited_by(just(Token::CurlyOpen), just(Token::CurlyClose))
697    .labelled("node-body")
698}
699
700pub fn node<'tokens, 'src: 'tokens>()
701-> impl Parser<'tokens, InputType<'tokens, 'src>, Node<'src>, RichExtra<'tokens, 'src>> + Clone {
702    recursive(|node| {
703        let node = node.labelled("node").as_non_terminal();
704
705        doc_comment()
706            .repeated()
707            .collect()
708            .then(ident(false).labelled("node-type"))
709            .then(ident(true).labelled("node-name"))
710            .then(repeat().labelled("repeat").as_non_terminal().or_not())
711            .then(
712                simple_expression()
713                    .labelled("simple-expression")
714                    .as_non_terminal()
715                    .repeated()
716                    .collect::<Vec<_>>(),
717            )
718            .then(
719                type_specifier(node.clone())
720                    .labelled("type-specifier")
721                    .as_non_terminal()
722                    .or_not(),
723            )
724            .then(
725                node_body(node.clone())
726                    .labelled("node-body")
727                    .as_non_terminal()
728                    .or_not(),
729            )
730            .map_with(
731                |(
732                    (((((doc_comments, node_type), name), repeat), expressions), type_specifier),
733                    body,
734                ),
735                 extra| {
736                    let (properties, sub_nodes) = body.unwrap_or_default();
737
738                    let mut span: Span = extra.span();
739                    span = span.start_from(node_type.span);
740
741                    Node {
742                        doc_comments,
743                        node_type,
744                        name,
745                        repeat,
746                        type_specifier,
747                        properties,
748                        short_properties: expressions,
749                        sub_nodes,
750                        span,
751                    }
752                },
753            )
754            .labelled("node")
755    })
756}