1use std::{
4 any::type_name,
5 collections::{HashMap, HashSet},
6 env::{current_dir, var},
7 error::Error,
8 fmt::{self, Debug, Write as fmtWrite},
9 fs::{self, File, create_dir_all, read_to_string},
10 hash::Hash,
11 io::Write,
12 marker::PhantomData,
13 path::{Path, PathBuf},
14 sync::Mutex,
15};
16
17use crate::{
18 LexerTypes, RTParserBuilder, RecoveryKind,
19 diagnostics::{DiagnosticFormatter, SpannedDiagnosticFormatter},
20};
21
22#[cfg(feature = "_unstable_api")]
23use crate::unstable_api::UnstableApi;
24
25use bincode::{Decode, Encode, decode_from_slice, encode_to_vec};
26use cfgrammar::{
27 Location, RIdx, Symbol,
28 header::{GrmtoolsSectionParser, Header, HeaderValue, Value},
29 markmap::{Entry, MergeBehavior},
30 yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind, ast::ASTWithValidityInfo},
31};
32use filetime::FileTime;
33use lazy_static::lazy_static;
34use lrtable::{Minimiser, StateGraph, StateTable, from_yacc, statetable::Conflicts};
35use num_traits::{AsPrimitive, PrimInt, Unsigned};
36use proc_macro2::{Literal, TokenStream};
37use quote::{ToTokens, TokenStreamExt, format_ident, quote};
38use regex::Regex;
39
40const ACTION_PREFIX: &str = "__gt_";
41const GLOBAL_PREFIX: &str = "__GT_";
42const ACTIONS_KIND: &str = "__GtActionsKind";
43const ACTIONS_KIND_PREFIX: &str = "Ak";
44const ACTIONS_KIND_HIDDEN: &str = "__GtActionsKindHidden";
45
46const RUST_FILE_EXT: &str = "rs";
47
48const WARNING: &str = "[Warning]";
49const ERROR: &str = "[Error]";
50
51lazy_static! {
52 static ref RE_DOL_NUM: Regex = Regex::new(r"\$([0-9]+)").unwrap();
53 static ref GENERATED_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new());
54}
55
56struct CTConflictsError<StorageT: Eq + Hash> {
57 conflicts_diagnostic: String,
58 #[cfg(test)]
59 stable: StateTable<StorageT>,
60 phantom: PhantomData<StorageT>,
61}
62
63struct QuoteOption<T>(Option<T>);
69
70impl<T: ToTokens> ToTokens for QuoteOption<T> {
71 fn to_tokens(&self, tokens: &mut TokenStream) {
72 tokens.append_all(match self.0 {
73 Some(ref t) => quote! { ::std::option::Option::Some(#t) },
74 None => quote! { ::std::option::Option::None },
75 });
76 }
77}
78
79struct UnsuffixedUsize(usize);
84
85impl ToTokens for UnsuffixedUsize {
86 fn to_tokens(&self, tokens: &mut TokenStream) {
87 tokens.append(Literal::usize_unsuffixed(self.0))
88 }
89}
90
91struct QuoteTuple<T>(T);
94
95impl<A: ToTokens, B: ToTokens> ToTokens for QuoteTuple<(A, B)> {
96 fn to_tokens(&self, tokens: &mut TokenStream) {
97 let (a, b) = &self.0;
98 tokens.append_all(quote!((#a, #b)));
99 }
100}
101
102struct QuoteToString<'a>(&'a str);
104
105impl ToTokens for QuoteToString<'_> {
106 fn to_tokens(&self, tokens: &mut TokenStream) {
107 let x = &self.0;
108 tokens.append_all(quote! { #x.to_string() });
109 }
110}
111
112impl<StorageT> fmt::Display for CTConflictsError<StorageT>
113where
114 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
115 usize: AsPrimitive<StorageT>,
116{
117 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118 write!(f, "{}", self.conflicts_diagnostic)
119 }
120}
121
122impl<StorageT> fmt::Debug for CTConflictsError<StorageT>
123where
124 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
125 usize: AsPrimitive<StorageT>,
126{
127 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128 write!(f, "{}", self.conflicts_diagnostic)
129 }
130}
131
132impl<StorageT> Error for CTConflictsError<StorageT>
133where
134 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
135 usize: AsPrimitive<StorageT>,
136{
137}
138
139struct ErrorString(String);
141impl fmt::Display for ErrorString {
142 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143 let ErrorString(s) = self;
144 write!(f, "{}", s)
145 }
146}
147impl fmt::Debug for ErrorString {
148 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149 let ErrorString(s) = self;
150 write!(f, "{}", s)
151 }
152}
153impl Error for ErrorString {}
154
155#[derive(Clone, PartialEq, Eq, Debug)]
157#[non_exhaustive]
158pub enum Visibility {
159 Private,
161 Public,
163 PublicSuper,
165 PublicSelf,
167 PublicCrate,
169 PublicIn(String),
171}
172
173#[derive(Clone, Copy, PartialEq, Eq, Debug)]
177#[non_exhaustive]
178pub enum RustEdition {
179 Rust2015,
180 Rust2018,
181 Rust2021,
182}
183
184impl RustEdition {
185 fn to_variant_tokens(self) -> TokenStream {
186 match self {
187 RustEdition::Rust2015 => quote!(::lrpar::RustEdition::Rust2015),
188 RustEdition::Rust2018 => quote!(::lrpar::RustEdition::Rust2018),
189 RustEdition::Rust2021 => quote!(::lrpar::RustEdition::Rust2021),
190 }
191 }
192}
193
194impl ToTokens for Visibility {
195 fn to_tokens(&self, tokens: &mut TokenStream) {
196 tokens.extend(match self {
197 Visibility::Private => quote!(),
198 Visibility::Public => quote! {pub},
199 Visibility::PublicSuper => quote! {pub(super)},
200 Visibility::PublicSelf => quote! {pub(self)},
201 Visibility::PublicCrate => quote! {pub(crate)},
202 Visibility::PublicIn(data) => {
203 let other = str::parse::<TokenStream>(data).unwrap();
204 quote! {pub(in #other)}
205 }
206 })
207 }
208}
209
210impl Visibility {
211 fn to_variant_tokens(&self) -> TokenStream {
212 match self {
213 Visibility::Private => quote!(::lrpar::Visibility::Private),
214 Visibility::Public => quote!(::lrpar::Visibility::Public),
215 Visibility::PublicSuper => quote!(::lrpar::Visibility::PublicSuper),
216 Visibility::PublicSelf => quote!(::lrpar::Visibility::PublicSelf),
217 Visibility::PublicCrate => quote!(::lrpar::Visibility::PublicCrate),
218 Visibility::PublicIn(data) => {
219 let data = QuoteToString(data);
220 quote!(::lrpar::Visibility::PublicIn(#data))
221 }
222 }
223 }
224}
225
226pub struct CTParserBuilder<'a, LexerTypesT: LexerTypes>
229where
230 LexerTypesT::StorageT: Eq + Hash,
231 usize: AsPrimitive<LexerTypesT::StorageT>,
232{
233 grammar_path: Option<PathBuf>,
237 grammar_src: Option<String>,
239 from_ast: Option<ASTWithValidityInfo>,
241 output_path: Option<PathBuf>,
242 mod_name: Option<&'a str>,
243 recoverer: Option<RecoveryKind>,
244 yacckind: Option<YaccKind>,
245 error_on_conflicts: bool,
246 warnings_are_errors: bool,
247 show_warnings: bool,
248 visibility: Visibility,
249 rust_edition: RustEdition,
250 inspect_rt: Option<
251 Box<
252 dyn for<'b> FnMut(
253 &'b mut Header<Location>,
254 RTParserBuilder<LexerTypesT::StorageT, LexerTypesT>,
255 &'b HashMap<String, LexerTypesT::StorageT>,
256 &PathBuf,
257 ) -> Result<(), Box<dyn Error>>,
258 >,
259 >,
260 #[cfg(test)]
262 inspect_callback: Option<Box<dyn Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>>,
263 phantom: PhantomData<LexerTypesT>,
264}
265
266impl<
267 'a,
268 StorageT: 'static + Debug + Hash + PrimInt + Encode + Unsigned,
269 LexerTypesT: LexerTypes<StorageT = StorageT>,
270> CTParserBuilder<'a, LexerTypesT>
271where
272 usize: AsPrimitive<StorageT>,
273{
274 pub fn new() -> Self {
296 CTParserBuilder {
297 grammar_path: None,
298 grammar_src: None,
299 from_ast: None,
300 output_path: None,
301 mod_name: None,
302 recoverer: None,
303 yacckind: None,
304 error_on_conflicts: true,
305 warnings_are_errors: true,
306 show_warnings: true,
307 visibility: Visibility::Private,
308 rust_edition: RustEdition::Rust2021,
309 inspect_rt: None,
310 #[cfg(test)]
311 inspect_callback: None,
312 phantom: PhantomData,
313 }
314 }
315
316 pub fn grammar_in_src_dir<P>(mut self, srcp: P) -> Result<Self, Box<dyn Error>>
333 where
334 P: AsRef<Path>,
335 {
336 if !srcp.as_ref().is_relative() {
337 return Err(format!(
338 "Grammar path '{}' must be a relative path.",
339 srcp.as_ref().to_str().unwrap_or("<invalid UTF-8>")
340 )
341 .into());
342 }
343
344 let mut grmp = current_dir()?;
345 grmp.push("src");
346 grmp.push(srcp.as_ref());
347 self.grammar_path = Some(grmp);
348
349 let mut outp = PathBuf::new();
350 outp.push(var("OUT_DIR").unwrap());
351 outp.push(srcp.as_ref().parent().unwrap().to_str().unwrap());
352 create_dir_all(&outp)?;
353 let mut leaf = srcp
354 .as_ref()
355 .file_name()
356 .unwrap()
357 .to_str()
358 .unwrap()
359 .to_owned();
360 write!(leaf, ".{}", RUST_FILE_EXT).ok();
361 outp.push(leaf);
362 Ok(self.output_path(outp))
363 }
364
365 #[cfg(feature = "_unstable_api")]
368 pub fn grammar_ast(mut self, valid_ast: ASTWithValidityInfo, _api_key: UnstableApi) -> Self {
369 self.from_ast = Some(valid_ast);
370 self
371 }
372
373 pub fn grammar_path<P>(mut self, inp: P) -> Self
377 where
378 P: AsRef<Path>,
379 {
380 self.grammar_path = Some(inp.as_ref().to_owned());
381 self
382 }
383
384 #[cfg(feature = "_unstable_api")]
385 pub fn with_grammar_src(mut self, src: String, _api_key: UnstableApi) -> Self {
386 self.grammar_src = Some(src);
387 self
388 }
389
390 pub fn output_path<P>(mut self, outp: P) -> Self
395 where
396 P: AsRef<Path>,
397 {
398 self.output_path = Some(outp.as_ref().to_owned());
399 self
400 }
401
402 pub fn mod_name(mut self, mod_name: &'a str) -> Self {
406 self.mod_name = Some(mod_name);
407 self
408 }
409
410 pub fn visibility(mut self, vis: Visibility) -> Self {
412 self.visibility = vis;
413 self
414 }
415
416 pub fn recoverer(mut self, rk: RecoveryKind) -> Self {
418 self.recoverer = Some(rk);
419 self
420 }
421
422 pub fn yacckind(mut self, yk: YaccKind) -> Self {
424 self.yacckind = Some(yk);
425 self
426 }
427
428 pub fn error_on_conflicts(mut self, b: bool) -> Self {
431 self.error_on_conflicts = b;
432 self
433 }
434
435 pub fn warnings_are_errors(mut self, b: bool) -> Self {
438 self.warnings_are_errors = b;
439 self
440 }
441
442 pub fn show_warnings(mut self, b: bool) -> Self {
445 self.show_warnings = b;
446 self
447 }
448
449 pub fn rust_edition(mut self, edition: RustEdition) -> Self {
452 self.rust_edition = edition;
453 self
454 }
455
456 #[cfg(test)]
457 pub fn inspect_recoverer(
458 mut self,
459 cb: Box<dyn for<'h, 'y> Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>,
460 ) -> Self {
461 self.inspect_callback = Some(cb);
462 self
463 }
464
465 #[doc(hidden)]
466 pub fn inspect_rt(
467 mut self,
468 cb: Box<
469 dyn for<'b, 'y> FnMut(
470 &'b mut Header<Location>,
471 RTParserBuilder<'y, StorageT, LexerTypesT>,
472 &'b HashMap<String, StorageT>,
473 &PathBuf,
474 ) -> Result<(), Box<dyn Error>>,
475 >,
476 ) -> Self {
477 self.inspect_rt = Some(cb);
478 self
479 }
480
481 pub fn build(mut self) -> Result<CTParser<StorageT>, Box<dyn Error>> {
534 let grmp = self
535 .grammar_path
536 .as_ref()
537 .expect("grammar_path must be specified before processing.");
538 let outp = self
539 .output_path
540 .as_ref()
541 .expect("output_path must be specified before processing.");
542 let mut header = Header::new();
543
544 match header.entry("yacckind".to_string()) {
545 Entry::Occupied(_) => unreachable!(),
546 Entry::Vacant(mut v) => match self.yacckind {
547 Some(YaccKind::Eco) => panic!("Eco compile-time grammar generation not supported."),
548 Some(yk) => {
549 let yk_value = Value::try_from(yk)?;
550 let mut o = v.insert_entry(HeaderValue(
551 Location::Other("CTParserBuilder".to_string()),
552 yk_value,
553 ));
554 o.set_merge_behavior(MergeBehavior::Ours);
555 }
556 None => {
557 v.mark_required();
558 }
559 },
560 }
561 if let Some(recoverer) = self.recoverer {
562 match header.entry("recoverer".to_string()) {
563 Entry::Occupied(_) => unreachable!(),
564 Entry::Vacant(v) => {
565 let rk_value: Value<Location> = Value::try_from(recoverer)?;
566 let mut o = v.insert_entry(HeaderValue(
567 Location::Other("CTParserBuilder".to_string()),
568 rk_value,
569 ));
570 o.set_merge_behavior(MergeBehavior::Ours);
571 }
572 }
573 }
574
575 {
576 let mut lk = GENERATED_PATHS.lock().unwrap();
577 if lk.contains(outp.as_path()) {
578 return Err(format!("Generating two parsers to the same path ('{}') is not allowed: use CTParserBuilder::output_path (and, optionally, CTParserBuilder::mod_name) to differentiate them.", &outp.to_str().unwrap()).into());
579 }
580 lk.insert(outp.clone());
581 }
582
583 let inc = if let Some(grammar_src) = &self.grammar_src {
584 grammar_src.clone()
585 } else {
586 read_to_string(grmp).map_err(|e| format!("When reading '{}': {e}", grmp.display()))?
587 };
588
589 let yacc_diag = SpannedDiagnosticFormatter::new(&inc, grmp);
590 let parsed_header = GrmtoolsSectionParser::new(&inc, false).parse();
591 if let Err(errs) = parsed_header {
592 let mut out = String::new();
593 out.push_str(&format!(
594 "\n{ERROR}{}\n",
595 yacc_diag.file_location_msg(" parsing the `%grmtools` section", None)
596 ));
597 for e in errs {
598 out.push_str(&indent(" ", &yacc_diag.format_error(e).to_string()));
599 }
600 return Err(ErrorString(out))?;
601 }
602 let (parsed_header, _) = parsed_header.unwrap();
603 header.merge_from(parsed_header)?;
604 self.yacckind = header
605 .get("yacckind")
606 .map(|HeaderValue(_, val)| val)
607 .map(YaccKind::try_from)
608 .transpose()?;
609 header.mark_used(&"yacckind".to_string());
610 let ast_validation = if let Some(ast) = &self.from_ast {
611 ast.clone()
612 } else if let Some(yk) = self.yacckind {
613 ASTWithValidityInfo::new(yk, &inc)
614 } else {
615 Err("Missing 'yacckind'".to_string())?
616 };
617
618 header.mark_used(&"recoverer".to_string());
619 let rk_val = header.get("recoverer").map(|HeaderValue(_, rk_val)| rk_val);
620
621 if let Some(rk_val) = rk_val {
622 self.recoverer = Some(RecoveryKind::try_from(rk_val)?);
623 } else {
624 self.recoverer = Some(RecoveryKind::CPCTPlus);
626 }
627 self.yacckind = Some(ast_validation.yacc_kind());
628 let warnings = ast_validation.ast().warnings();
629 let res = YaccGrammar::<StorageT>::new_from_ast_with_validity_info(&ast_validation);
630 let grm = match res {
631 Ok(_) if self.warnings_are_errors && !warnings.is_empty() => {
632 let mut out = String::new();
633 out.push_str(&format!(
634 "\n{ERROR}{}\n",
635 yacc_diag.file_location_msg("", None)
636 ));
637 for e in warnings {
638 out.push_str(&format!(
639 "{}\n",
640 indent(" ", &yacc_diag.format_warning(e).to_string())
641 ));
642 }
643 return Err(ErrorString(out))?;
644 }
645 Ok(grm) => {
646 if !warnings.is_empty() {
647 for w in warnings {
648 let ws_loc = yacc_diag.file_location_msg("", None);
649 let ws = indent(" ", &yacc_diag.format_warning(w).to_string());
650 if std::env::var("OUT_DIR").is_ok() && self.show_warnings {
652 for line in ws_loc.lines().chain(ws.lines()) {
653 println!("cargo:warning={}", line);
654 }
655 } else if self.show_warnings {
656 eprintln!("{}", ws_loc);
657 eprintln!("{WARNING} {}", ws);
658 }
659 }
660 }
661 grm
662 }
663 Err(errs) => {
664 let mut out = String::new();
665 out.push_str(&format!(
666 "\n{ERROR}{}\n",
667 yacc_diag.file_location_msg("", None)
668 ));
669 for e in errs {
670 out.push_str(&indent(" ", &yacc_diag.format_error(e).to_string()));
671 out.push('\n');
672 }
673
674 return Err(ErrorString(out))?;
675 }
676 };
677
678 #[cfg(test)]
679 if let Some(cb) = &self.inspect_callback {
680 cb(self.recoverer.expect("has a default value"))?;
681 }
682
683 let rule_ids = grm
684 .tokens_map()
685 .iter()
686 .map(|(&n, &i)| (n.to_owned(), i.as_storaget()))
687 .collect::<HashMap<_, _>>();
688
689 let derived_mod_name = match self.mod_name {
690 Some(s) => s.to_owned(),
691 None => {
692 let mut stem = grmp.to_str().unwrap();
697 loop {
698 let new_stem = Path::new(stem).file_stem().unwrap().to_str().unwrap();
699 if stem == new_stem {
700 break;
701 }
702 stem = new_stem;
703 }
704 format!("{}_y", stem)
705 }
706 };
707
708 let cache = self.rebuild_cache(&derived_mod_name, &grm);
709
710 if let Ok(ref inmd) = fs::metadata(grmp) {
718 if let Ok(ref out_rs_md) = fs::metadata(outp) {
719 if FileTime::from_last_modification_time(out_rs_md)
720 > FileTime::from_last_modification_time(inmd)
721 {
722 if let Ok(outc) = read_to_string(outp) {
723 if outc.contains(&cache.to_string()) {
724 return Ok(CTParser {
725 regenerated: false,
726 rule_ids,
727 yacc_grammar: grm,
728 grammar_src: inc,
729 grammar_path: self.grammar_path.unwrap(),
730 conflicts: None,
731 });
732 } else {
733 #[cfg(grmtools_extra_checks)]
734 if std::env::var("CACHE_EXPECTED").is_ok() {
735 eprintln!("outc: {}", outc);
736 eprintln!("using cache: {}", cache,);
737 panic!("The cache regenerated however, it was expected to match");
739 }
740 }
741 }
742 }
743 }
744 }
745
746 fs::remove_file(outp).ok();
753
754 let (sgraph, stable) = from_yacc(&grm, Minimiser::Pager)?;
755 if self.error_on_conflicts {
756 if let Some(c) = stable.conflicts() {
757 match (grm.expect(), grm.expectrr()) {
758 (Some(i), Some(j)) if i == c.sr_len() && j == c.rr_len() => (),
759 (Some(i), None) if i == c.sr_len() && 0 == c.rr_len() => (),
760 (None, Some(j)) if 0 == c.sr_len() && j == c.rr_len() => (),
761 (None, None) if 0 == c.rr_len() && 0 == c.sr_len() => (),
762 _ => {
763 let conflicts_diagnostic = yacc_diag.format_conflicts::<LexerTypesT>(
764 &grm,
765 ast_validation.ast(),
766 c,
767 &sgraph,
768 &stable,
769 );
770 return Err(Box::new(CTConflictsError {
771 conflicts_diagnostic,
772 phantom: PhantomData,
773 #[cfg(test)]
774 stable,
775 }));
776 }
777 }
778 }
779 }
780
781 if let Some(ref mut inspector_rt) = self.inspect_rt {
782 let rt: RTParserBuilder<'_, StorageT, LexerTypesT> =
783 RTParserBuilder::new(&grm, &stable);
784 let rt = if let Some(rk) = self.recoverer {
785 rt.recoverer(rk)
786 } else {
787 rt
788 };
789 inspector_rt(&mut header, rt, &rule_ids, grmp)?
790 }
791
792 let unused_keys = header.unused();
793 if !unused_keys.is_empty() {
794 return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into());
795 }
796 let missing_keys = header
797 .missing()
798 .iter()
799 .map(|s| s.as_str())
800 .collect::<Vec<_>>();
801 if !missing_keys.is_empty() {
802 return Err(format!(
803 "Required values were missing from the header: {}",
804 missing_keys.join(", ")
805 )
806 .into());
807 }
808
809 self.output_file(
810 &grm,
811 &stable,
812 &derived_mod_name,
813 outp,
814 &format!("/* CACHE INFORMATION {} */\n", cache),
815 )?;
816 let conflicts = if stable.conflicts().is_some() {
817 Some((sgraph, stable))
818 } else {
819 None
820 };
821 Ok(CTParser {
822 regenerated: true,
823 rule_ids,
824 yacc_grammar: grm,
825 grammar_src: inc,
826 grammar_path: self.grammar_path.unwrap(),
827 conflicts,
828 })
829 }
830
831 #[deprecated(
838 since = "0.11.0",
839 note = "Please use grammar_in_src_dir(), build(), and token_map() instead"
840 )]
841 #[allow(deprecated)]
842 pub fn process_file_in_src(
843 &mut self,
844 srcp: &str,
845 ) -> Result<HashMap<String, StorageT>, Box<dyn Error>> {
846 let mut inp = current_dir()?;
847 inp.push("src");
848 inp.push(srcp);
849 let mut outp = PathBuf::new();
850 outp.push(var("OUT_DIR").unwrap());
851 outp.push(Path::new(srcp).parent().unwrap().to_str().unwrap());
852 create_dir_all(&outp)?;
853 let mut leaf = Path::new(srcp)
854 .file_name()
855 .unwrap()
856 .to_str()
857 .unwrap()
858 .to_owned();
859 write!(leaf, ".{}", RUST_FILE_EXT).ok();
860 outp.push(leaf);
861 self.process_file(inp, outp)
862 }
863
864 #[deprecated(
900 since = "0.11.0",
901 note = "Please use grammar_path(), output_path(), build(), and token_map() instead"
902 )]
903 #[allow(deprecated)]
904 pub fn process_file<P, Q>(
905 &mut self,
906 inp: P,
907 outp: Q,
908 ) -> Result<HashMap<String, StorageT>, Box<dyn Error>>
909 where
910 P: AsRef<Path>,
911 Q: AsRef<Path>,
912 {
913 self.grammar_path = Some(inp.as_ref().to_owned());
914 self.output_path = Some(outp.as_ref().to_owned());
915 let cl: CTParserBuilder<LexerTypesT> = CTParserBuilder {
916 grammar_path: self.grammar_path.clone(),
917 grammar_src: None,
918 from_ast: None,
919 output_path: self.output_path.clone(),
920 mod_name: self.mod_name,
921 recoverer: self.recoverer,
922 yacckind: self.yacckind,
923 error_on_conflicts: self.error_on_conflicts,
924 warnings_are_errors: self.warnings_are_errors,
925 show_warnings: self.show_warnings,
926 visibility: self.visibility.clone(),
927 rust_edition: self.rust_edition,
928 inspect_rt: None,
929 #[cfg(test)]
930 inspect_callback: None,
931 phantom: PhantomData,
932 };
933 Ok(cl.build()?.rule_ids)
934 }
935
936 fn output_file<P: AsRef<Path>>(
937 &self,
938 grm: &YaccGrammar<StorageT>,
939 stable: &StateTable<StorageT>,
940 mod_name: &str,
941 outp_rs: P,
942 cache: &str,
943 ) -> Result<(), Box<dyn Error>> {
944 let visibility = self.visibility.clone();
945 let user_actions = if let Some(
946 YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools,
947 ) = self.yacckind
948 {
949 Some(self.gen_user_actions(grm)?)
950 } else {
951 None
952 };
953 let rule_consts = self.gen_rule_consts(grm)?;
954 let token_epp = self.gen_token_epp(grm)?;
955 let parse_function = self.gen_parse_function(grm, stable)?;
956 let action_wrappers = match self.yacckind.unwrap() {
957 YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
958 Some(self.gen_wrappers(grm)?)
959 }
960 YaccKind::Original(YaccOriginalActionKind::NoAction)
961 | YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => None,
962 _ => unreachable!(),
963 };
964 let mod_name = format_ident!("{}", mod_name);
965 let out_tokens = quote! {
966 #visibility mod #mod_name {
967 #user_actions
969 mod _parser_ {
970 #![allow(clippy::type_complexity)]
971 #![allow(clippy::unnecessary_wraps)]
972 #![deny(unsafe_code)]
973 #[allow(unused_imports)]
974 use super::*;
975 #parse_function
976 #rule_consts
977 #token_epp
978 #action_wrappers
979 } #[allow(unused_imports)]
981 pub use _parser_::*;
982 #[allow(unused_imports)]
983 use ::lrpar::Lexeme;
984 } };
986 let unformatted = out_tokens.to_string();
988 let outs = syn::parse_str(&unformatted)
989 .map(|syntax_tree| prettyplease::unparse(&syntax_tree))
990 .unwrap_or(unformatted);
991 let mut f = File::create(outp_rs)?;
992 f.write_all(outs.as_bytes())?;
993 f.write_all(cache.as_bytes())?;
994 Ok(())
995 }
996
997 fn rebuild_cache(&self, derived_mod_name: &'_ str, grm: &YaccGrammar<StorageT>) -> TokenStream {
1000 let Self {
1007 grammar_path,
1010 grammar_src: _,
1012 from_ast: _,
1014 mod_name,
1015 recoverer,
1016 yacckind,
1017 output_path: _,
1018 error_on_conflicts,
1019 warnings_are_errors,
1020 show_warnings,
1021 visibility,
1022 rust_edition,
1023 inspect_rt: _,
1024 #[cfg(test)]
1025 inspect_callback: _,
1026 phantom: _,
1027 } = self;
1028 let build_time = env!("VERGEN_BUILD_TIMESTAMP");
1029 let grammar_path = grammar_path.as_ref().unwrap().to_string_lossy();
1030 let mod_name = QuoteOption(mod_name.as_deref());
1031 let visibility = visibility.to_variant_tokens();
1032 let rust_edition = rust_edition.to_variant_tokens();
1033 let yacckind = yacckind.expect("is_some() by this point");
1034 let rule_map = grm
1035 .iter_tidxs()
1036 .map(|tidx| {
1037 QuoteTuple((
1038 usize::from(tidx),
1039 grm.token_name(tidx).unwrap_or("<unknown>"),
1040 ))
1041 })
1042 .collect::<Vec<_>>();
1043 let cache_info = quote! {
1044 BUILD_TIME = #build_time
1045 DERIVED_MOD_NAME = #derived_mod_name
1046 GRAMMAR_PATH = #grammar_path
1047 MOD_NAME = #mod_name
1048 RECOVERER = #recoverer
1049 YACC_KIND = #yacckind
1050 ERROR_ON_CONFLICTS = #error_on_conflicts
1051 SHOW_WARNINGS = #show_warnings
1052 WARNINGS_ARE_ERRORS = #warnings_are_errors
1053 RUST_EDITION = #rust_edition
1054 RULE_IDS_MAP = [#(#rule_map,)*]
1055 VISIBILITY = #visibility
1056 };
1057 let cache_info_str = cache_info.to_string();
1058 quote!(#cache_info_str)
1059 }
1060
1061 fn gen_parse_function(
1063 &self,
1064 grm: &YaccGrammar<StorageT>,
1065 stable: &StateTable<StorageT>,
1066 ) -> Result<TokenStream, Box<dyn Error>> {
1067 let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1068 let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1069 let recoverer = self.recoverer;
1070 let run_parser = match self.yacckind.unwrap() {
1071 YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => {
1072 quote! {
1073 ::lrpar::RTParserBuilder::new(&grm, &stable)
1074 .recoverer(#recoverer)
1075 .parse_generictree(lexer)
1076 }
1077 }
1078 YaccKind::Original(YaccOriginalActionKind::NoAction) => {
1079 quote! {
1080 ::lrpar::RTParserBuilder::new(&grm, &stable)
1081 .recoverer(#recoverer)
1082 .parse_noaction(lexer)
1083 }
1084 }
1085 YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1086 let actionskind = str::parse::<TokenStream>(ACTIONS_KIND)?;
1087 let (action_fn_parse_param, action_fn_parse_param_ty) = match grm.parse_param() {
1090 Some((name, ty)) => {
1091 let name = str::parse::<TokenStream>(name)?;
1092 let ty = str::parse::<TokenStream>(ty)?;
1093 (quote!(#name), quote!(#ty))
1094 }
1095 None => (quote!(()), quote!(())),
1096 };
1097 let wrappers = grm.iter_pidxs().map(|pidx| {
1098 let pidx = usize::from(pidx);
1099 format_ident!("{}wrapper_{}", ACTION_PREFIX, pidx)
1100 });
1101 let edition_lifetime = if self.rust_edition != RustEdition::Rust2015 {
1102 quote!('_,)
1103 } else {
1104 quote!()
1105 };
1106 let ridx = usize::from(self.user_start_ridx(grm));
1107 let action_ident = format_ident!("{}{}", ACTIONS_KIND_PREFIX, ridx);
1108
1109 quote! {
1110 let actions: ::std::vec::Vec<
1111 &dyn Fn(
1112 ::cfgrammar::RIdx<#storaget>,
1113 &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1114 ::cfgrammar::Span,
1115 ::std::vec::Drain<#edition_lifetime ::lrpar::parser::AStackType<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #actionskind<'input>>>,
1116 #action_fn_parse_param_ty
1117 ) -> #actionskind<'input>
1118 > = ::std::vec![#(&#wrappers,)*];
1119 match ::lrpar::RTParserBuilder::new(&grm, &stable)
1120 .recoverer(#recoverer)
1121 .parse_actions(lexer, &actions, #action_fn_parse_param) {
1122 (Some(#actionskind::#action_ident(x)), y) => (Some(x), y),
1123 (None, y) => (None, y),
1124 _ => unreachable!()
1125 }
1126 }
1127 }
1128 kind => panic!("YaccKind {:?} not supported", kind),
1129 };
1130
1131 let parse_fn_parse_param = match self.yacckind.unwrap() {
1133 YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1134 if let Some((name, tyname)) = grm.parse_param() {
1135 let name = str::parse::<TokenStream>(name)?;
1136 let tyname = str::parse::<TokenStream>(tyname)?;
1137 Some(quote! {#name: #tyname})
1138 } else {
1139 None
1140 }
1141 }
1142 _ => None,
1143 };
1144 let parse_fn_return_ty = match self.yacckind.unwrap() {
1145 YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1146 let actiont = grm
1147 .actiontype(self.user_start_ridx(grm))
1148 .as_ref()
1149 .map(|at| str::parse::<TokenStream>(at))
1150 .transpose()?;
1151 quote! {
1152 (::std::option::Option<#actiont>, ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>)
1153 }
1154 }
1155 YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => quote! {
1156 (::std::option::Option<::lrpar::Node<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #storaget>>,
1157 ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>)
1158 },
1159 YaccKind::Original(YaccOriginalActionKind::NoAction) => quote! {
1160 ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>
1161 },
1162 _ => unreachable!(),
1163 };
1164
1165 let grm_data = encode_to_vec(grm, bincode::config::standard())?;
1166 let stable_data = encode_to_vec(stable, bincode::config::standard())?;
1167 Ok(quote! {
1168 const __GRM_DATA: &[u8] = &[#(#grm_data,)*];
1169 const __STABLE_DATA: &[u8] = &[#(#stable_data,)*];
1170
1171 #[allow(dead_code)]
1172 pub fn parse<'lexer, 'input: 'lexer>(
1173 lexer: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1174 #parse_fn_parse_param
1175 ) -> #parse_fn_return_ty {
1176 let (grm, stable) = ::lrpar::ctbuilder::_reconstitute(__GRM_DATA, __STABLE_DATA);
1177 #run_parser
1178 }
1179 })
1180 }
1181
1182 fn gen_rule_consts(
1183 &self,
1184 grm: &YaccGrammar<StorageT>,
1185 ) -> Result<TokenStream, proc_macro2::LexError> {
1186 let mut toks = TokenStream::new();
1187 for ridx in grm.iter_rules() {
1188 if !grm.rule_to_prods(ridx).contains(&grm.start_prod()) {
1189 let r_const = format_ident!("R_{}", grm.rule_name_str(ridx).to_ascii_uppercase());
1190 let storage_ty = str::parse::<TokenStream>(type_name::<StorageT>())?;
1191 let ridx = UnsuffixedUsize(usize::from(ridx));
1192 toks.extend(quote! {
1193 #[allow(dead_code)]
1194 pub const #r_const: #storage_ty = #ridx;
1195 });
1196 }
1197 }
1198 Ok(toks)
1199 }
1200
1201 fn gen_token_epp(
1202 &self,
1203 grm: &YaccGrammar<StorageT>,
1204 ) -> Result<TokenStream, proc_macro2::LexError> {
1205 let mut tidxs = Vec::new();
1206 for tidx in grm.iter_tidxs() {
1207 tidxs.push(QuoteOption(grm.token_epp(tidx)));
1208 }
1209 let const_epp_ident = format_ident!("{}EPP", GLOBAL_PREFIX);
1210 let storage_ty = str::parse::<TokenStream>(type_name::<StorageT>())?;
1211 Ok(quote! {
1212 const #const_epp_ident: &[::std::option::Option<&str>] = &[
1213 #(#tidxs,)*
1214 ];
1215
1216 #[allow(dead_code)]
1219 pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<#storage_ty>) -> ::std::option::Option<&'a str> {
1220 #const_epp_ident[usize::from(tidx)]
1221 }
1222 })
1223 }
1224
1225 fn gen_wrappers(
1227 &self,
1228 grm: &YaccGrammar<StorageT>,
1229 ) -> Result<TokenStream, proc_macro2::LexError> {
1230 let (parse_paramname, parse_paramdef);
1231 match grm.parse_param() {
1232 Some((name, tyname)) => {
1233 parse_paramname = str::parse::<TokenStream>(name)?;
1234 let ty = str::parse::<TokenStream>(tyname)?;
1235 parse_paramdef = quote!(#parse_paramname: #ty);
1236 }
1237 None => {
1238 parse_paramname = quote!(());
1239 parse_paramdef = quote! {_: ()};
1240 }
1241 };
1242
1243 let mut wrappers = TokenStream::new();
1244 for pidx in grm.iter_pidxs() {
1245 let ridx = grm.prod_to_rule(pidx);
1246
1247 let wrapper_fn = format_ident!("{}wrapper_{}", ACTION_PREFIX, usize::from(pidx));
1251 let ridx_var = format_ident!("{}ridx", ACTION_PREFIX);
1252 let lexer_var = format_ident!("{}lexer", ACTION_PREFIX);
1253 let span_var = format_ident!("{}span", ACTION_PREFIX);
1254 let args_var = format_ident!("{}args", ACTION_PREFIX);
1255 let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1256 let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1257 let actionskind = str::parse::<TokenStream>(ACTIONS_KIND)?;
1258 let edition_lifetime = if self.rust_edition != RustEdition::Rust2015 {
1259 Some(quote!('_,))
1260 } else {
1261 None
1262 };
1263 let mut wrapper_fn_body = TokenStream::new();
1264 if grm.action(pidx).is_some() {
1265 for i in 0..grm.prod(pidx).len() {
1267 let arg = format_ident!("{}arg_{}", ACTION_PREFIX, i + 1);
1268 wrapper_fn_body.extend(match grm.prod(pidx)[i] {
1269 Symbol::Rule(ref_ridx) => {
1270 let ref_ridx = usize::from(ref_ridx);
1271 let actionvariant = format_ident!("{}{}", ACTIONS_KIND_PREFIX, ref_ridx);
1272 quote!{
1273 #[allow(clippy::let_unit_value)]
1274 let #arg = match #args_var.next().unwrap() {
1275 ::lrpar::parser::AStackType::ActionType(#actionskind::#actionvariant(x)) => x,
1276 _ => unreachable!()
1277 };
1278 }
1279 }
1280 Symbol::Token(_) => {
1281 quote!{
1282 let #arg = match #args_var.next().unwrap() {
1283 ::lrpar::parser::AStackType::Lexeme(l) => {
1284 if l.faulty() {
1285 Err(l)
1286 } else {
1287 Ok(l)
1288 }
1289 },
1290 ::lrpar::parser::AStackType::ActionType(_) => unreachable!()
1291 };
1292 }
1293 }
1294 })
1295 }
1296
1297 let args = (0..grm.prod(pidx).len())
1299 .map(|i| format_ident!("{}arg_{}", ACTION_PREFIX, i + 1))
1300 .collect::<Vec<_>>();
1301 let action_fn = format_ident!("{}action_{}", ACTION_PREFIX, usize::from(pidx));
1302 let actionsvariant = format_ident!("{}{}", ACTIONS_KIND_PREFIX, usize::from(ridx));
1303
1304 wrapper_fn_body.extend(match grm.actiontype(ridx) {
1305 Some(s) if s == "()" => {
1306 quote!{
1310 #action_fn(#ridx_var, #lexer_var, #span_var, #parse_paramname, #(#args,)*);
1311 #actionskind::#actionsvariant(())
1312 }
1313 }
1314 _ => {
1315 quote!{
1316 #actionskind::#actionsvariant(#action_fn(#ridx_var, #lexer_var, #span_var, #parse_paramname, #(#args,)*))
1317 }
1318 }
1319 })
1320 } else if pidx == grm.start_prod() {
1321 wrapper_fn_body.extend(quote!(unreachable!()));
1322 } else {
1323 panic!(
1324 "Production in rule '{}' must have an action body.",
1325 grm.rule_name_str(grm.prod_to_rule(pidx))
1326 );
1327 };
1328
1329 let attrib = if pidx == grm.start_prod() {
1330 Some(quote!(#[allow(unused_variables)]))
1332 } else {
1333 None
1334 };
1335 wrappers.extend(quote!{
1336 #attrib
1337 fn #wrapper_fn<'lexer, 'input: 'lexer>(
1338 #ridx_var: ::cfgrammar::RIdx<#storaget>,
1339 #lexer_var: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1340 #span_var: ::cfgrammar::Span,
1341 mut #args_var: ::std::vec::Drain<#edition_lifetime ::lrpar::parser::AStackType<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #actionskind<'input>>>,
1342 #parse_paramdef
1343 ) -> #actionskind<'input> {
1344 #wrapper_fn_body
1345 }
1346 })
1347 }
1348 let mut actionskindvariants = Vec::new();
1349 let actionskindhidden = format_ident!("_{}", ACTIONS_KIND_HIDDEN);
1350 let actionskind = str::parse::<TokenStream>(ACTIONS_KIND).unwrap();
1351 for ridx in grm.iter_rules() {
1352 if let Some(actiont) = grm.actiontype(ridx) {
1353 let actionskindvariant =
1354 format_ident!("{}{}", ACTIONS_KIND_PREFIX, usize::from(ridx));
1355 let actiont = str::parse::<TokenStream>(actiont).unwrap();
1356 actionskindvariants.push(quote! {
1357 #actionskindvariant(#actiont)
1358 })
1359 }
1360 }
1361 actionskindvariants
1362 .push(quote!(#actionskindhidden(::std::marker::PhantomData<&'input ()>)));
1363 wrappers.extend(quote! {
1364 #[allow(dead_code)]
1365 enum #actionskind<'input> {
1366 #(#actionskindvariants,)*
1367 }
1368 });
1369 Ok(wrappers)
1370 }
1371
1372 fn gen_user_actions(&self, grm: &YaccGrammar<StorageT>) -> Result<TokenStream, Box<dyn Error>> {
1374 let programs = grm
1375 .programs()
1376 .as_ref()
1377 .map(|s| str::parse::<TokenStream>(s))
1378 .transpose()?;
1379 let mut action_fns = TokenStream::new();
1380 let (parse_paramname, parse_paramdef, parse_param_unit);
1382 match grm.parse_param() {
1383 Some((name, tyname)) => {
1384 parse_param_unit = tyname.trim() == "()";
1385 parse_paramname = str::parse::<TokenStream>(name)?;
1386 let ty = str::parse::<TokenStream>(tyname)?;
1387 parse_paramdef = quote!(#parse_paramname: #ty);
1388 }
1389 None => {
1390 parse_param_unit = true;
1391 parse_paramname = quote!(());
1392 parse_paramdef = quote! {_: ()};
1393 }
1394 };
1395 for pidx in grm.iter_pidxs() {
1396 if pidx == grm.start_prod() {
1397 continue;
1398 }
1399
1400 let mut args = Vec::with_capacity(grm.prod(pidx).len());
1402 for i in 0..grm.prod(pidx).len() {
1403 let argt = match grm.prod(pidx)[i] {
1404 Symbol::Rule(ref_ridx) => {
1405 str::parse::<TokenStream>(grm.actiontype(ref_ridx).as_ref().unwrap())?
1406 }
1407 Symbol::Token(_) => {
1408 let lexemet =
1409 str::parse::<TokenStream>(type_name::<LexerTypesT::LexemeT>())?;
1410 quote!(::std::result::Result<#lexemet, #lexemet>)
1411 }
1412 };
1413 let arg = format_ident!("{}arg_{}", ACTION_PREFIX, i + 1);
1414 args.push(quote!(mut #arg: #argt));
1415 }
1416
1417 let returnt = {
1421 let actiont = grm.actiontype(grm.prod_to_rule(pidx)).as_ref().unwrap();
1422 if actiont == "()" {
1423 None
1424 } else {
1425 let actiont = str::parse::<TokenStream>(actiont)?;
1426 Some(quote!( -> #actiont))
1427 }
1428 };
1429 let action_fn = format_ident!("{}action_{}", ACTION_PREFIX, usize::from(pidx));
1430 let lexer_var = format_ident!("{}lexer", ACTION_PREFIX);
1431 let span_var = format_ident!("{}span", ACTION_PREFIX);
1432 let ridx_var = format_ident!("{}ridx", ACTION_PREFIX);
1433 let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1434 let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1435 let bind_parse_param = if !parse_param_unit {
1436 Some(quote! {let _ = #parse_paramname;})
1437 } else {
1438 None
1439 };
1440
1441 let pre_action = grm.action(pidx).as_ref().ok_or_else(|| {
1444 format!(
1445 "Rule {} has a production which is missing action code",
1446 grm.rule_name_str(grm.prod_to_rule(pidx))
1447 )
1448 })?;
1449 let mut last = 0;
1450 let mut outs = String::new();
1451 loop {
1452 match pre_action[last..].find('$') {
1453 Some(off) => {
1454 if pre_action[last + off..].starts_with("$$") {
1455 outs.push_str(&pre_action[last..last + off + "$".len()]);
1456 last = last + off + "$$".len();
1457 } else if pre_action[last + off..].starts_with("$lexer") {
1458 outs.push_str(&pre_action[last..last + off]);
1459 write!(outs, "{prefix}lexer", prefix = ACTION_PREFIX).ok();
1460 last = last + off + "$lexer".len();
1461 } else if pre_action[last + off..].starts_with("$span") {
1462 outs.push_str(&pre_action[last..last + off]);
1463 write!(outs, "{prefix}span", prefix = ACTION_PREFIX).ok();
1464 last = last + off + "$span".len();
1465 } else if last + off + 1 < pre_action.len()
1466 && pre_action[last + off + 1..].starts_with(|c: char| c.is_numeric())
1467 {
1468 outs.push_str(&pre_action[last..last + off]);
1469 write!(outs, "{prefix}arg_", prefix = ACTION_PREFIX).ok();
1470 last = last + off + "$".len();
1471 } else {
1472 panic!(
1473 "Unknown text following '$' operator: {}",
1474 &pre_action[last + off..]
1475 );
1476 }
1477 }
1478 None => {
1479 outs.push_str(&pre_action[last..]);
1480 break;
1481 }
1482 }
1483 }
1484
1485 let action_body = str::parse::<TokenStream>(&outs)?;
1486 action_fns.extend(quote!{
1487 #[allow(clippy::too_many_arguments)]
1488 fn #action_fn<'lexer, 'input: 'lexer>(#ridx_var: ::cfgrammar::RIdx<#storaget>,
1489 #lexer_var: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1490 #span_var: ::cfgrammar::Span,
1491 #parse_paramdef,
1492 #(#args,)*)#returnt {
1493 #bind_parse_param
1494 #action_body
1495 }
1496
1497 })
1498 }
1499 Ok(quote! {
1500 #programs
1501 #action_fns
1502 })
1503 }
1504
1505 fn user_start_ridx(&self, grm: &YaccGrammar<StorageT>) -> RIdx<StorageT> {
1509 debug_assert_eq!(grm.prod(grm.start_prod()).len(), 1);
1510 match grm.prod(grm.start_prod())[0] {
1511 Symbol::Rule(ridx) => ridx,
1512 _ => unreachable!(),
1513 }
1514 }
1515}
1516
1517#[doc(hidden)]
1520pub fn _reconstitute<StorageT: Decode<()> + Hash + PrimInt + Unsigned + 'static>(
1521 grm_buf: &[u8],
1522 stable_buf: &[u8],
1523) -> (YaccGrammar<StorageT>, StateTable<StorageT>) {
1524 let (grm, _) = decode_from_slice(grm_buf, bincode::config::standard()).unwrap();
1525 let (stable, _) = decode_from_slice(stable_buf, bincode::config::standard()).unwrap();
1526 (grm, stable)
1527}
1528
1529pub struct CTParser<StorageT = u32>
1531where
1532 StorageT: Eq + Hash,
1533{
1534 regenerated: bool,
1535 rule_ids: HashMap<String, StorageT>,
1536 yacc_grammar: YaccGrammar<StorageT>,
1537 grammar_src: String,
1538 grammar_path: PathBuf,
1539 conflicts: Option<(StateGraph<StorageT>, StateTable<StorageT>)>,
1540}
1541
1542impl<StorageT> CTParser<StorageT>
1543where
1544 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
1545 usize: AsPrimitive<StorageT>,
1546{
1547 pub fn regenerated(&self) -> bool {
1549 self.regenerated
1550 }
1551
1552 pub fn token_map(&self) -> &HashMap<String, StorageT> {
1555 &self.rule_ids
1556 }
1557
1558 #[allow(private_interfaces)]
1564 pub fn conflicts(
1565 &self,
1566 _: crate::unstable::UnstableApi,
1567 ) -> Option<(
1568 &YaccGrammar<StorageT>,
1569 &StateGraph<StorageT>,
1570 &StateTable<StorageT>,
1571 &Conflicts<StorageT>,
1572 )> {
1573 if let Some((sgraph, stable)) = &self.conflicts {
1574 return Some((
1575 &self.yacc_grammar,
1576 sgraph,
1577 stable,
1578 stable.conflicts().unwrap(),
1579 ));
1580 }
1581 None
1582 }
1583
1584 #[doc(hidden)]
1585 pub fn yacc_grammar(&self) -> &YaccGrammar<StorageT> {
1586 &self.yacc_grammar
1587 }
1588 #[doc(hidden)]
1589 pub fn grammar_src(&self) -> &str {
1590 &self.grammar_src
1591 }
1592 #[doc(hidden)]
1593 pub fn grammar_path(&self) -> &Path {
1594 self.grammar_path.as_path()
1595 }
1596}
1597
1598fn indent(indent: &str, s: &str) -> String {
1609 format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
1610}
1611
1612#[cfg(all(not(target_arch = "wasm32"), test))]
1614mod test {
1615 use std::{fs::File, io::Write, path::PathBuf};
1616
1617 use super::{CTConflictsError, CTParserBuilder};
1618 use crate::test_utils::TestLexerTypes;
1619 use cfgrammar::yacc::{YaccKind, YaccOriginalActionKind};
1620 use tempfile::TempDir;
1621
1622 #[test]
1623 fn test_conflicts() {
1624 let temp = TempDir::new().unwrap();
1625 let mut file_path = PathBuf::from(temp.as_ref());
1626 file_path.push("grm.y");
1627 let mut f = File::create(&file_path).unwrap();
1628 let _ = f.write_all(
1629 "%start A
1630%%
1631A : 'a' 'b' | B 'b';
1632B : 'a' | C;
1633C : 'a';"
1634 .as_bytes(),
1635 );
1636
1637 match CTParserBuilder::<TestLexerTypes>::new()
1638 .error_on_conflicts(false)
1639 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1640 .grammar_path(file_path.to_str().unwrap())
1641 .output_path(file_path.with_extension("ignored"))
1642 .build()
1643 .unwrap()
1644 .conflicts(crate::unstable::UnstableApi)
1645 {
1646 Some((_, _, _, conflicts)) => {
1647 assert_eq!(conflicts.sr_len(), 1);
1648 assert_eq!(conflicts.rr_len(), 1);
1649 }
1650 None => panic!("Expected error data"),
1651 }
1652 }
1653
1654 #[test]
1655 fn test_conflicts_error() {
1656 let temp = TempDir::new().unwrap();
1657 let mut file_path = PathBuf::from(temp.as_ref());
1658 file_path.push("grm.y");
1659 let mut f = File::create(&file_path).unwrap();
1660 let _ = f.write_all(
1661 "%start A
1662%%
1663A : 'a' 'b' | B 'b';
1664B : 'a' | C;
1665C : 'a';"
1666 .as_bytes(),
1667 );
1668
1669 match CTParserBuilder::<TestLexerTypes>::new()
1670 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1671 .grammar_path(file_path.to_str().unwrap())
1672 .output_path(file_path.with_extension("ignored"))
1673 .build()
1674 {
1675 Ok(_) => panic!("Expected error"),
1676 Err(e) => {
1677 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1678 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1679 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1680 }
1681 }
1682 }
1683
1684 #[test]
1685 fn test_expect_error() {
1686 let temp = TempDir::new().unwrap();
1687 let mut file_path = PathBuf::from(temp.as_ref());
1688 file_path.push("grm.y");
1689 let mut f = File::create(&file_path).unwrap();
1690 let _ = f.write_all(
1691 "%start A
1692%expect 2
1693%%
1694A: 'a' 'b' | B 'b';
1695B: 'a';"
1696 .as_bytes(),
1697 );
1698
1699 match CTParserBuilder::<TestLexerTypes>::new()
1700 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1701 .grammar_path(file_path.to_str().unwrap())
1702 .output_path(file_path.with_extension("ignored"))
1703 .build()
1704 {
1705 Ok(_) => panic!("Expected error"),
1706 Err(e) => {
1707 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1708 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 0);
1709 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1710 }
1711 }
1712 }
1713
1714 #[test]
1715 fn test_expectrr_error() {
1716 let temp = TempDir::new().unwrap();
1717 let mut file_path = PathBuf::from(temp.as_ref());
1718 file_path.push("grm.y");
1719 let mut f = File::create(&file_path).unwrap();
1720 let _ = f.write_all(
1721 "%start A
1722%expect 1
1723%expect-rr 2
1724%%
1725A : 'a' 'b' | B 'b';
1726B : 'a' | C;
1727C : 'a';"
1728 .as_bytes(),
1729 );
1730
1731 match CTParserBuilder::<TestLexerTypes>::new()
1732 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1733 .grammar_path(file_path.to_str().unwrap())
1734 .output_path(file_path.with_extension("ignored"))
1735 .build()
1736 {
1737 Ok(_) => panic!("Expected error"),
1738 Err(e) => {
1739 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1740 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1741 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1742 }
1743 }
1744 }
1745
1746 #[cfg(test)]
1747 #[test]
1748 fn test_recoverer_header() -> Result<(), Box<dyn std::error::Error>> {
1749 use crate::RecoveryKind as RK;
1750 #[rustfmt::skip]
1751 let recovery_kinds = [
1752 (Some(RK::None), Some(RK::None), Some(RK::None)),
1755 (Some(RK::None), Some(RK::CPCTPlus), Some(RK::None)),
1756 (Some(RK::CPCTPlus), Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1757 (Some(RK::CPCTPlus), Some(RK::None), Some(RK::CPCTPlus)),
1758 (None, Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1759 (None, Some(RK::None), Some(RK::None)),
1760 (None, None, Some(RK::CPCTPlus)),
1761 (Some(RK::None), None, Some(RK::None)),
1762 (Some(RK::CPCTPlus), None, Some(RK::CPCTPlus)),
1763 ];
1764
1765 for (i, (builder_arg, header_arg, expected_rk)) in
1766 recovery_kinds.iter().cloned().enumerate()
1767 {
1768 let y_src = if let Some(header_arg) = header_arg {
1769 format!(
1770 "\
1771 %grmtools{{yacckind: Original(NoAction), recoverer: {}}} \
1772 %% \
1773 start: ; \
1774 ",
1775 match header_arg {
1776 RK::None => "RecoveryKind::None",
1777 RK::CPCTPlus => "RecoveryKind::CPCTPlus",
1778 }
1779 )
1780 } else {
1781 r#"
1782 %grmtools{yacckind: Original(NoAction)}
1783 %%
1784 Start: ;
1785 "#
1786 .to_string()
1787 };
1788 let out_dir = std::env::var("OUT_DIR").unwrap();
1789 let y_path = format!("{out_dir}/recoverykind_test_{i}.y");
1790 let y_out_path = format!("{y_path}.rs");
1791 std::fs::File::create(y_path.clone()).unwrap();
1792 std::fs::write(y_path.clone(), y_src).unwrap();
1793 let mut cp_builder = CTParserBuilder::<TestLexerTypes>::new();
1794 cp_builder = cp_builder
1795 .output_path(y_out_path.clone())
1796 .grammar_path(y_path.clone());
1797 cp_builder = if let Some(builder_arg) = builder_arg {
1798 cp_builder.recoverer(builder_arg)
1799 } else {
1800 cp_builder
1801 }
1802 .inspect_recoverer(Box::new(move |rk| {
1803 if matches!(
1804 (rk, expected_rk),
1805 (RK::None, Some(RK::None)) | (RK::CPCTPlus, Some(RK::CPCTPlus))
1806 ) {
1807 Ok(())
1808 } else {
1809 panic!("Unexpected recovery kind")
1810 }
1811 }));
1812 cp_builder.build()?;
1813 }
1814 Ok(())
1815 }
1816}