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::Mutex,
15};
16
17use crate::{
18    LexerTypes, RTParserBuilder, RecoveryKind,
19    diagnostics::{DiagnosticFormatter, SpannedDiagnosticFormatter},
20};
21
22#[cfg(feature = "_unstable_api")]
23use crate::unstable_api::UnstableApi;
24
25use bincode::{Decode, Encode, decode_from_slice, encode_to_vec};
26use cfgrammar::{
27    Location, RIdx, Symbol,
28    header::{GrmtoolsSectionParser, Header, HeaderValue, Value},
29    markmap::{Entry, MergeBehavior},
30    yacc::{YaccGrammar, YaccKind, YaccOriginalActionKind, ast::ASTWithValidityInfo},
31};
32use filetime::FileTime;
33use lazy_static::lazy_static;
34use lrtable::{Minimiser, StateGraph, StateTable, from_yacc, statetable::Conflicts};
35use num_traits::{AsPrimitive, PrimInt, Unsigned};
36use proc_macro2::{Literal, TokenStream};
37use quote::{ToTokens, TokenStreamExt, format_ident, quote};
38use regex::Regex;
39
40const ACTION_PREFIX: &str = "__gt_";
41const GLOBAL_PREFIX: &str = "__GT_";
42const ACTIONS_KIND: &str = "__GtActionsKind";
43const ACTIONS_KIND_PREFIX: &str = "Ak";
44const ACTIONS_KIND_HIDDEN: &str = "__GtActionsKindHidden";
45
46const RUST_FILE_EXT: &str = "rs";
47
48const WARNING: &str = "[Warning]";
49const ERROR: &str = "[Error]";
50
51lazy_static! {
52    static ref RE_DOL_NUM: Regex = Regex::new(r"\$([0-9]+)").unwrap();
53    static ref GENERATED_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new());
54}
55
56struct CTConflictsError<StorageT: Eq + Hash> {
57    conflicts_diagnostic: String,
58    #[cfg(test)]
59    stable: StateTable<StorageT>,
60    phantom: PhantomData<StorageT>,
61}
62
63/// The quote impl of `ToTokens` for `Option` prints an empty string for `None`
64/// and the inner value for `Some(inner_value)`.
65///
66/// This wrapper instead emits both `Some` and `None` variants.
67/// See: [quote #20](https://github.com/dtolnay/quote/issues/20)
68struct QuoteOption<T>(Option<T>);
69
70impl<T: ToTokens> ToTokens for QuoteOption<T> {
71    fn to_tokens(&self, tokens: &mut TokenStream) {
72        tokens.append_all(match self.0 {
73            Some(ref t) => quote! { ::std::option::Option::Some(#t) },
74            None => quote! { ::std::option::Option::None },
75        });
76    }
77}
78
79/// The quote impl of `ToTokens` for `usize` prints literal values
80/// including a type suffix for example `0usize`.
81///
82/// This wrapper omits the type suffix emitting `0` instead.
83struct UnsuffixedUsize(usize);
84
85impl ToTokens for UnsuffixedUsize {
86    fn to_tokens(&self, tokens: &mut TokenStream) {
87        tokens.append(Literal::usize_unsuffixed(self.0))
88    }
89}
90
91/// This wrapper adds a missing impl of `ToTokens` for tuples.
92/// For a tuple `(a, b)` emits `(a.to_tokens(), b.to_tokens())`
93struct QuoteTuple<T>(T);
94
95impl<A: ToTokens, B: ToTokens> ToTokens for QuoteTuple<(A, B)> {
96    fn to_tokens(&self, tokens: &mut TokenStream) {
97        let (a, b) = &self.0;
98        tokens.append_all(quote!((#a, #b)));
99    }
100}
101
102/// The wrapped `&str` value will be emitted with a call to `to_string()`
103struct QuoteToString<'a>(&'a str);
104
105impl ToTokens for QuoteToString<'_> {
106    fn to_tokens(&self, tokens: &mut TokenStream) {
107        let x = &self.0;
108        tokens.append_all(quote! { #x.to_string() });
109    }
110}
111
112impl<StorageT> fmt::Display for CTConflictsError<StorageT>
113where
114    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
115    usize: AsPrimitive<StorageT>,
116{
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        write!(f, "{}", self.conflicts_diagnostic)
119    }
120}
121
122impl<StorageT> fmt::Debug for CTConflictsError<StorageT>
123where
124    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
125    usize: AsPrimitive<StorageT>,
126{
127    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128        write!(f, "{}", self.conflicts_diagnostic)
129    }
130}
131
132impl<StorageT> Error for CTConflictsError<StorageT>
133where
134    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
135    usize: AsPrimitive<StorageT>,
136{
137}
138
139/// A string which uses `Display` for it's `Debug` impl.
140struct ErrorString(String);
141impl fmt::Display for ErrorString {
142    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143        let ErrorString(s) = self;
144        write!(f, "{}", s)
145    }
146}
147impl fmt::Debug for ErrorString {
148    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149        let ErrorString(s) = self;
150        write!(f, "{}", s)
151    }
152}
153impl Error for ErrorString {}
154
155/// Specify the visibility of the module generated by `CTBuilder`.
156#[derive(Clone, PartialEq, Eq, Debug)]
157#[non_exhaustive]
158pub enum Visibility {
159    /// Module-level visibility only.
160    Private,
161    /// `pub`
162    Public,
163    /// `pub(super)`
164    PublicSuper,
165    /// `pub(self)`
166    PublicSelf,
167    /// `pub(crate)`
168    PublicCrate,
169    /// `pub(in {arg})`
170    PublicIn(String),
171}
172
173/// Specifies the [Rust Edition] that will be emitted during code generation.
174///
175/// [Rust Edition]: https://doc.rust-lang.org/edition-guide/rust-2021/index.html
176#[derive(Clone, Copy, PartialEq, Eq, Debug)]
177#[non_exhaustive]
178pub enum RustEdition {
179    Rust2015,
180    Rust2018,
181    Rust2021,
182}
183
184impl RustEdition {
185    fn to_variant_tokens(self) -> TokenStream {
186        match self {
187            RustEdition::Rust2015 => quote!(::lrpar::RustEdition::Rust2015),
188            RustEdition::Rust2018 => quote!(::lrpar::RustEdition::Rust2018),
189            RustEdition::Rust2021 => quote!(::lrpar::RustEdition::Rust2021),
190        }
191    }
192}
193
194impl ToTokens for Visibility {
195    fn to_tokens(&self, tokens: &mut TokenStream) {
196        tokens.extend(match self {
197            Visibility::Private => quote!(),
198            Visibility::Public => quote! {pub},
199            Visibility::PublicSuper => quote! {pub(super)},
200            Visibility::PublicSelf => quote! {pub(self)},
201            Visibility::PublicCrate => quote! {pub(crate)},
202            Visibility::PublicIn(data) => {
203                let other = str::parse::<TokenStream>(data).unwrap();
204                quote! {pub(in #other)}
205            }
206        })
207    }
208}
209
210impl Visibility {
211    fn to_variant_tokens(&self) -> TokenStream {
212        match self {
213            Visibility::Private => quote!(::lrpar::Visibility::Private),
214            Visibility::Public => quote!(::lrpar::Visibility::Public),
215            Visibility::PublicSuper => quote!(::lrpar::Visibility::PublicSuper),
216            Visibility::PublicSelf => quote!(::lrpar::Visibility::PublicSelf),
217            Visibility::PublicCrate => quote!(::lrpar::Visibility::PublicCrate),
218            Visibility::PublicIn(data) => {
219                let data = QuoteToString(data);
220                quote!(::lrpar::Visibility::PublicIn(#data))
221            }
222        }
223    }
224}
225
226/// A `CTParserBuilder` allows one to specify the criteria for building a statically generated
227/// parser.
228pub struct CTParserBuilder<'a, LexerTypesT: LexerTypes>
229where
230    LexerTypesT::StorageT: Eq + Hash,
231    usize: AsPrimitive<LexerTypesT::StorageT>,
232{
233    // Anything stored in here (except `output_path`, `conflicts`, and `error_on_conflict`) almost
234    // certainly needs to be included as part of the rebuild_cache function below so that, if it's
235    // changed, the grammar is rebuilt.
236    grammar_path: Option<PathBuf>,
237    // If specified rather than reading source from `grammar_path`, use this string directly
238    grammar_src: Option<String>,
239    // If specified along with `grammar_src`, use this rather than building an ast from `grammar_src`.
240    from_ast: Option<ASTWithValidityInfo>,
241    output_path: Option<PathBuf>,
242    mod_name: Option<&'a str>,
243    recoverer: Option<RecoveryKind>,
244    yacckind: Option<YaccKind>,
245    error_on_conflicts: bool,
246    warnings_are_errors: bool,
247    show_warnings: bool,
248    visibility: Visibility,
249    rust_edition: RustEdition,
250    inspect_rt: Option<
251        Box<
252            dyn for<'b> FnMut(
253                &'b mut Header<Location>,
254                RTParserBuilder<LexerTypesT::StorageT, LexerTypesT>,
255                &'b HashMap<String, LexerTypesT::StorageT>,
256                &PathBuf,
257            ) -> Result<(), Box<dyn Error>>,
258        >,
259    >,
260    // test function for inspecting private state
261    #[cfg(test)]
262    inspect_callback: Option<Box<dyn Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>>,
263    phantom: PhantomData<LexerTypesT>,
264}
265
266impl<
267    'a,
268    StorageT: 'static + Debug + Hash + PrimInt + Encode + Unsigned,
269    LexerTypesT: LexerTypes<StorageT = StorageT>,
270> CTParserBuilder<'a, LexerTypesT>
271where
272    usize: AsPrimitive<StorageT>,
273{
274    /// Create a new `CTParserBuilder`.
275    ///
276    /// `StorageT` must be an unsigned integer type (e.g. `u8`, `u16`) which is:
277    ///   * big enough to index (separately) all the tokens, rules, productions in the grammar,
278    ///   * big enough to index the state table created from the grammar,
279    ///   * less than or equal in size to `u32`.
280    ///
281    /// In other words, if you have a grammar with 256 tokens, 256 rules, and 256 productions,
282    /// which creates a state table of 256 states you can safely specify `u8` here; but if any of
283    /// those counts becomes 257 or greater you will need to specify `u16`. If you are parsing
284    /// large files, the additional storage requirements of larger integer types can be noticeable,
285    /// and in such cases it can be worth specifying a smaller type. `StorageT` defaults to `u32`
286    /// if unspecified.
287    ///
288    /// # Examples
289    ///
290    /// ```text
291    /// CTParserBuilder::<DefaultLexerTypes<u8>>::new()
292    ///     .grammar_in_src_dir("grm.y")?
293    ///     .build()?;
294    /// ```
295    pub fn new() -> Self {
296        CTParserBuilder {
297            grammar_path: None,
298            grammar_src: None,
299            from_ast: None,
300            output_path: None,
301            mod_name: None,
302            recoverer: None,
303            yacckind: None,
304            error_on_conflicts: true,
305            warnings_are_errors: true,
306            show_warnings: true,
307            visibility: Visibility::Private,
308            rust_edition: RustEdition::Rust2021,
309            inspect_rt: None,
310            #[cfg(test)]
311            inspect_callback: None,
312            phantom: PhantomData,
313        }
314    }
315
316    /// Set the input grammar path to a file relative to this project's `src` directory. This will
317    /// also set the output path (i.e. you do not need to call [CTParserBuilder::output_path]).
318    ///
319    /// For example if `a/b.y` is passed as `inp` then [CTParserBuilder::build] will:
320    ///   * use `src/a/b.y` as the input file.
321    ///   * write output to a file which can then be imported by calling `lrpar_mod!("a/b.y")`.
322    ///   * create a module in that output file named `b_y`.
323    ///
324    /// You can override the output path and/or module name by calling [CTParserBuilder::output_path]
325    /// and/or [CTParserBuilder::mod_name], respectively, after calling this function.
326    ///
327    /// This is a convenience function that makes it easier to compile grammar files stored in a
328    /// project's `src/` directory: please see [CTParserBuilder::build] for additional constraints
329    /// and information about the generated files. Note also that each `.y` file can only be
330    /// processed once using this function: if you want to generate multiple grammars from a single
331    /// `.y` file, you will need to use [CTParserBuilder::output_path].
332    pub fn grammar_in_src_dir<P>(mut self, srcp: P) -> Result<Self, Box<dyn Error>>
333    where
334        P: AsRef<Path>,
335    {
336        if !srcp.as_ref().is_relative() {
337            return Err(format!(
338                "Grammar path '{}' must be a relative path.",
339                srcp.as_ref().to_str().unwrap_or("<invalid UTF-8>")
340            )
341            .into());
342        }
343
344        let mut grmp = current_dir()?;
345        grmp.push("src");
346        grmp.push(srcp.as_ref());
347        self.grammar_path = Some(grmp);
348
349        let mut outp = PathBuf::new();
350        outp.push(var("OUT_DIR").unwrap());
351        outp.push(srcp.as_ref().parent().unwrap().to_str().unwrap());
352        create_dir_all(&outp)?;
353        let mut leaf = srcp
354            .as_ref()
355            .file_name()
356            .unwrap()
357            .to_str()
358            .unwrap()
359            .to_owned();
360        write!(leaf, ".{}", RUST_FILE_EXT).ok();
361        outp.push(leaf);
362        Ok(self.output_path(outp))
363    }
364
365    /// If set, specifies that this grammar should be built from a pre-validated AST
366    /// instead of a `.y`` file. When this is specified, `grammar_path` will not be read.
367    #[cfg(feature = "_unstable_api")]
368    pub fn grammar_ast(mut self, valid_ast: ASTWithValidityInfo, _api_key: UnstableApi) -> Self {
369        self.from_ast = Some(valid_ast);
370        self
371    }
372
373    /// Set the input grammar path to `inp`. If specified, you must also call
374    /// [CTParserBuilder::output_path]. In general it is easier to use
375    /// [CTParserBuilder::grammar_in_src_dir].
376    pub fn grammar_path<P>(mut self, inp: P) -> Self
377    where
378        P: AsRef<Path>,
379    {
380        self.grammar_path = Some(inp.as_ref().to_owned());
381        self
382    }
383
384    #[cfg(feature = "_unstable_api")]
385    pub fn with_grammar_src(mut self, src: String, _api_key: UnstableApi) -> Self {
386        self.grammar_src = Some(src);
387        self
388    }
389
390    /// Set the output grammar path to `outp`. Note that there are no requirements on `outp`: the
391    /// file can exist anywhere you can create a valid [Path] to. However, if you wish to use
392    /// [crate::lrpar_mod!] you will need to make sure that `outp` is in
393    /// [std::env::var]`("OUT_DIR")` or one of its subdirectories.
394    pub fn output_path<P>(mut self, outp: P) -> Self
395    where
396        P: AsRef<Path>,
397    {
398        self.output_path = Some(outp.as_ref().to_owned());
399        self
400    }
401
402    /// Set the generated module name to `mod_name`. If no module name is specified,
403    /// [CTParserBuilder::build] will attempt to create a sensible default based on the grammar
404    /// filename.
405    pub fn mod_name(mut self, mod_name: &'a str) -> Self {
406        self.mod_name = Some(mod_name);
407        self
408    }
409
410    /// Set the visibility of the generated module to `vis`. Defaults to `Visibility::Private`.
411    pub fn visibility(mut self, vis: Visibility) -> Self {
412        self.visibility = vis;
413        self
414    }
415
416    /// Set the recoverer for this parser to `rk`. Defaults to `RecoveryKind::CPCTPlus`.
417    pub fn recoverer(mut self, rk: RecoveryKind) -> Self {
418        self.recoverer = Some(rk);
419        self
420    }
421
422    /// Set the `YaccKind` for this parser to `ak`.
423    pub fn yacckind(mut self, yk: YaccKind) -> Self {
424        self.yacckind = Some(yk);
425        self
426    }
427
428    /// If set to true, [CTParserBuilder::build] will return an error if the given grammar contains
429    /// any Shift/Reduce or Reduce/Reduce conflicts. Defaults to `true`.
430    pub fn error_on_conflicts(mut self, b: bool) -> Self {
431        self.error_on_conflicts = b;
432        self
433    }
434
435    /// If set to true, [CTParserBuilder::build] will return an error if the given grammar contains
436    /// any warnings. Defaults to `true`.
437    pub fn warnings_are_errors(mut self, b: bool) -> Self {
438        self.warnings_are_errors = b;
439        self
440    }
441
442    /// If set to true, [CTParserBuilder::build] will print warnings to stderr, or via cargo when
443    /// running under cargo. Defaults to `true`.
444    pub fn show_warnings(mut self, b: bool) -> Self {
445        self.show_warnings = b;
446        self
447    }
448
449    /// Sets the rust edition to be used for generated code. Defaults to the latest edition of
450    /// rust supported by grmtools.
451    pub fn rust_edition(mut self, edition: RustEdition) -> Self {
452        self.rust_edition = edition;
453        self
454    }
455
456    #[cfg(test)]
457    pub fn inspect_recoverer(
458        mut self,
459        cb: Box<dyn for<'h, 'y> Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>,
460    ) -> Self {
461        self.inspect_callback = Some(cb);
462        self
463    }
464
465    #[doc(hidden)]
466    pub fn inspect_rt(
467        mut self,
468        cb: Box<
469            dyn for<'b, 'y> FnMut(
470                &'b mut Header<Location>,
471                RTParserBuilder<'y, StorageT, LexerTypesT>,
472                &'b HashMap<String, StorageT>,
473                &PathBuf,
474            ) -> Result<(), Box<dyn Error>>,
475        >,
476    ) -> Self {
477        self.inspect_rt = Some(cb);
478        self
479    }
480
481    /// Statically compile the Yacc file specified by [CTParserBuilder::grammar_path()] into Rust,
482    /// placing the output into the file spec [CTParserBuilder::output_path()]. Note that three
483    /// additional files will be created with the same name as specified in [self.output_path] but
484    /// with the extensions `grm`, and `stable`, overwriting any existing files with those names.
485    ///
486    /// If `%parse-param` is not specified, the generated module follows the form:
487    ///
488    /// ```text
489    ///   mod <modname> {
490    ///     pub fn parse<'lexer, 'input: 'lexer>(lexer: &'lexer dyn NonStreamingLexer<...>)
491    ///       -> (Option<ActionT>, Vec<LexParseError<...>> { ... }
492    ///
493    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
494    ///       ...
495    ///     }
496    ///
497    ///     ...
498    ///   }
499    /// ```
500    ///
501    /// If `%parse-param x: t` is specified, the generated module follows the form:
502    ///
503    /// ```text
504    ///   mod <modname> {
505    ///     pub fn parse<'lexer, 'input: 'lexer>(lexer: &'lexer dyn NonStreamingLexer<...>, x: t)
506    ///       -> (Option<ActionT>, Vec<LexParseError<...>> { ... }
507    ///
508    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
509    ///       ...
510    ///     }
511    ///
512    ///     ...
513    ///   }
514    /// ```
515    ///
516    /// where:
517    ///  * `modname` is either:
518    ///    * the module name specified by [CTParserBuilder::mod_name()];
519    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.y` the
520    ///      module name is `c_y` (i.e. the file's leaf name, minus its extension, with a prefix of
521    ///      `_y`).
522    ///  * `ActionT` is either:
523    ///    * if the `yacckind` was set to `YaccKind::GrmTools` or
524    ///      `YaccKind::Original(YaccOriginalActionKind::UserAction)`, it is
525    ///      the return type of the `%start` rule;
526    ///    * or, if the `yacckind` was set to
527    ///      `YaccKind::Original(YaccOriginalActionKind::GenericParseTree)`, it
528    ///      is [`crate::Node<StorageT>`].
529    ///
530    /// # Panics
531    ///
532    /// If `StorageT` is not big enough to index the grammar's tokens, rules, or productions.
533    pub fn build(mut self) -> Result<CTParser<StorageT>, Box<dyn Error>> {
534        let grmp = self
535            .grammar_path
536            .as_ref()
537            .expect("grammar_path must be specified before processing.");
538        let outp = self
539            .output_path
540            .as_ref()
541            .expect("output_path must be specified before processing.");
542        let mut header = Header::new();
543
544        match header.entry("yacckind".to_string()) {
545            Entry::Occupied(_) => unreachable!(),
546            Entry::Vacant(mut v) => match self.yacckind {
547                Some(YaccKind::Eco) => panic!("Eco compile-time grammar generation not supported."),
548                Some(yk) => {
549                    let yk_value = Value::try_from(yk)?;
550                    let mut o = v.insert_entry(HeaderValue(
551                        Location::Other("CTParserBuilder".to_string()),
552                        yk_value,
553                    ));
554                    o.set_merge_behavior(MergeBehavior::Ours);
555                }
556                None => {
557                    v.mark_required();
558                }
559            },
560        }
561        if let Some(recoverer) = self.recoverer {
562            match header.entry("recoverer".to_string()) {
563                Entry::Occupied(_) => unreachable!(),
564                Entry::Vacant(v) => {
565                    let rk_value: Value<Location> = Value::try_from(recoverer)?;
566                    let mut o = v.insert_entry(HeaderValue(
567                        Location::Other("CTParserBuilder".to_string()),
568                        rk_value,
569                    ));
570                    o.set_merge_behavior(MergeBehavior::Ours);
571                }
572            }
573        }
574
575        {
576            let mut lk = GENERATED_PATHS.lock().unwrap();
577            if lk.contains(outp.as_path()) {
578                return Err(format!("Generating two parsers to the same path ('{}') is not allowed: use CTParserBuilder::output_path (and, optionally, CTParserBuilder::mod_name) to differentiate them.", &outp.to_str().unwrap()).into());
579            }
580            lk.insert(outp.clone());
581        }
582
583        let inc = if let Some(grammar_src) = &self.grammar_src {
584            grammar_src.clone()
585        } else {
586            read_to_string(grmp).map_err(|e| format!("When reading '{}': {e}", grmp.display()))?
587        };
588
589        let yacc_diag = SpannedDiagnosticFormatter::new(&inc, grmp);
590        let parsed_header = GrmtoolsSectionParser::new(&inc, false).parse();
591        if let Err(errs) = parsed_header {
592            let mut out = String::new();
593            out.push_str(&format!(
594                "\n{ERROR}{}\n",
595                yacc_diag.file_location_msg(" parsing the `%grmtools` section", None)
596            ));
597            for e in errs {
598                out.push_str(&indent("     ", &yacc_diag.format_error(e).to_string()));
599            }
600            return Err(ErrorString(out))?;
601        }
602        let (parsed_header, _) = parsed_header.unwrap();
603        header.merge_from(parsed_header)?;
604        self.yacckind = header
605            .get("yacckind")
606            .map(|HeaderValue(_, val)| val)
607            .map(YaccKind::try_from)
608            .transpose()?;
609        header.mark_used(&"yacckind".to_string());
610        let ast_validation = if let Some(ast) = &self.from_ast {
611            ast.clone()
612        } else if let Some(yk) = self.yacckind {
613            ASTWithValidityInfo::new(yk, &inc)
614        } else {
615            Err("Missing 'yacckind'".to_string())?
616        };
617
618        header.mark_used(&"recoverer".to_string());
619        let rk_val = header.get("recoverer").map(|HeaderValue(_, rk_val)| rk_val);
620
621        if let Some(rk_val) = rk_val {
622            self.recoverer = Some(RecoveryKind::try_from(rk_val)?);
623        } else {
624            // Fallback to the default recoverykind.
625            self.recoverer = Some(RecoveryKind::CPCTPlus);
626        }
627        self.yacckind = Some(ast_validation.yacc_kind());
628        let warnings = ast_validation.ast().warnings();
629        let res = YaccGrammar::<StorageT>::new_from_ast_with_validity_info(&ast_validation);
630        let grm = match res {
631            Ok(_) if self.warnings_are_errors && !warnings.is_empty() => {
632                let mut out = String::new();
633                out.push_str(&format!(
634                    "\n{ERROR}{}\n",
635                    yacc_diag.file_location_msg("", None)
636                ));
637                for e in warnings {
638                    out.push_str(&format!(
639                        "{}\n",
640                        indent("     ", &yacc_diag.format_warning(e).to_string())
641                    ));
642                }
643                return Err(ErrorString(out))?;
644            }
645            Ok(grm) => {
646                if !warnings.is_empty() {
647                    for w in warnings {
648                        let ws_loc = yacc_diag.file_location_msg("", None);
649                        let ws = indent("     ", &yacc_diag.format_warning(w).to_string());
650                        // Assume if this variable is set we are running under cargo.
651                        if std::env::var("OUT_DIR").is_ok() && self.show_warnings {
652                            for line in ws_loc.lines().chain(ws.lines()) {
653                                println!("cargo:warning={}", line);
654                            }
655                        } else if self.show_warnings {
656                            eprintln!("{}", ws_loc);
657                            eprintln!("{WARNING} {}", ws);
658                        }
659                    }
660                }
661                grm
662            }
663            Err(errs) => {
664                let mut out = String::new();
665                out.push_str(&format!(
666                    "\n{ERROR}{}\n",
667                    yacc_diag.file_location_msg("", None)
668                ));
669                for e in errs {
670                    out.push_str(&indent("     ", &yacc_diag.format_error(e).to_string()));
671                    out.push('\n');
672                }
673
674                return Err(ErrorString(out))?;
675            }
676        };
677
678        #[cfg(test)]
679        if let Some(cb) = &self.inspect_callback {
680            cb(self.recoverer.expect("has a default value"))?;
681        }
682
683        let rule_ids = grm
684            .tokens_map()
685            .iter()
686            .map(|(&n, &i)| (n.to_owned(), i.as_storaget()))
687            .collect::<HashMap<_, _>>();
688
689        let derived_mod_name = match self.mod_name {
690            Some(s) => s.to_owned(),
691            None => {
692                // The user hasn't specified a module name, so we create one automatically: what we
693                // do is strip off all the filename extensions (note that it's likely that inp ends
694                // with `y.rs`, so we potentially have to strip off more than one extension) and
695                // then add `_y` to the end.
696                let mut stem = grmp.to_str().unwrap();
697                loop {
698                    let new_stem = Path::new(stem).file_stem().unwrap().to_str().unwrap();
699                    if stem == new_stem {
700                        break;
701                    }
702                    stem = new_stem;
703                }
704                format!("{}_y", stem)
705            }
706        };
707
708        let cache = self.rebuild_cache(&derived_mod_name, &grm);
709
710        // We don't need to go through the full rigmarole of generating an output file if all of
711        // the following are true: the output file exists; it is newer than the input file; and the
712        // cache hasn't changed. The last of these might be surprising, but it's vital: we don't
713        // know, for example, what the IDs map might be from one run to the next, and it might
714        // change for reasons beyond lrpar's control. If it does change, that means that the lexer
715        // and lrpar would get out of sync, so we have to play it safe and regenerate in such
716        // cases.
717        if let Ok(ref inmd) = fs::metadata(grmp) {
718            if let Ok(ref out_rs_md) = fs::metadata(outp) {
719                if FileTime::from_last_modification_time(out_rs_md)
720                    > FileTime::from_last_modification_time(inmd)
721                {
722                    if let Ok(outc) = read_to_string(outp) {
723                        if outc.contains(&cache.to_string()) {
724                            return Ok(CTParser {
725                                regenerated: false,
726                                rule_ids,
727                                yacc_grammar: grm,
728                                grammar_src: inc,
729                                grammar_path: self.grammar_path.unwrap(),
730                                conflicts: None,
731                            });
732                        } else {
733                            #[cfg(grmtools_extra_checks)]
734                            if std::env::var("CACHE_EXPECTED").is_ok() {
735                                eprintln!("outc: {}", outc);
736                                eprintln!("using cache: {}", cache,);
737                                // Primarily for use in the testsuite.
738                                panic!("The cache regenerated however, it was expected to match");
739                            }
740                        }
741                    }
742                }
743            }
744        }
745
746        // At this point, we know we're going to generate fresh output; however, if something goes
747        // wrong in the process between now and us writing /out/blah.rs, rustc thinks that
748        // everything's gone swimmingly (even if build.rs errored!), and tries to carry on
749        // compilation, leading to weird errors. We therefore delete /out/blah.rs at this point,
750        // which means, at worse, the user gets a "file not found" error from rustc (which is less
751        // confusing than the alternatives).
752        fs::remove_file(outp).ok();
753
754        let (sgraph, stable) = from_yacc(&grm, Minimiser::Pager)?;
755        if self.error_on_conflicts {
756            if let Some(c) = stable.conflicts() {
757                match (grm.expect(), grm.expectrr()) {
758                    (Some(i), Some(j)) if i == c.sr_len() && j == c.rr_len() => (),
759                    (Some(i), None) if i == c.sr_len() && 0 == c.rr_len() => (),
760                    (None, Some(j)) if 0 == c.sr_len() && j == c.rr_len() => (),
761                    (None, None) if 0 == c.rr_len() && 0 == c.sr_len() => (),
762                    _ => {
763                        let conflicts_diagnostic = yacc_diag.format_conflicts::<LexerTypesT>(
764                            &grm,
765                            ast_validation.ast(),
766                            c,
767                            &sgraph,
768                            &stable,
769                        );
770                        return Err(Box::new(CTConflictsError {
771                            conflicts_diagnostic,
772                            phantom: PhantomData,
773                            #[cfg(test)]
774                            stable,
775                        }));
776                    }
777                }
778            }
779        }
780
781        if let Some(ref mut inspector_rt) = self.inspect_rt {
782            let rt: RTParserBuilder<'_, StorageT, LexerTypesT> =
783                RTParserBuilder::new(&grm, &stable);
784            let rt = if let Some(rk) = self.recoverer {
785                rt.recoverer(rk)
786            } else {
787                rt
788            };
789            inspector_rt(&mut header, rt, &rule_ids, grmp)?
790        }
791
792        let unused_keys = header.unused();
793        if !unused_keys.is_empty() {
794            return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into());
795        }
796        let missing_keys = header
797            .missing()
798            .iter()
799            .map(|s| s.as_str())
800            .collect::<Vec<_>>();
801        if !missing_keys.is_empty() {
802            return Err(format!(
803                "Required values were missing from the header: {}",
804                missing_keys.join(", ")
805            )
806            .into());
807        }
808
809        self.output_file(
810            &grm,
811            &stable,
812            &derived_mod_name,
813            outp,
814            &format!("/* CACHE INFORMATION {} */\n", cache),
815        )?;
816        let conflicts = if stable.conflicts().is_some() {
817            Some((sgraph, stable))
818        } else {
819            None
820        };
821        Ok(CTParser {
822            regenerated: true,
823            rule_ids,
824            yacc_grammar: grm,
825            grammar_src: inc,
826            grammar_path: self.grammar_path.unwrap(),
827            conflicts,
828        })
829    }
830
831    /// Given the filename `a/b.y` as input, statically compile the grammar `src/a/b.y` into a Rust
832    /// module which can then be imported using `lrpar_mod!("a/b.y")`. This is a convenience
833    /// function around [`process_file`](#method.process_file) which makes it easier to compile
834    /// grammar files stored in a project's `src/` directory: please see
835    /// [`process_file`](#method.process_file) for additional constraints and information about the
836    /// generated files.
837    #[deprecated(
838        since = "0.11.0",
839        note = "Please use grammar_in_src_dir(), build(), and token_map() instead"
840    )]
841    #[allow(deprecated)]
842    pub fn process_file_in_src(
843        &mut self,
844        srcp: &str,
845    ) -> Result<HashMap<String, StorageT>, Box<dyn Error>> {
846        let mut inp = current_dir()?;
847        inp.push("src");
848        inp.push(srcp);
849        let mut outp = PathBuf::new();
850        outp.push(var("OUT_DIR").unwrap());
851        outp.push(Path::new(srcp).parent().unwrap().to_str().unwrap());
852        create_dir_all(&outp)?;
853        let mut leaf = Path::new(srcp)
854            .file_name()
855            .unwrap()
856            .to_str()
857            .unwrap()
858            .to_owned();
859        write!(leaf, ".{}", RUST_FILE_EXT).ok();
860        outp.push(leaf);
861        self.process_file(inp, outp)
862    }
863
864    /// Statically compile the Yacc file `inp` into Rust, placing the output into the file `outp`.
865    /// Note that three additional files will be created with the same name as `outp` but with the
866    /// extensions `grm`, and `stable`, overwriting any existing files with those names.
867    ///
868    /// `outp` defines a module as follows:
869    ///
870    /// ```text
871    ///   mod modname {
872    ///     pub fn parse(lexemes: &::std::vec::Vec<::lrpar::Lexeme<StorageT>>) { ... }
873    ///         -> (::std::option::Option<ActionT>,
874    ///             ::std::vec::Vec<::lrpar::LexParseError<StorageT>>)> { ...}
875    ///
876    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
877    ///       ...
878    ///     }
879    ///
880    ///     ...
881    ///   }
882    /// ```
883    ///
884    /// where:
885    ///  * `modname` is either:
886    ///    * the module name specified [`mod_name`](#method.mod_name)
887    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.y` the
888    ///      module name is `c_y` (i.e. the file's leaf name, minus its extension, with a prefix of
889    ///      `_y`).
890    ///  * `ActionT` is either:
891    ///    * the `%actiontype` value given to the grammar
892    ///    * or, if the `yacckind` was set YaccKind::Original(YaccOriginalActionKind::UserAction),
893    ///      it is [`Node<StorageT>`](../parser/enum.Node.html)
894    ///
895    /// # Panics
896    ///
897    /// If `StorageT` is not big enough to index the grammar's tokens, rules, or
898    /// productions.
899    #[deprecated(
900        since = "0.11.0",
901        note = "Please use grammar_path(), output_path(), build(), and token_map() instead"
902    )]
903    #[allow(deprecated)]
904    pub fn process_file<P, Q>(
905        &mut self,
906        inp: P,
907        outp: Q,
908    ) -> Result<HashMap<String, StorageT>, Box<dyn Error>>
909    where
910        P: AsRef<Path>,
911        Q: AsRef<Path>,
912    {
913        self.grammar_path = Some(inp.as_ref().to_owned());
914        self.output_path = Some(outp.as_ref().to_owned());
915        let cl: CTParserBuilder<LexerTypesT> = CTParserBuilder {
916            grammar_path: self.grammar_path.clone(),
917            grammar_src: None,
918            from_ast: None,
919            output_path: self.output_path.clone(),
920            mod_name: self.mod_name,
921            recoverer: self.recoverer,
922            yacckind: self.yacckind,
923            error_on_conflicts: self.error_on_conflicts,
924            warnings_are_errors: self.warnings_are_errors,
925            show_warnings: self.show_warnings,
926            visibility: self.visibility.clone(),
927            rust_edition: self.rust_edition,
928            inspect_rt: None,
929            #[cfg(test)]
930            inspect_callback: None,
931            phantom: PhantomData,
932        };
933        Ok(cl.build()?.rule_ids)
934    }
935
936    fn output_file<P: AsRef<Path>>(
937        &self,
938        grm: &YaccGrammar<StorageT>,
939        stable: &StateTable<StorageT>,
940        mod_name: &str,
941        outp_rs: P,
942        cache: &str,
943    ) -> Result<(), Box<dyn Error>> {
944        let visibility = self.visibility.clone();
945        let user_actions = if let Some(
946            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools,
947        ) = self.yacckind
948        {
949            Some(self.gen_user_actions(grm)?)
950        } else {
951            None
952        };
953        let rule_consts = self.gen_rule_consts(grm)?;
954        let token_epp = self.gen_token_epp(grm)?;
955        let parse_function = self.gen_parse_function(grm, stable)?;
956        let action_wrappers = match self.yacckind.unwrap() {
957            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
958                Some(self.gen_wrappers(grm)?)
959            }
960            YaccKind::Original(YaccOriginalActionKind::NoAction)
961            | YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => None,
962            _ => unreachable!(),
963        };
964        let mod_name = format_ident!("{}", mod_name);
965        let out_tokens = quote! {
966            #visibility mod #mod_name {
967                // At the top so that `user_actions` may contain #![inner_attribute]
968                #user_actions
969                mod _parser_ {
970                    #![allow(clippy::type_complexity)]
971                    #![allow(clippy::unnecessary_wraps)]
972                    #![deny(unsafe_code)]
973                    #[allow(unused_imports)]
974                    use super::*;
975                    #parse_function
976                    #rule_consts
977                    #token_epp
978                    #action_wrappers
979                } // End of `mod _parser_`
980                #[allow(unused_imports)]
981                pub use _parser_::*;
982                #[allow(unused_imports)]
983                use ::lrpar::Lexeme;
984            } // End of `mod #mod_name`
985        };
986        // Try and run a code formatter on the generated code.
987        let unformatted = out_tokens.to_string();
988        let outs = syn::parse_str(&unformatted)
989            .map(|syntax_tree| prettyplease::unparse(&syntax_tree))
990            .unwrap_or(unformatted);
991        let mut f = File::create(outp_rs)?;
992        f.write_all(outs.as_bytes())?;
993        f.write_all(cache.as_bytes())?;
994        Ok(())
995    }
996
997    /// Generate the cache, which determines if anything's changed enough that we need to
998    /// regenerate outputs and force rustc to recompile.
999    fn rebuild_cache(&self, derived_mod_name: &'_ str, grm: &YaccGrammar<StorageT>) -> TokenStream {
1000        // We don't need to be particularly clever here: we just need to record the various things
1001        // that could change between builds.
1002        //
1003        // Record the time that this version of lrpar was built. If the source code changes and
1004        // rustc forces a recompile, this will change this value, causing anything which depends on
1005        // this build of lrpar to be recompiled too.
1006        let Self {
1007            // All variables except for `output_path`, `inspect_callback` and `phantom` should
1008            // be written into the cache.
1009            grammar_path,
1010            // I struggle to imagine the correct thing for `grammar_src`.
1011            grammar_src: _,
1012            // I struggle to imagine the correct thing for `from_ast`.
1013            from_ast: _,
1014            mod_name,
1015            recoverer,
1016            yacckind,
1017            output_path: _,
1018            error_on_conflicts,
1019            warnings_are_errors,
1020            show_warnings,
1021            visibility,
1022            rust_edition,
1023            inspect_rt: _,
1024            #[cfg(test)]
1025                inspect_callback: _,
1026            phantom: _,
1027        } = self;
1028        let build_time = env!("VERGEN_BUILD_TIMESTAMP");
1029        let grammar_path = grammar_path.as_ref().unwrap().to_string_lossy();
1030        let mod_name = QuoteOption(mod_name.as_deref());
1031        let visibility = visibility.to_variant_tokens();
1032        let rust_edition = rust_edition.to_variant_tokens();
1033        let yacckind = yacckind.expect("is_some() by this point");
1034        let rule_map = grm
1035            .iter_tidxs()
1036            .map(|tidx| {
1037                QuoteTuple((
1038                    usize::from(tidx),
1039                    grm.token_name(tidx).unwrap_or("<unknown>"),
1040                ))
1041            })
1042            .collect::<Vec<_>>();
1043        let cache_info = quote! {
1044            BUILD_TIME = #build_time
1045            DERIVED_MOD_NAME = #derived_mod_name
1046            GRAMMAR_PATH = #grammar_path
1047            MOD_NAME = #mod_name
1048            RECOVERER = #recoverer
1049            YACC_KIND = #yacckind
1050            ERROR_ON_CONFLICTS = #error_on_conflicts
1051            SHOW_WARNINGS = #show_warnings
1052            WARNINGS_ARE_ERRORS = #warnings_are_errors
1053            RUST_EDITION = #rust_edition
1054            RULE_IDS_MAP = [#(#rule_map,)*]
1055            VISIBILITY = #visibility
1056        };
1057        let cache_info_str = cache_info.to_string();
1058        quote!(#cache_info_str)
1059    }
1060
1061    /// Generate the main parse() function for the output file.
1062    fn gen_parse_function(
1063        &self,
1064        grm: &YaccGrammar<StorageT>,
1065        stable: &StateTable<StorageT>,
1066    ) -> Result<TokenStream, Box<dyn Error>> {
1067        let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1068        let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1069        let recoverer = self.recoverer;
1070        let run_parser = match self.yacckind.unwrap() {
1071            YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => {
1072                quote! {
1073                    ::lrpar::RTParserBuilder::new(&grm, &stable)
1074                        .recoverer(#recoverer)
1075                        .parse_generictree(lexer)
1076                }
1077            }
1078            YaccKind::Original(YaccOriginalActionKind::NoAction) => {
1079                quote! {
1080                    ::lrpar::RTParserBuilder::new(&grm, &stable)
1081                        .recoverer(#recoverer)
1082                        .parse_noaction(lexer)
1083                }
1084            }
1085            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1086                let actionskind = str::parse::<TokenStream>(ACTIONS_KIND)?;
1087                // actions always have a parse_param argument, and when the `parse` function lacks one
1088                // that parameter will be unit.
1089                let (action_fn_parse_param, action_fn_parse_param_ty) = match grm.parse_param() {
1090                    Some((name, ty)) => {
1091                        let name = str::parse::<TokenStream>(name)?;
1092                        let ty = str::parse::<TokenStream>(ty)?;
1093                        (quote!(#name), quote!(#ty))
1094                    }
1095                    None => (quote!(()), quote!(())),
1096                };
1097                let wrappers = grm.iter_pidxs().map(|pidx| {
1098                    let pidx = usize::from(pidx);
1099                    format_ident!("{}wrapper_{}", ACTION_PREFIX, pidx)
1100                });
1101                let edition_lifetime = if self.rust_edition != RustEdition::Rust2015 {
1102                    quote!('_,)
1103                } else {
1104                    quote!()
1105                };
1106                let ridx = usize::from(self.user_start_ridx(grm));
1107                let action_ident = format_ident!("{}{}", ACTIONS_KIND_PREFIX, ridx);
1108
1109                quote! {
1110                    let actions: ::std::vec::Vec<
1111                            &dyn Fn(
1112                                    ::cfgrammar::RIdx<#storaget>,
1113                                    &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1114                                    ::cfgrammar::Span,
1115                                    ::std::vec::Drain<#edition_lifetime ::lrpar::parser::AStackType<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #actionskind<'input>>>,
1116                                    #action_fn_parse_param_ty
1117                            ) -> #actionskind<'input>
1118                        > = ::std::vec![#(&#wrappers,)*];
1119                    match ::lrpar::RTParserBuilder::new(&grm, &stable)
1120                        .recoverer(#recoverer)
1121                        .parse_actions(lexer, &actions, #action_fn_parse_param) {
1122                            (Some(#actionskind::#action_ident(x)), y) => (Some(x), y),
1123                            (None, y) => (None, y),
1124                            _ => unreachable!()
1125                    }
1126                }
1127            }
1128            kind => panic!("YaccKind {:?} not supported", kind),
1129        };
1130
1131        // `parse()` may or may not have an argument for `%parseparam`.
1132        let parse_fn_parse_param = match self.yacckind.unwrap() {
1133            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1134                if let Some((name, tyname)) = grm.parse_param() {
1135                    let name = str::parse::<TokenStream>(name)?;
1136                    let tyname = str::parse::<TokenStream>(tyname)?;
1137                    Some(quote! {#name: #tyname})
1138                } else {
1139                    None
1140                }
1141            }
1142            _ => None,
1143        };
1144        let parse_fn_return_ty = match self.yacckind.unwrap() {
1145            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1146                let actiont = grm
1147                    .actiontype(self.user_start_ridx(grm))
1148                    .as_ref()
1149                    .map(|at| str::parse::<TokenStream>(at))
1150                    .transpose()?;
1151                quote! {
1152                    (::std::option::Option<#actiont>, ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>)
1153                }
1154            }
1155            YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => quote! {
1156                (::std::option::Option<::lrpar::Node<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #storaget>>,
1157                    ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>)
1158            },
1159            YaccKind::Original(YaccOriginalActionKind::NoAction) => quote! {
1160                ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>
1161            },
1162            _ => unreachable!(),
1163        };
1164
1165        let grm_data = encode_to_vec(grm, bincode::config::standard())?;
1166        let stable_data = encode_to_vec(stable, bincode::config::standard())?;
1167        Ok(quote! {
1168            const __GRM_DATA: &[u8] = &[#(#grm_data,)*];
1169            const __STABLE_DATA: &[u8] = &[#(#stable_data,)*];
1170
1171            #[allow(dead_code)]
1172            pub fn parse<'lexer, 'input: 'lexer>(
1173                 lexer: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1174                 #parse_fn_parse_param
1175            ) -> #parse_fn_return_ty {
1176                let (grm, stable) = ::lrpar::ctbuilder::_reconstitute(__GRM_DATA, __STABLE_DATA);
1177                #run_parser
1178            }
1179        })
1180    }
1181
1182    fn gen_rule_consts(
1183        &self,
1184        grm: &YaccGrammar<StorageT>,
1185    ) -> Result<TokenStream, proc_macro2::LexError> {
1186        let mut toks = TokenStream::new();
1187        for ridx in grm.iter_rules() {
1188            if !grm.rule_to_prods(ridx).contains(&grm.start_prod()) {
1189                let r_const = format_ident!("R_{}", grm.rule_name_str(ridx).to_ascii_uppercase());
1190                let storage_ty = str::parse::<TokenStream>(type_name::<StorageT>())?;
1191                let ridx = UnsuffixedUsize(usize::from(ridx));
1192                toks.extend(quote! {
1193                    #[allow(dead_code)]
1194                    pub const #r_const: #storage_ty = #ridx;
1195                });
1196            }
1197        }
1198        Ok(toks)
1199    }
1200
1201    fn gen_token_epp(
1202        &self,
1203        grm: &YaccGrammar<StorageT>,
1204    ) -> Result<TokenStream, proc_macro2::LexError> {
1205        let mut tidxs = Vec::new();
1206        for tidx in grm.iter_tidxs() {
1207            tidxs.push(QuoteOption(grm.token_epp(tidx)));
1208        }
1209        let const_epp_ident = format_ident!("{}EPP", GLOBAL_PREFIX);
1210        let storage_ty = str::parse::<TokenStream>(type_name::<StorageT>())?;
1211        Ok(quote! {
1212            const #const_epp_ident: &[::std::option::Option<&str>] = &[
1213                #(#tidxs,)*
1214            ];
1215
1216            /// Return the %epp entry for token `tidx` (where `None` indicates \"the token has no
1217            /// pretty-printed value\"). Panics if `tidx` doesn't exist.
1218            #[allow(dead_code)]
1219            pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<#storage_ty>) -> ::std::option::Option<&'a str> {
1220                #const_epp_ident[usize::from(tidx)]
1221            }
1222        })
1223    }
1224
1225    /// Generate the wrappers that call user actions
1226    fn gen_wrappers(
1227        &self,
1228        grm: &YaccGrammar<StorageT>,
1229    ) -> Result<TokenStream, proc_macro2::LexError> {
1230        let (parse_paramname, parse_paramdef);
1231        match grm.parse_param() {
1232            Some((name, tyname)) => {
1233                parse_paramname = str::parse::<TokenStream>(name)?;
1234                let ty = str::parse::<TokenStream>(tyname)?;
1235                parse_paramdef = quote!(#parse_paramname: #ty);
1236            }
1237            None => {
1238                parse_paramname = quote!(());
1239                parse_paramdef = quote! {_: ()};
1240            }
1241        };
1242
1243        let mut wrappers = TokenStream::new();
1244        for pidx in grm.iter_pidxs() {
1245            let ridx = grm.prod_to_rule(pidx);
1246
1247            // Iterate over all $-arguments and replace them with their respective
1248            // element from the argument vector (e.g. $1 is replaced by args[0]). At
1249            // the same time extract &str from tokens and actiontype from nonterminals.
1250            let wrapper_fn = format_ident!("{}wrapper_{}", ACTION_PREFIX, usize::from(pidx));
1251            let ridx_var = format_ident!("{}ridx", ACTION_PREFIX);
1252            let lexer_var = format_ident!("{}lexer", ACTION_PREFIX);
1253            let span_var = format_ident!("{}span", ACTION_PREFIX);
1254            let args_var = format_ident!("{}args", ACTION_PREFIX);
1255            let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1256            let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1257            let actionskind = str::parse::<TokenStream>(ACTIONS_KIND)?;
1258            let edition_lifetime = if self.rust_edition != RustEdition::Rust2015 {
1259                Some(quote!('_,))
1260            } else {
1261                None
1262            };
1263            let mut wrapper_fn_body = TokenStream::new();
1264            if grm.action(pidx).is_some() {
1265                // Unpack the arguments passed to us by the drain
1266                for i in 0..grm.prod(pidx).len() {
1267                    let arg = format_ident!("{}arg_{}", ACTION_PREFIX, i + 1);
1268                    wrapper_fn_body.extend(match grm.prod(pidx)[i] {
1269                        Symbol::Rule(ref_ridx) => {
1270                            let ref_ridx = usize::from(ref_ridx);
1271                            let actionvariant = format_ident!("{}{}", ACTIONS_KIND_PREFIX, ref_ridx);
1272                            quote!{
1273                                #[allow(clippy::let_unit_value)]
1274                                let #arg = match #args_var.next().unwrap() {
1275                                    ::lrpar::parser::AStackType::ActionType(#actionskind::#actionvariant(x)) => x,
1276                                    _ => unreachable!()
1277                                };
1278                            }
1279                        }
1280                        Symbol::Token(_) => {
1281                            quote!{
1282                                let #arg = match #args_var.next().unwrap() {
1283                                    ::lrpar::parser::AStackType::Lexeme(l) => {
1284                                        if l.faulty() {
1285                                            Err(l)
1286                                        } else {
1287                                            Ok(l)
1288                                        }
1289                                    },
1290                                    ::lrpar::parser::AStackType::ActionType(_) => unreachable!()
1291                                };
1292                            }
1293                        }
1294                    })
1295                }
1296
1297                // Call the user code
1298                let args = (0..grm.prod(pidx).len())
1299                    .map(|i| format_ident!("{}arg_{}", ACTION_PREFIX, i + 1))
1300                    .collect::<Vec<_>>();
1301                let action_fn = format_ident!("{}action_{}", ACTION_PREFIX, usize::from(pidx));
1302                let actionsvariant = format_ident!("{}{}", ACTIONS_KIND_PREFIX, usize::from(ridx));
1303
1304                wrapper_fn_body.extend(match grm.actiontype(ridx) {
1305                    Some(s) if s == "()" => {
1306                        // If the rule `r` that we're calling has the unit type then Clippy will warn that
1307                        // `enum::A(wrapper_r())` is pointless. We thus have to split it into two:
1308                        // `wrapper_r(); enum::A(())`.
1309                        quote!{
1310                            #action_fn(#ridx_var, #lexer_var, #span_var, #parse_paramname, #(#args,)*);
1311                            #actionskind::#actionsvariant(())
1312                        }
1313                    }
1314                    _ => {
1315                        quote!{
1316                            #actionskind::#actionsvariant(#action_fn(#ridx_var, #lexer_var, #span_var, #parse_paramname, #(#args,)*))
1317                        }
1318                    }
1319                })
1320            } else if pidx == grm.start_prod() {
1321                wrapper_fn_body.extend(quote!(unreachable!()));
1322            } else {
1323                panic!(
1324                    "Production in rule '{}' must have an action body.",
1325                    grm.rule_name_str(grm.prod_to_rule(pidx))
1326                );
1327            };
1328
1329            let attrib = if pidx == grm.start_prod() {
1330                // The start prod has an unreachable body so it doesn't use it's variables.
1331                Some(quote!(#[allow(unused_variables)]))
1332            } else {
1333                None
1334            };
1335            wrappers.extend(quote!{
1336                #attrib
1337                fn #wrapper_fn<'lexer, 'input: 'lexer>(
1338                    #ridx_var: ::cfgrammar::RIdx<#storaget>,
1339                    #lexer_var: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1340                    #span_var: ::cfgrammar::Span,
1341                    mut #args_var: ::std::vec::Drain<#edition_lifetime ::lrpar::parser::AStackType<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #actionskind<'input>>>,
1342                    #parse_paramdef
1343                ) -> #actionskind<'input> {
1344                    #wrapper_fn_body
1345                }
1346             })
1347        }
1348        let mut actionskindvariants = Vec::new();
1349        let actionskindhidden = format_ident!("_{}", ACTIONS_KIND_HIDDEN);
1350        let actionskind = str::parse::<TokenStream>(ACTIONS_KIND).unwrap();
1351        for ridx in grm.iter_rules() {
1352            if let Some(actiont) = grm.actiontype(ridx) {
1353                let actionskindvariant =
1354                    format_ident!("{}{}", ACTIONS_KIND_PREFIX, usize::from(ridx));
1355                let actiont = str::parse::<TokenStream>(actiont).unwrap();
1356                actionskindvariants.push(quote! {
1357                    #actionskindvariant(#actiont)
1358                })
1359            }
1360        }
1361        actionskindvariants
1362            .push(quote!(#actionskindhidden(::std::marker::PhantomData<&'input ()>)));
1363        wrappers.extend(quote! {
1364            #[allow(dead_code)]
1365            enum #actionskind<'input> {
1366                #(#actionskindvariants,)*
1367            }
1368        });
1369        Ok(wrappers)
1370    }
1371
1372    /// Generate the user action functions (if any).
1373    fn gen_user_actions(&self, grm: &YaccGrammar<StorageT>) -> Result<TokenStream, Box<dyn Error>> {
1374        let programs = grm
1375            .programs()
1376            .as_ref()
1377            .map(|s| str::parse::<TokenStream>(s))
1378            .transpose()?;
1379        let mut action_fns = TokenStream::new();
1380        // Convert actions to functions
1381        let (parse_paramname, parse_paramdef, parse_param_unit);
1382        match grm.parse_param() {
1383            Some((name, tyname)) => {
1384                parse_param_unit = tyname.trim() == "()";
1385                parse_paramname = str::parse::<TokenStream>(name)?;
1386                let ty = str::parse::<TokenStream>(tyname)?;
1387                parse_paramdef = quote!(#parse_paramname: #ty);
1388            }
1389            None => {
1390                parse_param_unit = true;
1391                parse_paramname = quote!(());
1392                parse_paramdef = quote! {_: ()};
1393            }
1394        };
1395        for pidx in grm.iter_pidxs() {
1396            if pidx == grm.start_prod() {
1397                continue;
1398            }
1399
1400            // Work out the right type for each argument
1401            let mut args = Vec::with_capacity(grm.prod(pidx).len());
1402            for i in 0..grm.prod(pidx).len() {
1403                let argt = match grm.prod(pidx)[i] {
1404                    Symbol::Rule(ref_ridx) => {
1405                        str::parse::<TokenStream>(grm.actiontype(ref_ridx).as_ref().unwrap())?
1406                    }
1407                    Symbol::Token(_) => {
1408                        let lexemet =
1409                            str::parse::<TokenStream>(type_name::<LexerTypesT::LexemeT>())?;
1410                        quote!(::std::result::Result<#lexemet, #lexemet>)
1411                    }
1412                };
1413                let arg = format_ident!("{}arg_{}", ACTION_PREFIX, i + 1);
1414                args.push(quote!(mut #arg: #argt));
1415            }
1416
1417            // If this rule's `actiont` is `()` then Clippy will warn that the return type `-> ()`
1418            // is pointless (which is true). We therefore avoid outputting a return type if actiont
1419            // is the unit type.
1420            let returnt = {
1421                let actiont = grm.actiontype(grm.prod_to_rule(pidx)).as_ref().unwrap();
1422                if actiont == "()" {
1423                    None
1424                } else {
1425                    let actiont = str::parse::<TokenStream>(actiont)?;
1426                    Some(quote!( -> #actiont))
1427                }
1428            };
1429            let action_fn = format_ident!("{}action_{}", ACTION_PREFIX, usize::from(pidx));
1430            let lexer_var = format_ident!("{}lexer", ACTION_PREFIX);
1431            let span_var = format_ident!("{}span", ACTION_PREFIX);
1432            let ridx_var = format_ident!("{}ridx", ACTION_PREFIX);
1433            let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1434            let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1435            let bind_parse_param = if !parse_param_unit {
1436                Some(quote! {let _ = #parse_paramname;})
1437            } else {
1438                None
1439            };
1440
1441            // Iterate over all $-arguments and replace them with their respective
1442            // element from the argument vector (e.g. $1 is replaced by args[0]).
1443            let pre_action = grm.action(pidx).as_ref().ok_or_else(|| {
1444                format!(
1445                    "Rule {} has a production which is missing action code",
1446                    grm.rule_name_str(grm.prod_to_rule(pidx))
1447                )
1448            })?;
1449            let mut last = 0;
1450            let mut outs = String::new();
1451            loop {
1452                match pre_action[last..].find('$') {
1453                    Some(off) => {
1454                        if pre_action[last + off..].starts_with("$$") {
1455                            outs.push_str(&pre_action[last..last + off + "$".len()]);
1456                            last = last + off + "$$".len();
1457                        } else if pre_action[last + off..].starts_with("$lexer") {
1458                            outs.push_str(&pre_action[last..last + off]);
1459                            write!(outs, "{prefix}lexer", prefix = ACTION_PREFIX).ok();
1460                            last = last + off + "$lexer".len();
1461                        } else if pre_action[last + off..].starts_with("$span") {
1462                            outs.push_str(&pre_action[last..last + off]);
1463                            write!(outs, "{prefix}span", prefix = ACTION_PREFIX).ok();
1464                            last = last + off + "$span".len();
1465                        } else if last + off + 1 < pre_action.len()
1466                            && pre_action[last + off + 1..].starts_with(|c: char| c.is_numeric())
1467                        {
1468                            outs.push_str(&pre_action[last..last + off]);
1469                            write!(outs, "{prefix}arg_", prefix = ACTION_PREFIX).ok();
1470                            last = last + off + "$".len();
1471                        } else {
1472                            panic!(
1473                                "Unknown text following '$' operator: {}",
1474                                &pre_action[last + off..]
1475                            );
1476                        }
1477                    }
1478                    None => {
1479                        outs.push_str(&pre_action[last..]);
1480                        break;
1481                    }
1482                }
1483            }
1484
1485            let action_body = str::parse::<TokenStream>(&outs)?;
1486            action_fns.extend(quote!{
1487                    #[allow(clippy::too_many_arguments)]
1488                    fn #action_fn<'lexer, 'input: 'lexer>(#ridx_var: ::cfgrammar::RIdx<#storaget>,
1489                                    #lexer_var: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1490                                    #span_var: ::cfgrammar::Span,
1491                                    #parse_paramdef,
1492                                    #(#args,)*)#returnt {
1493                        #bind_parse_param
1494                        #action_body
1495                    }
1496
1497            })
1498        }
1499        Ok(quote! {
1500            #programs
1501            #action_fns
1502        })
1503    }
1504
1505    /// Return the `RIdx` of the %start rule in the grammar (which will not be the same as
1506    /// grm.start_rule_idx because the latter has an additional rule insert by cfgrammar
1507    /// which then calls the user's %start rule).
1508    fn user_start_ridx(&self, grm: &YaccGrammar<StorageT>) -> RIdx<StorageT> {
1509        debug_assert_eq!(grm.prod(grm.start_prod()).len(), 1);
1510        match grm.prod(grm.start_prod())[0] {
1511            Symbol::Rule(ridx) => ridx,
1512            _ => unreachable!(),
1513        }
1514    }
1515}
1516
1517/// This function is called by generated files; it exists so that generated files don't require a
1518/// direct dependency on bincode.
1519#[doc(hidden)]
1520pub fn _reconstitute<StorageT: Decode<()> + Hash + PrimInt + Unsigned + 'static>(
1521    grm_buf: &[u8],
1522    stable_buf: &[u8],
1523) -> (YaccGrammar<StorageT>, StateTable<StorageT>) {
1524    let (grm, _) = decode_from_slice(grm_buf, bincode::config::standard()).unwrap();
1525    let (stable, _) = decode_from_slice(stable_buf, bincode::config::standard()).unwrap();
1526    (grm, stable)
1527}
1528
1529/// An interface to the result of [CTParserBuilder::build()].
1530pub struct CTParser<StorageT = u32>
1531where
1532    StorageT: Eq + Hash,
1533{
1534    regenerated: bool,
1535    rule_ids: HashMap<String, StorageT>,
1536    yacc_grammar: YaccGrammar<StorageT>,
1537    grammar_src: String,
1538    grammar_path: PathBuf,
1539    conflicts: Option<(StateGraph<StorageT>, StateTable<StorageT>)>,
1540}
1541
1542impl<StorageT> CTParser<StorageT>
1543where
1544    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
1545    usize: AsPrimitive<StorageT>,
1546{
1547    /// Returns `true` if this compile-time parser was regenerated or `false` if it was not.
1548    pub fn regenerated(&self) -> bool {
1549        self.regenerated
1550    }
1551
1552    /// Returns a [HashMap] from lexeme string types to numeric types (e.g. `INT: 2`), suitable for
1553    /// handing to a lexer to coordinate the IDs of lexer and parser.
1554    pub fn token_map(&self) -> &HashMap<String, StorageT> {
1555        &self.rule_ids
1556    }
1557
1558    /// If there are any conflicts in the grammar, return a tuple which allows users to inspect and
1559    /// pretty print them; otherwise returns `None`. If the grammar was not regenerated, this will
1560    /// always return `None`, even if the grammar actually has conflicts.
1561    ///
1562    /// **Note: The conflicts feature is currently unstable and may change in the future.**
1563    #[allow(private_interfaces)]
1564    pub fn conflicts(
1565        &self,
1566        _: crate::unstable::UnstableApi,
1567    ) -> Option<(
1568        &YaccGrammar<StorageT>,
1569        &StateGraph<StorageT>,
1570        &StateTable<StorageT>,
1571        &Conflicts<StorageT>,
1572    )> {
1573        if let Some((sgraph, stable)) = &self.conflicts {
1574            return Some((
1575                &self.yacc_grammar,
1576                sgraph,
1577                stable,
1578                stable.conflicts().unwrap(),
1579            ));
1580        }
1581        None
1582    }
1583
1584    #[doc(hidden)]
1585    pub fn yacc_grammar(&self) -> &YaccGrammar<StorageT> {
1586        &self.yacc_grammar
1587    }
1588    #[doc(hidden)]
1589    pub fn grammar_src(&self) -> &str {
1590        &self.grammar_src
1591    }
1592    #[doc(hidden)]
1593    pub fn grammar_path(&self) -> &Path {
1594        self.grammar_path.as_path()
1595    }
1596}
1597
1598/// Indents a multi-line string and trims any trailing newline.
1599/// This currently assumes that indentation on blank lines does not matter.
1600///
1601/// The algorithm used by this function is:
1602/// 1. Prefix `s` with the indentation, indenting the first line.
1603/// 2. Trim any trailing newlines.
1604/// 3. Replace all newlines with `\n{indent}`` to indent all lines after the first.
1605///
1606/// It is plausible that we should a step 4, but currently do not:
1607/// 4. Replace all `\n{indent}\n` with `\n\n`
1608fn indent(indent: &str, s: &str) -> String {
1609    format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
1610}
1611
1612// Tests dealing with the filesystem not supported under wasm32
1613#[cfg(all(not(target_arch = "wasm32"), test))]
1614mod test {
1615    use std::{fs::File, io::Write, path::PathBuf};
1616
1617    use super::{CTConflictsError, CTParserBuilder};
1618    use crate::test_utils::TestLexerTypes;
1619    use cfgrammar::yacc::{YaccKind, YaccOriginalActionKind};
1620    use tempfile::TempDir;
1621
1622    #[test]
1623    fn test_conflicts() {
1624        let temp = TempDir::new().unwrap();
1625        let mut file_path = PathBuf::from(temp.as_ref());
1626        file_path.push("grm.y");
1627        let mut f = File::create(&file_path).unwrap();
1628        let _ = f.write_all(
1629            "%start A
1630%%
1631A : 'a' 'b' | B 'b';
1632B : 'a' | C;
1633C : 'a';"
1634                .as_bytes(),
1635        );
1636
1637        match CTParserBuilder::<TestLexerTypes>::new()
1638            .error_on_conflicts(false)
1639            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1640            .grammar_path(file_path.to_str().unwrap())
1641            .output_path(file_path.with_extension("ignored"))
1642            .build()
1643            .unwrap()
1644            .conflicts(crate::unstable::UnstableApi)
1645        {
1646            Some((_, _, _, conflicts)) => {
1647                assert_eq!(conflicts.sr_len(), 1);
1648                assert_eq!(conflicts.rr_len(), 1);
1649            }
1650            None => panic!("Expected error data"),
1651        }
1652    }
1653
1654    #[test]
1655    fn test_conflicts_error() {
1656        let temp = TempDir::new().unwrap();
1657        let mut file_path = PathBuf::from(temp.as_ref());
1658        file_path.push("grm.y");
1659        let mut f = File::create(&file_path).unwrap();
1660        let _ = f.write_all(
1661            "%start A
1662%%
1663A : 'a' 'b' | B 'b';
1664B : 'a' | C;
1665C : 'a';"
1666                .as_bytes(),
1667        );
1668
1669        match CTParserBuilder::<TestLexerTypes>::new()
1670            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1671            .grammar_path(file_path.to_str().unwrap())
1672            .output_path(file_path.with_extension("ignored"))
1673            .build()
1674        {
1675            Ok(_) => panic!("Expected error"),
1676            Err(e) => {
1677                let cs = e.downcast_ref::<CTConflictsError<u16>>();
1678                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1679                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1680            }
1681        }
1682    }
1683
1684    #[test]
1685    fn test_expect_error() {
1686        let temp = TempDir::new().unwrap();
1687        let mut file_path = PathBuf::from(temp.as_ref());
1688        file_path.push("grm.y");
1689        let mut f = File::create(&file_path).unwrap();
1690        let _ = f.write_all(
1691            "%start A
1692%expect 2
1693%%
1694A: 'a' 'b' | B 'b';
1695B: 'a';"
1696                .as_bytes(),
1697        );
1698
1699        match CTParserBuilder::<TestLexerTypes>::new()
1700            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1701            .grammar_path(file_path.to_str().unwrap())
1702            .output_path(file_path.with_extension("ignored"))
1703            .build()
1704        {
1705            Ok(_) => panic!("Expected error"),
1706            Err(e) => {
1707                let cs = e.downcast_ref::<CTConflictsError<u16>>();
1708                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 0);
1709                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1710            }
1711        }
1712    }
1713
1714    #[test]
1715    fn test_expectrr_error() {
1716        let temp = TempDir::new().unwrap();
1717        let mut file_path = PathBuf::from(temp.as_ref());
1718        file_path.push("grm.y");
1719        let mut f = File::create(&file_path).unwrap();
1720        let _ = f.write_all(
1721            "%start A
1722%expect 1
1723%expect-rr 2
1724%%
1725A : 'a' 'b' | B 'b';
1726B : 'a' | C;
1727C : 'a';"
1728                .as_bytes(),
1729        );
1730
1731        match CTParserBuilder::<TestLexerTypes>::new()
1732            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1733            .grammar_path(file_path.to_str().unwrap())
1734            .output_path(file_path.with_extension("ignored"))
1735            .build()
1736        {
1737            Ok(_) => panic!("Expected error"),
1738            Err(e) => {
1739                let cs = e.downcast_ref::<CTConflictsError<u16>>();
1740                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1741                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1742            }
1743        }
1744    }
1745
1746    #[cfg(test)]
1747    #[test]
1748    fn test_recoverer_header() -> Result<(), Box<dyn std::error::Error>> {
1749        use crate::RecoveryKind as RK;
1750        #[rustfmt::skip]
1751            let recovery_kinds = [
1752                //  Builder,          Header setting,     Expected result.
1753                // -----------       ------------------  -------------------
1754                (Some(RK::None),      Some(RK::None),     Some(RK::None)),
1755                (Some(RK::None),      Some(RK::CPCTPlus), Some(RK::None)),
1756                (Some(RK::CPCTPlus),  Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1757                (Some(RK::CPCTPlus),  Some(RK::None),     Some(RK::CPCTPlus)),
1758                (None,                Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1759                (None,                Some(RK::None),     Some(RK::None)),
1760                (None,                None,               Some(RK::CPCTPlus)),
1761                (Some(RK::None),      None,               Some(RK::None)),
1762                (Some(RK::CPCTPlus),  None,               Some(RK::CPCTPlus)),
1763            ];
1764
1765        for (i, (builder_arg, header_arg, expected_rk)) in
1766            recovery_kinds.iter().cloned().enumerate()
1767        {
1768            let y_src = if let Some(header_arg) = header_arg {
1769                format!(
1770                    "\
1771                    %grmtools{{yacckind: Original(NoAction), recoverer: {}}} \
1772                    %% \
1773                    start: ; \
1774                    ",
1775                    match header_arg {
1776                        RK::None => "RecoveryKind::None",
1777                        RK::CPCTPlus => "RecoveryKind::CPCTPlus",
1778                    }
1779                )
1780            } else {
1781                r#"
1782                    %grmtools{yacckind: Original(NoAction)}
1783                    %%
1784                    Start: ;
1785                    "#
1786                .to_string()
1787            };
1788            let out_dir = std::env::var("OUT_DIR").unwrap();
1789            let y_path = format!("{out_dir}/recoverykind_test_{i}.y");
1790            let y_out_path = format!("{y_path}.rs");
1791            std::fs::File::create(y_path.clone()).unwrap();
1792            std::fs::write(y_path.clone(), y_src).unwrap();
1793            let mut cp_builder = CTParserBuilder::<TestLexerTypes>::new();
1794            cp_builder = cp_builder
1795                .output_path(y_out_path.clone())
1796                .grammar_path(y_path.clone());
1797            cp_builder = if let Some(builder_arg) = builder_arg {
1798                cp_builder.recoverer(builder_arg)
1799            } else {
1800                cp_builder
1801            }
1802            .inspect_recoverer(Box::new(move |rk| {
1803                if matches!(
1804                    (rk, expected_rk),
1805                    (RK::None, Some(RK::None)) | (RK::CPCTPlus, Some(RK::CPCTPlus))
1806                ) {
1807                    Ok(())
1808                } else {
1809                    panic!("Unexpected recovery kind")
1810                }
1811            }));
1812            cp_builder.build()?;
1813        }
1814        Ok(())
1815    }
1816}