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                base_ty = match method {
333                    Method::Optional => format!("Option<{base_ty}>"),
334                    Method::Strict => base_ty,
335                    Method::Many if is_token => "impl Iterator<Item = SyntaxToken>".into(),
336                    Method::Many => format!("AstChildren<{base_ty}>"),
337                };
338                let body = if is_token {
339                    let kind = utils::kind_name_of(&child_name);
340                    match method {
341                        Method::Optional => format!("support::token(self.syntax(), SyntaxKind::{kind})"),
342                        Method::Strict => format!("support::token(self.syntax(), SyntaxKind::{kind}).unwrap()"),
343                        Method::Many => format!("::rowan_peg_utils::tokens(self.syntax(), SyntaxKind::{kind})"),
344                    }
345                } else {
346                    match method {
347                        Method::Optional => "support::child(self.syntax())",
348                        Method::Strict => "support::child(self.syntax()).unwrap()",
349                        Method::Many => "support::children(self.syntax())",
350                    }.into()
351                };
352                let method_name = if method == Method::Many {
353                    if is_token {
354                        format!("{child_name}_tokens")
355                    } else if child_name.ends_with('s') {
356                        format!("{child_name}es")
357                    } else {
358                        format!("{child_name}s")
359                    }
360                } else {
361                    child_name
362                };
363                writeln!(self.out, "    pub fn {method_name}(&self) -> {base_ty} {{").unwrap();
364                writeln!(self.out, "        {body}").unwrap();
365                writeln!(self.out, "    }}").unwrap();
366            }
367            writeln!(self.out, "}}").unwrap();
368        }
369        Ok(())
370    }
371
372    fn decl_is_token(&self, decl: &Decl) -> bool {
373        let Some(list) = utils::one_elem(decl.pat_choice().pat_lists()) else { return false };
374        let Some(op) = utils::one_elem(list.pat_ops()) else { return false };
375        if op.dollar().is_some() {
376            return true;
377        }
378        op.pat_atom().syntax().text_range() != op.syntax().text_range()
379            && op.pat_atom().string().is_some()
380    }
381
382    fn process_decl(&mut self, decl: Decl) -> Result<()> {
383        let (name, kind_name) = self.regist_name(decl.named().ident().text());
384        self.is_token_decl = self.decl_is_token(&decl);
385        self.decl_name = name;
386        let name = &self.decl_name;
387        let mut vis = "";
388
389        if self.is_token_decl {
390            self.is_tokens.insert(name.clone());
391        }
392        if let Some(new_name) = self.exports.get(name) {
393            if name == new_name {
394                vis = "pub ";
395            } else {
396                writeln!(self.out, "    pub rule {new_name}() = {name}").unwrap();
397            }
398        }
399
400        self.refs_bound.clear();
401        write!(self.out, "    {vis}rule {name}() = ").unwrap();
402        if self.is_token_decl {
403            write!(self.out, "()").unwrap();
404            self.process_pat_choice(decl.pat_choice())?;
405        } else {
406            self.gen_node_wrap(&kind_name, |this| {
407                this.process_pat_choice(decl.pat_choice())
408            })?;
409        }
410
411        writeln!(self.out).unwrap();
412        let methods = self.refs_bound.iter().filter_map(|(name, bound)| {
413            let ty = match bound {
414                UsedBound(0, 0) => return None,
415                UsedBound(0, 1) => Method::Optional,
416                UsedBound(1, 1) => Method::Strict,
417                _ => Method::Many,
418            };
419            Some((name.clone(), ty))
420        }).collect();
421        let name = self.exports.get(&self.decl_name).unwrap_or(&self.decl_name);
422        self.methods.insert(name.clone(), methods);
423        Ok(())
424    }
425
426    fn process_pat_choice(&mut self, patchoice: PatChoice) -> Result<()> {
427        let mut first = true;
428        let refs_bound = self.take_refs_bound();
429        let mut prev_bound: Option<HashMap<String, UsedBound>> = None;
430        write!(self.out, "(").unwrap();
431        for patlist in patchoice.pat_lists() {
432            first.in_false(|| write!(self.out, " / ").unwrap());
433            write!(self.out, "()").unwrap();
434            self.gen_back_wrap(|this| this.process_pat_list(patlist))?;
435            if let Some(prev_bound) = &mut prev_bound {
436                self.merge_cover_to(prev_bound);
437            } else {
438                prev_bound = Some(self.take_refs_bound());
439            }
440        }
441        assert_eq!(self.refs_bound.len(), 0);
442        self.merge_add(refs_bound);
443        self.merge_add(prev_bound.unwrap());
444        if let Some(expected) = patchoice.pat_expect() {
445            let name = value::label(&expected.label());
446            write!(self.out, " / expected!({name:?})").unwrap();
447        }
448        write!(self.out, ")").unwrap();
449        Ok(())
450    }
451
452    fn merge_cover_to(&mut self, prev_bound: &mut HashMap<String, UsedBound>) {
453        for key in self.refs_bound.keys() {
454            if !prev_bound.contains_key(key) {
455                prev_bound.insert(key.clone(), UsedBound::default());
456            }
457        }
458        for (key, value) in &mut *prev_bound {
459            let other = self.refs_bound.get(key).copied().unwrap_or_default();
460            *value = value.cover(other);
461        }
462        self.refs_bound.clear();
463    }
464
465    fn merge_add(&mut self, refs_bound: HashMap<String, UsedBound>) {
466        for (key, value) in refs_bound {
467            *self.refs_bound.entry(key).or_default() += value;
468        }
469    }
470
471    fn process_pat_list(&mut self, patlist: PatList) -> Result<()> {
472        let mut first = true;
473        for patop in patlist.pat_ops() {
474            first.in_false(|| write!(self.out, " ").unwrap());
475            self.process_patop(patop)?;
476        }
477        Ok(())
478    }
479
480    fn process_patop(&mut self, patop: PatOp) -> Result<()> {
481        let atom = patop.pat_atom();
482        if patop.amp().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.bang().is_some() {
486            write!(self.out, "!").unwrap();
487            self.gen_quiet_wrap(|this| this.dis_refs_bound(|this| this.process_patatom(atom)))?;
488        } else if patop.tilde().is_some() {
489            write!(self.out, "quiet!{{").unwrap();
490            self.dis_refs_bound(|this| this.process_patatom(atom))?;
491            write!(self.out, "}}").unwrap();
492        } else if patop.dollar().is_some() {
493            if self.is_token_decl && self.slice == 0 {
494                let name = &self.decl_name.clone();
495                let (_, kind_name) = self.regist_name(name);
496                self.slice += 1;
497                self.gen_tok_wrap(&kind_name, |this| {
498                    this.dis_refs_bound(|this| this.process_patatom(atom))
499                })?;
500                self.slice -= 1;
501            } else {
502                return Err(Error::DisallowedSlice(patop.syntax().clone()));
503            }
504        } else if let Some(repeat) = patop.repeat() {
505            let refs_bound = self.take_refs_bound();
506            self.gen_back_wrap(|this| this.process_patatom(atom))?;
507            let (lower_bound, upper_bound) = repeat.count_bounds();
508            match (lower_bound, upper_bound) {
509                (1, None) => write!(self.out, "+"),
510                (0, None) => write!(self.out, "*"),
511                (lower, None) => write!(self.out, "*<{lower},>"),
512                (lower, Some(upper)) => write!(self.out, "*<{lower},{upper}>"),
513            }.unwrap();
514            let repeat_meta = UsedBound(
515                lower_bound.try_into().unwrap(),
516                upper_bound.unwrap_or(255).try_into().unwrap(),
517            );
518            self.refs_bound.iter_mut().for_each(|(_, bound)| *bound *= repeat_meta);
519            self.merge_add(refs_bound);
520        } else {
521            self.process_patatom(atom)?;
522        }
523        Ok(())
524    }
525
526    fn process_patatom(&mut self, atom: PatAtom) -> Result<()> {
527        match_options! {match atom {
528            l_paren as _ => self.process_pat_choice(atom.pat_choice().unwrap())?,
529            l_brack as _ => {
530                let refs_bound = self.take_refs_bound();
531                self.gen_back_wrap(|this| this.process_pat_choice(atom.pat_choice().unwrap()))?;
532                write!(self.out, "?").unwrap();
533                self.refs_bound.iter_mut().for_each(|(_, bound)| bound.0 = 0);
534                self.merge_add(refs_bound);
535            },
536            ident => {
537                let name = utils::rule_name_of(ident.text());
538                write!(self.out, "{}()", name).unwrap();
539                self.add_bound(name);
540            },
541            string => self.tok_or_in_slice(&string)?,
542            matches if value::matches(&matches).chars().count() == 1 => {
543                // special unit string
544                self.tok_or_in_slice(&matches)?;
545            },
546            matches => {
547                if self.slice == 0 {
548                    return Err(Error::MatchesWithoutSlice(matches));
549                }
550                let content = value::matches(&matches);
551                write!(self.out, "(quiet!{{").unwrap();
552                if let Some(pat) = content.strip_prefix('^') {
553                    write!(self.out, "[^::char_classes::any!(@\"{pat}\")]").unwrap();
554                } else {
555                    write!(self.out, "[::char_classes::any!(@\"{content}\")]").unwrap();
556                }
557                write!(self.out, "}}/expected!({:?}))", matches.text()).unwrap();
558            },
559            _ => unreachable!(),
560        }}
561        Ok(())
562    }
563
564    fn tok_or_in_slice(&mut self, token: &SyntaxToken) -> Result<()> {
565        let content = if token.kind() == SyntaxKind::STRING {
566            value::string(token)
567        } else {
568            value::matches(token)
569        };
570        if self.slice == 0 {
571            let (name, kind_name) = self.regist_tok_name(token)?;
572            self.add_bound(name);
573            self.gen_tok_wrap(&kind_name, |this| {
574                write!(this.out, "{content:?}").unwrap();
575            });
576        } else {
577            write!(self.out, "{content:?}").unwrap();
578        }
579        Ok(())
580    }
581}
582
583pub fn quick_process(src: &str) -> Result<String, String> {
584    let state = &mut rowan_peg_utils::ParseState::default();
585    match parser::decl_list(src, state) {
586        Ok(()) => (),
587        Err(e) => {
588            return Err(format!("parse grammar {e}"));
589        },
590    }
591    let syntax_node = SyntaxNode::new_root(state.finish());
592    let decl_list = DeclList::cast(syntax_node).unwrap();
593    let mut buf = String::new();
594    let mut proc = Processor::from(&mut buf);
595    match proc.start_process(&decl_list) {
596        Ok(()) => {},
597        Err(e) => {
598            let range = match &e {
599                Error::EmptyLiteral(tok)
600                | Error::UnknownLiteral(tok)
601                | Error::MatchesWithoutSlice(tok) => tok.text_range(),
602                Error::DisallowedSlice(node) => node.text_range(),
603            };
604            let index = range.start().into();
605            let (line, col) = line_column::line_column(src, index);
606            return Err(format!("processing error at {line}:{col} {e}"));
607        },
608    }
609    Ok(buf)
610}
611
612#[cfg(test)]
613mod tests {
614    use rowan::TextSize;
615
616    use super::*;
617
618    #[test]
619    fn full_parser() {
620        let s = r#"
621;; use ABNF like grammar
622;; char-val to case-sensitive
623;; prose-val -> regexp
624;; add peg lookaheads `!` `&`
625;; add quiet `~`
626;; add slice `$`
627;; remove num-var
628;;
629;; vim:nowrap
630
631comment     = ~<;[^\n]*(?:\n|$)> @comment
632_           = ~<[ \t\r\n]*> [comment _]
633ident       = ~<(?![0-9])(?:[0-9a-zA-Z\-_]|[^\x00-\xa0])+> @ident
634number      = ~<[0-9]+> @number
635string      = ~(<"> <[^\"\r\n]*> <">) @string
636match       = ~("<" <[^\x3e\r\n]*> ">") @match
637label       = ident / string
638repeat      = "+"
639            / "*" [number]
640            / number ["*" [number]]
641patatom     = ident !(_ "=")            ; a rule reference
642            / string                    ; keyword
643            / match                     ; regular expressions
644            / "[" _ patchoice _ "]"     ; optional
645            / "(" _ patchoice _ ")"     ; simple paren
646            / "{" _ patchoice _ "}"     ; list group brace
647patrepeat   = repeat _ patatom
648            / patatom
649patop       = "&" patrepeat ; positive lookahead
650            / "!" patrepeat ; negative lookahead
651            / "~" patrepeat ; quiet
652            / "$" patrepeat ; slice
653            / patrepeat
654patlist     = patop *(_ patop)
655patchoice   = patlist *(_ "/" _ patlist)
656              *(_ "@" label); extra expected branch
657decl        = ident _ "=" _ patchoice
658decl-list   = +(_ decl) _
659    "#;
660        let mut state = rowan_peg_utils::ParseState::default();
661        parser::decl_list(s, &state).unwrap();
662        dbg!(&state);
663        let node = SyntaxNode::new_root(state.finish());
664        dbg!(&node);
665        assert_eq!(TextSize::of(s), node.text_range().end());
666        dbg!(&s.len());
667        let decl_list = DeclList::cast(node).unwrap();
668        println!("{decl_list}")
669    }
670}