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