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