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({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({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, " use SyntaxKind::*;").unwrap();
294 writeln!(self.out, "{PRE_DEFINE_RULES}").unwrap();
295 for decl in decl_list.decls() {
296 self.process_decl(decl)?;
297 }
298 writeln!(self.out, "}});").unwrap();
299 writeln!(self.out, "#[repr(u16)]").unwrap();
300 writeln!(self.out, "#[allow(non_camel_case_types)]").unwrap();
301 writeln!(self.out, "#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]").unwrap();
302 writeln!(self.out, "pub enum SyntaxKind {{").unwrap();
303 let mut first = true;
304 let mut last = None;
305 for kind in self.kind_names_map.values() {
306 first.to_false(|| {
307 writeln!(self.out, " {kind} = 0,").unwrap();
308 }).unwrap_or_else(|| {
309 writeln!(self.out, " {kind},").unwrap();
310 });
311 last = kind.into();
312 }
313 writeln!(self.out, "}}").unwrap();
314 writeln!(self.out, "impl From<::rowan::SyntaxKind> for SyntaxKind {{ \
315 fn from(kind: ::rowan::SyntaxKind) -> Self {{ \
316 ::core::assert!(kind.0 <= Self::{} as u16); \
317 unsafe {{ ::core::mem::transmute::<u16, SyntaxKind>(kind.0) }} \
318 }} \
319 }}", last.unwrap()).unwrap();
320 writeln!(self.out, "impl From<SyntaxKind> for ::rowan::SyntaxKind {{ \
321 fn from(kind: SyntaxKind) -> Self {{ \
322 ::rowan::SyntaxKind(kind as u16) \
323 }} \
324 }}").unwrap();
325 for (rule_name, mut meta) in self.decls.drain() {
326 if self.is_tokens.contains(&rule_name) { continue }
327 let node_name = utils::node_name_of(&rule_name);
328 let node_kind = utils::kind_name_of(&rule_name);
329 meta.methods.sort_by(|a, b| a.0.cmp(&b.0));
330
331 writeln!(self.out, "decl_ast_node!(\
332 {node_name}, \
333 {node_kind}, \
334 #[doc = {:?}]\
335 );",
336 meta.docs,
337 ).unwrap();
338 writeln!(self.out, "impl {node_name} {{").unwrap();
339 for (child_name, method) in meta.methods {
340 let is_token = self.is_tokens.contains(&child_name);
341 let mut base_ty = if is_token {
342 "SyntaxToken".to_owned()
343 } else {
344 utils::node_name_of(&child_name)
345 };
346 base_ty = match method {
347 Method::Optional => format!("Option<{base_ty}>"),
348 Method::Strict => base_ty,
349 Method::Many if is_token => "impl Iterator<Item = SyntaxToken>".into(),
350 Method::Many => format!("AstChildren<{base_ty}>"),
351 };
352 let body = if is_token {
353 let kind = utils::kind_name_of(&child_name);
354 match method {
355 Method::Optional => format!("support::token(self.syntax(), SyntaxKind::{kind})"),
356 Method::Strict => format!("support::token(self.syntax(), SyntaxKind::{kind}).unwrap()"),
357 Method::Many => format!("::rowan_peg_utils::tokens(self.syntax(), SyntaxKind::{kind})"),
358 }
359 } else {
360 match method {
361 Method::Optional => "support::child(self.syntax())",
362 Method::Strict => "support::child(self.syntax()).unwrap()",
363 Method::Many => "support::children(self.syntax())",
364 }.into()
365 };
366 let method_name = if method == Method::Many {
367 if is_token {
368 format!("{child_name}_tokens")
369 } else if child_name.ends_with('s') {
370 format!("{child_name}es")
371 } else {
372 format!("{child_name}s")
373 }
374 } else {
375 child_name.clone()
376 };
377 if let Some(punct) = utils::punct_of(&child_name)
378 && punct.trim() == punct
379 {
380 let hint = child_name.replace('_', " ");
381 if punct.contains('`') {
382 writeln!(self.out, r#" /// Get {hint} `` {punct} ``"#).unwrap();
383 } else {
384 writeln!(self.out, r#" /// Get {hint} `{punct}`"#).unwrap();
385 }
386 if !punct.chars().any(any!("'\" \t\r\n_\\")) {
387 writeln!(self.out, r#" #[doc(alias = {punct:?})]"#).unwrap();
388 }
389 }
390 writeln!(self.out, " pub fn {method_name}(&self) -> {base_ty} {{").unwrap();
391 writeln!(self.out, " {body}").unwrap();
392 writeln!(self.out, " }}").unwrap();
393 }
394 writeln!(self.out, "}}").unwrap();
395 }
396 Ok(())
397 }
398
399 fn decl_is_token(&self, decl: &Decl) -> bool {
400 let Some(list) = utils::one_elem(decl.pat_choice().pat_lists()) else { return false };
401 let Some(op) = utils::one_elem(list.pat_ops()) else { return false };
402 if op.dollar().is_some() {
403 return true;
404 }
405 op.pat_atom().syntax().text_range() != op.syntax().text_range()
406 && op.pat_atom().string().is_some()
407 }
408
409 fn process_decl(&mut self, decl: Decl) -> Result<()> {
410 let (name, kind_name) = self.regist_name(decl.named().ident().text());
411 self.is_token_decl = self.decl_is_token(&decl);
412 self.decl_name = name;
413 let name = &self.decl_name;
414 let mut vis = "";
415
416 if self.is_token_decl {
417 self.is_tokens.insert(name.clone());
418 }
419 if let Some(new_name) = self.exports.get(name) {
420 if name == new_name {
421 vis = "pub ";
422 } else {
423 writeln!(self.out, " pub rule {new_name}() = {name}").unwrap();
424 }
425 }
426
427 self.refs_bound.clear();
428 write!(self.out, " {vis}rule {name}() = ").unwrap();
429 if self.is_token_decl {
430 write!(self.out, "()").unwrap();
431 self.process_pat_choice(decl.pat_choice())?;
432 } else {
433 self.gen_node_wrap(&kind_name, |this| {
434 this.process_pat_choice(decl.pat_choice())
435 })?;
436 }
437
438 writeln!(self.out).unwrap();
439 let methods = self.refs_bound.iter().filter_map(|(name, bound)| {
440 let ty = match bound {
441 UsedBound(0, 0) => return None,
442 UsedBound(0, 1) => Method::Optional,
443 UsedBound(1, 1) => Method::Strict,
444 _ => Method::Many,
445 };
446 Some((name.clone(), ty))
447 }).collect();
448 let name = self.exports.get(&self.decl_name).unwrap_or(&self.decl_name);
449 self.decls.insert(name.clone(), DeclMeta {
450 methods,
451 docs: format!("```abnf\n{decl}\n```"),
452 });
453 Ok(())
454 }
455
456 fn process_pat_choice(&mut self, patchoice: PatChoice) -> Result<()> {
457 let mut first = true;
458 let refs_bound = self.take_refs_bound();
459 let mut prev_bound: Option<HashMap<String, UsedBound>> = None;
460 write!(self.out, "(").unwrap();
461 for patlist in patchoice.pat_lists() {
462 first.in_false(|| write!(self.out, " / ").unwrap());
463 write!(self.out, "()").unwrap();
464 self.gen_back_wrap(|this| this.process_pat_list(patlist))?;
465 if let Some(prev_bound) = &mut prev_bound {
466 self.merge_cover_to(prev_bound);
467 } else {
468 prev_bound = Some(self.take_refs_bound());
469 }
470 }
471 assert_eq!(self.refs_bound.len(), 0);
472 self.merge_add(refs_bound);
473 self.merge_add(prev_bound.unwrap());
474 if let Some(expected) = patchoice.pat_expect() {
475 let name = value::label(&expected.label());
476 write!(self.out, " / expected!({name:?})").unwrap();
477 }
478 write!(self.out, ")").unwrap();
479 Ok(())
480 }
481
482 fn merge_cover_to(&mut self, prev_bound: &mut HashMap<String, UsedBound>) {
483 for key in self.refs_bound.keys() {
484 if !prev_bound.contains_key(key) {
485 prev_bound.insert(key.clone(), UsedBound::default());
486 }
487 }
488 for (key, value) in &mut *prev_bound {
489 let other = self.refs_bound.get(key).copied().unwrap_or_default();
490 *value = value.cover(other);
491 }
492 self.refs_bound.clear();
493 }
494
495 fn merge_add(&mut self, refs_bound: HashMap<String, UsedBound>) {
496 for (key, value) in refs_bound {
497 *self.refs_bound.entry(key).or_default() += value;
498 }
499 }
500
501 fn process_pat_list(&mut self, patlist: PatList) -> Result<()> {
502 let mut first = true;
503 for patop in patlist.pat_ops() {
504 first.in_false(|| write!(self.out, " ").unwrap());
505 self.process_patop(patop)?;
506 }
507 Ok(())
508 }
509
510 fn process_patop(&mut self, patop: PatOp) -> Result<()> {
511 let atom = patop.pat_atom();
512 if patop.amp().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.bang().is_some() {
516 write!(self.out, "!").unwrap();
517 self.gen_quiet_wrap(|this| this.dis_refs_bound(|this| this.process_patatom(atom)))?;
518 } else if patop.tilde().is_some() {
519 write!(self.out, "quiet!{{").unwrap();
520 self.dis_refs_bound(|this| this.process_patatom(atom))?;
521 write!(self.out, "}}").unwrap();
522 } else if patop.dollar().is_some() {
523 if self.is_token_decl && self.slice == 0 {
524 let name = &self.decl_name.clone();
525 let (_, kind_name) = self.regist_name(name);
526 self.slice += 1;
527 self.gen_tok_wrap(&kind_name, |this| {
528 this.dis_refs_bound(|this| this.process_patatom(atom))
529 })?;
530 self.slice -= 1;
531 } else {
532 return Err(Error::DisallowedSlice(patop.syntax().clone()));
533 }
534 } else if let Some(repeat) = patop.repeat() {
535 let refs_bound = self.take_refs_bound();
536 self.gen_back_wrap(|this| this.process_patatom(atom))?;
537 let (lower_bound, upper_bound) = repeat.count_bounds();
538 match (lower_bound, upper_bound) {
539 (1, None) => write!(self.out, "+"),
540 (0, None) => write!(self.out, "*"),
541 (lower, None) => write!(self.out, "*<{lower},>"),
542 (lower, Some(upper)) => write!(self.out, "*<{lower},{upper}>"),
543 }.unwrap();
544 let repeat_meta = UsedBound(
545 lower_bound.try_into().unwrap(),
546 upper_bound.unwrap_or(255).try_into().unwrap(),
547 );
548 self.refs_bound.iter_mut().for_each(|(_, bound)| *bound *= repeat_meta);
549 self.merge_add(refs_bound);
550 } else {
551 self.process_patatom(atom)?;
552 }
553 Ok(())
554 }
555
556 fn process_patatom(&mut self, atom: PatAtom) -> Result<()> {
557 match_options! {match atom {
558 l_paren as _ => self.process_pat_choice(atom.pat_choice().unwrap())?,
559 l_brack as _ => {
560 let refs_bound = self.take_refs_bound();
561 self.gen_back_wrap(|this| this.process_pat_choice(atom.pat_choice().unwrap()))?;
562 write!(self.out, "?").unwrap();
563 self.refs_bound.iter_mut().for_each(|(_, bound)| bound.0 = 0);
564 self.merge_add(refs_bound);
565 },
566 ident => {
567 let name = utils::rule_name_of(ident.text());
568 write!(self.out, "{}()", name).unwrap();
569 self.add_bound(name);
570 },
571 string => self.tok_or_in_slice(&string)?,
572 matches if value::matches(&matches).chars().count() == 1 => {
573 self.tok_or_in_slice(&matches)?;
575 },
576 matches => {
577 if self.slice == 0 {
578 return Err(Error::MatchesWithoutSlice(matches));
579 }
580 let content = value::matches(&matches);
581 write!(self.out, "(quiet!{{").unwrap();
582 if let Some(pat) = content.strip_prefix('^') {
583 write!(self.out, "[^::char_classes::any!(@\"{pat}\")]").unwrap();
584 } else {
585 write!(self.out, "[::char_classes::any!(@\"{content}\")]").unwrap();
586 }
587 write!(self.out, "}}/expected!({:?}))", matches.text()).unwrap();
588 },
589 _ => unreachable!(),
590 }}
591 Ok(())
592 }
593
594 fn tok_or_in_slice(&mut self, token: &SyntaxToken) -> Result<()> {
595 let content = if token.kind() == SyntaxKind::STRING {
596 value::string(token)
597 } else {
598 value::matches(token)
599 };
600 if self.slice == 0 {
601 let (name, kind_name) = self.regist_tok_name(token)?;
602 self.add_bound(name);
603 self.gen_tok_wrap(&kind_name, |this| {
604 write!(this.out, "{content:?}").unwrap();
605 });
606 } else {
607 write!(self.out, "{content:?}").unwrap();
608 }
609 Ok(())
610 }
611}
612
613pub fn quick_process(src: &str) -> Result<String, String> {
614 let state = &mut rowan_peg_utils::ParseState::default();
615 match parser::decl_list(src, state) {
616 Ok(()) => (),
617 Err(e) => {
618 return Err(format!("parse grammar {e}"));
619 },
620 }
621 let syntax_node = SyntaxNode::new_root(state.finish());
622 let decl_list = DeclList::cast(syntax_node).unwrap();
623 let mut buf = String::new();
624 let mut proc = Processor::from(&mut buf);
625 match proc.start_process(&decl_list) {
626 Ok(()) => {},
627 Err(e) => {
628 let range = match &e {
629 Error::EmptyLiteral(tok)
630 | Error::UnknownLiteral(tok)
631 | Error::MatchesWithoutSlice(tok) => tok.text_range(),
632 Error::DisallowedSlice(node) => node.text_range(),
633 };
634 let index = range.start().into();
635 let (line, col) = line_column::line_column(src, index);
636 return Err(format!("processing error at {line}:{col} {e}"));
637 },
638 }
639 Ok(buf)
640}
641
642#[cfg(test)]
643mod tests {
644 use rowan::TextSize;
645
646 use super::*;
647
648 #[test]
649 fn full_parser() {
650 let s = r#"
651;; use ABNF like grammar
652;; char-val to case-sensitive
653;; prose-val -> regexp
654;; add peg lookaheads `!` `&`
655;; add quiet `~`
656;; add slice `$`
657;; remove num-var
658;;
659;; vim:nowrap
660
661comment = ~<;[^\n]*(?:\n|$)> @comment
662_ = ~<[ \t\r\n]*> [comment _]
663ident = ~<(?![0-9])(?:[0-9a-zA-Z\-_]|[^\x00-\xa0])+> @ident
664number = ~<[0-9]+> @number
665string = ~(<"> <[^\"\r\n]*> <">) @string
666match = ~("<" <[^\x3e\r\n]*> ">") @match
667label = ident / string
668repeat = "+"
669 / "*" [number]
670 / number ["*" [number]]
671patatom = ident !(_ "=") ; a rule reference
672 / string ; keyword
673 / match ; regular expressions
674 / "[" _ patchoice _ "]" ; optional
675 / "(" _ patchoice _ ")" ; simple paren
676 / "{" _ patchoice _ "}" ; list group brace
677patrepeat = repeat _ patatom
678 / patatom
679patop = "&" patrepeat ; positive lookahead
680 / "!" patrepeat ; negative lookahead
681 / "~" patrepeat ; quiet
682 / "$" patrepeat ; slice
683 / patrepeat
684patlist = patop *(_ patop)
685patchoice = patlist *(_ "/" _ patlist)
686 *(_ "@" label); extra expected branch
687decl = ident _ "=" _ patchoice
688decl-list = +(_ decl) _
689 "#;
690 let mut state = rowan_peg_utils::ParseState::default();
691 parser::decl_list(s, &state).unwrap();
692 dbg!(&state);
693 let node = SyntaxNode::new_root(state.finish());
694 dbg!(&node);
695 assert_eq!(TextSize::of(s), node.text_range().end());
696 dbg!(&s.len());
697 let decl_list = DeclList::cast(node).unwrap();
698 println!("{decl_list}")
699 }
700}