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