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