1use std::{
4 collections::{HashMap, HashSet},
5 env::{current_dir, var},
6 error::Error,
7 fmt::{self, Debug, Write as fmtWrite},
8 fs::{self, File, create_dir_all, read_to_string},
9 hash::Hash,
10 io::Write,
11 marker::PhantomData,
12 path::{Path, PathBuf},
13 sync::{LazyLock, Mutex},
14};
15
16use crate::{
17 LexerTypes, RTParserBuilder, RecoveryKind,
18 codegen::{
19 ParserBuildEnv, ParserBuildEnvArgs, ParserBuildEnvError, ParserCodegen, ParserSrcEnv,
20 ParserSrcEnvError,
21 },
22 diagnostics::{DiagnosticFormatter, SpannedDiagnosticFormatter},
23};
24
25#[cfg(feature = "_unstable_api")]
26use crate::unstable_api::UnstableApi;
27
28use cfgrammar::{
29 Location,
30 header::{Header, HeaderError, HeaderErrorKind, HeaderValue, Value},
31 markmap::{Entry, MergeBehavior},
32 yacc::{YaccGrammar, YaccKind, ast::ASTWithValidityInfo},
33};
34use filetime::FileTime;
35use lrtable::{StateGraph, StateTable, statetable::Conflicts};
36use num_traits::{AsPrimitive, PrimInt, Unsigned};
37use wincode::{SchemaRead, SchemaReadOwned, SchemaWrite};
38
39const RUST_FILE_EXT: &str = "rs";
40const WARNING: &str = "[Warning]";
41pub(crate) const ERROR: &str = "[Error]";
42
43static GENERATED_PATHS: LazyLock<Mutex<HashSet<PathBuf>>> =
44 LazyLock::new(|| Mutex::new(HashSet::new()));
45
46pub(crate) struct CTConflictsError<StorageT: Eq + Hash> {
47 pub(crate) conflicts_diagnostic: String,
48 #[cfg(test)]
49 #[cfg_attr(test, allow(dead_code))]
50 pub(crate) stable: StateTable<StorageT>,
51 pub(crate) phantom: PhantomData<StorageT>,
52}
53
54impl<StorageT> fmt::Display for CTConflictsError<StorageT>
55where
56 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
57 usize: AsPrimitive<StorageT>,
58{
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 write!(f, "{}", self.conflicts_diagnostic)
61 }
62}
63
64impl<StorageT> fmt::Debug for CTConflictsError<StorageT>
65where
66 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
67 usize: AsPrimitive<StorageT>,
68{
69 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70 write!(f, "{}", self.conflicts_diagnostic)
71 }
72}
73
74impl<StorageT> Error for CTConflictsError<StorageT>
75where
76 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
77 usize: AsPrimitive<StorageT>,
78{
79}
80
81struct ErrorString(String);
83impl fmt::Display for ErrorString {
84 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85 let ErrorString(s) = self;
86 write!(f, "{}", s)
87 }
88}
89impl fmt::Debug for ErrorString {
90 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91 let ErrorString(s) = self;
92 write!(f, "{}", s)
93 }
94}
95impl Error for ErrorString {}
96
97#[derive(Clone, PartialEq, Eq, Debug)]
99#[non_exhaustive]
100pub enum Visibility {
101 Private,
103 Public,
105 PublicSuper,
107 PublicSelf,
109 PublicCrate,
111 PublicIn(String),
113}
114
115#[derive(Clone, Copy, PartialEq, Eq, Debug)]
119#[non_exhaustive]
120pub enum RustEdition {
121 Rust2015,
122 Rust2018,
123 Rust2021,
124}
125
126#[non_exhaustive]
130#[derive(SchemaRead, SchemaWrite, Debug, Clone, Copy)]
131pub enum SerialisationFormat {
132 FixedSizeInteger,
134 VariableSizedInteger,
136}
137
138impl TryFrom<SerialisationFormat> for Value<Location> {
139 type Error = cfgrammar::header::HeaderError<Location>;
140 fn try_from(kind: SerialisationFormat) -> Result<Value<Location>, HeaderError<Location>> {
141 let from_loc = Location::Other("From<SerialisationFormat>".to_string());
142 Ok(Value::Namespaced(
143 format!("SerialisationFormat::{kind:?}"),
144 from_loc,
145 ))
146 }
147}
148
149impl<T: Clone + Debug> TryFrom<&Value<T>> for SerialisationFormat {
150 type Error = HeaderError<T>;
151 fn try_from(value: &Value<T>) -> Result<SerialisationFormat, HeaderError<T>> {
152 match value {
153 Value::Namespaced(serialisation_fmt, loc) => match serialisation_fmt.as_str() {
154 "SerialisationFormat::FixedSizeInteger" | "FixedSizeInteger" => {
155 Ok(SerialisationFormat::FixedSizeInteger)
156 }
157 "SerialisationFormat::VariableSizedInteger" | "VariableSizedInteger" => {
158 Ok(SerialisationFormat::VariableSizedInteger)
159 }
160 _ => Err(HeaderError {
161 kind: HeaderErrorKind::InvalidEntry("serialisation_format"),
162 locations: vec![loc.clone()],
163 }),
164 },
165 val => Err(HeaderError {
166 kind: HeaderErrorKind::InvalidEntry("serialisation_format"),
167 locations: vec![val.primary_location().clone()],
168 }),
169 }
170 }
171}
172
173#[doc(hidden)]
175pub use wincode;
176
177pub struct CTParserBuilder<'a, LexerTypesT: LexerTypes>
180where
181 LexerTypesT::StorageT: Eq + Hash,
182 usize: AsPrimitive<LexerTypesT::StorageT>,
183{
184 grammar_path: Option<PathBuf>,
188 grammar_src: Option<String>,
190 from_ast: Option<ASTWithValidityInfo>,
192 output_path: Option<PathBuf>,
193 mod_name: Option<&'a str>,
194 recoverer: Option<RecoveryKind>,
195 yacckind: Option<YaccKind>,
196 error_on_conflicts: bool,
197 warnings_are_errors: bool,
198 show_warnings: bool,
199 visibility: Visibility,
200 rust_edition: RustEdition,
201 inspect_rt: Option<
202 Box<
203 dyn for<'b> FnMut(
204 &'b mut Header<Location>,
205 RTParserBuilder<LexerTypesT::StorageT, LexerTypesT>,
206 &'b HashMap<String, LexerTypesT::StorageT>,
207 &PathBuf,
208 ) -> Result<(), Box<dyn Error>>,
209 >,
210 >,
211 serialisation_format: Option<SerialisationFormat>,
212 #[cfg(test)]
214 inspect_callback: Option<Box<dyn Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>>,
215 phantom: PhantomData<LexerTypesT>,
216}
217
218pub(crate) type FixIntConfig = wincode::config::Configuration;
220pub(crate) type VarIntConfig = wincode::config::Configuration<
222 true,
223 4194304,
224 wincode::len::BincodeLen,
225 wincode::int_encoding::LittleEndian,
226 wincode::int_encoding::VarInt,
227>;
228
229impl<
230 'a,
231 StorageT: 'static
232 + Debug
233 + Hash
234 + PrimInt
235 + SchemaWrite<FixIntConfig, Src = StorageT>
236 + SchemaWrite<VarIntConfig, Src = StorageT>
237 + Unsigned,
238 LexerTypesT: LexerTypes<StorageT = StorageT>,
239> CTParserBuilder<'a, LexerTypesT>
240where
241 usize: AsPrimitive<StorageT>,
242{
243 pub fn new() -> Self {
265 CTParserBuilder {
266 grammar_path: None,
267 grammar_src: None,
268 from_ast: None,
269 output_path: None,
270 mod_name: None,
271 recoverer: None,
272 yacckind: None,
273 error_on_conflicts: true,
274 warnings_are_errors: true,
275 show_warnings: true,
276 visibility: Visibility::Private,
277 rust_edition: RustEdition::Rust2021,
278 inspect_rt: None,
279 serialisation_format: None,
280 #[cfg(test)]
281 inspect_callback: None,
282 phantom: PhantomData,
283 }
284 }
285
286 pub fn grammar_in_src_dir<P>(mut self, srcp: P) -> Result<Self, Box<dyn Error>>
303 where
304 P: AsRef<Path>,
305 {
306 if !srcp.as_ref().is_relative() {
307 return Err(format!(
308 "Grammar path '{}' must be a relative path.",
309 srcp.as_ref().to_str().unwrap_or("<invalid UTF-8>")
310 )
311 .into());
312 }
313
314 let mut grmp = current_dir()?;
315 grmp.push("src");
316 grmp.push(srcp.as_ref());
317 self.grammar_path = Some(grmp);
318
319 let mut outp = PathBuf::new();
320 outp.push(var("OUT_DIR").unwrap());
321 outp.push(srcp.as_ref().parent().unwrap().to_str().unwrap());
322 create_dir_all(&outp)?;
323 let mut leaf = srcp
324 .as_ref()
325 .file_name()
326 .unwrap()
327 .to_str()
328 .unwrap()
329 .to_owned();
330 write!(leaf, ".{}", RUST_FILE_EXT).ok();
331 outp.push(leaf);
332 Ok(self.output_path(outp))
333 }
334
335 #[cfg(feature = "_unstable_api")]
338 pub fn grammar_ast(mut self, valid_ast: ASTWithValidityInfo, _api_key: UnstableApi) -> Self {
339 self.from_ast = Some(valid_ast);
340 self
341 }
342
343 pub fn grammar_path<P>(mut self, inp: P) -> Self
347 where
348 P: AsRef<Path>,
349 {
350 self.grammar_path = Some(inp.as_ref().to_owned());
351 self
352 }
353
354 #[cfg(feature = "_unstable_api")]
355 pub fn with_grammar_src(mut self, src: String, _api_key: UnstableApi) -> Self {
356 self.grammar_src = Some(src);
357 self
358 }
359
360 pub fn output_path<P>(mut self, outp: P) -> Self
365 where
366 P: AsRef<Path>,
367 {
368 self.output_path = Some(outp.as_ref().to_owned());
369 self
370 }
371
372 pub fn mod_name(mut self, mod_name: &'a str) -> Self {
376 self.mod_name = Some(mod_name);
377 self
378 }
379
380 pub fn visibility(mut self, vis: Visibility) -> Self {
382 self.visibility = vis;
383 self
384 }
385
386 pub fn recoverer(mut self, rk: RecoveryKind) -> Self {
388 self.recoverer = Some(rk);
389 self
390 }
391
392 pub fn yacckind(mut self, yk: YaccKind) -> Self {
394 self.yacckind = Some(yk);
395 self
396 }
397
398 pub fn error_on_conflicts(mut self, b: bool) -> Self {
401 self.error_on_conflicts = b;
402 self
403 }
404
405 pub fn warnings_are_errors(mut self, b: bool) -> Self {
408 self.warnings_are_errors = b;
409 self
410 }
411
412 pub fn show_warnings(mut self, b: bool) -> Self {
415 self.show_warnings = b;
416 self
417 }
418
419 pub fn rust_edition(mut self, edition: RustEdition) -> Self {
422 self.rust_edition = edition;
423 self
424 }
425
426 pub fn serialisation_format(mut self, serialisation_format: SerialisationFormat) -> Self {
427 self.serialisation_format = Some(serialisation_format);
428 self
429 }
430
431 #[cfg(test)]
432 pub fn inspect_recoverer(
433 mut self,
434 cb: Box<dyn for<'h, 'y> Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>,
435 ) -> Self {
436 self.inspect_callback = Some(cb);
437 self
438 }
439
440 #[doc(hidden)]
441 pub fn inspect_rt(
442 mut self,
443 cb: Box<
444 dyn for<'b, 'y> FnMut(
445 &'b mut Header<Location>,
446 RTParserBuilder<'y, StorageT, LexerTypesT>,
447 &'b HashMap<String, StorageT>,
448 &PathBuf,
449 ) -> Result<(), Box<dyn Error>>,
450 >,
451 ) -> Self {
452 self.inspect_rt = Some(cb);
453 self
454 }
455
456 pub fn build(mut self) -> Result<CTParser<StorageT>, Box<dyn Error>> {
509 let grmp = self
510 .grammar_path
511 .as_ref()
512 .expect("grammar_path must be specified before processing.");
513 let outp = self
514 .output_path
515 .as_ref()
516 .expect("output_path must be specified before processing.");
517 let mut header = Header::new();
518
519 match header.entry("cfgrammar.yacckind".to_string()) {
520 Entry::Occupied(_) => unreachable!(),
521 Entry::Vacant(mut v) => match self.yacckind {
522 Some(YaccKind::Eco) => panic!("Eco compile-time grammar generation not supported."),
523 Some(yk) => {
524 let yk_value = Value::try_from(yk)?;
525 let mut o = v.insert_entry(HeaderValue(
526 Location::Other("CTParserBuilder".to_string()),
527 yk_value,
528 ));
529 o.set_merge_behavior(MergeBehavior::Ours);
530 }
531 None => {
532 v.mark_required();
533 }
534 },
535 }
536 if let Some(recoverer) = self.recoverer {
537 match header.entry("lrpar.recoverer".to_string()) {
538 Entry::Occupied(_) => unreachable!(),
539 Entry::Vacant(v) => {
540 let rk_value = Value::try_from(recoverer)?;
541 let mut o = v.insert_entry(HeaderValue(
542 Location::Other("CTParserBuilder".to_string()),
543 rk_value,
544 ));
545 o.set_merge_behavior(MergeBehavior::Ours);
546 }
547 }
548 }
549
550 if let Some(encoding) = self.serialisation_format {
551 match header.entry("lrpar.serialisation_format".to_string()) {
552 Entry::Occupied(_) => unreachable!(),
553 Entry::Vacant(v) => {
554 let rk_value = Value::try_from(encoding)?;
555 let mut o = v.insert_entry(HeaderValue(
556 Location::Other("CTParserBuilder".to_string()),
557 rk_value,
558 ));
559 o.set_merge_behavior(MergeBehavior::Ours);
560 }
561 }
562 }
563
564 {
565 let mut lk = GENERATED_PATHS.lock().unwrap();
566 if lk.contains(outp.as_path()) {
567 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());
568 }
569 lk.insert(outp.clone());
570 }
571
572 let inc = if let Some(grammar_src) = &self.grammar_src {
573 grammar_src.clone()
574 } else {
575 read_to_string(grmp).map_err(|e| format!("When reading '{}': {e}", grmp.display()))?
576 };
577
578 let src_env = ParserSrcEnv::new_with_header(&inc, Some(grmp), header);
579 let yacc_diag = SpannedDiagnosticFormatter::new(&inc, grmp);
580 let build_args = ParserBuildEnvArgs::new()
581 .ast_with_validity_info(self.from_ast.as_ref())
582 .mod_name(self.mod_name)
583 .show_warnings(self.show_warnings)
584 .error_on_conflicts(self.error_on_conflicts)
585 .warnings_are_errors(self.warnings_are_errors)
586 .visibility(self.visibility.clone())
587 .rust_edition(self.rust_edition);
588 let mut build_env = src_env.build_env(build_args).map_err(|e| match e {
589 ParserSrcEnvError::GrmtoolsSectionParseError(es) => {
590 let mut out = String::new();
591 out.push_str(&format!(
592 "\n{ERROR}{}\n",
593 yacc_diag.file_location_msg(" parsing the `%grmtools` section", None)
594 ));
595 for e in es {
596 out.push_str(&indent(" ", &yacc_diag.format_error(e).to_string()));
597 out.push('\n');
598 }
599 Box::<dyn Error>::from(ErrorString(out))
600 }
601 e => e.to_string().into(),
602 })?;
603 self.recoverer = Some(build_env.recoverer());
606 self.yacckind = Some(build_env.yacc_kind());
607
608 let warnings = build_env.ast_with_validity_info().ast().warnings();
609 if self.warnings_are_errors && !warnings.is_empty() {
610 let mut out = String::new();
611 out.push_str(&format!(
612 "\n{ERROR}{}\n",
613 yacc_diag.file_location_msg("", None)
614 ));
615 for e in warnings {
616 out.push_str(&format!(
617 "{}\n",
618 indent(" ", &yacc_diag.format_warning(e).to_string())
619 ));
620 }
621 return Err(ErrorString(out).into());
622 } else if !warnings.is_empty() {
623 for w in warnings {
624 let ws_loc = yacc_diag.file_location_msg("", None);
625 let ws = indent(" ", &yacc_diag.format_warning(w).to_string());
626 if std::env::var("OUT_DIR").is_ok() && self.show_warnings {
628 for line in ws_loc.lines().chain(ws.lines()) {
629 println!("cargo:warning={}", line);
630 }
631 } else if self.show_warnings {
632 eprintln!("{}", ws_loc);
633 eprintln!("{WARNING} {}", ws);
634 }
635 }
636 }
637
638 #[cfg(test)]
639 if let Some(cb) = &self.inspect_callback {
640 cb(build_env.recoverer())?;
641 }
642
643 let timestamp = env!("VERGEN_BUILD_TIMESTAMP");
644 let code_gen = build_env.code_generator(timestamp).map_err(|e| match e {
645 ParserBuildEnvError::YaccGrammarErrors(errs) => {
646 let mut out = String::new();
647 out.push_str(&format!(
648 "\n{ERROR}{}\n",
649 yacc_diag.file_location_msg("", None)
650 ));
651 for e in errs {
652 out.push_str(&indent(" ", &yacc_diag.format_error(e).to_string()));
653 out.push('\n');
654 }
655 ErrorString(out)
656 }
657 e => ErrorString(e.to_string()),
658 })?;
659 let grm = code_gen.grm();
660 let rule_ids = grm
661 .tokens_map()
662 .iter()
663 .map(|(&n, &i)| (n.to_owned(), i.as_storaget()))
664 .collect::<HashMap<_, _>>();
665
666 let cache = code_gen.cache_str(&build_env);
667
668 if let Ok(ref inmd) = fs::metadata(grmp)
676 && let Ok(ref out_rs_md) = fs::metadata(outp)
677 && FileTime::from_last_modification_time(out_rs_md)
678 > FileTime::from_last_modification_time(inmd)
679 && let Ok(outc) = read_to_string(outp)
680 {
681 if outc.contains(&cache) {
682 let (grm, _, _) = code_gen.finish();
683
684 return Ok(CTParser {
685 regenerated: false,
686 rule_ids,
687 yacc_grammar: grm,
688 grammar_src: inc,
689 grammar_path: self.grammar_path.unwrap(),
690 conflicts: None,
691 });
692 } else {
693 #[cfg(grmtools_extra_checks)]
694 if std::env::var("CACHE_EXPECTED").is_ok() {
695 eprintln!("outc: {}", outc);
696 eprintln!("using cache: {}", cache,);
697 panic!("The cache regenerated however, it was expected to match");
699 }
700 }
701 }
702
703 fs::remove_file(outp).ok();
710
711 let stable = code_gen.stable();
712 if self.error_on_conflicts
713 && let Some(c) = stable.conflicts()
714 {
715 match (grm.expect(), grm.expectrr()) {
716 (Some(i), Some(j)) if i == c.sr_len() && j == c.rr_len() => (),
717 (Some(i), None) if i == c.sr_len() && 0 == c.rr_len() => (),
718 (None, Some(j)) if 0 == c.sr_len() && j == c.rr_len() => (),
719 (None, None) if 0 == c.rr_len() && 0 == c.sr_len() => (),
720 _ => {
721 let conflicts_diagnostic = yacc_diag.format_conflicts::<LexerTypesT>(
722 grm,
723 build_env.ast_with_validity_info().ast(),
724 c,
725 code_gen.sgraph(),
726 stable,
727 );
728 #[cfg(test)]
729 let (_, _, stable) = code_gen.finish();
730 return Err(Box::new(CTConflictsError {
731 conflicts_diagnostic,
732 phantom: PhantomData,
733 #[cfg(test)]
734 stable,
735 }));
736 }
737 }
738 }
739
740 if let Some(ref mut inspector_rt) = self.inspect_rt {
741 let rt: RTParserBuilder<'_, StorageT, LexerTypesT> = RTParserBuilder::new(grm, stable);
742 let rt = if let Some(rk) = self.recoverer {
743 rt.recoverer(rk)
744 } else {
745 rt
746 };
747 inspector_rt(build_env.header_mut(), rt, &rule_ids, grmp)?
748 }
749
750 build_env
751 .check_unused_header_keys()
752 .map_err(|e| ErrorString(e.to_string()))?;
753
754 self.output_file(
755 &code_gen,
756 outp,
757 &format!("/* CACHE INFORMATION {} */\n", cache),
758 &build_env,
759 )?;
760 let (grm, sgraph, stable) = code_gen.finish();
761 let conflicts = if stable.conflicts().is_some() {
762 Some((sgraph, stable))
763 } else {
764 None
765 };
766 Ok(CTParser {
767 regenerated: true,
768 rule_ids,
769 yacc_grammar: grm,
770 grammar_src: inc,
771 grammar_path: self.grammar_path.unwrap(),
772 conflicts,
773 })
774 }
775
776 #[deprecated(
783 since = "0.11.0",
784 note = "Please use grammar_in_src_dir(), build(), and token_map() instead"
785 )]
786 #[allow(deprecated)]
787 pub fn process_file_in_src(
788 &mut self,
789 srcp: &str,
790 ) -> Result<HashMap<String, StorageT>, Box<dyn Error>> {
791 let mut inp = current_dir()?;
792 inp.push("src");
793 inp.push(srcp);
794 let mut outp = PathBuf::new();
795 outp.push(var("OUT_DIR").unwrap());
796 outp.push(Path::new(srcp).parent().unwrap().to_str().unwrap());
797 create_dir_all(&outp)?;
798 let mut leaf = Path::new(srcp)
799 .file_name()
800 .unwrap()
801 .to_str()
802 .unwrap()
803 .to_owned();
804 write!(leaf, ".{}", RUST_FILE_EXT).ok();
805 outp.push(leaf);
806 self.process_file(inp, outp)
807 }
808
809 #[deprecated(
845 since = "0.11.0",
846 note = "Please use grammar_path(), output_path(), build(), and token_map() instead"
847 )]
848 pub fn process_file<P, Q>(
849 &mut self,
850 inp: P,
851 outp: Q,
852 ) -> Result<HashMap<String, StorageT>, Box<dyn Error>>
853 where
854 P: AsRef<Path>,
855 Q: AsRef<Path>,
856 {
857 self.grammar_path = Some(inp.as_ref().to_owned());
858 self.output_path = Some(outp.as_ref().to_owned());
859 let cl: CTParserBuilder<LexerTypesT> = CTParserBuilder {
860 grammar_path: self.grammar_path.clone(),
861 grammar_src: None,
862 from_ast: None,
863 output_path: self.output_path.clone(),
864 mod_name: self.mod_name,
865 recoverer: self.recoverer,
866 yacckind: self.yacckind,
867 error_on_conflicts: self.error_on_conflicts,
868 warnings_are_errors: self.warnings_are_errors,
869 show_warnings: self.show_warnings,
870 visibility: self.visibility.clone(),
871 rust_edition: self.rust_edition,
872 inspect_rt: None,
873 serialisation_format: self.serialisation_format,
874 #[cfg(test)]
875 inspect_callback: None,
876 phantom: PhantomData,
877 };
878 Ok(cl.build()?.rule_ids)
879 }
880
881 fn output_file<P: AsRef<Path>>(
882 &self,
883 code_gen: &ParserCodegen<LexerTypesT>,
884 outp_rs: P,
885 cache: &str,
886 build_env: &ParserBuildEnv<'_, LexerTypesT>,
887 ) -> Result<(), Box<dyn Error>> {
888 let outs = code_gen
889 .generate(build_env)
890 .map_err(|e| ErrorString(e.to_string()))?;
891 let mut f = File::create(outp_rs)?;
892 f.write_all(outs.as_bytes())?;
893 f.write_all(cache.as_bytes())?;
894 Ok(())
895 }
896}
897
898#[doc(hidden)]
901pub struct ParserData<StorageT: Eq + Hash> {
902 grm: YaccGrammar<StorageT>,
903 stable: StateTable<StorageT>,
904}
905
906impl<StorageT: Eq + Hash> ParserData<StorageT> {
907 pub fn grm(&self) -> &YaccGrammar<StorageT> {
908 &self.grm
909 }
910
911 pub fn stable(&self) -> &StateTable<StorageT> {
912 &self.stable
913 }
914}
915
916#[doc(hidden)]
919pub fn _reconstitute<
920 C: wincode::config::Config + Clone + Copy,
921 StorageT: SchemaReadOwned<C, Dst = StorageT> + Eq + Hash + PrimInt + Unsigned + 'static,
922>(
923 grm_buf: &[u8],
924 stable_buf: &[u8],
925 config: C,
926) -> ParserData<StorageT> {
927 let grm: YaccGrammar<StorageT> = wincode::config::deserialize_from(grm_buf, config).unwrap();
928 let stable = wincode::config::deserialize_from(stable_buf, config).unwrap();
929 ParserData { grm, stable }
930}
931
932pub struct CTParser<StorageT = u32>
934where
935 StorageT: Eq + Hash,
936{
937 regenerated: bool,
938 rule_ids: HashMap<String, StorageT>,
939 yacc_grammar: YaccGrammar<StorageT>,
940 grammar_src: String,
941 grammar_path: PathBuf,
942 conflicts: Option<(StateGraph<StorageT>, StateTable<StorageT>)>,
943}
944
945impl<StorageT> CTParser<StorageT>
946where
947 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
948 usize: AsPrimitive<StorageT>,
949{
950 pub fn regenerated(&self) -> bool {
952 self.regenerated
953 }
954
955 pub fn token_map(&self) -> &HashMap<String, StorageT> {
958 &self.rule_ids
959 }
960
961 #[allow(private_interfaces)]
967 pub fn conflicts(
968 &self,
969 _: crate::unstable::UnstableApi,
970 ) -> Option<(
971 &YaccGrammar<StorageT>,
972 &StateGraph<StorageT>,
973 &StateTable<StorageT>,
974 &Conflicts<StorageT>,
975 )> {
976 if let Some((sgraph, stable)) = &self.conflicts {
977 return Some((
978 &self.yacc_grammar,
979 sgraph,
980 stable,
981 stable.conflicts().unwrap(),
982 ));
983 }
984 None
985 }
986
987 #[doc(hidden)]
988 pub fn yacc_grammar(&self) -> &YaccGrammar<StorageT> {
989 &self.yacc_grammar
990 }
991 #[doc(hidden)]
992 pub fn grammar_src(&self) -> &str {
993 &self.grammar_src
994 }
995 #[doc(hidden)]
996 pub fn grammar_path(&self) -> &Path {
997 self.grammar_path.as_path()
998 }
999}
1000
1001pub(crate) fn indent(indent: &str, s: &str) -> String {
1012 format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
1013}
1014
1015#[cfg(all(not(target_arch = "wasm32"), test))]
1017mod test {
1018 use std::{fs::File, io::Write, path::PathBuf};
1019
1020 use super::{CTConflictsError, CTParserBuilder};
1021 use crate::test_utils::TestLexerTypes;
1022 use cfgrammar::yacc::{YaccKind, YaccOriginalActionKind};
1023 use tempfile::TempDir;
1024
1025 #[test]
1026 fn test_conflicts() {
1027 let temp = TempDir::new().unwrap();
1028 let mut file_path = PathBuf::from(temp.as_ref());
1029 file_path.push("grm.y");
1030 let mut f = File::create(&file_path).unwrap();
1031 let _ = f.write_all(
1032 "%start A
1033%%
1034A : 'a' 'b' | B 'b';
1035B : 'a' | C;
1036C : 'a';"
1037 .as_bytes(),
1038 );
1039
1040 match CTParserBuilder::<TestLexerTypes>::new()
1041 .error_on_conflicts(false)
1042 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1043 .grammar_path(file_path.to_str().unwrap())
1044 .output_path(file_path.with_extension("ignored"))
1045 .build()
1046 .unwrap()
1047 .conflicts(crate::unstable::UnstableApi)
1048 {
1049 Some((_, _, _, conflicts)) => {
1050 assert_eq!(conflicts.sr_len(), 1);
1051 assert_eq!(conflicts.rr_len(), 1);
1052 }
1053 None => panic!("Expected error data"),
1054 }
1055 }
1056
1057 #[test]
1058 fn test_conflicts_error() {
1059 let temp = TempDir::new().unwrap();
1060 let mut file_path = PathBuf::from(temp.as_ref());
1061 file_path.push("grm.y");
1062 let mut f = File::create(&file_path).unwrap();
1063 let _ = f.write_all(
1064 "%start A
1065%%
1066A : 'a' 'b' | B 'b';
1067B : 'a' | C;
1068C : 'a';"
1069 .as_bytes(),
1070 );
1071
1072 match CTParserBuilder::<TestLexerTypes>::new()
1073 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1074 .grammar_path(file_path.to_str().unwrap())
1075 .output_path(file_path.with_extension("ignored"))
1076 .build()
1077 {
1078 Ok(_) => panic!("Expected error"),
1079 Err(e) => {
1080 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1081 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1082 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1083 }
1084 }
1085 }
1086
1087 #[test]
1088 fn test_expect_error() {
1089 let temp = TempDir::new().unwrap();
1090 let mut file_path = PathBuf::from(temp.as_ref());
1091 file_path.push("grm.y");
1092 let mut f = File::create(&file_path).unwrap();
1093 let _ = f.write_all(
1094 "%start A
1095%expect 2
1096%%
1097A: 'a' 'b' | B 'b';
1098B: 'a';"
1099 .as_bytes(),
1100 );
1101
1102 match CTParserBuilder::<TestLexerTypes>::new()
1103 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1104 .grammar_path(file_path.to_str().unwrap())
1105 .output_path(file_path.with_extension("ignored"))
1106 .build()
1107 {
1108 Ok(_) => panic!("Expected error"),
1109 Err(e) => {
1110 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1111 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 0);
1112 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1113 }
1114 }
1115 }
1116
1117 #[test]
1118 fn test_expectrr_error() {
1119 let temp = TempDir::new().unwrap();
1120 let mut file_path = PathBuf::from(temp.as_ref());
1121 file_path.push("grm.y");
1122 let mut f = File::create(&file_path).unwrap();
1123 let _ = f.write_all(
1124 "%start A
1125%expect 1
1126%expect-rr 2
1127%%
1128A : 'a' 'b' | B 'b';
1129B : 'a' | C;
1130C : 'a';"
1131 .as_bytes(),
1132 );
1133
1134 match CTParserBuilder::<TestLexerTypes>::new()
1135 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1136 .grammar_path(file_path.to_str().unwrap())
1137 .output_path(file_path.with_extension("ignored"))
1138 .build()
1139 {
1140 Ok(_) => panic!("Expected error"),
1141 Err(e) => {
1142 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1143 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1144 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1145 }
1146 }
1147 }
1148
1149 #[test]
1150 fn test_invalid_identifier_in_derived_mod_name() {
1153 let temp = TempDir::new().unwrap();
1154 let mut file_path = PathBuf::from(temp.as_ref());
1155 file_path.push("contains-a-dash.y");
1156 let mut f = File::create(&file_path).unwrap();
1157 let _ = f.write_all(
1158 "%start A
1159%%
1160A : 'a';"
1161 .as_bytes(),
1162 );
1163 match CTParserBuilder::<TestLexerTypes>::new()
1164 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1165 .grammar_path(file_path.to_str().unwrap())
1166 .output_path(file_path.with_extension("ignored"))
1167 .build()
1168 {
1169 Ok(_) => panic!("Expected error"),
1170 Err(e) => {
1171 let err_string = e.to_string();
1172 assert_eq!(
1173 err_string,
1174 "mod_name \'contains-a-dash_y\' is not a valid rust identifier due to 'unexpected token'"
1175 );
1176 }
1177 }
1178 }
1179
1180 #[cfg(test)]
1181 #[test]
1182 fn test_recoverer_header() -> Result<(), Box<dyn std::error::Error>> {
1183 use crate::RecoveryKind as RK;
1184 #[rustfmt::skip]
1185 let recovery_kinds = [
1186 (Some(RK::None), Some(RK::None), Some(RK::None)),
1189 (Some(RK::None), Some(RK::CPCTPlus), Some(RK::None)),
1190 (Some(RK::CPCTPlus), Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1191 (Some(RK::CPCTPlus), Some(RK::None), Some(RK::CPCTPlus)),
1192 (None, Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1193 (None, Some(RK::None), Some(RK::None)),
1194 (None, None, Some(RK::CPCTPlus)),
1195 (Some(RK::None), None, Some(RK::None)),
1196 (Some(RK::CPCTPlus), None, Some(RK::CPCTPlus)),
1197 ];
1198
1199 for (i, (builder_arg, header_arg, expected_rk)) in
1200 recovery_kinds.iter().cloned().enumerate()
1201 {
1202 let y_src = if let Some(header_arg) = header_arg {
1203 format!(
1204 "\
1205 %grmtools{{yacckind: Original(NoAction), recoverer: {}}} \
1206 %% \
1207 start: ; \
1208 ",
1209 match header_arg {
1210 RK::None => "RecoveryKind::None",
1211 RK::CPCTPlus => "RecoveryKind::CPCTPlus",
1212 }
1213 )
1214 } else {
1215 r#"
1216 %grmtools{yacckind: Original(NoAction)}
1217 %%
1218 Start: ;
1219 "#
1220 .to_string()
1221 };
1222 let out_dir = std::env::var("OUT_DIR").unwrap();
1223 let y_path = format!("{out_dir}/recoverykind_test_{i}.y");
1224 let y_out_path = format!("{y_path}.rs");
1225 std::fs::File::create(y_path.clone()).unwrap();
1226 std::fs::write(y_path.clone(), y_src).unwrap();
1227 let mut cp_builder = CTParserBuilder::<TestLexerTypes>::new();
1228 cp_builder = cp_builder
1229 .output_path(y_out_path.clone())
1230 .grammar_path(y_path.clone());
1231 cp_builder = if let Some(builder_arg) = builder_arg {
1232 cp_builder.recoverer(builder_arg)
1233 } else {
1234 cp_builder
1235 }
1236 .inspect_recoverer(Box::new(move |rk| {
1237 if matches!(
1238 (rk, expected_rk),
1239 (RK::None, Some(RK::None)) | (RK::CPCTPlus, Some(RK::CPCTPlus))
1240 ) {
1241 Ok(())
1242 } else {
1243 panic!("Unexpected recovery kind")
1244 }
1245 }));
1246 cp_builder.build()?;
1247 }
1248 Ok(())
1249 }
1250}