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, Namespaced, Setting, 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(match kind {
143 SerialisationFormat::FixedSizeInteger => Value::Setting(Setting::Unitary(Namespaced {
144 namespace: Some(("serialisationformat".to_string(), from_loc.clone())),
145 member: ("fixedsizeinteger".to_string(), from_loc),
146 })),
147 SerialisationFormat::VariableSizedInteger => {
148 Value::Setting(Setting::Unitary(Namespaced {
149 namespace: Some(("serialisationformat".to_string(), from_loc.clone())),
150 member: ("variablesizedinteger".to_string(), from_loc),
151 }))
152 }
153 })
154 }
155}
156
157impl<T: Clone + Debug> TryFrom<&Value<T>> for SerialisationFormat {
158 type Error = HeaderError<T>;
159 fn try_from(value: &Value<T>) -> Result<SerialisationFormat, HeaderError<T>> {
160 let mut err_locs = Vec::new();
161 match value {
162 Value::Setting(Setting::Unitary(Namespaced {
164 namespace,
165 member: (enc_value, enc_value_loc),
166 })) => {
167 if let Some((ns, ns_loc)) = namespace
168 && ns != "serialisationformat"
169 {
170 err_locs.push(ns_loc.clone());
171 }
172 let encodings = [
173 (
174 "fixedsizeinteger".to_string(),
175 SerialisationFormat::FixedSizeInteger,
176 ),
177 (
178 "variablesizedinteger".to_string(),
179 SerialisationFormat::VariableSizedInteger,
180 ),
181 ];
182 let enc_found = encodings
183 .iter()
184 .find_map(|(enc_str, enc)| (enc_str == enc_value).then_some(enc));
185 if let Some(enc) = enc_found {
186 if err_locs.is_empty() {
187 Ok(*enc)
188 } else {
189 Err(HeaderError {
190 kind: HeaderErrorKind::InvalidEntry("serialisation_format"),
191 locations: err_locs,
192 })
193 }
194 } else {
195 err_locs.push(enc_value_loc.clone());
196 Err(HeaderError {
197 kind: HeaderErrorKind::InvalidEntry("serialisation_format"),
198 locations: err_locs,
199 })
200 }
201 }
202 val => {
203 err_locs.push(val.primary_location().clone());
204 Err(HeaderError {
205 kind: HeaderErrorKind::InvalidEntry("serialisation_format"),
206 locations: err_locs,
207 })
208 }
209 }
210 }
211}
212
213#[doc(hidden)]
215pub use wincode;
216
217pub struct CTParserBuilder<'a, LexerTypesT: LexerTypes>
220where
221 LexerTypesT::StorageT: Eq + Hash,
222 usize: AsPrimitive<LexerTypesT::StorageT>,
223{
224 grammar_path: Option<PathBuf>,
228 grammar_src: Option<String>,
230 from_ast: Option<ASTWithValidityInfo>,
232 output_path: Option<PathBuf>,
233 mod_name: Option<&'a str>,
234 recoverer: Option<RecoveryKind>,
235 yacckind: Option<YaccKind>,
236 error_on_conflicts: bool,
237 warnings_are_errors: bool,
238 show_warnings: bool,
239 visibility: Visibility,
240 rust_edition: RustEdition,
241 inspect_rt: Option<
242 Box<
243 dyn for<'b> FnMut(
244 &'b mut Header<Location>,
245 RTParserBuilder<LexerTypesT::StorageT, LexerTypesT>,
246 &'b HashMap<String, LexerTypesT::StorageT>,
247 &PathBuf,
248 ) -> Result<(), Box<dyn Error>>,
249 >,
250 >,
251 serialisation_format: Option<SerialisationFormat>,
252 #[cfg(test)]
254 inspect_callback: Option<Box<dyn Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>>,
255 phantom: PhantomData<LexerTypesT>,
256}
257
258pub(crate) type FixIntConfig = wincode::config::Configuration;
260pub(crate) type VarIntConfig = wincode::config::Configuration<
262 true,
263 4194304,
264 wincode::len::BincodeLen,
265 wincode::int_encoding::LittleEndian,
266 wincode::int_encoding::VarInt,
267>;
268
269impl<
270 'a,
271 StorageT: 'static
272 + Debug
273 + Hash
274 + PrimInt
275 + SchemaWrite<FixIntConfig, Src = StorageT>
276 + SchemaWrite<VarIntConfig, Src = StorageT>
277 + Unsigned,
278 LexerTypesT: LexerTypes<StorageT = StorageT>,
279> CTParserBuilder<'a, LexerTypesT>
280where
281 usize: AsPrimitive<StorageT>,
282{
283 pub fn new() -> Self {
305 CTParserBuilder {
306 grammar_path: None,
307 grammar_src: None,
308 from_ast: None,
309 output_path: None,
310 mod_name: None,
311 recoverer: None,
312 yacckind: None,
313 error_on_conflicts: true,
314 warnings_are_errors: true,
315 show_warnings: true,
316 visibility: Visibility::Private,
317 rust_edition: RustEdition::Rust2021,
318 inspect_rt: None,
319 serialisation_format: None,
320 #[cfg(test)]
321 inspect_callback: None,
322 phantom: PhantomData,
323 }
324 }
325
326 pub fn grammar_in_src_dir<P>(mut self, srcp: P) -> Result<Self, Box<dyn Error>>
343 where
344 P: AsRef<Path>,
345 {
346 if !srcp.as_ref().is_relative() {
347 return Err(format!(
348 "Grammar path '{}' must be a relative path.",
349 srcp.as_ref().to_str().unwrap_or("<invalid UTF-8>")
350 )
351 .into());
352 }
353
354 let mut grmp = current_dir()?;
355 grmp.push("src");
356 grmp.push(srcp.as_ref());
357 self.grammar_path = Some(grmp);
358
359 let mut outp = PathBuf::new();
360 outp.push(var("OUT_DIR").unwrap());
361 outp.push(srcp.as_ref().parent().unwrap().to_str().unwrap());
362 create_dir_all(&outp)?;
363 let mut leaf = srcp
364 .as_ref()
365 .file_name()
366 .unwrap()
367 .to_str()
368 .unwrap()
369 .to_owned();
370 write!(leaf, ".{}", RUST_FILE_EXT).ok();
371 outp.push(leaf);
372 Ok(self.output_path(outp))
373 }
374
375 #[cfg(feature = "_unstable_api")]
378 pub fn grammar_ast(mut self, valid_ast: ASTWithValidityInfo, _api_key: UnstableApi) -> Self {
379 self.from_ast = Some(valid_ast);
380 self
381 }
382
383 pub fn grammar_path<P>(mut self, inp: P) -> Self
387 where
388 P: AsRef<Path>,
389 {
390 self.grammar_path = Some(inp.as_ref().to_owned());
391 self
392 }
393
394 #[cfg(feature = "_unstable_api")]
395 pub fn with_grammar_src(mut self, src: String, _api_key: UnstableApi) -> Self {
396 self.grammar_src = Some(src);
397 self
398 }
399
400 pub fn output_path<P>(mut self, outp: P) -> Self
405 where
406 P: AsRef<Path>,
407 {
408 self.output_path = Some(outp.as_ref().to_owned());
409 self
410 }
411
412 pub fn mod_name(mut self, mod_name: &'a str) -> Self {
416 self.mod_name = Some(mod_name);
417 self
418 }
419
420 pub fn visibility(mut self, vis: Visibility) -> Self {
422 self.visibility = vis;
423 self
424 }
425
426 pub fn recoverer(mut self, rk: RecoveryKind) -> Self {
428 self.recoverer = Some(rk);
429 self
430 }
431
432 pub fn yacckind(mut self, yk: YaccKind) -> Self {
434 self.yacckind = Some(yk);
435 self
436 }
437
438 pub fn error_on_conflicts(mut self, b: bool) -> Self {
441 self.error_on_conflicts = b;
442 self
443 }
444
445 pub fn warnings_are_errors(mut self, b: bool) -> Self {
448 self.warnings_are_errors = b;
449 self
450 }
451
452 pub fn show_warnings(mut self, b: bool) -> Self {
455 self.show_warnings = b;
456 self
457 }
458
459 pub fn rust_edition(mut self, edition: RustEdition) -> Self {
462 self.rust_edition = edition;
463 self
464 }
465
466 pub fn serialisation_format(mut self, serialisation_format: SerialisationFormat) -> Self {
467 self.serialisation_format = Some(serialisation_format);
468 self
469 }
470
471 #[cfg(test)]
472 pub fn inspect_recoverer(
473 mut self,
474 cb: Box<dyn for<'h, 'y> Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>,
475 ) -> Self {
476 self.inspect_callback = Some(cb);
477 self
478 }
479
480 #[doc(hidden)]
481 pub fn inspect_rt(
482 mut self,
483 cb: Box<
484 dyn for<'b, 'y> FnMut(
485 &'b mut Header<Location>,
486 RTParserBuilder<'y, StorageT, LexerTypesT>,
487 &'b HashMap<String, StorageT>,
488 &PathBuf,
489 ) -> Result<(), Box<dyn Error>>,
490 >,
491 ) -> Self {
492 self.inspect_rt = Some(cb);
493 self
494 }
495
496 pub fn build(mut self) -> Result<CTParser<StorageT>, Box<dyn Error>> {
549 let grmp = self
550 .grammar_path
551 .as_ref()
552 .expect("grammar_path must be specified before processing.");
553 let outp = self
554 .output_path
555 .as_ref()
556 .expect("output_path must be specified before processing.");
557 let mut header = Header::new();
558
559 match header.entry("cfgrammar.yacckind".to_string()) {
560 Entry::Occupied(_) => unreachable!(),
561 Entry::Vacant(mut v) => match self.yacckind {
562 Some(YaccKind::Eco) => panic!("Eco compile-time grammar generation not supported."),
563 Some(yk) => {
564 let yk_value = Value::try_from(yk)?;
565 let mut o = v.insert_entry(HeaderValue(
566 Location::Other("CTParserBuilder".to_string()),
567 yk_value,
568 ));
569 o.set_merge_behavior(MergeBehavior::Ours);
570 }
571 None => {
572 v.mark_required();
573 }
574 },
575 }
576 if let Some(recoverer) = self.recoverer {
577 match header.entry("lrpar.recoverer".to_string()) {
578 Entry::Occupied(_) => unreachable!(),
579 Entry::Vacant(v) => {
580 let rk_value: Value<Location> = Value::try_from(recoverer)?;
581 let mut o = v.insert_entry(HeaderValue(
582 Location::Other("CTParserBuilder".to_string()),
583 rk_value,
584 ));
585 o.set_merge_behavior(MergeBehavior::Ours);
586 }
587 }
588 }
589
590 if let Some(encoding) = self.serialisation_format {
591 match header.entry("lrpar.serialisation_format".to_string()) {
592 Entry::Occupied(_) => unreachable!(),
593 Entry::Vacant(v) => {
594 let rk_value: Value<Location> = Value::try_from(encoding)?;
595 let mut o = v.insert_entry(HeaderValue(
596 Location::Other("CTParserBuilder".to_string()),
597 rk_value,
598 ));
599 o.set_merge_behavior(MergeBehavior::Ours);
600 }
601 }
602 }
603
604 {
605 let mut lk = GENERATED_PATHS.lock().unwrap();
606 if lk.contains(outp.as_path()) {
607 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());
608 }
609 lk.insert(outp.clone());
610 }
611
612 let inc = if let Some(grammar_src) = &self.grammar_src {
613 grammar_src.clone()
614 } else {
615 read_to_string(grmp).map_err(|e| format!("When reading '{}': {e}", grmp.display()))?
616 };
617
618 let src_env = ParserSrcEnv::new_with_header(&inc, Some(grmp), header);
619 let yacc_diag = SpannedDiagnosticFormatter::new(&inc, grmp);
620 let build_args = ParserBuildEnvArgs::new()
621 .ast_with_validity_info(self.from_ast.as_ref())
622 .mod_name(self.mod_name)
623 .show_warnings(self.show_warnings)
624 .error_on_conflicts(self.error_on_conflicts)
625 .warnings_are_errors(self.warnings_are_errors)
626 .visibility(self.visibility.clone())
627 .rust_edition(self.rust_edition);
628 let mut build_env = src_env.build_env(build_args).map_err(|e| match e {
629 ParserSrcEnvError::GrmtoolsSectionParseError(es) => {
630 let mut out = String::new();
631 out.push_str(&format!(
632 "\n{ERROR}{}\n",
633 yacc_diag.file_location_msg(" parsing the `%grmtools` section", None)
634 ));
635 for e in es {
636 out.push_str(&indent(" ", &yacc_diag.format_error(e).to_string()));
637 out.push('\n');
638 }
639 Box::<dyn Error>::from(ErrorString(out))
640 }
641 e => e.to_string().into(),
642 })?;
643 self.recoverer = Some(build_env.recoverer());
646 self.yacckind = Some(build_env.yacc_kind());
647
648 let warnings = build_env.ast_with_validity_info().ast().warnings();
649 if self.warnings_are_errors && !warnings.is_empty() {
650 let mut out = String::new();
651 out.push_str(&format!(
652 "\n{ERROR}{}\n",
653 yacc_diag.file_location_msg("", None)
654 ));
655 for e in warnings {
656 out.push_str(&format!(
657 "{}\n",
658 indent(" ", &yacc_diag.format_warning(e).to_string())
659 ));
660 }
661 return Err(ErrorString(out).into());
662 } else if !warnings.is_empty() {
663 for w in warnings {
664 let ws_loc = yacc_diag.file_location_msg("", None);
665 let ws = indent(" ", &yacc_diag.format_warning(w).to_string());
666 if std::env::var("OUT_DIR").is_ok() && self.show_warnings {
668 for line in ws_loc.lines().chain(ws.lines()) {
669 println!("cargo:warning={}", line);
670 }
671 } else if self.show_warnings {
672 eprintln!("{}", ws_loc);
673 eprintln!("{WARNING} {}", ws);
674 }
675 }
676 }
677
678 #[cfg(test)]
679 if let Some(cb) = &self.inspect_callback {
680 cb(build_env.recoverer())?;
681 }
682
683 let timestamp = env!("VERGEN_BUILD_TIMESTAMP");
684 let code_gen = build_env.code_generator(timestamp).map_err(|e| match e {
685 ParserBuildEnvError::YaccGrammarErrors(errs) => {
686 let mut out = String::new();
687 out.push_str(&format!(
688 "\n{ERROR}{}\n",
689 yacc_diag.file_location_msg("", None)
690 ));
691 for e in errs {
692 out.push_str(&indent(" ", &yacc_diag.format_error(e).to_string()));
693 out.push('\n');
694 }
695 ErrorString(out)
696 }
697 e => ErrorString(e.to_string()),
698 })?;
699 let grm = code_gen.grm();
700 let rule_ids = grm
701 .tokens_map()
702 .iter()
703 .map(|(&n, &i)| (n.to_owned(), i.as_storaget()))
704 .collect::<HashMap<_, _>>();
705
706 let cache = code_gen.cache_str(&build_env);
707
708 if let Ok(ref inmd) = fs::metadata(grmp)
716 && let Ok(ref out_rs_md) = fs::metadata(outp)
717 && FileTime::from_last_modification_time(out_rs_md)
718 > FileTime::from_last_modification_time(inmd)
719 && let Ok(outc) = read_to_string(outp)
720 {
721 if outc.contains(&cache) {
722 let (grm, _, _) = code_gen.finish();
723
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 fs::remove_file(outp).ok();
750
751 let stable = code_gen.stable();
752 if self.error_on_conflicts
753 && let Some(c) = stable.conflicts()
754 {
755 match (grm.expect(), grm.expectrr()) {
756 (Some(i), Some(j)) if i == c.sr_len() && j == c.rr_len() => (),
757 (Some(i), None) if i == c.sr_len() && 0 == c.rr_len() => (),
758 (None, Some(j)) if 0 == c.sr_len() && j == c.rr_len() => (),
759 (None, None) if 0 == c.rr_len() && 0 == c.sr_len() => (),
760 _ => {
761 let conflicts_diagnostic = yacc_diag.format_conflicts::<LexerTypesT>(
762 grm,
763 build_env.ast_with_validity_info().ast(),
764 c,
765 code_gen.sgraph(),
766 stable,
767 );
768 #[cfg(test)]
769 let (_, _, stable) = code_gen.finish();
770 return Err(Box::new(CTConflictsError {
771 conflicts_diagnostic,
772 phantom: PhantomData,
773 #[cfg(test)]
774 stable,
775 }));
776 }
777 }
778 }
779
780 if let Some(ref mut inspector_rt) = self.inspect_rt {
781 let rt: RTParserBuilder<'_, StorageT, LexerTypesT> = RTParserBuilder::new(grm, stable);
782 let rt = if let Some(rk) = self.recoverer {
783 rt.recoverer(rk)
784 } else {
785 rt
786 };
787 inspector_rt(build_env.header_mut(), rt, &rule_ids, grmp)?
788 }
789
790 build_env
791 .check_unused_header_keys()
792 .map_err(|e| ErrorString(e.to_string()))?;
793
794 self.output_file(
795 &code_gen,
796 outp,
797 &format!("/* CACHE INFORMATION {} */\n", cache),
798 &build_env,
799 )?;
800 let (grm, sgraph, stable) = code_gen.finish();
801 let conflicts = if stable.conflicts().is_some() {
802 Some((sgraph, stable))
803 } else {
804 None
805 };
806 Ok(CTParser {
807 regenerated: true,
808 rule_ids,
809 yacc_grammar: grm,
810 grammar_src: inc,
811 grammar_path: self.grammar_path.unwrap(),
812 conflicts,
813 })
814 }
815
816 #[deprecated(
823 since = "0.11.0",
824 note = "Please use grammar_in_src_dir(), build(), and token_map() instead"
825 )]
826 #[allow(deprecated)]
827 pub fn process_file_in_src(
828 &mut self,
829 srcp: &str,
830 ) -> Result<HashMap<String, StorageT>, Box<dyn Error>> {
831 let mut inp = current_dir()?;
832 inp.push("src");
833 inp.push(srcp);
834 let mut outp = PathBuf::new();
835 outp.push(var("OUT_DIR").unwrap());
836 outp.push(Path::new(srcp).parent().unwrap().to_str().unwrap());
837 create_dir_all(&outp)?;
838 let mut leaf = Path::new(srcp)
839 .file_name()
840 .unwrap()
841 .to_str()
842 .unwrap()
843 .to_owned();
844 write!(leaf, ".{}", RUST_FILE_EXT).ok();
845 outp.push(leaf);
846 self.process_file(inp, outp)
847 }
848
849 #[deprecated(
885 since = "0.11.0",
886 note = "Please use grammar_path(), output_path(), build(), and token_map() instead"
887 )]
888 pub fn process_file<P, Q>(
889 &mut self,
890 inp: P,
891 outp: Q,
892 ) -> Result<HashMap<String, StorageT>, Box<dyn Error>>
893 where
894 P: AsRef<Path>,
895 Q: AsRef<Path>,
896 {
897 self.grammar_path = Some(inp.as_ref().to_owned());
898 self.output_path = Some(outp.as_ref().to_owned());
899 let cl: CTParserBuilder<LexerTypesT> = CTParserBuilder {
900 grammar_path: self.grammar_path.clone(),
901 grammar_src: None,
902 from_ast: None,
903 output_path: self.output_path.clone(),
904 mod_name: self.mod_name,
905 recoverer: self.recoverer,
906 yacckind: self.yacckind,
907 error_on_conflicts: self.error_on_conflicts,
908 warnings_are_errors: self.warnings_are_errors,
909 show_warnings: self.show_warnings,
910 visibility: self.visibility.clone(),
911 rust_edition: self.rust_edition,
912 inspect_rt: None,
913 serialisation_format: self.serialisation_format,
914 #[cfg(test)]
915 inspect_callback: None,
916 phantom: PhantomData,
917 };
918 Ok(cl.build()?.rule_ids)
919 }
920
921 fn output_file<P: AsRef<Path>>(
922 &self,
923 code_gen: &ParserCodegen<LexerTypesT>,
924 outp_rs: P,
925 cache: &str,
926 build_env: &ParserBuildEnv<'_, LexerTypesT>,
927 ) -> Result<(), Box<dyn Error>> {
928 let outs = code_gen
929 .generate(build_env)
930 .map_err(|e| ErrorString(e.to_string()))?;
931 let mut f = File::create(outp_rs)?;
932 f.write_all(outs.as_bytes())?;
933 f.write_all(cache.as_bytes())?;
934 Ok(())
935 }
936}
937
938#[doc(hidden)]
941pub struct ParserData<StorageT: Eq + Hash> {
942 grm: YaccGrammar<StorageT>,
943 stable: StateTable<StorageT>,
944}
945
946impl<StorageT: Eq + Hash> ParserData<StorageT> {
947 pub fn grm(&self) -> &YaccGrammar<StorageT> {
948 &self.grm
949 }
950
951 pub fn stable(&self) -> &StateTable<StorageT> {
952 &self.stable
953 }
954}
955
956#[doc(hidden)]
959pub fn _reconstitute<
960 C: wincode::config::Config + Clone + Copy,
961 StorageT: SchemaReadOwned<C, Dst = StorageT> + Eq + Hash + PrimInt + Unsigned + 'static,
962>(
963 grm_buf: &[u8],
964 stable_buf: &[u8],
965 config: C,
966) -> ParserData<StorageT> {
967 let grm: YaccGrammar<StorageT> = wincode::config::deserialize_from(grm_buf, config).unwrap();
968 let stable = wincode::config::deserialize_from(stable_buf, config).unwrap();
969 ParserData { grm, stable }
970}
971
972pub struct CTParser<StorageT = u32>
974where
975 StorageT: Eq + Hash,
976{
977 regenerated: bool,
978 rule_ids: HashMap<String, StorageT>,
979 yacc_grammar: YaccGrammar<StorageT>,
980 grammar_src: String,
981 grammar_path: PathBuf,
982 conflicts: Option<(StateGraph<StorageT>, StateTable<StorageT>)>,
983}
984
985impl<StorageT> CTParser<StorageT>
986where
987 StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
988 usize: AsPrimitive<StorageT>,
989{
990 pub fn regenerated(&self) -> bool {
992 self.regenerated
993 }
994
995 pub fn token_map(&self) -> &HashMap<String, StorageT> {
998 &self.rule_ids
999 }
1000
1001 #[allow(private_interfaces)]
1007 pub fn conflicts(
1008 &self,
1009 _: crate::unstable::UnstableApi,
1010 ) -> Option<(
1011 &YaccGrammar<StorageT>,
1012 &StateGraph<StorageT>,
1013 &StateTable<StorageT>,
1014 &Conflicts<StorageT>,
1015 )> {
1016 if let Some((sgraph, stable)) = &self.conflicts {
1017 return Some((
1018 &self.yacc_grammar,
1019 sgraph,
1020 stable,
1021 stable.conflicts().unwrap(),
1022 ));
1023 }
1024 None
1025 }
1026
1027 #[doc(hidden)]
1028 pub fn yacc_grammar(&self) -> &YaccGrammar<StorageT> {
1029 &self.yacc_grammar
1030 }
1031 #[doc(hidden)]
1032 pub fn grammar_src(&self) -> &str {
1033 &self.grammar_src
1034 }
1035 #[doc(hidden)]
1036 pub fn grammar_path(&self) -> &Path {
1037 self.grammar_path.as_path()
1038 }
1039}
1040
1041pub(crate) fn indent(indent: &str, s: &str) -> String {
1052 format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
1053}
1054
1055#[cfg(all(not(target_arch = "wasm32"), test))]
1057mod test {
1058 use std::{fs::File, io::Write, path::PathBuf};
1059
1060 use super::{CTConflictsError, CTParserBuilder};
1061 use crate::test_utils::TestLexerTypes;
1062 use cfgrammar::yacc::{YaccKind, YaccOriginalActionKind};
1063 use tempfile::TempDir;
1064
1065 #[test]
1066 fn test_conflicts() {
1067 let temp = TempDir::new().unwrap();
1068 let mut file_path = PathBuf::from(temp.as_ref());
1069 file_path.push("grm.y");
1070 let mut f = File::create(&file_path).unwrap();
1071 let _ = f.write_all(
1072 "%start A
1073%%
1074A : 'a' 'b' | B 'b';
1075B : 'a' | C;
1076C : 'a';"
1077 .as_bytes(),
1078 );
1079
1080 match CTParserBuilder::<TestLexerTypes>::new()
1081 .error_on_conflicts(false)
1082 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1083 .grammar_path(file_path.to_str().unwrap())
1084 .output_path(file_path.with_extension("ignored"))
1085 .build()
1086 .unwrap()
1087 .conflicts(crate::unstable::UnstableApi)
1088 {
1089 Some((_, _, _, conflicts)) => {
1090 assert_eq!(conflicts.sr_len(), 1);
1091 assert_eq!(conflicts.rr_len(), 1);
1092 }
1093 None => panic!("Expected error data"),
1094 }
1095 }
1096
1097 #[test]
1098 fn test_conflicts_error() {
1099 let temp = TempDir::new().unwrap();
1100 let mut file_path = PathBuf::from(temp.as_ref());
1101 file_path.push("grm.y");
1102 let mut f = File::create(&file_path).unwrap();
1103 let _ = f.write_all(
1104 "%start A
1105%%
1106A : 'a' 'b' | B 'b';
1107B : 'a' | C;
1108C : 'a';"
1109 .as_bytes(),
1110 );
1111
1112 match CTParserBuilder::<TestLexerTypes>::new()
1113 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1114 .grammar_path(file_path.to_str().unwrap())
1115 .output_path(file_path.with_extension("ignored"))
1116 .build()
1117 {
1118 Ok(_) => panic!("Expected error"),
1119 Err(e) => {
1120 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1121 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1122 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1123 }
1124 }
1125 }
1126
1127 #[test]
1128 fn test_expect_error() {
1129 let temp = TempDir::new().unwrap();
1130 let mut file_path = PathBuf::from(temp.as_ref());
1131 file_path.push("grm.y");
1132 let mut f = File::create(&file_path).unwrap();
1133 let _ = f.write_all(
1134 "%start A
1135%expect 2
1136%%
1137A: 'a' 'b' | B 'b';
1138B: 'a';"
1139 .as_bytes(),
1140 );
1141
1142 match CTParserBuilder::<TestLexerTypes>::new()
1143 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1144 .grammar_path(file_path.to_str().unwrap())
1145 .output_path(file_path.with_extension("ignored"))
1146 .build()
1147 {
1148 Ok(_) => panic!("Expected error"),
1149 Err(e) => {
1150 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1151 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 0);
1152 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1153 }
1154 }
1155 }
1156
1157 #[test]
1158 fn test_expectrr_error() {
1159 let temp = TempDir::new().unwrap();
1160 let mut file_path = PathBuf::from(temp.as_ref());
1161 file_path.push("grm.y");
1162 let mut f = File::create(&file_path).unwrap();
1163 let _ = f.write_all(
1164 "%start A
1165%expect 1
1166%expect-rr 2
1167%%
1168A : 'a' 'b' | B 'b';
1169B : 'a' | C;
1170C : 'a';"
1171 .as_bytes(),
1172 );
1173
1174 match CTParserBuilder::<TestLexerTypes>::new()
1175 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1176 .grammar_path(file_path.to_str().unwrap())
1177 .output_path(file_path.with_extension("ignored"))
1178 .build()
1179 {
1180 Ok(_) => panic!("Expected error"),
1181 Err(e) => {
1182 let cs = e.downcast_ref::<CTConflictsError<u16>>();
1183 assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1184 assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1185 }
1186 }
1187 }
1188
1189 #[test]
1190 fn test_invalid_identifier_in_derived_mod_name() {
1193 let temp = TempDir::new().unwrap();
1194 let mut file_path = PathBuf::from(temp.as_ref());
1195 file_path.push("contains-a-dash.y");
1196 let mut f = File::create(&file_path).unwrap();
1197 let _ = f.write_all(
1198 "%start A
1199%%
1200A : 'a';"
1201 .as_bytes(),
1202 );
1203 match CTParserBuilder::<TestLexerTypes>::new()
1204 .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1205 .grammar_path(file_path.to_str().unwrap())
1206 .output_path(file_path.with_extension("ignored"))
1207 .build()
1208 {
1209 Ok(_) => panic!("Expected error"),
1210 Err(e) => {
1211 let err_string = e.to_string();
1212 assert_eq!(
1213 err_string,
1214 "mod_name \'contains-a-dash_y\' is not a valid rust identifier due to 'unexpected token'"
1215 );
1216 }
1217 }
1218 }
1219
1220 #[cfg(test)]
1221 #[test]
1222 fn test_recoverer_header() -> Result<(), Box<dyn std::error::Error>> {
1223 use crate::RecoveryKind as RK;
1224 #[rustfmt::skip]
1225 let recovery_kinds = [
1226 (Some(RK::None), Some(RK::None), Some(RK::None)),
1229 (Some(RK::None), Some(RK::CPCTPlus), Some(RK::None)),
1230 (Some(RK::CPCTPlus), Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1231 (Some(RK::CPCTPlus), Some(RK::None), Some(RK::CPCTPlus)),
1232 (None, Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1233 (None, Some(RK::None), Some(RK::None)),
1234 (None, None, Some(RK::CPCTPlus)),
1235 (Some(RK::None), None, Some(RK::None)),
1236 (Some(RK::CPCTPlus), None, Some(RK::CPCTPlus)),
1237 ];
1238
1239 for (i, (builder_arg, header_arg, expected_rk)) in
1240 recovery_kinds.iter().cloned().enumerate()
1241 {
1242 let y_src = if let Some(header_arg) = header_arg {
1243 format!(
1244 "\
1245 %grmtools{{yacckind: Original(NoAction), recoverer: {}}} \
1246 %% \
1247 start: ; \
1248 ",
1249 match header_arg {
1250 RK::None => "RecoveryKind::None",
1251 RK::CPCTPlus => "RecoveryKind::CPCTPlus",
1252 }
1253 )
1254 } else {
1255 r#"
1256 %grmtools{yacckind: Original(NoAction)}
1257 %%
1258 Start: ;
1259 "#
1260 .to_string()
1261 };
1262 let out_dir = std::env::var("OUT_DIR").unwrap();
1263 let y_path = format!("{out_dir}/recoverykind_test_{i}.y");
1264 let y_out_path = format!("{y_path}.rs");
1265 std::fs::File::create(y_path.clone()).unwrap();
1266 std::fs::write(y_path.clone(), y_src).unwrap();
1267 let mut cp_builder = CTParserBuilder::<TestLexerTypes>::new();
1268 cp_builder = cp_builder
1269 .output_path(y_out_path.clone())
1270 .grammar_path(y_path.clone());
1271 cp_builder = if let Some(builder_arg) = builder_arg {
1272 cp_builder.recoverer(builder_arg)
1273 } else {
1274 cp_builder
1275 }
1276 .inspect_recoverer(Box::new(move |rk| {
1277 if matches!(
1278 (rk, expected_rk),
1279 (RK::None, Some(RK::None)) | (RK::CPCTPlus, Some(RK::CPCTPlus))
1280 ) {
1281 Ok(())
1282 } else {
1283 panic!("Unexpected recovery kind")
1284 }
1285 }));
1286 cp_builder.build()?;
1287 }
1288 Ok(())
1289 }
1290}