rowan_peg/
lib.rs

1use core::fmt;
2use std::{collections::HashSet, fmt::Display, mem::take};
3use linked_hash_map::LinkedHashMap as HashMap;
4
5use rowan::ast::AstNode;
6use to_true::{InTrue, ToTrue};
7use unicode_ident::{is_xid_continue, is_xid_start};
8use rowan_peg_utils::match_options;
9
10use crate::utils::UsedBound;
11
12mod utils;
13mod bootstarp;
14
15pub use bootstarp::*;
16
17impl Repeat {
18    pub fn count_bounds(&self) -> (u32, Option<u32>) {
19        if self.plus().is_some() {
20            (1, None)
21        } else if let Some(rest) = self.repeat_rest() {
22            let lower_bound = self.number().as_ref().map_or(0, value::number);
23            let upper_bound = rest.number().as_ref().map(value::number);
24            (lower_bound, upper_bound)
25        } else if let Some(number) = self.number() {
26            let bound = value::number(&number);
27            (bound, bound.into())
28        } else {
29            unreachable!()
30        }
31    }
32}
33
34pub mod value {
35    use crate::{SyntaxKind as Kind, Label, SyntaxToken};
36
37    #[track_caller]
38    pub fn string(s: &SyntaxToken) -> &str {
39        debug_assert_eq!(s.kind(), Kind::STRING);
40        let s = s.text();
41        &s[1..s.len()-1]
42    }
43
44    #[track_caller]
45    pub fn matches(s: &SyntaxToken) -> &str {
46        debug_assert_eq!(s.kind(), Kind::MATCHES);
47        let s = s.text();
48        &s[1..s.len()-1]
49    }
50
51    #[track_caller]
52    pub fn label(l: &Label) -> String {
53        l.ident()
54            .map(|ident| ident.text().to_owned())
55            .unwrap_or_else(|| string(&l.string().unwrap()).to_owned())
56    }
57
58    pub fn number(s: &SyntaxToken) -> u32 {
59        debug_assert_eq!(s.kind(), Kind::NUMBER);
60        s.text().parse().unwrap()
61    }
62}
63
64#[derive(Debug)]
65pub enum Error {
66    EmptyLiteral(SyntaxToken),
67    UnknownLiteral(SyntaxToken),
68    MatchesWithoutSlice(SyntaxToken),
69    DisallowedSlice(SyntaxNode),
70}
71
72impl Display for Error {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        match self {
75            Error::EmptyLiteral(t) => {
76                write!(f, "empty literal {:?}", t.text())
77            },
78            Error::UnknownLiteral(t) => {
79                write!(f, "unknown literal {:?}", t.text())
80            },
81            Error::MatchesWithoutSlice(t) => {
82                write!(f, "matches without slice {:?}", t.text())
83            },
84            Error::DisallowedSlice(t) => {
85                write!(f, "disallowed slice {:?}", t.text())
86            },
87        }
88    }
89}
90
91type Result<T, E = Error> = core::result::Result<T, E>;
92
93#[derive(Debug, PartialEq, Eq)]
94enum Method {
95    Optional,
96    Strict,
97    Many,
98}
99
100pub struct Processor<W: fmt::Write> {
101    out: W,
102    kind_names_map: HashMap<String, String>,
103    slice: u32,
104    is_token_decl: bool,
105    exports: HashMap<String, String>,
106    decl_name: String,
107    refs_bound: HashMap<String, UsedBound>,
108    is_tokens: HashSet<String>,
109    methods: HashMap<String, Vec<(String, Method)>>,
110}
111
112impl<W: fmt::Write> From<W> for Processor<W> {
113    fn from(out: W) -> Self {
114        Self {
115            out,
116            kind_names_map: HashMap::new(),
117            slice: 0,
118            is_token_decl: false,
119            exports: HashMap::new(),
120            decl_name: String::new(),
121            refs_bound: HashMap::new(),
122            is_tokens: HashSet::new(),
123            methods: HashMap::new(),
124        }
125    }
126}
127
128const PRE_DEFINE_ITEMS: &str = {
129r#"// Generated by rowan-peg, do not edit it
130use rowan::{ast::{support, AstChildren, AstNode}, Language};
131
132macro_rules! decl_ast_node {
133    ($node:ident, $kind:ident) => {
134        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
135        pub struct $node(SyntaxNode);
136        impl AstNode for $node {
137            type Language = Lang;
138
139            fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
140                &self.0
141            }
142
143            fn can_cast(kind: <Self::Language as Language>::Kind) -> bool {
144                kind == SyntaxKind::$kind
145            }
146
147            fn cast(node: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
148                if Self::can_cast(node.kind()) {
149                    Some(Self(node))
150                } else {
151                    None
152                }
153            }
154        }
155        impl core::fmt::Display for $node {
156            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157                core::fmt::Display::fmt(self.syntax(), f)
158            }
159        }
160    };
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
164pub enum Lang {}
165impl Language for Lang {
166    type Kind = SyntaxKind;
167
168    fn kind_to_raw(kind: Self::Kind) -> ::rowan::SyntaxKind {
169        kind.into()
170    }
171
172    fn kind_from_raw(raw: ::rowan::SyntaxKind) -> Self::Kind {
173        raw.into()
174    }
175}
176
177pub type SyntaxNode = ::rowan::SyntaxNode<Lang>;
178pub type SyntaxToken = ::rowan::SyntaxToken<Lang>;
179"#};
180const PRE_DEFINE_RULES: &str = r#""#;
181
182impl<W: fmt::Write> Processor<W> {
183    fn gen_tok_wrap<F, R>(&mut self, kind: &str, f: F) -> R
184    where F: FnOnce(&mut Self) -> R,
185    {
186        write!(self.out, "(g:({{state.quiet().guard_token({kind})}}) s:$((").unwrap();
187        let result = f(self);
188        write!(self.out, ")) {{g.accept_token(s)}})").unwrap();
189        result
190    }
191
192    fn gen_node_wrap<F, R>(&mut self, kind: &str, f: F) -> R
193    where F: FnOnce(&mut Self) -> R,
194    {
195        write!(self.out, "(g:({{state.guard({kind})}}) (").unwrap();
196        let result = f(self);
197        write!(self.out, ") {{g.accept()}})").unwrap();
198        result
199    }
200
201    fn gen_quiet_wrap<F, R>(&mut self, f: F) -> R
202    where F: FnOnce(&mut Self) -> R,
203    {
204        write!(self.out, "(g:({{state.quiet().guard_none()}}) (quiet!{{").unwrap();
205        let result = f(self);
206        write!(self.out, "}}) {{g.accept_none()}})").unwrap();
207        result
208    }
209
210    fn gen_back_wrap<F, R>(&mut self, f: F) -> R
211    where F: FnOnce(&mut Self) -> R,
212    {
213        write!(self.out, "(g:({{state.guard_none()}})(").unwrap();
214        let result = f(self);
215        write!(self.out, "){{g.accept_none()}})").unwrap();
216        result
217    }
218
219    fn regist_name(&mut self, name: &str) -> (String, String) {
220        let name = utils::rule_name_of(name);
221        let kind_name = self.kind_names_map.entry(name.to_owned())
222            .or_insert_with(|| utils::kind_name_of(self.exports.get(&name).unwrap_or(&name)));
223        (name, kind_name.clone())
224    }
225
226    fn regist_tok_name(&mut self, token: &SyntaxToken) -> Result<(String, String)> {
227        let content = if token.kind() == SyntaxKind::STRING { value::string(token) } else { value::matches(token) };
228        if content.is_empty() {
229            return Err(Error::EmptyLiteral(token.clone()));
230        }
231        let (name, kind_name) = if let Some(name) = utils::punct_name_of(content) {
232            (name.to_owned(), utils::kind_name_of(&name))
233        } else if is_xid_start(content.chars().next().unwrap())
234            && content.chars().all(|ch| matches!(ch, '-' | '_') || is_xid_continue(ch))
235        {
236            let name = utils::rule_name_of(&format!("{content}_kw"));
237            let kind_name = utils::kind_name_of(&name);
238            (name, kind_name)
239        } else {
240            return Err(Error::UnknownLiteral(token.to_owned()));
241        };
242
243        self.is_tokens.insert(name.clone());
244        self.kind_names_map.insert(name.clone(), kind_name.clone());
245
246        Ok((name, kind_name))
247    }
248
249    fn add_bound(&mut self, name: impl Into<String>) {
250        if !self.is_token_decl {
251            let mut name = name.into();
252            if let Some(renamed_name) = self.exports.get(&name) {
253                name = renamed_name.to_owned();
254            }
255            *self.refs_bound.entry(name).or_default() += 1;
256        }
257    }
258
259    fn dis_refs_bound<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
260        let refs_bound = self.take_refs_bound();
261        let result = f(self);
262        self.refs_bound = refs_bound;
263        result
264    }
265
266    #[must_use]
267    fn take_refs_bound(&mut self) -> HashMap<String, UsedBound> {
268        take(&mut self.refs_bound)
269    }
270
271    pub fn start_process(&mut self, decl_list: &DeclList) -> Result<()> {
272        for export in decl_list.export_list().iter().flat_map(|list| list.exports()) {
273            let name = export.ident();
274            let new_name = export.named()
275                .map_or(name.clone(), |it| it.ident());
276            self.exports.insert(
277                utils::rule_name_of(name.text()),
278                utils::rule_name_of(new_name.text()),
279            );
280        }
281
282        writeln!(self.out, "{PRE_DEFINE_ITEMS}").unwrap();
283        writeln!(self.out, "::peg::parser!(pub grammar parser<'b>(state: \
284            &'b ::rowan_peg_utils::ParseState<'input>) for str {{").unwrap();
285        writeln!(self.out, "    use SyntaxKind::*;").unwrap();
286        writeln!(self.out, "{PRE_DEFINE_RULES}").unwrap();
287        for decl in decl_list.decls() {
288            self.process_decl(decl)?;
289        }
290        writeln!(self.out, "}});").unwrap();
291        writeln!(self.out, "#[repr(u16)]").unwrap();
292        writeln!(self.out, "#[allow(non_camel_case_types)]").unwrap();
293        writeln!(self.out, "#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]").unwrap();
294        writeln!(self.out, "pub enum SyntaxKind {{").unwrap();
295        let mut first = true;
296        let mut last = None;
297        for kind in self.kind_names_map.values() {
298            first.to_false(|| {
299                writeln!(self.out, "    {kind} = 0,").unwrap();
300            }).unwrap_or_else(|| {
301                writeln!(self.out, "    {kind},").unwrap();
302            });
303            last = kind.into();
304        }
305        writeln!(self.out, "}}").unwrap();
306        writeln!(self.out, "impl From<::rowan::SyntaxKind> for SyntaxKind {{ \
307            fn from(kind: ::rowan::SyntaxKind) -> Self {{ \
308                ::core::assert!(kind.0 <= Self::{} as u16); \
309                unsafe {{ ::core::mem::transmute::<u16, SyntaxKind>(kind.0) }} \
310            }} \
311        }}", last.unwrap()).unwrap();
312        writeln!(self.out, "impl From<SyntaxKind> for ::rowan::SyntaxKind {{ \
313            fn from(kind: SyntaxKind) -> Self {{ \
314                ::rowan::SyntaxKind(kind as u16) \
315            }} \
316        }}").unwrap();
317        for (rule_name, mut methods) in self.methods.drain() {
318            if self.is_tokens.contains(&rule_name) { continue }
319            let node_name = utils::node_name_of(&rule_name);
320            let node_kind = utils::kind_name_of(&rule_name);
321            methods.sort_by(|a, b| a.0.cmp(&b.0));
322
323            writeln!(self.out, "decl_ast_node!({node_name}, {node_kind});").unwrap();
324            writeln!(self.out, "impl {node_name} {{").unwrap();
325            for (child_name, method) in methods {
326                let is_token = self.is_tokens.contains(&child_name);
327                let mut base_ty = if is_token {
328                    "SyntaxToken".to_owned()
329                } else {
330                    utils::node_name_of(&child_name)
331                };
332                match method {
333                    Method::Optional => base_ty = format!("Option<{base_ty}>"),
334                    Method::Strict => (),
335                    Method::Many => base_ty = format!("AstChildren<{base_ty}>"),
336                }
337                let body = if is_token {
338                    let kind = utils::kind_name_of(&child_name);
339                    match method {
340                        Method::Optional => format!("support::token(self.syntax(), SyntaxKind::{kind})"),
341                        Method::Strict => format!("support::token(self.syntax(), SyntaxKind::{kind}).unwrap()"),
342                        Method::Many => continue,
343                    }
344                } else {
345                    match method {
346                        Method::Optional => "support::child(self.syntax())",
347                        Method::Strict => "support::child(self.syntax()).unwrap()",
348                        Method::Many => "support::children(self.syntax())",
349                    }.into()
350                };
351                let method_name = if method == Method::Many {
352                    if child_name.ends_with('s') {
353                        format!("{child_name}es")
354                    } else {
355                        format!("{child_name}s")
356                    }
357                } else {
358                    child_name
359                };
360                writeln!(self.out, "    pub fn {method_name}(&self) -> {base_ty} {{").unwrap();
361                writeln!(self.out, "        {body}").unwrap();
362                writeln!(self.out, "    }}").unwrap();
363            }
364            writeln!(self.out, "}}").unwrap();
365        }
366        Ok(())
367    }
368
369    fn decl_is_token(&self, decl: &Decl) -> bool {
370        let Some(list) = utils::one_elem(decl.pat_choice().pat_lists()) else { return false };
371        let Some(op) = utils::one_elem(list.pat_ops()) else { return false };
372        if op.dollar().is_some() {
373            return true;
374        }
375        op.pat_atom().syntax().text_range() != op.syntax().text_range()
376            && op.pat_atom().string().is_some()
377    }
378
379    fn process_decl(&mut self, decl: Decl) -> Result<()> {
380        let (name, kind_name) = self.regist_name(decl.named().ident().text());
381        self.is_token_decl = self.decl_is_token(&decl);
382        self.decl_name = name;
383        let name = &self.decl_name;
384        let mut vis = "";
385
386        if self.is_token_decl {
387            self.is_tokens.insert(name.clone());
388        }
389        if let Some(new_name) = self.exports.get(name) {
390            if name == new_name {
391                vis = "pub ";
392            } else {
393                writeln!(self.out, "    pub rule {new_name}() = {name}").unwrap();
394            }
395        }
396
397        self.refs_bound.clear();
398        write!(self.out, "    {vis}rule {name}() = ").unwrap();
399        if self.is_token_decl {
400            write!(self.out, "()").unwrap();
401            self.process_pat_choice(decl.pat_choice())?;
402        } else {
403            self.gen_node_wrap(&kind_name, |this| {
404                this.process_pat_choice(decl.pat_choice())
405            })?;
406        }
407
408        writeln!(self.out).unwrap();
409        let methods = self.refs_bound.iter().filter_map(|(name, bound)| {
410            let ty = match bound {
411                UsedBound(0, 0) => return None,
412                UsedBound(0, 1) => Method::Optional,
413                UsedBound(1, 1) => Method::Strict,
414                _ => Method::Many,
415            };
416            Some((name.clone(), ty))
417        }).collect();
418        let name = self.exports.get(&self.decl_name).unwrap_or(&self.decl_name);
419        self.methods.insert(name.clone(), methods);
420        Ok(())
421    }
422
423    fn process_pat_choice(&mut self, patchoice: PatChoice) -> Result<()> {
424        let mut first = true;
425        let refs_bound = self.take_refs_bound();
426        let mut prev_bound: Option<HashMap<String, UsedBound>> = None;
427        write!(self.out, "(").unwrap();
428        for patlist in patchoice.pat_lists() {
429            first.in_false(|| write!(self.out, " / ").unwrap());
430            write!(self.out, "()").unwrap();
431            self.gen_back_wrap(|this| this.process_pat_list(patlist))?;
432            if let Some(prev_bound) = &mut prev_bound {
433                self.merge_cover_to(prev_bound);
434            } else {
435                prev_bound = Some(self.take_refs_bound());
436            }
437        }
438        assert_eq!(self.refs_bound.len(), 0);
439        self.merge_add(refs_bound);
440        self.merge_add(prev_bound.unwrap());
441        if let Some(expected) = patchoice.pat_expect() {
442            let name = value::label(&expected.label());
443            write!(self.out, " / expected!({name:?})").unwrap();
444        }
445        write!(self.out, ")").unwrap();
446        Ok(())
447    }
448
449    fn merge_cover_to(&mut self, prev_bound: &mut HashMap<String, UsedBound>) {
450        for key in self.refs_bound.keys() {
451            if !prev_bound.contains_key(key) {
452                prev_bound.insert(key.clone(), UsedBound::default());
453            }
454        }
455        for (key, value) in &mut *prev_bound {
456            let other = self.refs_bound.get(key).copied().unwrap_or_default();
457            *value = value.cover(other);
458        }
459        self.refs_bound.clear();
460    }
461
462    fn merge_add(&mut self, refs_bound: HashMap<String, UsedBound>) {
463        for (key, value) in refs_bound {
464            *self.refs_bound.entry(key).or_default() += value;
465        }
466    }
467
468    fn process_pat_list(&mut self, patlist: PatList) -> Result<()> {
469        let mut first = true;
470        for patop in patlist.pat_ops() {
471            first.in_false(|| write!(self.out, " ").unwrap());
472            self.process_patop(patop)?;
473        }
474        Ok(())
475    }
476
477    fn process_patop(&mut self, patop: PatOp) -> Result<()> {
478        let atom = patop.pat_atom();
479        if patop.amp().is_some() {
480            write!(self.out, "&").unwrap();
481            self.gen_quiet_wrap(|this| this.dis_refs_bound(|this| this.process_patatom(atom)))?;
482        } else if patop.bang().is_some() {
483            write!(self.out, "!").unwrap();
484            self.gen_quiet_wrap(|this| this.dis_refs_bound(|this| this.process_patatom(atom)))?;
485        } else if patop.tilde().is_some() {
486            write!(self.out, "quiet!{{").unwrap();
487            self.dis_refs_bound(|this| this.process_patatom(atom))?;
488            write!(self.out, "}}").unwrap();
489        } else if patop.dollar().is_some() {
490            if self.is_token_decl && self.slice == 0 {
491                let name = &self.decl_name.clone();
492                let (_, kind_name) = self.regist_name(name);
493                self.slice += 1;
494                self.gen_tok_wrap(&kind_name, |this| {
495                    this.dis_refs_bound(|this| this.process_patatom(atom))
496                })?;
497                self.slice -= 1;
498            } else {
499                return Err(Error::DisallowedSlice(patop.syntax().clone()));
500            }
501        } else if let Some(repeat) = patop.repeat() {
502            let refs_bound = self.take_refs_bound();
503            self.gen_back_wrap(|this| this.process_patatom(atom))?;
504            let (lower_bound, upper_bound) = repeat.count_bounds();
505            match (lower_bound, upper_bound) {
506                (1, None) => write!(self.out, "+"),
507                (0, None) => write!(self.out, "*"),
508                (lower, None) => write!(self.out, "*<{lower},>"),
509                (lower, Some(upper)) => write!(self.out, "*<{lower},{upper}>"),
510            }.unwrap();
511            let repeat_meta = UsedBound(
512                lower_bound.try_into().unwrap(),
513                upper_bound.unwrap_or(255).try_into().unwrap(),
514            );
515            self.refs_bound.iter_mut().for_each(|(_, bound)| *bound *= repeat_meta);
516            self.merge_add(refs_bound);
517        } else {
518            self.process_patatom(atom)?;
519        }
520        Ok(())
521    }
522
523    fn process_patatom(&mut self, atom: PatAtom) -> Result<()> {
524        match_options! {match atom {
525            l_paren as _ => self.process_pat_choice(atom.pat_choice().unwrap())?,
526            l_brack as _ => {
527                let refs_bound = self.take_refs_bound();
528                self.gen_back_wrap(|this| this.process_pat_choice(atom.pat_choice().unwrap()))?;
529                write!(self.out, "?").unwrap();
530                self.refs_bound.iter_mut().for_each(|(_, bound)| bound.0 = 0);
531                self.merge_add(refs_bound);
532            },
533            ident => {
534                let name = utils::rule_name_of(ident.text());
535                write!(self.out, "{}()", name).unwrap();
536                self.add_bound(name);
537            },
538            string => self.tok_or_in_slice(&string)?,
539            matches if value::matches(&matches).chars().count() == 1 => {
540                // special unit string
541                self.tok_or_in_slice(&matches)?;
542            },
543            matches => {
544                if self.slice == 0 {
545                    return Err(Error::MatchesWithoutSlice(matches));
546                }
547                let content = value::matches(&matches);
548                write!(self.out, "(quiet!{{").unwrap();
549                if let Some(pat) = content.strip_prefix('^') {
550                    write!(self.out, "[^::char_classes::any!(@\"{pat}\")]").unwrap();
551                } else {
552                    write!(self.out, "[::char_classes::any!(@\"{content}\")]").unwrap();
553                }
554                write!(self.out, "}}/expected!({:?}))", matches.text()).unwrap();
555            },
556            _ => unreachable!(),
557        }}
558        Ok(())
559    }
560
561    fn tok_or_in_slice(&mut self, token: &SyntaxToken) -> Result<()> {
562        let content = if token.kind() == SyntaxKind::STRING {
563            value::string(token)
564        } else {
565            value::matches(token)
566        };
567        if self.slice == 0 {
568            let (name, kind_name) = self.regist_tok_name(token)?;
569            self.add_bound(name);
570            self.gen_tok_wrap(&kind_name, |this| {
571                write!(this.out, "{content:?}").unwrap();
572            });
573        } else {
574            write!(self.out, "{content:?}").unwrap();
575        }
576        Ok(())
577    }
578}
579
580pub fn quick_process(src: &str) -> Result<String, String> {
581    let state = &mut rowan_peg_utils::ParseState::default();
582    match parser::decl_list(src, state) {
583        Ok(()) => (),
584        Err(e) => {
585            return Err(format!("parse grammar {e}"));
586        },
587    }
588    let syntax_node = SyntaxNode::new_root(state.finish());
589    let decl_list = DeclList::cast(syntax_node).unwrap();
590    let mut buf = String::new();
591    let mut proc = Processor::from(&mut buf);
592    match proc.start_process(&decl_list) {
593        Ok(()) => {},
594        Err(e) => {
595            let range = match &e {
596                Error::EmptyLiteral(tok)
597                | Error::UnknownLiteral(tok)
598                | Error::MatchesWithoutSlice(tok) => tok.text_range(),
599                Error::DisallowedSlice(node) => node.text_range(),
600            };
601            let index = range.start().into();
602            let (line, col) = line_column::line_column(src, index);
603            return Err(format!("processing error at {line}:{col} {e}"));
604        },
605    }
606    Ok(buf)
607}
608
609#[cfg(test)]
610mod tests {
611    use rowan::TextSize;
612
613    use super::*;
614
615    #[test]
616    fn full_parser() {
617        let s = r#"
618;; use ABNF like grammar
619;; char-val to case-sensitive
620;; prose-val -> regexp
621;; add peg lookaheads `!` `&`
622;; add quiet `~`
623;; add slice `$`
624;; remove num-var
625;;
626;; vim:nowrap
627
628comment     = ~<;[^\n]*(?:\n|$)> @comment
629_           = ~<[ \t\r\n]*> [comment _]
630ident       = ~<(?![0-9])(?:[0-9a-zA-Z\-_]|[^\x00-\xa0])+> @ident
631number      = ~<[0-9]+> @number
632string      = ~(<"> <[^\"\r\n]*> <">) @string
633match       = ~("<" <[^\x3e\r\n]*> ">") @match
634label       = ident / string
635repeat      = "+"
636            / "*" [number]
637            / number ["*" [number]]
638patatom     = ident !(_ "=")            ; a rule reference
639            / string                    ; keyword
640            / match                     ; regular expressions
641            / "[" _ patchoice _ "]"     ; optional
642            / "(" _ patchoice _ ")"     ; simple paren
643            / "{" _ patchoice _ "}"     ; list group brace
644patrepeat   = repeat _ patatom
645            / patatom
646patop       = "&" patrepeat ; positive lookahead
647            / "!" patrepeat ; negative lookahead
648            / "~" patrepeat ; quiet
649            / "$" patrepeat ; slice
650            / patrepeat
651patlist     = patop *(_ patop)
652patchoice   = patlist *(_ "/" _ patlist)
653              *(_ "@" label); extra expected branch
654decl        = ident _ "=" _ patchoice
655decl-list   = +(_ decl) _
656    "#;
657        let mut state = rowan_peg_utils::ParseState::default();
658        parser::decl_list(s, &state).unwrap();
659        dbg!(&state);
660        let node = SyntaxNode::new_root(state.finish());
661        dbg!(&node);
662        assert_eq!(TextSize::of(s), node.text_range().end());
663        dbg!(&s.len());
664        let decl_list = DeclList::cast(node).unwrap();
665        println!("{decl_list}")
666    }
667}