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