Skip to main content

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