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, 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        let yacc_diag = SpannedDiagnosticFormatter::new(&inc, grmp);
731        let parsed_header = GrmtoolsSectionParser::new(&inc, false).parse();
732        if let Err(errs) = parsed_header {
733            let mut out = String::new();
734            out.push_str(&format!(
735                "\n{ERROR}{}\n",
736                yacc_diag.file_location_msg(" parsing the `%grmtools` section", None)
737            ));
738            for e in errs {
739                out.push_str(&indent("     ", &yacc_diag.format_error(e).to_string()));
740            }
741            return Err(ErrorString(out).into());
742        };
743        let (parsed_header, _) = parsed_header.unwrap();
744        header.merge_from(parsed_header)?;
745        self.yacckind = header
746            .get("yacckind")
747            .map(|HeaderValue(_, val)| val)
748            .map(YaccKind::try_from)
749            .transpose()?;
750        header.mark_used(&"yacckind".to_string());
751        let ast_validation = if let Some(ast) = &self.from_ast {
752            ast.clone()
753        } else if let Some(yk) = self.yacckind {
754            ASTWithValidityInfo::new(yk, &inc)
755        } else {
756            Err("Missing 'yacckind'".to_string())?
757        };
758
759        header.mark_used(&"recoverer".to_string());
760        let rk_val = header.get("recoverer").map(|HeaderValue(_, rk_val)| rk_val);
761
762        if let Some(rk_val) = rk_val {
763            self.recoverer = Some(RecoveryKind::try_from(rk_val)?);
764        } else {
765            // Fallback to the default recoverykind.
766            self.recoverer = Some(RecoveryKind::CPCTPlus);
767        }
768        header.mark_used(&"serialisation_format".to_string());
769        if let Some(ec_val) = header
770            .get("serialisation_format")
771            .map(|HeaderValue(_, ec_val)| ec_val)
772        {
773            self.serialisation_format = Some(SerialisationFormat::try_from(ec_val)?);
774        } else {
775            self.serialisation_format = Some(SerialisationFormat::VariableSizedInteger);
776        }
777
778        self.yacckind = Some(ast_validation.yacc_kind());
779        let warnings = ast_validation.ast().warnings();
780        if self.warnings_are_errors && !warnings.is_empty() {
781            let mut out = String::new();
782            out.push_str(&format!(
783                "\n{ERROR}{}\n",
784                yacc_diag.file_location_msg("", None)
785            ));
786            for e in warnings {
787                out.push_str(&format!(
788                    "{}\n",
789                    indent("     ", &yacc_diag.format_warning(e).to_string())
790                ));
791            }
792            return Err(ErrorString(out).into());
793        } else if !warnings.is_empty() {
794            for w in warnings {
795                let ws_loc = yacc_diag.file_location_msg("", None);
796                let ws = indent("     ", &yacc_diag.format_warning(w).to_string());
797                // Assume if this variable is set we are running under cargo.
798                if std::env::var("OUT_DIR").is_ok() && self.show_warnings {
799                    for line in ws_loc.lines().chain(ws.lines()) {
800                        println!("cargo:warning={}", line);
801                    }
802                } else if self.show_warnings {
803                    eprintln!("{}", ws_loc);
804                    eprintln!("{WARNING} {}", ws);
805                }
806            }
807        }
808        let grm = match YaccGrammar::<StorageT>::new_from_ast_with_validity_info(&ast_validation) {
809            Ok(grm) => grm,
810            Err(errs) => {
811                let mut out = String::new();
812                out.push_str(&format!(
813                    "\n{ERROR}{}\n",
814                    yacc_diag.file_location_msg("", None)
815                ));
816                for e in errs {
817                    out.push_str(&indent("     ", &yacc_diag.format_error(e).to_string()));
818                    out.push('\n');
819                }
820                return Err(ErrorString(out).into());
821            }
822        };
823        #[cfg(test)]
824        if let Some(cb) = &self.inspect_callback {
825            cb(self.recoverer.expect("has a default value"))?;
826        }
827
828        let rule_ids = grm
829            .tokens_map()
830            .iter()
831            .map(|(&n, &i)| (n.to_owned(), i.as_storaget()))
832            .collect::<HashMap<_, _>>();
833
834        let derived_mod_name = match self.mod_name {
835            Some(s) => s.to_owned(),
836            None => {
837                // The user hasn't specified a module name, so we create one automatically: what we
838                // do is strip off all the filename extensions (note that it's likely that inp ends
839                // with `y.rs`, so we potentially have to strip off more than one extension) and
840                // then add `_y` to the end.
841                let mut stem = grmp.to_str().unwrap();
842                loop {
843                    let new_stem = Path::new(stem).file_stem().unwrap().to_str().unwrap();
844                    if stem == new_stem {
845                        break;
846                    }
847                    stem = new_stem;
848                }
849                format!("{}_y", stem)
850            }
851        };
852
853        let cache = self.rebuild_cache(&derived_mod_name, &grm);
854
855        // We don't need to go through the full rigmarole of generating an output file if all of
856        // the following are true: the output file exists; it is newer than the input file; and the
857        // cache hasn't changed. The last of these might be surprising, but it's vital: we don't
858        // know, for example, what the IDs map might be from one run to the next, and it might
859        // change for reasons beyond lrpar's control. If it does change, that means that the lexer
860        // and lrpar would get out of sync, so we have to play it safe and regenerate in such
861        // cases.
862        if let Ok(ref inmd) = fs::metadata(grmp)
863            && let Ok(ref out_rs_md) = fs::metadata(outp)
864            && FileTime::from_last_modification_time(out_rs_md)
865                > FileTime::from_last_modification_time(inmd)
866            && let Ok(outc) = read_to_string(outp)
867        {
868            if outc.contains(&cache.to_string()) {
869                return Ok(CTParser {
870                    regenerated: false,
871                    rule_ids,
872                    yacc_grammar: grm,
873                    grammar_src: inc,
874                    grammar_path: self.grammar_path.unwrap(),
875                    conflicts: None,
876                });
877            } else {
878                #[cfg(grmtools_extra_checks)]
879                if std::env::var("CACHE_EXPECTED").is_ok() {
880                    eprintln!("outc: {}", outc);
881                    eprintln!("using cache: {}", cache,);
882                    // Primarily for use in the testsuite.
883                    panic!("The cache regenerated however, it was expected to match");
884                }
885            }
886        }
887
888        // At this point, we know we're going to generate fresh output; however, if something goes
889        // wrong in the process between now and us writing /out/blah.rs, rustc thinks that
890        // everything's gone swimmingly (even if build.rs errored!), and tries to carry on
891        // compilation, leading to weird errors. We therefore delete /out/blah.rs at this point,
892        // which means, at worse, the user gets a "file not found" error from rustc (which is less
893        // confusing than the alternatives).
894        fs::remove_file(outp).ok();
895
896        let (sgraph, stable) = from_yacc(&grm, Minimiser::Pager)?;
897        if self.error_on_conflicts
898            && let Some(c) = stable.conflicts()
899        {
900            match (grm.expect(), grm.expectrr()) {
901                (Some(i), Some(j)) if i == c.sr_len() && j == c.rr_len() => (),
902                (Some(i), None) if i == c.sr_len() && 0 == c.rr_len() => (),
903                (None, Some(j)) if 0 == c.sr_len() && j == c.rr_len() => (),
904                (None, None) if 0 == c.rr_len() && 0 == c.sr_len() => (),
905                _ => {
906                    let conflicts_diagnostic = yacc_diag.format_conflicts::<LexerTypesT>(
907                        &grm,
908                        ast_validation.ast(),
909                        c,
910                        &sgraph,
911                        &stable,
912                    );
913                    return Err(Box::new(CTConflictsError {
914                        conflicts_diagnostic,
915                        phantom: PhantomData,
916                        #[cfg(test)]
917                        stable,
918                    }));
919                }
920            }
921        }
922
923        if let Some(ref mut inspector_rt) = self.inspect_rt {
924            let rt: RTParserBuilder<'_, StorageT, LexerTypesT> =
925                RTParserBuilder::new(&grm, &stable);
926            let rt = if let Some(rk) = self.recoverer {
927                rt.recoverer(rk)
928            } else {
929                rt
930            };
931            inspector_rt(&mut header, rt, &rule_ids, grmp)?
932        }
933
934        let unused_keys = header.unused();
935        if !unused_keys.is_empty() {
936            return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into());
937        }
938        let missing_keys = header
939            .missing()
940            .iter()
941            .map(|s| s.as_str())
942            .collect::<Vec<_>>();
943        if !missing_keys.is_empty() {
944            return Err(format!(
945                "Required values were missing from the header: {}",
946                missing_keys.join(", ")
947            )
948            .into());
949        }
950
951        self.output_file(
952            &grm,
953            &stable,
954            &derived_mod_name,
955            outp,
956            &format!("/* CACHE INFORMATION {} */\n", cache),
957        )?;
958        let conflicts = if stable.conflicts().is_some() {
959            Some((sgraph, stable))
960        } else {
961            None
962        };
963        Ok(CTParser {
964            regenerated: true,
965            rule_ids,
966            yacc_grammar: grm,
967            grammar_src: inc,
968            grammar_path: self.grammar_path.unwrap(),
969            conflicts,
970        })
971    }
972
973    /// Given the filename `a/b.y` as input, statically compile the grammar `src/a/b.y` into a Rust
974    /// module which can then be imported using `lrpar_mod!("a/b.y")`. This is a convenience
975    /// function around [`process_file`](#method.process_file) which makes it easier to compile
976    /// grammar files stored in a project's `src/` directory: please see
977    /// [`process_file`](#method.process_file) for additional constraints and information about the
978    /// generated files.
979    #[deprecated(
980        since = "0.11.0",
981        note = "Please use grammar_in_src_dir(), build(), and token_map() instead"
982    )]
983    #[allow(deprecated)]
984    pub fn process_file_in_src(
985        &mut self,
986        srcp: &str,
987    ) -> Result<HashMap<String, StorageT>, Box<dyn Error>> {
988        let mut inp = current_dir()?;
989        inp.push("src");
990        inp.push(srcp);
991        let mut outp = PathBuf::new();
992        outp.push(var("OUT_DIR").unwrap());
993        outp.push(Path::new(srcp).parent().unwrap().to_str().unwrap());
994        create_dir_all(&outp)?;
995        let mut leaf = Path::new(srcp)
996            .file_name()
997            .unwrap()
998            .to_str()
999            .unwrap()
1000            .to_owned();
1001        write!(leaf, ".{}", RUST_FILE_EXT).ok();
1002        outp.push(leaf);
1003        self.process_file(inp, outp)
1004    }
1005
1006    /// Statically compile the Yacc file `inp` into Rust, placing the output into the file `outp`.
1007    /// Note that three additional files will be created with the same name as `outp` but with the
1008    /// extensions `grm`, and `stable`, overwriting any existing files with those names.
1009    ///
1010    /// `outp` defines a module as follows:
1011    ///
1012    /// ```text
1013    ///   mod modname {
1014    ///     pub fn parse(lexemes: &::std::vec::Vec<::lrpar::Lexeme<StorageT>>) { ... }
1015    ///         -> (::std::option::Option<ActionT>,
1016    ///             ::std::vec::Vec<::lrpar::LexParseError<StorageT>>)> { ...}
1017    ///
1018    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
1019    ///       ...
1020    ///     }
1021    ///
1022    ///     ...
1023    ///   }
1024    /// ```
1025    ///
1026    /// where:
1027    ///  * `modname` is either:
1028    ///    * the module name specified [`mod_name`](#method.mod_name)
1029    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.y` the
1030    ///      module name is `c_y` (i.e. the file's leaf name, minus its extension, with a prefix of
1031    ///      `_y`).
1032    ///  * `ActionT` is either:
1033    ///    * the `%actiontype` value given to the grammar
1034    ///    * or, if the `yacckind` was set YaccKind::Original(YaccOriginalActionKind::UserAction),
1035    ///      it is [`Node<StorageT>`](../parser/enum.Node.html)
1036    ///
1037    /// # Panics
1038    ///
1039    /// If `StorageT` is not big enough to index the grammar's tokens, rules, or
1040    /// productions.
1041    #[deprecated(
1042        since = "0.11.0",
1043        note = "Please use grammar_path(), output_path(), build(), and token_map() instead"
1044    )]
1045    pub fn process_file<P, Q>(
1046        &mut self,
1047        inp: P,
1048        outp: Q,
1049    ) -> Result<HashMap<String, StorageT>, Box<dyn Error>>
1050    where
1051        P: AsRef<Path>,
1052        Q: AsRef<Path>,
1053    {
1054        self.grammar_path = Some(inp.as_ref().to_owned());
1055        self.output_path = Some(outp.as_ref().to_owned());
1056        let cl: CTParserBuilder<LexerTypesT> = CTParserBuilder {
1057            grammar_path: self.grammar_path.clone(),
1058            grammar_src: None,
1059            from_ast: None,
1060            output_path: self.output_path.clone(),
1061            mod_name: self.mod_name,
1062            recoverer: self.recoverer,
1063            yacckind: self.yacckind,
1064            error_on_conflicts: self.error_on_conflicts,
1065            warnings_are_errors: self.warnings_are_errors,
1066            show_warnings: self.show_warnings,
1067            visibility: self.visibility.clone(),
1068            rust_edition: self.rust_edition,
1069            inspect_rt: None,
1070            serialisation_format: self.serialisation_format,
1071            #[cfg(test)]
1072            inspect_callback: None,
1073            phantom: PhantomData,
1074        };
1075        Ok(cl.build()?.rule_ids)
1076    }
1077
1078    fn output_file<P: AsRef<Path>>(
1079        &self,
1080        grm: &YaccGrammar<StorageT>,
1081        stable: &StateTable<StorageT>,
1082        mod_name: &str,
1083        outp_rs: P,
1084        cache: &str,
1085    ) -> Result<(), Box<dyn Error>> {
1086        let visibility = self.visibility.clone();
1087        let user_actions = if let Some(
1088            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools,
1089        ) = self.yacckind
1090        {
1091            Some(self.gen_user_actions(grm)?)
1092        } else {
1093            None
1094        };
1095        let rule_consts = self.gen_rule_consts(grm)?;
1096        let token_epp = self.gen_token_epp(grm)?;
1097        let parse_function = self.gen_parse_function(grm, stable)?;
1098        let action_wrappers = match self.yacckind.unwrap() {
1099            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1100                Some(self.gen_wrappers(grm)?)
1101            }
1102            YaccKind::Original(YaccOriginalActionKind::NoAction)
1103            | YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => None,
1104            _ => unreachable!(),
1105        };
1106
1107        let additional_decls =
1108            if let Some(YaccKind::Original(YaccOriginalActionKind::GenericParseTree)) =
1109                self.yacckind
1110            {
1111                // `lrpar::Node`` is deprecated within the lrpar crate, but not from within this module,
1112                // Once it is removed from `lrpar`, we should move the declaration here entirely.
1113                Some(quote! {
1114                            #[allow(unused_imports)]
1115                            pub use ::lrpar::parser::_deprecated_moved_::Node;
1116                })
1117            } else {
1118                None
1119            };
1120
1121        let mod_name =
1122            match syn::parse_str::<proc_macro2::Ident>(mod_name) {
1123                Ok(s) => s,
1124                Err(e) => return Err(format!(
1125                    "CTParserBuilder::mod_name(\"{}\") is not a valid rust identifier due to '{}'",
1126                    mod_name, e
1127                )
1128                .into()),
1129            };
1130        let out_tokens = quote! {
1131            #visibility mod #mod_name {
1132                // At the top so that `user_actions` may contain #![inner_attribute]
1133                #user_actions
1134                mod _parser_ {
1135                    #![allow(clippy::type_complexity)]
1136                    #![allow(clippy::unnecessary_wraps)]
1137                    #![deny(unsafe_code)]
1138                    #[allow(unused_imports)]
1139                    use super::*;
1140                    #additional_decls
1141                    #parse_function
1142                    #rule_consts
1143                    #token_epp
1144                    #action_wrappers
1145                } // End of `mod _parser_`
1146                #[allow(unused_imports)]
1147                pub use _parser_::*;
1148                #[allow(unused_imports)]
1149                use ::lrpar::Lexeme;
1150            } // End of `mod #mod_name`
1151        };
1152        // Try and run a code formatter on the generated code.
1153        let unformatted = out_tokens.to_string();
1154        let outs = syn::parse_str(&unformatted)
1155            .map(|syntax_tree| prettyplease::unparse(&syntax_tree))
1156            .unwrap_or(unformatted);
1157        let mut f = File::create(outp_rs)?;
1158        f.write_all(outs.as_bytes())?;
1159        f.write_all(cache.as_bytes())?;
1160        Ok(())
1161    }
1162
1163    /// Generate the cache, which determines if anything's changed enough that we need to
1164    /// regenerate outputs and force rustc to recompile.
1165    fn rebuild_cache(&self, derived_mod_name: &'_ str, grm: &YaccGrammar<StorageT>) -> TokenStream {
1166        // We don't need to be particularly clever here: we just need to record the various things
1167        // that could change between builds.
1168        //
1169        // Record the time that this version of lrpar was built. If the source code changes and
1170        // rustc forces a recompile, this will change this value, causing anything which depends on
1171        // this build of lrpar to be recompiled too.
1172        let Self {
1173            // All variables except for `output_path`, `inspect_callback` and `phantom` should
1174            // be written into the cache.
1175            grammar_path,
1176            // I struggle to imagine the correct thing for `grammar_src`.
1177            grammar_src: _,
1178            // I struggle to imagine the correct thing for `from_ast`.
1179            from_ast: _,
1180            mod_name,
1181            recoverer,
1182            yacckind,
1183            output_path: _,
1184            error_on_conflicts,
1185            warnings_are_errors,
1186            show_warnings,
1187            visibility,
1188            rust_edition,
1189            serialisation_format,
1190            inspect_rt: _,
1191            #[cfg(test)]
1192                inspect_callback: _,
1193            phantom: _,
1194        } = self;
1195        let build_time = env!("VERGEN_BUILD_TIMESTAMP");
1196        let grammar_path = grammar_path.as_ref().unwrap().to_string_lossy();
1197        let mod_name = QuoteOption(mod_name.as_deref());
1198        let visibility = visibility.to_variant_tokens();
1199        let rust_edition = rust_edition.to_variant_tokens();
1200        let yacckind = yacckind.expect("is_some() by this point");
1201        let rule_map = grm
1202            .iter_tidxs()
1203            .map(|tidx| {
1204                QuoteTuple((
1205                    usize::from(tidx),
1206                    grm.token_name(tidx).unwrap_or("<unknown>"),
1207                ))
1208            })
1209            .collect::<Vec<_>>();
1210        let cache_info = quote! {
1211            BUILD_TIME = #build_time
1212            DERIVED_MOD_NAME = #derived_mod_name
1213            ENCODING_CONFIG = #serialisation_format
1214            GRAMMAR_PATH = #grammar_path
1215            MOD_NAME = #mod_name
1216            RECOVERER = #recoverer
1217            YACC_KIND = #yacckind
1218            ERROR_ON_CONFLICTS = #error_on_conflicts
1219            SHOW_WARNINGS = #show_warnings
1220            WARNINGS_ARE_ERRORS = #warnings_are_errors
1221            RUST_EDITION = #rust_edition
1222            RULE_IDS_MAP = [#(#rule_map,)*]
1223            VISIBILITY = #visibility
1224
1225        };
1226        let cache_info_str = cache_info.to_string();
1227        quote!(#cache_info_str)
1228    }
1229
1230    /// Generate the main parse() function for the output file.
1231    fn gen_parse_function(
1232        &self,
1233        grm: &YaccGrammar<StorageT>,
1234        stable: &StateTable<StorageT>,
1235    ) -> Result<TokenStream, Box<dyn Error>> {
1236        let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1237        let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1238        let recoverer = self.recoverer;
1239        let run_parser = match self.yacckind.unwrap() {
1240            YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => {
1241                quote! {
1242                    ::lrpar::RTParserBuilder::new(grm, stable)
1243                        .recoverer(#recoverer)
1244                        .parse_map(
1245                            lexer,
1246                            &|lexeme| Node::Term{lexeme},
1247                            &|ridx, nodes| Node::Nonterm{ridx, nodes}
1248                        )
1249                }
1250            }
1251            YaccKind::Original(YaccOriginalActionKind::NoAction) => {
1252                quote! {
1253                    ::lrpar::RTParserBuilder::new(grm, stable)
1254                        .recoverer(#recoverer)
1255                        .parse_map(lexer, &|_| (), &|_, _| ()).1
1256                }
1257            }
1258            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1259                let actionskind = str::parse::<TokenStream>(ACTIONS_KIND)?;
1260                let parsed_parse_generics = make_generics(grm.parse_generics().as_deref())?;
1261                let (_, type_generics, _) = parsed_parse_generics.split_for_impl();
1262                // actions always have a parse_param argument, and when the `parse` function lacks one
1263                // that parameter will be unit.
1264                let (action_fn_parse_param, action_fn_parse_param_ty) = match grm.parse_param() {
1265                    Some((name, ty)) => {
1266                        let name = str::parse::<TokenStream>(name)?;
1267                        let ty = str::parse::<TokenStream>(ty)?;
1268                        (quote!(#name), quote!(#ty))
1269                    }
1270                    None => (quote!(()), quote!(())),
1271                };
1272                let wrappers = grm.iter_pidxs().map(|pidx| {
1273                    let pidx = usize::from(pidx);
1274                    format_ident!("{}wrapper_{}", ACTION_PREFIX, pidx)
1275                });
1276                let edition_lifetime = if self.rust_edition != RustEdition::Rust2015 {
1277                    quote!('_,)
1278                } else {
1279                    quote!()
1280                };
1281                let ridx = usize::from(self.user_start_ridx(grm));
1282                let action_ident = format_ident!("{}{}", ACTIONS_KIND_PREFIX, ridx);
1283
1284                quote! {
1285                    let actions: ::std::vec::Vec<
1286                            &dyn Fn(
1287                                    ::cfgrammar::RIdx<#storaget>,
1288                                    &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1289                                    ::cfgrammar::Span,
1290                                    ::std::vec::Drain<#edition_lifetime ::lrpar::parser::AStackType<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #actionskind #type_generics>>,
1291                                    #action_fn_parse_param_ty
1292                            ) -> #actionskind #type_generics
1293                        > = ::std::vec![#(&#wrappers,)*];
1294                    match ::lrpar::RTParserBuilder::new(grm, stable)
1295                        .recoverer(#recoverer)
1296                        .parse_actions(lexer, &actions, #action_fn_parse_param) {
1297                            (Some(#actionskind::#action_ident(x)), y) => (Some(x), y),
1298                            (None, y) => (None, y),
1299                            _ => unreachable!()
1300                    }
1301                }
1302            }
1303            kind => panic!("YaccKind {:?} not supported", kind),
1304        };
1305
1306        let parsed_parse_generics: Generics = match self.yacckind.unwrap() {
1307            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1308                make_generics(grm.parse_generics().as_deref())?
1309            }
1310            _ => make_generics(None)?,
1311        };
1312        let (generics, _, where_clause) = parsed_parse_generics.split_for_impl();
1313
1314        // `parse()` may or may not have an argument for `%parseparam`.
1315        let parse_fn_parse_param = match self.yacckind.unwrap() {
1316            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1317                if let Some((name, tyname)) = grm.parse_param() {
1318                    let name = str::parse::<TokenStream>(name)?;
1319                    let tyname = str::parse::<TokenStream>(tyname)?;
1320                    Some(quote! {#name: #tyname})
1321                } else {
1322                    None
1323                }
1324            }
1325            _ => None,
1326        };
1327        let parse_fn_return_ty = match self.yacckind.unwrap() {
1328            YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools => {
1329                let actiont = grm
1330                    .actiontype(self.user_start_ridx(grm))
1331                    .as_ref()
1332                    .map(|at| str::parse::<TokenStream>(at))
1333                    .transpose()?;
1334                quote! {
1335                    (::std::option::Option<#actiont>, ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>)
1336                }
1337            }
1338            YaccKind::Original(YaccOriginalActionKind::GenericParseTree) => quote! {
1339                (::std::option::Option<Node<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #storaget>>,
1340                    ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>)
1341            },
1342            YaccKind::Original(YaccOriginalActionKind::NoAction) => quote! {
1343                ::std::vec::Vec<::lrpar::LexParseError<#storaget, #lexertypest>>
1344            },
1345            _ => unreachable!(),
1346        };
1347
1348        let serialisation_format = self
1349            .serialisation_format
1350            .expect("Should already have a default value");
1351        // Note that the configuration types use associated consts, and thus these configurations represent distinct types.
1352        let (grm_data, stable_data): (Vec<u8>, Vec<u8>) = match serialisation_format {
1353            SerialisationFormat::FixedSizeInteger => {
1354                let config = wincode::config::Configuration::default().with_fixint_encoding();
1355                let grm = wincode::config::serialize(grm, config)?;
1356                let stable = wincode::config::serialize(stable, config)?;
1357                (grm, stable)
1358            }
1359            SerialisationFormat::VariableSizedInteger => {
1360                let config = wincode::config::Configuration::default().with_varint_encoding();
1361                let grm = wincode::config::serialize(grm, config)?;
1362                let stable = wincode::config::serialize(stable, config)?;
1363                (grm, stable)
1364            }
1365        };
1366        let serialisation_format_str = quote!(serialisation_format).to_string();
1367        Ok(quote! {
1368            const __GRM_DATA: &[u8] = &[#(#grm_data,)*];
1369            const __STABLE_DATA: &[u8] = &[#(#stable_data,)*];
1370            const __SERIALISATION_FORMAT: ::lrpar::ctbuilder::SerialisationFormat = #serialisation_format;
1371
1372            fn __lrpar_parser_data() -> &'static ::lrpar::ParserData<#storaget> {
1373                static DATA: ::std::sync::OnceLock<::lrpar::ParserData<#storaget>>
1374                    = ::std::sync::OnceLock::new();
1375                DATA.get_or_init(
1376                    || {
1377                        // We have to call reconstitute like this because the config parameter takes a trait
1378                        // which uses const generics. Thus the two config parameters here are not actually of the same type.
1379                        match __SERIALISATION_FORMAT {
1380                            ::lrpar::ctbuilder::SerialisationFormat::FixedSizeInteger => {
1381                                ::lrpar::ctbuilder::_reconstitute(__GRM_DATA, __STABLE_DATA, ::lrpar::ctbuilder::wincode::config::Configuration::default().with_fixint_encoding())
1382                            }
1383                            ::lrpar::ctbuilder::SerialisationFormat::VariableSizedInteger => {
1384                                ::lrpar::ctbuilder::_reconstitute(__GRM_DATA, __STABLE_DATA, ::lrpar::ctbuilder::wincode::config::Configuration::default().with_varint_encoding())
1385                            }
1386                            _ => {
1387                                panic!("Parser source was generated using unknown `SerialisationFormat`: {:?}", #serialisation_format_str)
1388                            }
1389                        }
1390                    }
1391                )
1392            }
1393
1394            #[allow(dead_code)]
1395            pub fn parse #generics (
1396                 lexer: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1397                 #parse_fn_parse_param
1398            ) -> #parse_fn_return_ty
1399            #where_clause
1400            {
1401                let __data = __lrpar_parser_data();
1402                let grm = __data.grm();
1403                let stable = __data.stable();
1404                #run_parser
1405            }
1406        })
1407    }
1408
1409    fn gen_rule_consts(
1410        &self,
1411        grm: &YaccGrammar<StorageT>,
1412    ) -> Result<TokenStream, proc_macro2::LexError> {
1413        let mut toks = TokenStream::new();
1414        for ridx in grm.iter_rules() {
1415            if !grm.rule_to_prods(ridx).contains(&grm.start_prod()) {
1416                let r_const = format_ident!("R_{}", grm.rule_name_str(ridx).to_ascii_uppercase());
1417                let storage_ty = str::parse::<TokenStream>(type_name::<StorageT>())?;
1418                let ridx = UnsuffixedUsize(usize::from(ridx));
1419                toks.extend(quote! {
1420                    #[allow(dead_code)]
1421                    pub const #r_const: #storage_ty = #ridx;
1422                });
1423            }
1424        }
1425        Ok(toks)
1426    }
1427
1428    fn gen_token_epp(
1429        &self,
1430        grm: &YaccGrammar<StorageT>,
1431    ) -> Result<TokenStream, proc_macro2::LexError> {
1432        let mut tidxs = Vec::new();
1433        for tidx in grm.iter_tidxs() {
1434            tidxs.push(QuoteOption(grm.token_epp(tidx)));
1435        }
1436        let const_epp_ident = format_ident!("{}EPP", GLOBAL_PREFIX);
1437        let storage_ty = str::parse::<TokenStream>(type_name::<StorageT>())?;
1438        Ok(quote! {
1439            const #const_epp_ident: &[::std::option::Option<&str>] = &[
1440                #(#tidxs,)*
1441            ];
1442
1443            /// Return the %epp entry for token `tidx` (where `None` indicates \"the token has no
1444            /// pretty-printed value\"). Panics if `tidx` doesn't exist.
1445            #[allow(dead_code)]
1446            pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<#storage_ty>) -> ::std::option::Option<&'a str> {
1447                #const_epp_ident[usize::from(tidx)]
1448            }
1449        })
1450    }
1451
1452    /// Generate the wrappers that call user actions
1453    fn gen_wrappers(&self, grm: &YaccGrammar<StorageT>) -> Result<TokenStream, Box<dyn Error>> {
1454        let parsed_parse_generics = make_generics(grm.parse_generics().as_deref())?;
1455        let (generics, type_generics, where_clause) = parsed_parse_generics.split_for_impl();
1456
1457        let (parse_paramname, parse_paramdef);
1458        match grm.parse_param() {
1459            Some((name, tyname)) => {
1460                parse_paramname = str::parse::<TokenStream>(name)?;
1461                let ty = str::parse::<TokenStream>(tyname)?;
1462                parse_paramdef = quote!(#parse_paramname: #ty);
1463            }
1464            None => {
1465                parse_paramname = quote!(());
1466                parse_paramdef = quote! {_: ()};
1467            }
1468        };
1469
1470        let mut wrappers = TokenStream::new();
1471        for pidx in grm.iter_pidxs() {
1472            let ridx = grm.prod_to_rule(pidx);
1473
1474            // Iterate over all $-arguments and replace them with their respective
1475            // element from the argument vector (e.g. $1 is replaced by args[0]). At
1476            // the same time extract &str from tokens and actiontype from nonterminals.
1477            let wrapper_fn = format_ident!("{}wrapper_{}", ACTION_PREFIX, usize::from(pidx));
1478            let ridx_var = format_ident!("{}ridx", ACTION_PREFIX);
1479            let lexer_var = format_ident!("{}lexer", ACTION_PREFIX);
1480            let span_var = format_ident!("{}span", ACTION_PREFIX);
1481            let args_var = format_ident!("{}args", ACTION_PREFIX);
1482            let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1483            let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1484            let actionskind = str::parse::<TokenStream>(ACTIONS_KIND)?;
1485            let edition_lifetime = if self.rust_edition != RustEdition::Rust2015 {
1486                Some(quote!('_,))
1487            } else {
1488                None
1489            };
1490            let mut wrapper_fn_body = TokenStream::new();
1491            if grm.action(pidx).is_some() {
1492                // Unpack the arguments passed to us by the drain
1493                for i in 0..grm.prod(pidx).len() {
1494                    let arg = format_ident!("{}arg_{}", ACTION_PREFIX, i + 1);
1495                    wrapper_fn_body.extend(match grm.prod(pidx)[i] {
1496                        Symbol::Rule(ref_ridx) => {
1497                            let ref_ridx = usize::from(ref_ridx);
1498                            let actionvariant = format_ident!("{}{}", ACTIONS_KIND_PREFIX, ref_ridx);
1499                            quote! {
1500                                #[allow(clippy::let_unit_value)]
1501                                let #arg = match #args_var.next().unwrap() {
1502                                    ::lrpar::parser::AStackType::ActionType(#actionskind::#type_generics::#actionvariant(x)) => x,
1503                                    _ => unreachable!()
1504                                };
1505                            }
1506                        }
1507                        Symbol::Token(_) => {
1508                            quote! {
1509                                let #arg = match #args_var.next().unwrap() {
1510                                    ::lrpar::parser::AStackType::Lexeme(l) => {
1511                                        if l.faulty() {
1512                                            Err(l)
1513                                        } else {
1514                                            Ok(l)
1515                                        }
1516                                    },
1517                                    ::lrpar::parser::AStackType::ActionType(_) => unreachable!()
1518                                };
1519                            }
1520                        }
1521                    })
1522                }
1523
1524                // Call the user code
1525                let args = (0..grm.prod(pidx).len())
1526                    .map(|i| format_ident!("{}arg_{}", ACTION_PREFIX, i + 1))
1527                    .collect::<Vec<_>>();
1528                let action_fn = format_ident!("{}action_{}", ACTION_PREFIX, usize::from(pidx));
1529                let actionsvariant = format_ident!("{}{}", ACTIONS_KIND_PREFIX, usize::from(ridx));
1530
1531                wrapper_fn_body.extend(match grm.actiontype(ridx) {
1532                    Some(s) if s == "()" => {
1533                        // If the rule `r` that we're calling has the unit type then Clippy will warn that
1534                        // `enum::A(wrapper_r())` is pointless. We thus have to split it into two:
1535                        // `wrapper_r(); enum::A(())`.
1536                        quote! {
1537                            #action_fn(#ridx_var, #lexer_var, #span_var, #parse_paramname, #(#args,)*);
1538                            #actionskind::#type_generics::#actionsvariant(())
1539                        }
1540                    }
1541                    _ => {
1542                        quote! {
1543                            #actionskind::#type_generics::#actionsvariant(#action_fn(#ridx_var, #lexer_var, #span_var, #parse_paramname, #(#args,)*))
1544                        }
1545                    }
1546                })
1547            } else if pidx == grm.start_prod() {
1548                wrapper_fn_body.extend(quote!(unreachable!()));
1549            } else {
1550                unreachable!(
1551                    "Production in rule '{}' must have an action body, which should have been handled by gen_user_actions.",
1552                    grm.rule_name_str(grm.prod_to_rule(pidx))
1553                );
1554            };
1555
1556            let attrib = if pidx == grm.start_prod() {
1557                // The start prod has an unreachable body so it doesn't use it's variables.
1558                Some(quote!(#[allow(unused_variables)]))
1559            } else {
1560                None
1561            };
1562            wrappers.extend(quote! {
1563                #attrib
1564                fn #wrapper_fn #generics (
1565                    #ridx_var: ::cfgrammar::RIdx<#storaget>,
1566                    #lexer_var: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1567                    #span_var: ::cfgrammar::Span,
1568                    mut #args_var: ::std::vec::Drain<#edition_lifetime ::lrpar::parser::AStackType<<#lexertypest as ::lrpar::LexerTypes>::LexemeT, #actionskind #type_generics>>,
1569                    #parse_paramdef
1570                ) -> #actionskind #type_generics
1571                #where_clause
1572                {
1573                    #wrapper_fn_body
1574                }
1575             })
1576        }
1577        let mut actionskindvariants = Vec::new();
1578        let actionskindhidden = format_ident!("_{}", ACTIONS_KIND_HIDDEN);
1579        let actionskind = str::parse::<TokenStream>(ACTIONS_KIND).unwrap();
1580        let mut phantom_data_type = Vec::new();
1581        for ridx in grm.iter_rules() {
1582            if let Some(actiont) = grm.actiontype(ridx) {
1583                let actionskindvariant =
1584                    format_ident!("{}{}", ACTIONS_KIND_PREFIX, usize::from(ridx));
1585                let actiont = str::parse::<TokenStream>(actiont).unwrap();
1586                actionskindvariants.push(quote! {
1587                    #actionskindvariant(#actiont)
1588                })
1589            }
1590        }
1591        for lifetime in parsed_parse_generics.lifetimes() {
1592            let lifetime = &lifetime.lifetime;
1593            phantom_data_type.push(quote! { &#lifetime () });
1594        }
1595        for type_param in parsed_parse_generics.type_params() {
1596            let ident = &type_param.ident;
1597            phantom_data_type.push(quote! { #ident });
1598        }
1599        actionskindvariants.push(quote! {
1600            #actionskindhidden(::std::marker::PhantomData<(#(#phantom_data_type,)*)>)
1601        });
1602        wrappers.extend(quote! {
1603            #[allow(dead_code)]
1604            enum #actionskind #generics #where_clause {
1605                #(#actionskindvariants,)*
1606            }
1607        });
1608        Ok(wrappers)
1609    }
1610
1611    /// Generate the user action functions (if any).
1612    fn gen_user_actions(&self, grm: &YaccGrammar<StorageT>) -> Result<TokenStream, Box<dyn Error>> {
1613        let programs = grm
1614            .programs()
1615            .as_ref()
1616            .map(|s| str::parse::<TokenStream>(s))
1617            .transpose()?;
1618        let mut action_fns = TokenStream::new();
1619        // Convert actions to functions
1620        let parsed_parse_generics = make_generics(grm.parse_generics().as_deref())?;
1621        let (generics, _, where_clause) = parsed_parse_generics.split_for_impl();
1622        let (parse_paramname, parse_paramdef, parse_param_unit);
1623        match grm.parse_param() {
1624            Some((name, tyname)) => {
1625                parse_param_unit = tyname.trim() == "()";
1626                parse_paramname = str::parse::<TokenStream>(name)?;
1627                let ty = str::parse::<TokenStream>(tyname)?;
1628                parse_paramdef = quote!(#parse_paramname: #ty);
1629            }
1630            None => {
1631                parse_param_unit = true;
1632                parse_paramname = quote!(());
1633                parse_paramdef = quote! {_: ()};
1634            }
1635        };
1636        for pidx in grm.iter_pidxs() {
1637            if pidx == grm.start_prod() {
1638                continue;
1639            }
1640
1641            // Work out the right type for each argument
1642            let mut args = Vec::with_capacity(grm.prod(pidx).len());
1643            for i in 0..grm.prod(pidx).len() {
1644                let argt = match grm.prod(pidx)[i] {
1645                    Symbol::Rule(ref_ridx) => {
1646                        let action_type = grm.actiontype(ref_ridx)
1647                           .as_ref()
1648                           .expect("actiontype should have been checked during complete_and_validate for this YaccKind");
1649                        str::parse::<TokenStream>(action_type)?
1650                    }
1651                    Symbol::Token(_) => {
1652                        let lexemet =
1653                            str::parse::<TokenStream>(type_name::<LexerTypesT::LexemeT>())?;
1654                        quote!(::std::result::Result<#lexemet, #lexemet>)
1655                    }
1656                };
1657                let arg = format_ident!("{}arg_{}", ACTION_PREFIX, i + 1);
1658                args.push(quote!(mut #arg: #argt));
1659            }
1660
1661            // If this rule's `actiont` is `()` then Clippy will warn that the return type `-> ()`
1662            // is pointless (which is true). We therefore avoid outputting a return type if actiont
1663            // is the unit type.
1664            let returnt = {
1665                let actiont = grm.actiontype(grm.prod_to_rule(pidx)).as_ref().unwrap();
1666                if actiont == "()" {
1667                    None
1668                } else {
1669                    let actiont = str::parse::<TokenStream>(actiont)?;
1670                    Some(quote!( -> #actiont))
1671                }
1672            };
1673            let action_fn = format_ident!("{}action_{}", ACTION_PREFIX, usize::from(pidx));
1674            let lexer_var = format_ident!("{}lexer", ACTION_PREFIX);
1675            let span_var = format_ident!("{}span", ACTION_PREFIX);
1676            let ridx_var = format_ident!("{}ridx", ACTION_PREFIX);
1677            let storaget = str::parse::<TokenStream>(type_name::<StorageT>())?;
1678            let lexertypest = str::parse::<TokenStream>(type_name::<LexerTypesT>())?;
1679            let bind_parse_param = if !parse_param_unit {
1680                Some(quote! {let _ = #parse_paramname;})
1681            } else {
1682                None
1683            };
1684
1685            // Iterate over all $-arguments and replace them with their respective
1686            // element from the argument vector (e.g. $1 is replaced by args[0]).
1687            let pre_action = grm.action(pidx).as_ref().expect("action code should have been checked during complete_and_validate for this YaccKind");
1688            let mut last = 0;
1689            let mut outs = String::new();
1690            loop {
1691                match pre_action[last..].find('$') {
1692                    Some(off) => {
1693                        if pre_action[last + off..].starts_with("$$") {
1694                            outs.push_str(&pre_action[last..last + off + "$".len()]);
1695                            last = last + off + "$$".len();
1696                        } else if pre_action[last + off..].starts_with("$lexer") {
1697                            outs.push_str(&pre_action[last..last + off]);
1698                            write!(outs, "{prefix}lexer", prefix = ACTION_PREFIX).ok();
1699                            last = last + off + "$lexer".len();
1700                        } else if pre_action[last + off..].starts_with("$span") {
1701                            outs.push_str(&pre_action[last..last + off]);
1702                            write!(outs, "{prefix}span", prefix = ACTION_PREFIX).ok();
1703                            last = last + off + "$span".len();
1704                        } else if last + off + 1 < pre_action.len()
1705                            && pre_action[last + off + 1..].starts_with(|c: char| c.is_numeric())
1706                        {
1707                            outs.push_str(&pre_action[last..last + off]);
1708                            write!(outs, "{prefix}arg_", prefix = ACTION_PREFIX).ok();
1709                            last = last + off + "$".len();
1710                        } else {
1711                            unreachable!("action variables checked during complete_and_validate");
1712                        }
1713                    }
1714                    None => {
1715                        outs.push_str(&pre_action[last..]);
1716                        break;
1717                    }
1718                }
1719            }
1720
1721            let action_body = str::parse::<TokenStream>(&outs)?;
1722            action_fns.extend(quote! {
1723                #[allow(clippy::too_many_arguments)]
1724                fn #action_fn #generics (
1725                    #ridx_var: ::cfgrammar::RIdx<#storaget>,
1726                    #lexer_var: &'lexer dyn ::lrpar::NonStreamingLexer<'input, #lexertypest>,
1727                    #span_var: ::cfgrammar::Span,
1728                    #parse_paramdef,
1729                    #(#args,)*
1730                ) #returnt
1731                #where_clause
1732                {
1733                    #bind_parse_param
1734                    #action_body
1735                }
1736            })
1737        }
1738        Ok(quote! {
1739            #programs
1740            #action_fns
1741        })
1742    }
1743
1744    /// Return the `RIdx` of the %start rule in the grammar (which will not be the same as
1745    /// grm.start_rule_idx because the latter has an additional rule insert by cfgrammar
1746    /// which then calls the user's %start rule).
1747    fn user_start_ridx(&self, grm: &YaccGrammar<StorageT>) -> RIdx<StorageT> {
1748        debug_assert_eq!(grm.prod(grm.start_prod()).len(), 1);
1749        match grm.prod(grm.start_prod())[0] {
1750            Symbol::Rule(ridx) => ridx,
1751            _ => unreachable!(),
1752        }
1753    }
1754}
1755
1756/// Bundles `YaccGrammar` + `StateTable` so that generated parsers can hold
1757/// them in a `OnceLock` without naming `lrtable` directly.
1758#[doc(hidden)]
1759pub struct ParserData<StorageT: Eq + Hash> {
1760    grm: YaccGrammar<StorageT>,
1761    stable: StateTable<StorageT>,
1762}
1763
1764impl<StorageT: Eq + Hash> ParserData<StorageT> {
1765    pub fn grm(&self) -> &YaccGrammar<StorageT> {
1766        &self.grm
1767    }
1768
1769    pub fn stable(&self) -> &StateTable<StorageT> {
1770        &self.stable
1771    }
1772}
1773
1774/// This function is called by generated files; it exists so that generated files don't require a
1775/// direct dependency on bincode.
1776#[doc(hidden)]
1777pub fn _reconstitute<
1778    C: wincode::config::Config + Clone + Copy,
1779    StorageT: SchemaReadOwned<C, Dst = StorageT> + Eq + Hash + PrimInt + Unsigned + 'static,
1780>(
1781    grm_buf: &[u8],
1782    stable_buf: &[u8],
1783    config: C,
1784) -> ParserData<StorageT> {
1785    let grm: YaccGrammar<StorageT> = wincode::config::deserialize_from(grm_buf, config).unwrap();
1786    let stable = wincode::config::deserialize_from(stable_buf, config).unwrap();
1787    ParserData { grm, stable }
1788}
1789
1790/// An interface to the result of [CTParserBuilder::build()].
1791pub struct CTParser<StorageT = u32>
1792where
1793    StorageT: Eq + Hash,
1794{
1795    regenerated: bool,
1796    rule_ids: HashMap<String, StorageT>,
1797    yacc_grammar: YaccGrammar<StorageT>,
1798    grammar_src: String,
1799    grammar_path: PathBuf,
1800    conflicts: Option<(StateGraph<StorageT>, StateTable<StorageT>)>,
1801}
1802
1803impl<StorageT> CTParser<StorageT>
1804where
1805    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
1806    usize: AsPrimitive<StorageT>,
1807{
1808    /// Returns `true` if this compile-time parser was regenerated or `false` if it was not.
1809    pub fn regenerated(&self) -> bool {
1810        self.regenerated
1811    }
1812
1813    /// Returns a [HashMap] from lexeme string types to numeric types (e.g. `INT: 2`), suitable for
1814    /// handing to a lexer to coordinate the IDs of lexer and parser.
1815    pub fn token_map(&self) -> &HashMap<String, StorageT> {
1816        &self.rule_ids
1817    }
1818
1819    /// If there are any conflicts in the grammar, return a tuple which allows users to inspect and
1820    /// pretty print them; otherwise returns `None`. If the grammar was not regenerated, this will
1821    /// always return `None`, even if the grammar actually has conflicts.
1822    ///
1823    /// **Note: The conflicts feature is currently unstable and may change in the future.**
1824    #[allow(private_interfaces)]
1825    pub fn conflicts(
1826        &self,
1827        _: crate::unstable::UnstableApi,
1828    ) -> Option<(
1829        &YaccGrammar<StorageT>,
1830        &StateGraph<StorageT>,
1831        &StateTable<StorageT>,
1832        &Conflicts<StorageT>,
1833    )> {
1834        if let Some((sgraph, stable)) = &self.conflicts {
1835            return Some((
1836                &self.yacc_grammar,
1837                sgraph,
1838                stable,
1839                stable.conflicts().unwrap(),
1840            ));
1841        }
1842        None
1843    }
1844
1845    #[doc(hidden)]
1846    pub fn yacc_grammar(&self) -> &YaccGrammar<StorageT> {
1847        &self.yacc_grammar
1848    }
1849    #[doc(hidden)]
1850    pub fn grammar_src(&self) -> &str {
1851        &self.grammar_src
1852    }
1853    #[doc(hidden)]
1854    pub fn grammar_path(&self) -> &Path {
1855        self.grammar_path.as_path()
1856    }
1857}
1858
1859/// Indents a multi-line string and trims any trailing newline.
1860/// This currently assumes that indentation on blank lines does not matter.
1861///
1862/// The algorithm used by this function is:
1863/// 1. Prefix `s` with the indentation, indenting the first line.
1864/// 2. Trim any trailing newlines.
1865/// 3. Replace all newlines with `\n{indent}`` to indent all lines after the first.
1866///
1867/// It is plausible that we should a step 4, but currently do not:
1868/// 4. Replace all `\n{indent}\n` with `\n\n`
1869fn indent(indent: &str, s: &str) -> String {
1870    format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
1871}
1872
1873fn make_generics(parse_generics: Option<&str>) -> Result<Generics, Box<dyn Error>> {
1874    if let Some(parse_generics) = parse_generics {
1875        let tokens = str::parse::<TokenStream>(parse_generics)?;
1876        match syn::parse2(quote!(<'lexer, 'input: 'lexer, #tokens>)) {
1877            Ok(res) => Ok(res),
1878            Err(err) => Err(format!("unable to parse %parse-generics: {}", err).into()),
1879        }
1880    } else {
1881        Ok(parse_quote!(<'lexer, 'input: 'lexer>))
1882    }
1883}
1884
1885// Tests dealing with the filesystem not supported under wasm32
1886#[cfg(all(not(target_arch = "wasm32"), test))]
1887mod test {
1888    use std::{fs::File, io::Write, path::PathBuf};
1889
1890    use super::{CTConflictsError, CTParserBuilder};
1891    use crate::test_utils::TestLexerTypes;
1892    use cfgrammar::yacc::{YaccKind, YaccOriginalActionKind};
1893    use tempfile::TempDir;
1894
1895    #[test]
1896    fn test_conflicts() {
1897        let temp = TempDir::new().unwrap();
1898        let mut file_path = PathBuf::from(temp.as_ref());
1899        file_path.push("grm.y");
1900        let mut f = File::create(&file_path).unwrap();
1901        let _ = f.write_all(
1902            "%start A
1903%%
1904A : 'a' 'b' | B 'b';
1905B : 'a' | C;
1906C : 'a';"
1907                .as_bytes(),
1908        );
1909
1910        match CTParserBuilder::<TestLexerTypes>::new()
1911            .error_on_conflicts(false)
1912            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1913            .grammar_path(file_path.to_str().unwrap())
1914            .output_path(file_path.with_extension("ignored"))
1915            .build()
1916            .unwrap()
1917            .conflicts(crate::unstable::UnstableApi)
1918        {
1919            Some((_, _, _, conflicts)) => {
1920                assert_eq!(conflicts.sr_len(), 1);
1921                assert_eq!(conflicts.rr_len(), 1);
1922            }
1923            None => panic!("Expected error data"),
1924        }
1925    }
1926
1927    #[test]
1928    fn test_conflicts_error() {
1929        let temp = TempDir::new().unwrap();
1930        let mut file_path = PathBuf::from(temp.as_ref());
1931        file_path.push("grm.y");
1932        let mut f = File::create(&file_path).unwrap();
1933        let _ = f.write_all(
1934            "%start A
1935%%
1936A : 'a' 'b' | B 'b';
1937B : 'a' | C;
1938C : 'a';"
1939                .as_bytes(),
1940        );
1941
1942        match CTParserBuilder::<TestLexerTypes>::new()
1943            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1944            .grammar_path(file_path.to_str().unwrap())
1945            .output_path(file_path.with_extension("ignored"))
1946            .build()
1947        {
1948            Ok(_) => panic!("Expected error"),
1949            Err(e) => {
1950                let cs = e.downcast_ref::<CTConflictsError<u16>>();
1951                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1952                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1953            }
1954        }
1955    }
1956
1957    #[test]
1958    fn test_expect_error() {
1959        let temp = TempDir::new().unwrap();
1960        let mut file_path = PathBuf::from(temp.as_ref());
1961        file_path.push("grm.y");
1962        let mut f = File::create(&file_path).unwrap();
1963        let _ = f.write_all(
1964            "%start A
1965%expect 2
1966%%
1967A: 'a' 'b' | B 'b';
1968B: 'a';"
1969                .as_bytes(),
1970        );
1971
1972        match CTParserBuilder::<TestLexerTypes>::new()
1973            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1974            .grammar_path(file_path.to_str().unwrap())
1975            .output_path(file_path.with_extension("ignored"))
1976            .build()
1977        {
1978            Ok(_) => panic!("Expected error"),
1979            Err(e) => {
1980                let cs = e.downcast_ref::<CTConflictsError<u16>>();
1981                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 0);
1982                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1983            }
1984        }
1985    }
1986
1987    #[test]
1988    fn test_expectrr_error() {
1989        let temp = TempDir::new().unwrap();
1990        let mut file_path = PathBuf::from(temp.as_ref());
1991        file_path.push("grm.y");
1992        let mut f = File::create(&file_path).unwrap();
1993        let _ = f.write_all(
1994            "%start A
1995%expect 1
1996%expect-rr 2
1997%%
1998A : 'a' 'b' | B 'b';
1999B : 'a' | C;
2000C : 'a';"
2001                .as_bytes(),
2002        );
2003
2004        match CTParserBuilder::<TestLexerTypes>::new()
2005            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
2006            .grammar_path(file_path.to_str().unwrap())
2007            .output_path(file_path.with_extension("ignored"))
2008            .build()
2009        {
2010            Ok(_) => panic!("Expected error"),
2011            Err(e) => {
2012                let cs = e.downcast_ref::<CTConflictsError<u16>>();
2013                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
2014                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
2015            }
2016        }
2017    }
2018
2019    #[test]
2020    /// Tests a yacc .y filename containing a dash character leading to an invalid rust identifier
2021    /// when that dash is subsequently used as the default `CTParserBuilder::mod_name`.
2022    fn test_invalid_identifier_in_derived_mod_name() {
2023        let temp = TempDir::new().unwrap();
2024        let mut file_path = PathBuf::from(temp.as_ref());
2025        file_path.push("contains-a-dash.y");
2026        let mut f = File::create(&file_path).unwrap();
2027        let _ = f.write_all(
2028            "%start A
2029%%
2030A : 'a';"
2031                .as_bytes(),
2032        );
2033        match CTParserBuilder::<TestLexerTypes>::new()
2034            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
2035            .grammar_path(file_path.to_str().unwrap())
2036            .output_path(file_path.with_extension("ignored"))
2037            .build()
2038        {
2039            Ok(_) => panic!("Expected error"),
2040            Err(e) => {
2041                let err_string = e.to_string();
2042                assert_eq!(
2043                    err_string,
2044                    "CTParserBuilder::mod_name(\"contains-a-dash_y\") is not a valid rust identifier due to 'unexpected token'"
2045                );
2046            }
2047        }
2048    }
2049
2050    #[cfg(test)]
2051    #[test]
2052    fn test_recoverer_header() -> Result<(), Box<dyn std::error::Error>> {
2053        use crate::RecoveryKind as RK;
2054        #[rustfmt::skip]
2055            let recovery_kinds = [
2056                //  Builder,          Header setting,     Expected result.
2057                // -----------       ------------------  -------------------
2058                (Some(RK::None),      Some(RK::None),     Some(RK::None)),
2059                (Some(RK::None),      Some(RK::CPCTPlus), Some(RK::None)),
2060                (Some(RK::CPCTPlus),  Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
2061                (Some(RK::CPCTPlus),  Some(RK::None),     Some(RK::CPCTPlus)),
2062                (None,                Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
2063                (None,                Some(RK::None),     Some(RK::None)),
2064                (None,                None,               Some(RK::CPCTPlus)),
2065                (Some(RK::None),      None,               Some(RK::None)),
2066                (Some(RK::CPCTPlus),  None,               Some(RK::CPCTPlus)),
2067            ];
2068
2069        for (i, (builder_arg, header_arg, expected_rk)) in
2070            recovery_kinds.iter().cloned().enumerate()
2071        {
2072            let y_src = if let Some(header_arg) = header_arg {
2073                format!(
2074                    "\
2075                    %grmtools{{yacckind: Original(NoAction), recoverer: {}}} \
2076                    %% \
2077                    start: ; \
2078                    ",
2079                    match header_arg {
2080                        RK::None => "RecoveryKind::None",
2081                        RK::CPCTPlus => "RecoveryKind::CPCTPlus",
2082                    }
2083                )
2084            } else {
2085                r#"
2086                    %grmtools{yacckind: Original(NoAction)}
2087                    %%
2088                    Start: ;
2089                    "#
2090                .to_string()
2091            };
2092            let out_dir = std::env::var("OUT_DIR").unwrap();
2093            let y_path = format!("{out_dir}/recoverykind_test_{i}.y");
2094            let y_out_path = format!("{y_path}.rs");
2095            std::fs::File::create(y_path.clone()).unwrap();
2096            std::fs::write(y_path.clone(), y_src).unwrap();
2097            let mut cp_builder = CTParserBuilder::<TestLexerTypes>::new();
2098            cp_builder = cp_builder
2099                .output_path(y_out_path.clone())
2100                .grammar_path(y_path.clone());
2101            cp_builder = if let Some(builder_arg) = builder_arg {
2102                cp_builder.recoverer(builder_arg)
2103            } else {
2104                cp_builder
2105            }
2106            .inspect_recoverer(Box::new(move |rk| {
2107                if matches!(
2108                    (rk, expected_rk),
2109                    (RK::None, Some(RK::None)) | (RK::CPCTPlus, Some(RK::CPCTPlus))
2110                ) {
2111                    Ok(())
2112                } else {
2113                    panic!("Unexpected recovery kind")
2114                }
2115            }));
2116            cp_builder.build()?;
2117        }
2118        Ok(())
2119    }
2120}