lrpar/
ctbuilder.rs

1//! Build grammars at compile-time so that they can be statically included into a binary.
2
3use 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
60/// The quote impl of `ToTokens` for `Option` prints an empty string for `None`
61/// and the inner value for `Some(inner_value)`.
62///
63/// This wrapper instead emits both `Some` and `None` variants.
64/// See: [quote #20](https://github.com/dtolnay/quote/issues/20)
65struct 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
76/// The quote impl of `ToTokens` for `usize` prints literal values
77/// including a type suffix for example `0usize`.
78///
79/// This wrapper omits the type suffix emitting `0` instead.
80struct 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
88/// This wrapper adds a missing impl of `ToTokens` for tuples.
89/// For a tuple `(a, b)` emits `(a.to_tokens(), b.to_tokens())`
90struct 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
99/// The wrapped `&str` value will be emitted with a call to `to_string()`
100struct 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
136/// A string which uses `Display` for it's `Debug` impl.
137struct 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/// Specify the visibility of the module generated by `CTBuilder`.
153#[derive(Clone, PartialEq, Eq, Debug)]
154#[non_exhaustive]
155pub enum Visibility {
156    /// Module-level visibility only.
157    Private,
158    /// `pub`
159    Public,
160    /// `pub(super)`
161    PublicSuper,
162    /// `pub(self)`
163    PublicSelf,
164    /// `pub(crate)`
165    PublicCrate,
166    /// `pub(in {arg})`
167    PublicIn(String),
168}
169
170/// Specifies the [Rust Edition] that will be emitted during code generation.
171///
172/// [Rust Edition]: https://doc.rust-lang.org/edition-guide/rust-2021/index.html
173#[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
223/// A `CTParserBuilder` allows one to specify the criteria for building a statically generated
224/// parser.
225pub struct CTParserBuilder<'a, LexerTypesT: LexerTypes>
226where
227    LexerTypesT::StorageT: Eq + Hash,
228    usize: AsPrimitive<LexerTypesT::StorageT>,
229{
230    // Anything stored in here (except `output_path`, `conflicts`, and `error_on_conflict`) almost
231    // certainly needs to be included as part of the rebuild_cache function below so that, if it's
232    // changed, the grammar is rebuilt.
233    grammar_path: Option<PathBuf>,
234    // If specified rather than reading source from `grammar_path`, use this string directly
235    grammar_src: Option<String>,
236    // If specified along with `grammar_src`, use this rather than building an ast from `grammar_src`.
237    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    // test function for inspecting private state
258    #[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    /// Create a new `CTParserBuilder`.
272    ///
273    /// `StorageT` must be an unsigned integer type (e.g. `u8`, `u16`) which is:
274    ///   * big enough to index (separately) all the tokens, rules, productions in the grammar,
275    ///   * big enough to index the state table created from the grammar,
276    ///   * less than or equal in size to `u32`.
277    ///
278    /// In other words, if you have a grammar with 256 tokens, 256 rules, and 256 productions,
279    /// which creates a state table of 256 states you can safely specify `u8` here; but if any of
280    /// those counts becomes 257 or greater you will need to specify `u16`. If you are parsing
281    /// large files, the additional storage requirements of larger integer types can be noticeable,
282    /// and in such cases it can be worth specifying a smaller type. `StorageT` defaults to `u32`
283    /// if unspecified.
284    ///
285    /// # Examples
286    ///
287    /// ```text
288    /// CTParserBuilder::<DefaultLexerTypes<u8>>::new()
289    ///     .grammar_in_src_dir("grm.y")?
290    ///     .build()?;
291    /// ```
292    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    /// Set the input grammar path to a file relative to this project's `src` directory. This will
314    /// also set the output path (i.e. you do not need to call [CTParserBuilder::output_path]).
315    ///
316    /// For example if `a/b.y` is passed as `inp` then [CTParserBuilder::build] will:
317    ///   * use `src/a/b.y` as the input file.
318    ///   * write output to a file which can then be imported by calling `lrpar_mod!("a/b.y")`.
319    ///   * create a module in that output file named `b_y`.
320    ///
321    /// You can override the output path and/or module name by calling [CTParserBuilder::output_path]
322    /// and/or [CTParserBuilder::mod_name], respectively, after calling this function.
323    ///
324    /// This is a convenience function that makes it easier to compile grammar files stored in a
325    /// project's `src/` directory: please see [CTParserBuilder::build] for additional constraints
326    /// and information about the generated files. Note also that each `.y` file can only be
327    /// processed once using this function: if you want to generate multiple grammars from a single
328    /// `.y` file, you will need to use [CTParserBuilder::output_path].
329    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    /// If set, specifies that this grammar should be built from a pre-validated AST
363    /// instead of a `.y`` file. When this is specified, `grammar_path` will not be read.
364    #[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    /// Set the input grammar path to `inp`. If specified, you must also call
371    /// [CTParserBuilder::output_path]. In general it is easier to use
372    /// [CTParserBuilder::grammar_in_src_dir].
373    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    /// Set the output grammar path to `outp`. Note that there are no requirements on `outp`: the
388    /// file can exist anywhere you can create a valid [Path] to. However, if you wish to use
389    /// [crate::lrpar_mod!] you will need to make sure that `outp` is in
390    /// [std::env::var]`("OUT_DIR")` or one of its subdirectories.
391    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    /// Set the generated module name to `mod_name`. If no module name is specified,
400    /// [CTParserBuilder::build] will attempt to create a sensible default based on the grammar
401    /// filename.
402    pub fn mod_name(mut self, mod_name: &'a str) -> Self {
403        self.mod_name = Some(mod_name);
404        self
405    }
406
407    /// Set the visibility of the generated module to `vis`. Defaults to `Visibility::Private`.
408    pub fn visibility(mut self, vis: Visibility) -> Self {
409        self.visibility = vis;
410        self
411    }
412
413    /// Set the recoverer for this parser to `rk`. Defaults to `RecoveryKind::CPCTPlus`.
414    pub fn recoverer(mut self, rk: RecoveryKind) -> Self {
415        self.recoverer = Some(rk);
416        self
417    }
418
419    /// Set the `YaccKind` for this parser to `ak`.
420    pub fn yacckind(mut self, yk: YaccKind) -> Self {
421        self.yacckind = Some(yk);
422        self
423    }
424
425    /// If set to true, [CTParserBuilder::build] will return an error if the given grammar contains
426    /// any Shift/Reduce or Reduce/Reduce conflicts. Defaults to `true`.
427    pub fn error_on_conflicts(mut self, b: bool) -> Self {
428        self.error_on_conflicts = b;
429        self
430    }
431
432    /// If set to true, [CTParserBuilder::build] will return an error if the given grammar contains
433    /// any warnings. Defaults to `true`.
434    pub fn warnings_are_errors(mut self, b: bool) -> Self {
435        self.warnings_are_errors = b;
436        self
437    }
438
439    /// If set to true, [CTParserBuilder::build] will print warnings to stderr, or via cargo when
440    /// running under cargo. Defaults to `true`.
441    pub fn show_warnings(mut self, b: bool) -> Self {
442        self.show_warnings = b;
443        self
444    }
445
446    /// Sets the rust edition to be used for generated code. Defaults to the latest edition of
447    /// rust supported by grmtools.
448    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    /// Statically compile the Yacc file specified by [CTParserBuilder::grammar_path()] into Rust,
479    /// placing the output into the file spec [CTParserBuilder::output_path()]. Note that three
480    /// additional files will be created with the same name as specified in [self.output_path] but
481    /// with the extensions `grm`, and `stable`, overwriting any existing files with those names.
482    ///
483    /// If `%parse-param` is not specified, the generated module follows the form:
484    ///
485    /// ```text
486    ///   mod <modname> {
487    ///     pub fn parse<'lexer, 'input: 'lexer>(lexer: &'lexer dyn NonStreamingLexer<...>)
488    ///       -> (Option<ActionT>, Vec<LexParseError<...>> { ... }
489    ///
490    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
491    ///       ...
492    ///     }
493    ///
494    ///     ...
495    ///   }
496    /// ```
497    ///
498    /// If `%parse-param x: t` is specified, the generated module follows the form:
499    ///
500    /// ```text
501    ///   mod <modname> {
502    ///     pub fn parse<'lexer, 'input: 'lexer>(lexer: &'lexer dyn NonStreamingLexer<...>, x: t)
503    ///       -> (Option<ActionT>, Vec<LexParseError<...>> { ... }
504    ///
505    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
506    ///       ...
507    ///     }
508    ///
509    ///     ...
510    ///   }
511    /// ```
512    ///
513    /// where:
514    ///  * `modname` is either:
515    ///    * the module name specified by [CTParserBuilder::mod_name()];
516    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.y` the
517    ///      module name is `c_y` (i.e. the file's leaf name, minus its extension, with a prefix of
518    ///      `_y`).
519    ///  * `ActionT` is either:
520    ///    * if the `yacckind` was set to `YaccKind::GrmTools` or
521    ///      `YaccKind::Original(YaccOriginalActionKind::UserAction)`, it is
522    ///      the return type of the `%start` rule;
523    ///    * or, if the `yacckind` was set to
524    ///      `YaccKind::Original(YaccOriginalActionKind::GenericParseTree)`, it
525    ///      is `Node<StorageT>` where the `Node` type is defined within your `lrpar_mod!`.
526    ///
527    /// # Panics
528    ///
529    /// If `StorageT` is not big enough to index the grammar's tokens, rules, or productions.
530    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            // Fallback to the default recoverykind.
622            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                        // Assume if this variable is set we are running under cargo.
648                        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                // The user hasn't specified a module name, so we create one automatically: what we
690                // do is strip off all the filename extensions (note that it's likely that inp ends
691                // with `y.rs`, so we potentially have to strip off more than one extension) and
692                // then add `_y` to the end.
693                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        // We don't need to go through the full rigmarole of generating an output file if all of
708        // the following are true: the output file exists; it is newer than the input file; and the
709        // cache hasn't changed. The last of these might be surprising, but it's vital: we don't
710        // know, for example, what the IDs map might be from one run to the next, and it might
711        // change for reasons beyond lrpar's control. If it does change, that means that the lexer
712        // and lrpar would get out of sync, so we have to play it safe and regenerate in such
713        // cases.
714        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                                // Primarily for use in the testsuite.
735                                panic!("The cache regenerated however, it was expected to match");
736                            }
737                        }
738                    }
739                }
740            }
741        }
742
743        // At this point, we know we're going to generate fresh output; however, if something goes
744        // wrong in the process between now and us writing /out/blah.rs, rustc thinks that
745        // everything's gone swimmingly (even if build.rs errored!), and tries to carry on
746        // compilation, leading to weird errors. We therefore delete /out/blah.rs at this point,
747        // which means, at worse, the user gets a "file not found" error from rustc (which is less
748        // confusing than the alternatives).
749        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    /// Given the filename `a/b.y` as input, statically compile the grammar `src/a/b.y` into a Rust
829    /// module which can then be imported using `lrpar_mod!("a/b.y")`. This is a convenience
830    /// function around [`process_file`](#method.process_file) which makes it easier to compile
831    /// grammar files stored in a project's `src/` directory: please see
832    /// [`process_file`](#method.process_file) for additional constraints and information about the
833    /// generated files.
834    #[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    /// Statically compile the Yacc file `inp` into Rust, placing the output into the file `outp`.
862    /// Note that three additional files will be created with the same name as `outp` but with the
863    /// extensions `grm`, and `stable`, overwriting any existing files with those names.
864    ///
865    /// `outp` defines a module as follows:
866    ///
867    /// ```text
868    ///   mod modname {
869    ///     pub fn parse(lexemes: &::std::vec::Vec<::lrpar::Lexeme<StorageT>>) { ... }
870    ///         -> (::std::option::Option<ActionT>,
871    ///             ::std::vec::Vec<::lrpar::LexParseError<StorageT>>)> { ...}
872    ///
873    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
874    ///       ...
875    ///     }
876    ///
877    ///     ...
878    ///   }
879    /// ```
880    ///
881    /// where:
882    ///  * `modname` is either:
883    ///    * the module name specified [`mod_name`](#method.mod_name)
884    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.y` the
885    ///      module name is `c_y` (i.e. the file's leaf name, minus its extension, with a prefix of
886    ///      `_y`).
887    ///  * `ActionT` is either:
888    ///    * the `%actiontype` value given to the grammar
889    ///    * or, if the `yacckind` was set YaccKind::Original(YaccOriginalActionKind::UserAction),
890    ///      it is [`Node<StorageT>`](../parser/enum.Node.html)
891    ///
892    /// # Panics
893    ///
894    /// If `StorageT` is not big enough to index the grammar's tokens, rules, or
895    /// productions.
896    #[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                // `lrpar::Node`` is deprecated within the lrpar crate, but not from within this module,
966                // Once it is removed from `lrpar`, we should move the declaration here entirely.
967                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                // At the top so that `user_actions` may contain #![inner_attribute]
979                #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                } // End of `mod _parser_`
992                #[allow(unused_imports)]
993                pub use _parser_::*;
994                #[allow(unused_imports)]
995                use ::lrpar::Lexeme;
996            } // End of `mod #mod_name`
997        };
998        // Try and run a code formatter on the generated code.
999        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    /// Generate the cache, which determines if anything's changed enough that we need to
1010    /// regenerate outputs and force rustc to recompile.
1011    fn rebuild_cache(&self, derived_mod_name: &'_ str, grm: &YaccGrammar<StorageT>) -> TokenStream {
1012        // We don't need to be particularly clever here: we just need to record the various things
1013        // that could change between builds.
1014        //
1015        // Record the time that this version of lrpar was built. If the source code changes and
1016        // rustc forces a recompile, this will change this value, causing anything which depends on
1017        // this build of lrpar to be recompiled too.
1018        let Self {
1019            // All variables except for `output_path`, `inspect_callback` and `phantom` should
1020            // be written into the cache.
1021            grammar_path,
1022            // I struggle to imagine the correct thing for `grammar_src`.
1023            grammar_src: _,
1024            // I struggle to imagine the correct thing for `from_ast`.
1025            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    /// Generate the main parse() function for the output file.
1074    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                // actions always have a parse_param argument, and when the `parse` function lacks one
1106                // that parameter will be unit.
1107                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        // `parse()` may or may not have an argument for `%parseparam`.
1158        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            /// Return the %epp entry for token `tidx` (where `None` indicates \"the token has no
1245            /// pretty-printed value\"). Panics if `tidx` doesn't exist.
1246            #[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    /// Generate the wrappers that call user actions
1254    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            // Iterate over all $-arguments and replace them with their respective
1276            // element from the argument vector (e.g. $1 is replaced by args[0]). At
1277            // the same time extract &str from tokens and actiontype from nonterminals.
1278            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                // Unpack the arguments passed to us by the drain
1294                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                // Call the user code
1326                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                        // If the rule `r` that we're calling has the unit type then Clippy will warn that
1335                        // `enum::A(wrapper_r())` is pointless. We thus have to split it into two:
1336                        // `wrapper_r(); enum::A(())`.
1337                        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                // The start prod has an unreachable body so it doesn't use it's variables.
1359                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    /// Generate the user action functions (if any).
1413    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        // Convert actions to functions
1421        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            // Work out the right type for each argument
1443            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            // If this rule's `actiont` is `()` then Clippy will warn that the return type `-> ()`
1460            // is pointless (which is true). We therefore avoid outputting a return type if actiont
1461            // is the unit type.
1462            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            // Iterate over all $-arguments and replace them with their respective
1484            // element from the argument vector (e.g. $1 is replaced by args[0]).
1485            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    /// Return the `RIdx` of the %start rule in the grammar (which will not be the same as
1551    /// grm.start_rule_idx because the latter has an additional rule insert by cfgrammar
1552    /// which then calls the user's %start rule).
1553    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/// This function is called by generated files; it exists so that generated files don't require a
1563/// direct dependency on bincode.
1564#[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
1574/// An interface to the result of [CTParserBuilder::build()].
1575pub 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    /// Returns `true` if this compile-time parser was regenerated or `false` if it was not.
1593    pub fn regenerated(&self) -> bool {
1594        self.regenerated
1595    }
1596
1597    /// Returns a [HashMap] from lexeme string types to numeric types (e.g. `INT: 2`), suitable for
1598    /// handing to a lexer to coordinate the IDs of lexer and parser.
1599    pub fn token_map(&self) -> &HashMap<String, StorageT> {
1600        &self.rule_ids
1601    }
1602
1603    /// If there are any conflicts in the grammar, return a tuple which allows users to inspect and
1604    /// pretty print them; otherwise returns `None`. If the grammar was not regenerated, this will
1605    /// always return `None`, even if the grammar actually has conflicts.
1606    ///
1607    /// **Note: The conflicts feature is currently unstable and may change in the future.**
1608    #[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
1643/// Indents a multi-line string and trims any trailing newline.
1644/// This currently assumes that indentation on blank lines does not matter.
1645///
1646/// The algorithm used by this function is:
1647/// 1. Prefix `s` with the indentation, indenting the first line.
1648/// 2. Trim any trailing newlines.
1649/// 3. Replace all newlines with `\n{indent}`` to indent all lines after the first.
1650///
1651/// It is plausible that we should a step 4, but currently do not:
1652/// 4. Replace all `\n{indent}\n` with `\n\n`
1653fn 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// Tests dealing with the filesystem not supported under wasm32
1670#[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                //  Builder,          Header setting,     Expected result.
1810                // -----------       ------------------  -------------------
1811                (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}