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 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}