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    collections::{HashMap, HashSet},
5    env::{current_dir, var},
6    error::Error,
7    fmt::{self, Debug, Write as fmtWrite},
8    fs::{self, File, create_dir_all, read_to_string},
9    hash::Hash,
10    io::Write,
11    marker::PhantomData,
12    path::{Path, PathBuf},
13    sync::{LazyLock, Mutex},
14};
15
16use crate::{
17    LexerTypes, RTParserBuilder, RecoveryKind,
18    codegen::{
19        ParserBuildEnv, ParserBuildEnvArgs, ParserBuildEnvError, ParserCodegen, ParserSrcEnv,
20        ParserSrcEnvError,
21    },
22    diagnostics::{DiagnosticFormatter, SpannedDiagnosticFormatter},
23};
24
25#[cfg(feature = "_unstable_api")]
26use crate::unstable_api::UnstableApi;
27
28use cfgrammar::{
29    Location,
30    header::{Header, HeaderError, HeaderErrorKind, HeaderValue, Value},
31    markmap::{Entry, MergeBehavior},
32    yacc::{YaccGrammar, YaccKind, ast::ASTWithValidityInfo},
33};
34use filetime::FileTime;
35use lrtable::{StateGraph, StateTable, statetable::Conflicts};
36use num_traits::{AsPrimitive, PrimInt, Unsigned};
37use wincode::{SchemaRead, SchemaReadOwned, SchemaWrite};
38
39const RUST_FILE_EXT: &str = "rs";
40const WARNING: &str = "[Warning]";
41pub(crate) const ERROR: &str = "[Error]";
42
43static GENERATED_PATHS: LazyLock<Mutex<HashSet<PathBuf>>> =
44    LazyLock::new(|| Mutex::new(HashSet::new()));
45
46pub(crate) struct CTConflictsError<StorageT: Eq + Hash> {
47    pub(crate) conflicts_diagnostic: String,
48    #[cfg(test)]
49    #[cfg_attr(test, allow(dead_code))]
50    pub(crate) stable: StateTable<StorageT>,
51    pub(crate) phantom: PhantomData<StorageT>,
52}
53
54impl<StorageT> fmt::Display for CTConflictsError<StorageT>
55where
56    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
57    usize: AsPrimitive<StorageT>,
58{
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        write!(f, "{}", self.conflicts_diagnostic)
61    }
62}
63
64impl<StorageT> fmt::Debug for CTConflictsError<StorageT>
65where
66    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
67    usize: AsPrimitive<StorageT>,
68{
69    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70        write!(f, "{}", self.conflicts_diagnostic)
71    }
72}
73
74impl<StorageT> Error for CTConflictsError<StorageT>
75where
76    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
77    usize: AsPrimitive<StorageT>,
78{
79}
80
81/// A string which uses `Display` for it's `Debug` impl.
82struct ErrorString(String);
83impl fmt::Display for ErrorString {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        let ErrorString(s) = self;
86        write!(f, "{}", s)
87    }
88}
89impl fmt::Debug for ErrorString {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        let ErrorString(s) = self;
92        write!(f, "{}", s)
93    }
94}
95impl Error for ErrorString {}
96
97/// Specify the visibility of the module generated by `CTBuilder`.
98#[derive(Clone, PartialEq, Eq, Debug)]
99#[non_exhaustive]
100pub enum Visibility {
101    /// Module-level visibility only.
102    Private,
103    /// `pub`
104    Public,
105    /// `pub(super)`
106    PublicSuper,
107    /// `pub(self)`
108    PublicSelf,
109    /// `pub(crate)`
110    PublicCrate,
111    /// `pub(in {arg})`
112    PublicIn(String),
113}
114
115/// Specifies the [Rust Edition] that will be emitted during code generation.
116///
117/// [Rust Edition]: https://doc.rust-lang.org/edition-guide/rust-2021/index.html
118#[derive(Clone, Copy, PartialEq, Eq, Debug)]
119#[non_exhaustive]
120pub enum RustEdition {
121    Rust2015,
122    Rust2018,
123    Rust2021,
124}
125
126/// Sets the underlying encoding algorithm for serialising the `ParserData` into the generated source files.
127///
128/// This correlates to a specific `Configuration` of [wincode::config](https://docs.rs/wincode/latest/wincode/config/index.html).
129#[non_exhaustive]
130#[derive(SchemaRead, SchemaWrite, Debug, Clone, Copy)]
131pub enum SerialisationFormat {
132    /// See [wincode::FixedInt](https://docs.rs/wincode/latest/wincode/int_encoding/struct.FixedInt.html)
133    FixedSizeInteger,
134    /// See [wincode::VarInt](https://docs.rs/wincode/latest/wincode/int_encoding/struct.VarInt.html)
135    VariableSizedInteger,
136}
137
138impl TryFrom<SerialisationFormat> for Value<Location> {
139    type Error = cfgrammar::header::HeaderError<Location>;
140    fn try_from(kind: SerialisationFormat) -> Result<Value<Location>, HeaderError<Location>> {
141        let from_loc = Location::Other("From<SerialisationFormat>".to_string());
142        Ok(Value::Namespaced(
143            format!("SerialisationFormat::{kind:?}"),
144            from_loc,
145        ))
146    }
147}
148
149impl<T: Clone + Debug> TryFrom<&Value<T>> for SerialisationFormat {
150    type Error = HeaderError<T>;
151    fn try_from(value: &Value<T>) -> Result<SerialisationFormat, HeaderError<T>> {
152        match value {
153            Value::Namespaced(serialisation_fmt, loc) => match serialisation_fmt.as_str() {
154                "SerialisationFormat::FixedSizeInteger" | "FixedSizeInteger" => {
155                    Ok(SerialisationFormat::FixedSizeInteger)
156                }
157                "SerialisationFormat::VariableSizedInteger" | "VariableSizedInteger" => {
158                    Ok(SerialisationFormat::VariableSizedInteger)
159                }
160                _ => Err(HeaderError {
161                    kind: HeaderErrorKind::InvalidEntry("serialisation_format"),
162                    locations: vec![loc.clone()],
163                }),
164            },
165            val => Err(HeaderError {
166                kind: HeaderErrorKind::InvalidEntry("serialisation_format"),
167                locations: vec![val.primary_location().clone()],
168            }),
169        }
170    }
171}
172
173// We export this for generated code to refer to.
174#[doc(hidden)]
175pub use wincode;
176
177/// A `CTParserBuilder` allows one to specify the criteria for building a statically generated
178/// parser.
179pub struct CTParserBuilder<'a, LexerTypesT: LexerTypes>
180where
181    LexerTypesT::StorageT: Eq + Hash,
182    usize: AsPrimitive<LexerTypesT::StorageT>,
183{
184    // Anything stored in here (except `output_path`, `conflicts`, and `error_on_conflict`) almost
185    // certainly needs to be included as part of the rebuild_cache function below so that, if it's
186    // changed, the grammar is rebuilt.
187    grammar_path: Option<PathBuf>,
188    // If specified rather than reading source from `grammar_path`, use this string directly
189    grammar_src: Option<String>,
190    // If specified along with `grammar_src`, use this rather than building an ast from `grammar_src`.
191    from_ast: Option<ASTWithValidityInfo>,
192    output_path: Option<PathBuf>,
193    mod_name: Option<&'a str>,
194    recoverer: Option<RecoveryKind>,
195    yacckind: Option<YaccKind>,
196    error_on_conflicts: bool,
197    warnings_are_errors: bool,
198    show_warnings: bool,
199    visibility: Visibility,
200    rust_edition: RustEdition,
201    inspect_rt: Option<
202        Box<
203            dyn for<'b> FnMut(
204                &'b mut Header<Location>,
205                RTParserBuilder<LexerTypesT::StorageT, LexerTypesT>,
206                &'b HashMap<String, LexerTypesT::StorageT>,
207                &PathBuf,
208            ) -> Result<(), Box<dyn Error>>,
209        >,
210    >,
211    serialisation_format: Option<SerialisationFormat>,
212    // test function for inspecting private state
213    #[cfg(test)]
214    inspect_callback: Option<Box<dyn Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>>,
215    phantom: PhantomData<LexerTypesT>,
216}
217
218/// Defaults to `wincode::int_encoding::VarInt`.
219pub(crate) type FixIntConfig = wincode::config::Configuration;
220/// The default config with the last parameter set to `VarInt`
221pub(crate) type VarIntConfig = wincode::config::Configuration<
222    true,
223    4194304,
224    wincode::len::BincodeLen,
225    wincode::int_encoding::LittleEndian,
226    wincode::int_encoding::VarInt,
227>;
228
229impl<
230    'a,
231    StorageT: 'static
232        + Debug
233        + Hash
234        + PrimInt
235        + SchemaWrite<FixIntConfig, Src = StorageT>
236        + SchemaWrite<VarIntConfig, Src = StorageT>
237        + Unsigned,
238    LexerTypesT: LexerTypes<StorageT = StorageT>,
239> CTParserBuilder<'a, LexerTypesT>
240where
241    usize: AsPrimitive<StorageT>,
242{
243    /// Create a new `CTParserBuilder`.
244    ///
245    /// `StorageT` must be an unsigned integer type (e.g. `u8`, `u16`) which is:
246    ///   * big enough to index (separately) all the tokens, rules, productions in the grammar,
247    ///   * big enough to index the state table created from the grammar,
248    ///   * less than or equal in size to `u32`.
249    ///
250    /// In other words, if you have a grammar with 256 tokens, 256 rules, and 256 productions,
251    /// which creates a state table of 256 states you can safely specify `u8` here; but if any of
252    /// those counts becomes 257 or greater you will need to specify `u16`. If you are parsing
253    /// large files, the additional storage requirements of larger integer types can be noticeable,
254    /// and in such cases it can be worth specifying a smaller type. `StorageT` defaults to `u32`
255    /// if unspecified.
256    ///
257    /// # Examples
258    ///
259    /// ```text
260    /// CTParserBuilder::<DefaultLexerTypes<u8>>::new()
261    ///     .grammar_in_src_dir("grm.y")?
262    ///     .build()?;
263    /// ```
264    pub fn new() -> Self {
265        CTParserBuilder {
266            grammar_path: None,
267            grammar_src: None,
268            from_ast: None,
269            output_path: None,
270            mod_name: None,
271            recoverer: None,
272            yacckind: None,
273            error_on_conflicts: true,
274            warnings_are_errors: true,
275            show_warnings: true,
276            visibility: Visibility::Private,
277            rust_edition: RustEdition::Rust2021,
278            inspect_rt: None,
279            serialisation_format: None,
280            #[cfg(test)]
281            inspect_callback: None,
282            phantom: PhantomData,
283        }
284    }
285
286    /// Set the input grammar path to a file relative to this project's `src` directory. This will
287    /// also set the output path (i.e. you do not need to call [CTParserBuilder::output_path]).
288    ///
289    /// For example if `a/b.y` is passed as `inp` then [CTParserBuilder::build] will:
290    ///   * use `src/a/b.y` as the input file.
291    ///   * write output to a file which can then be imported by calling `lrpar_mod!("a/b.y")`.
292    ///   * create a module in that output file named `b_y`.
293    ///
294    /// You can override the output path and/or module name by calling [CTParserBuilder::output_path]
295    /// and/or [CTParserBuilder::mod_name], respectively, after calling this function.
296    ///
297    /// This is a convenience function that makes it easier to compile grammar files stored in a
298    /// project's `src/` directory: please see [CTParserBuilder::build] for additional constraints
299    /// and information about the generated files. Note also that each `.y` file can only be
300    /// processed once using this function: if you want to generate multiple grammars from a single
301    /// `.y` file, you will need to use [CTParserBuilder::output_path].
302    pub fn grammar_in_src_dir<P>(mut self, srcp: P) -> Result<Self, Box<dyn Error>>
303    where
304        P: AsRef<Path>,
305    {
306        if !srcp.as_ref().is_relative() {
307            return Err(format!(
308                "Grammar path '{}' must be a relative path.",
309                srcp.as_ref().to_str().unwrap_or("<invalid UTF-8>")
310            )
311            .into());
312        }
313
314        let mut grmp = current_dir()?;
315        grmp.push("src");
316        grmp.push(srcp.as_ref());
317        self.grammar_path = Some(grmp);
318
319        let mut outp = PathBuf::new();
320        outp.push(var("OUT_DIR").unwrap());
321        outp.push(srcp.as_ref().parent().unwrap().to_str().unwrap());
322        create_dir_all(&outp)?;
323        let mut leaf = srcp
324            .as_ref()
325            .file_name()
326            .unwrap()
327            .to_str()
328            .unwrap()
329            .to_owned();
330        write!(leaf, ".{}", RUST_FILE_EXT).ok();
331        outp.push(leaf);
332        Ok(self.output_path(outp))
333    }
334
335    /// If set, specifies that this grammar should be built from a pre-validated AST
336    /// instead of a `.y`` file. When this is specified, `grammar_path` will not be read.
337    #[cfg(feature = "_unstable_api")]
338    pub fn grammar_ast(mut self, valid_ast: ASTWithValidityInfo, _api_key: UnstableApi) -> Self {
339        self.from_ast = Some(valid_ast);
340        self
341    }
342
343    /// Set the input grammar path to `inp`. If specified, you must also call
344    /// [CTParserBuilder::output_path]. In general it is easier to use
345    /// [CTParserBuilder::grammar_in_src_dir].
346    pub fn grammar_path<P>(mut self, inp: P) -> Self
347    where
348        P: AsRef<Path>,
349    {
350        self.grammar_path = Some(inp.as_ref().to_owned());
351        self
352    }
353
354    #[cfg(feature = "_unstable_api")]
355    pub fn with_grammar_src(mut self, src: String, _api_key: UnstableApi) -> Self {
356        self.grammar_src = Some(src);
357        self
358    }
359
360    /// Set the output grammar path to `outp`. Note that there are no requirements on `outp`: the
361    /// file can exist anywhere you can create a valid [Path] to. However, if you wish to use
362    /// [crate::lrpar_mod!] you will need to make sure that `outp` is in
363    /// [std::env::var]`("OUT_DIR")` or one of its subdirectories.
364    pub fn output_path<P>(mut self, outp: P) -> Self
365    where
366        P: AsRef<Path>,
367    {
368        self.output_path = Some(outp.as_ref().to_owned());
369        self
370    }
371
372    /// Set the generated module name to `mod_name`. If no module name is specified,
373    /// [CTParserBuilder::build] will attempt to create a sensible default based on the grammar
374    /// filename.
375    pub fn mod_name(mut self, mod_name: &'a str) -> Self {
376        self.mod_name = Some(mod_name);
377        self
378    }
379
380    /// Set the visibility of the generated module to `vis`. Defaults to `Visibility::Private`.
381    pub fn visibility(mut self, vis: Visibility) -> Self {
382        self.visibility = vis;
383        self
384    }
385
386    /// Set the recoverer for this parser to `rk`. Defaults to `RecoveryKind::CPCTPlus`.
387    pub fn recoverer(mut self, rk: RecoveryKind) -> Self {
388        self.recoverer = Some(rk);
389        self
390    }
391
392    /// Set the `YaccKind` for this parser to `ak`.
393    pub fn yacckind(mut self, yk: YaccKind) -> Self {
394        self.yacckind = Some(yk);
395        self
396    }
397
398    /// If set to true, [CTParserBuilder::build] will return an error if the given grammar contains
399    /// any Shift/Reduce or Reduce/Reduce conflicts. Defaults to `true`.
400    pub fn error_on_conflicts(mut self, b: bool) -> Self {
401        self.error_on_conflicts = b;
402        self
403    }
404
405    /// If set to true, [CTParserBuilder::build] will return an error if the given grammar contains
406    /// any warnings. Defaults to `true`.
407    pub fn warnings_are_errors(mut self, b: bool) -> Self {
408        self.warnings_are_errors = b;
409        self
410    }
411
412    /// If set to true, [CTParserBuilder::build] will print warnings to stderr, or via cargo when
413    /// running under cargo. Defaults to `true`.
414    pub fn show_warnings(mut self, b: bool) -> Self {
415        self.show_warnings = b;
416        self
417    }
418
419    /// Sets the rust edition to be used for generated code. Defaults to the latest edition of
420    /// rust supported by grmtools.
421    pub fn rust_edition(mut self, edition: RustEdition) -> Self {
422        self.rust_edition = edition;
423        self
424    }
425
426    pub fn serialisation_format(mut self, serialisation_format: SerialisationFormat) -> Self {
427        self.serialisation_format = Some(serialisation_format);
428        self
429    }
430
431    #[cfg(test)]
432    pub fn inspect_recoverer(
433        mut self,
434        cb: Box<dyn for<'h, 'y> Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>,
435    ) -> Self {
436        self.inspect_callback = Some(cb);
437        self
438    }
439
440    #[doc(hidden)]
441    pub fn inspect_rt(
442        mut self,
443        cb: Box<
444            dyn for<'b, 'y> FnMut(
445                &'b mut Header<Location>,
446                RTParserBuilder<'y, StorageT, LexerTypesT>,
447                &'b HashMap<String, StorageT>,
448                &PathBuf,
449            ) -> Result<(), Box<dyn Error>>,
450        >,
451    ) -> Self {
452        self.inspect_rt = Some(cb);
453        self
454    }
455
456    /// Statically compile the Yacc file specified by [CTParserBuilder::grammar_path()] into Rust,
457    /// placing the output into the file spec [CTParserBuilder::output_path()]. Note that three
458    /// additional files will be created with the same name as specified in [self.output_path] but
459    /// with the extensions `grm`, and `stable`, overwriting any existing files with those names.
460    ///
461    /// If `%parse-param` is not specified, the generated module follows the form:
462    ///
463    /// ```text
464    ///   mod <modname> {
465    ///     pub fn parse<'lexer, 'input: 'lexer>(lexer: &'lexer dyn NonStreamingLexer<...>)
466    ///       -> (Option<ActionT>, Vec<LexParseError<...>> { ... }
467    ///
468    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
469    ///       ...
470    ///     }
471    ///
472    ///     ...
473    ///   }
474    /// ```
475    ///
476    /// If `%parse-param x: t` is specified, the generated module follows the form:
477    ///
478    /// ```text
479    ///   mod <modname> {
480    ///     pub fn parse<'lexer, 'input: 'lexer>(lexer: &'lexer dyn NonStreamingLexer<...>, x: t)
481    ///       -> (Option<ActionT>, Vec<LexParseError<...>> { ... }
482    ///
483    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
484    ///       ...
485    ///     }
486    ///
487    ///     ...
488    ///   }
489    /// ```
490    ///
491    /// where:
492    ///  * `modname` is either:
493    ///    * the module name specified by [CTParserBuilder::mod_name()];
494    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.y` the
495    ///      module name is `c_y` (i.e. the file's leaf name, minus its extension, with a prefix of
496    ///      `_y`).
497    ///  * `ActionT` is either:
498    ///    * if the `yacckind` was set to `YaccKind::GrmTools` or
499    ///      `YaccKind::Original(YaccOriginalActionKind::UserAction)`, it is
500    ///      the return type of the `%start` rule;
501    ///    * or, if the `yacckind` was set to
502    ///      `YaccKind::Original(YaccOriginalActionKind::GenericParseTree)`, it
503    ///      is `Node<StorageT>` where the `Node` type is defined within your `lrpar_mod!`.
504    ///
505    /// # Panics
506    ///
507    /// If `StorageT` is not big enough to index the grammar's tokens, rules, or productions.
508    pub fn build(mut self) -> Result<CTParser<StorageT>, Box<dyn Error>> {
509        let grmp = self
510            .grammar_path
511            .as_ref()
512            .expect("grammar_path must be specified before processing.");
513        let outp = self
514            .output_path
515            .as_ref()
516            .expect("output_path must be specified before processing.");
517        let mut header = Header::new();
518
519        match header.entry("cfgrammar.yacckind".to_string()) {
520            Entry::Occupied(_) => unreachable!(),
521            Entry::Vacant(mut v) => match self.yacckind {
522                Some(YaccKind::Eco) => panic!("Eco compile-time grammar generation not supported."),
523                Some(yk) => {
524                    let yk_value = Value::try_from(yk)?;
525                    let mut o = v.insert_entry(HeaderValue(
526                        Location::Other("CTParserBuilder".to_string()),
527                        yk_value,
528                    ));
529                    o.set_merge_behavior(MergeBehavior::Ours);
530                }
531                None => {
532                    v.mark_required();
533                }
534            },
535        }
536        if let Some(recoverer) = self.recoverer {
537            match header.entry("lrpar.recoverer".to_string()) {
538                Entry::Occupied(_) => unreachable!(),
539                Entry::Vacant(v) => {
540                    let rk_value = Value::try_from(recoverer)?;
541                    let mut o = v.insert_entry(HeaderValue(
542                        Location::Other("CTParserBuilder".to_string()),
543                        rk_value,
544                    ));
545                    o.set_merge_behavior(MergeBehavior::Ours);
546                }
547            }
548        }
549
550        if let Some(encoding) = self.serialisation_format {
551            match header.entry("lrpar.serialisation_format".to_string()) {
552                Entry::Occupied(_) => unreachable!(),
553                Entry::Vacant(v) => {
554                    let rk_value = Value::try_from(encoding)?;
555                    let mut o = v.insert_entry(HeaderValue(
556                        Location::Other("CTParserBuilder".to_string()),
557                        rk_value,
558                    ));
559                    o.set_merge_behavior(MergeBehavior::Ours);
560                }
561            }
562        }
563
564        {
565            let mut lk = GENERATED_PATHS.lock().unwrap();
566            if lk.contains(outp.as_path()) {
567                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());
568            }
569            lk.insert(outp.clone());
570        }
571
572        let inc = if let Some(grammar_src) = &self.grammar_src {
573            grammar_src.clone()
574        } else {
575            read_to_string(grmp).map_err(|e| format!("When reading '{}': {e}", grmp.display()))?
576        };
577
578        let src_env = ParserSrcEnv::new_with_header(&inc, Some(grmp), header);
579        let yacc_diag = SpannedDiagnosticFormatter::new(&inc, grmp);
580        let build_args = ParserBuildEnvArgs::new()
581            .ast_with_validity_info(self.from_ast.as_ref())
582            .mod_name(self.mod_name)
583            .show_warnings(self.show_warnings)
584            .error_on_conflicts(self.error_on_conflicts)
585            .warnings_are_errors(self.warnings_are_errors)
586            .visibility(self.visibility.clone())
587            .rust_edition(self.rust_edition);
588        let mut build_env = src_env.build_env(build_args).map_err(|e| match e {
589            ParserSrcEnvError::GrmtoolsSectionParseError(es) => {
590                let mut out = String::new();
591                out.push_str(&format!(
592                    "\n{ERROR}{}\n",
593                    yacc_diag.file_location_msg(" parsing the `%grmtools` section", None)
594                ));
595                for e in es {
596                    out.push_str(&indent("     ", &yacc_diag.format_error(e).to_string()));
597                    out.push('\n');
598                }
599                Box::<dyn Error>::from(ErrorString(out))
600            }
601            e => e.to_string().into(),
602        })?;
603        // Temporarily we update self.yacckind and self.recoverer from the build_env
604        // Until codegen reads these variables from the build_env directly.
605        self.recoverer = Some(build_env.recoverer());
606        self.yacckind = Some(build_env.yacc_kind());
607
608        let warnings = build_env.ast_with_validity_info().ast().warnings();
609        if self.warnings_are_errors && !warnings.is_empty() {
610            let mut out = String::new();
611            out.push_str(&format!(
612                "\n{ERROR}{}\n",
613                yacc_diag.file_location_msg("", None)
614            ));
615            for e in warnings {
616                out.push_str(&format!(
617                    "{}\n",
618                    indent("     ", &yacc_diag.format_warning(e).to_string())
619                ));
620            }
621            return Err(ErrorString(out).into());
622        } else if !warnings.is_empty() {
623            for w in warnings {
624                let ws_loc = yacc_diag.file_location_msg("", None);
625                let ws = indent("     ", &yacc_diag.format_warning(w).to_string());
626                // Assume if this variable is set we are running under cargo.
627                if std::env::var("OUT_DIR").is_ok() && self.show_warnings {
628                    for line in ws_loc.lines().chain(ws.lines()) {
629                        println!("cargo:warning={}", line);
630                    }
631                } else if self.show_warnings {
632                    eprintln!("{}", ws_loc);
633                    eprintln!("{WARNING} {}", ws);
634                }
635            }
636        }
637
638        #[cfg(test)]
639        if let Some(cb) = &self.inspect_callback {
640            cb(build_env.recoverer())?;
641        }
642
643        let timestamp = env!("VERGEN_BUILD_TIMESTAMP");
644        let code_gen = build_env.code_generator(timestamp).map_err(|e| match e {
645            ParserBuildEnvError::YaccGrammarErrors(errs) => {
646                let mut out = String::new();
647                out.push_str(&format!(
648                    "\n{ERROR}{}\n",
649                    yacc_diag.file_location_msg("", None)
650                ));
651                for e in errs {
652                    out.push_str(&indent("     ", &yacc_diag.format_error(e).to_string()));
653                    out.push('\n');
654                }
655                ErrorString(out)
656            }
657            e => ErrorString(e.to_string()),
658        })?;
659        let grm = code_gen.grm();
660        let rule_ids = grm
661            .tokens_map()
662            .iter()
663            .map(|(&n, &i)| (n.to_owned(), i.as_storaget()))
664            .collect::<HashMap<_, _>>();
665
666        let cache = code_gen.cache_str(&build_env);
667
668        // We don't need to go through the full rigmarole of generating an output file if all of
669        // the following are true: the output file exists; it is newer than the input file; and the
670        // cache hasn't changed. The last of these might be surprising, but it's vital: we don't
671        // know, for example, what the IDs map might be from one run to the next, and it might
672        // change for reasons beyond lrpar's control. If it does change, that means that the lexer
673        // and lrpar would get out of sync, so we have to play it safe and regenerate in such
674        // cases.
675        if let Ok(ref inmd) = fs::metadata(grmp)
676            && let Ok(ref out_rs_md) = fs::metadata(outp)
677            && FileTime::from_last_modification_time(out_rs_md)
678                > FileTime::from_last_modification_time(inmd)
679            && let Ok(outc) = read_to_string(outp)
680        {
681            if outc.contains(&cache) {
682                let (grm, _, _) = code_gen.finish();
683
684                return Ok(CTParser {
685                    regenerated: false,
686                    rule_ids,
687                    yacc_grammar: grm,
688                    grammar_src: inc,
689                    grammar_path: self.grammar_path.unwrap(),
690                    conflicts: None,
691                });
692            } else {
693                #[cfg(grmtools_extra_checks)]
694                if std::env::var("CACHE_EXPECTED").is_ok() {
695                    eprintln!("outc: {}", outc);
696                    eprintln!("using cache: {}", cache,);
697                    // Primarily for use in the testsuite.
698                    panic!("The cache regenerated however, it was expected to match");
699                }
700            }
701        }
702
703        // At this point, we know we're going to generate fresh output; however, if something goes
704        // wrong in the process between now and us writing /out/blah.rs, rustc thinks that
705        // everything's gone swimmingly (even if build.rs errored!), and tries to carry on
706        // compilation, leading to weird errors. We therefore delete /out/blah.rs at this point,
707        // which means, at worse, the user gets a "file not found" error from rustc (which is less
708        // confusing than the alternatives).
709        fs::remove_file(outp).ok();
710
711        let stable = code_gen.stable();
712        if self.error_on_conflicts
713            && let Some(c) = stable.conflicts()
714        {
715            match (grm.expect(), grm.expectrr()) {
716                (Some(i), Some(j)) if i == c.sr_len() && j == c.rr_len() => (),
717                (Some(i), None) if i == c.sr_len() && 0 == c.rr_len() => (),
718                (None, Some(j)) if 0 == c.sr_len() && j == c.rr_len() => (),
719                (None, None) if 0 == c.rr_len() && 0 == c.sr_len() => (),
720                _ => {
721                    let conflicts_diagnostic = yacc_diag.format_conflicts::<LexerTypesT>(
722                        grm,
723                        build_env.ast_with_validity_info().ast(),
724                        c,
725                        code_gen.sgraph(),
726                        stable,
727                    );
728                    #[cfg(test)]
729                    let (_, _, stable) = code_gen.finish();
730                    return Err(Box::new(CTConflictsError {
731                        conflicts_diagnostic,
732                        phantom: PhantomData,
733                        #[cfg(test)]
734                        stable,
735                    }));
736                }
737            }
738        }
739
740        if let Some(ref mut inspector_rt) = self.inspect_rt {
741            let rt: RTParserBuilder<'_, StorageT, LexerTypesT> = RTParserBuilder::new(grm, stable);
742            let rt = if let Some(rk) = self.recoverer {
743                rt.recoverer(rk)
744            } else {
745                rt
746            };
747            inspector_rt(build_env.header_mut(), rt, &rule_ids, grmp)?
748        }
749
750        build_env
751            .check_unused_header_keys()
752            .map_err(|e| ErrorString(e.to_string()))?;
753
754        self.output_file(
755            &code_gen,
756            outp,
757            &format!("/* CACHE INFORMATION {} */\n", cache),
758            &build_env,
759        )?;
760        let (grm, sgraph, stable) = code_gen.finish();
761        let conflicts = if stable.conflicts().is_some() {
762            Some((sgraph, stable))
763        } else {
764            None
765        };
766        Ok(CTParser {
767            regenerated: true,
768            rule_ids,
769            yacc_grammar: grm,
770            grammar_src: inc,
771            grammar_path: self.grammar_path.unwrap(),
772            conflicts,
773        })
774    }
775
776    /// Given the filename `a/b.y` as input, statically compile the grammar `src/a/b.y` into a Rust
777    /// module which can then be imported using `lrpar_mod!("a/b.y")`. This is a convenience
778    /// function around [`process_file`](#method.process_file) which makes it easier to compile
779    /// grammar files stored in a project's `src/` directory: please see
780    /// [`process_file`](#method.process_file) for additional constraints and information about the
781    /// generated files.
782    #[deprecated(
783        since = "0.11.0",
784        note = "Please use grammar_in_src_dir(), build(), and token_map() instead"
785    )]
786    #[allow(deprecated)]
787    pub fn process_file_in_src(
788        &mut self,
789        srcp: &str,
790    ) -> Result<HashMap<String, StorageT>, Box<dyn Error>> {
791        let mut inp = current_dir()?;
792        inp.push("src");
793        inp.push(srcp);
794        let mut outp = PathBuf::new();
795        outp.push(var("OUT_DIR").unwrap());
796        outp.push(Path::new(srcp).parent().unwrap().to_str().unwrap());
797        create_dir_all(&outp)?;
798        let mut leaf = Path::new(srcp)
799            .file_name()
800            .unwrap()
801            .to_str()
802            .unwrap()
803            .to_owned();
804        write!(leaf, ".{}", RUST_FILE_EXT).ok();
805        outp.push(leaf);
806        self.process_file(inp, outp)
807    }
808
809    /// Statically compile the Yacc file `inp` into Rust, placing the output into the file `outp`.
810    /// Note that three additional files will be created with the same name as `outp` but with the
811    /// extensions `grm`, and `stable`, overwriting any existing files with those names.
812    ///
813    /// `outp` defines a module as follows:
814    ///
815    /// ```text
816    ///   mod modname {
817    ///     pub fn parse(lexemes: &::std::vec::Vec<::lrpar::Lexeme<StorageT>>) { ... }
818    ///         -> (::std::option::Option<ActionT>,
819    ///             ::std::vec::Vec<::lrpar::LexParseError<StorageT>>)> { ...}
820    ///
821    ///     pub fn token_epp<'a>(tidx: ::cfgrammar::TIdx<StorageT>) -> ::std::option::Option<&'a str> {
822    ///       ...
823    ///     }
824    ///
825    ///     ...
826    ///   }
827    /// ```
828    ///
829    /// where:
830    ///  * `modname` is either:
831    ///    * the module name specified [`mod_name`](#method.mod_name)
832    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.y` the
833    ///      module name is `c_y` (i.e. the file's leaf name, minus its extension, with a prefix of
834    ///      `_y`).
835    ///  * `ActionT` is either:
836    ///    * the `%actiontype` value given to the grammar
837    ///    * or, if the `yacckind` was set YaccKind::Original(YaccOriginalActionKind::UserAction),
838    ///      it is [`Node<StorageT>`](../parser/enum.Node.html)
839    ///
840    /// # Panics
841    ///
842    /// If `StorageT` is not big enough to index the grammar's tokens, rules, or
843    /// productions.
844    #[deprecated(
845        since = "0.11.0",
846        note = "Please use grammar_path(), output_path(), build(), and token_map() instead"
847    )]
848    pub fn process_file<P, Q>(
849        &mut self,
850        inp: P,
851        outp: Q,
852    ) -> Result<HashMap<String, StorageT>, Box<dyn Error>>
853    where
854        P: AsRef<Path>,
855        Q: AsRef<Path>,
856    {
857        self.grammar_path = Some(inp.as_ref().to_owned());
858        self.output_path = Some(outp.as_ref().to_owned());
859        let cl: CTParserBuilder<LexerTypesT> = CTParserBuilder {
860            grammar_path: self.grammar_path.clone(),
861            grammar_src: None,
862            from_ast: None,
863            output_path: self.output_path.clone(),
864            mod_name: self.mod_name,
865            recoverer: self.recoverer,
866            yacckind: self.yacckind,
867            error_on_conflicts: self.error_on_conflicts,
868            warnings_are_errors: self.warnings_are_errors,
869            show_warnings: self.show_warnings,
870            visibility: self.visibility.clone(),
871            rust_edition: self.rust_edition,
872            inspect_rt: None,
873            serialisation_format: self.serialisation_format,
874            #[cfg(test)]
875            inspect_callback: None,
876            phantom: PhantomData,
877        };
878        Ok(cl.build()?.rule_ids)
879    }
880
881    fn output_file<P: AsRef<Path>>(
882        &self,
883        code_gen: &ParserCodegen<LexerTypesT>,
884        outp_rs: P,
885        cache: &str,
886        build_env: &ParserBuildEnv<'_, LexerTypesT>,
887    ) -> Result<(), Box<dyn Error>> {
888        let outs = code_gen
889            .generate(build_env)
890            .map_err(|e| ErrorString(e.to_string()))?;
891        let mut f = File::create(outp_rs)?;
892        f.write_all(outs.as_bytes())?;
893        f.write_all(cache.as_bytes())?;
894        Ok(())
895    }
896}
897
898/// Bundles `YaccGrammar` + `StateTable` so that generated parsers can hold
899/// them in a `OnceLock` without naming `lrtable` directly.
900#[doc(hidden)]
901pub struct ParserData<StorageT: Eq + Hash> {
902    grm: YaccGrammar<StorageT>,
903    stable: StateTable<StorageT>,
904}
905
906impl<StorageT: Eq + Hash> ParserData<StorageT> {
907    pub fn grm(&self) -> &YaccGrammar<StorageT> {
908        &self.grm
909    }
910
911    pub fn stable(&self) -> &StateTable<StorageT> {
912        &self.stable
913    }
914}
915
916/// This function is called by generated files; it exists so that generated files don't require a
917/// direct dependency on bincode.
918#[doc(hidden)]
919pub fn _reconstitute<
920    C: wincode::config::Config + Clone + Copy,
921    StorageT: SchemaReadOwned<C, Dst = StorageT> + Eq + Hash + PrimInt + Unsigned + 'static,
922>(
923    grm_buf: &[u8],
924    stable_buf: &[u8],
925    config: C,
926) -> ParserData<StorageT> {
927    let grm: YaccGrammar<StorageT> = wincode::config::deserialize_from(grm_buf, config).unwrap();
928    let stable = wincode::config::deserialize_from(stable_buf, config).unwrap();
929    ParserData { grm, stable }
930}
931
932/// An interface to the result of [CTParserBuilder::build()].
933pub struct CTParser<StorageT = u32>
934where
935    StorageT: Eq + Hash,
936{
937    regenerated: bool,
938    rule_ids: HashMap<String, StorageT>,
939    yacc_grammar: YaccGrammar<StorageT>,
940    grammar_src: String,
941    grammar_path: PathBuf,
942    conflicts: Option<(StateGraph<StorageT>, StateTable<StorageT>)>,
943}
944
945impl<StorageT> CTParser<StorageT>
946where
947    StorageT: 'static + Debug + Hash + PrimInt + Unsigned,
948    usize: AsPrimitive<StorageT>,
949{
950    /// Returns `true` if this compile-time parser was regenerated or `false` if it was not.
951    pub fn regenerated(&self) -> bool {
952        self.regenerated
953    }
954
955    /// Returns a [HashMap] from lexeme string types to numeric types (e.g. `INT: 2`), suitable for
956    /// handing to a lexer to coordinate the IDs of lexer and parser.
957    pub fn token_map(&self) -> &HashMap<String, StorageT> {
958        &self.rule_ids
959    }
960
961    /// If there are any conflicts in the grammar, return a tuple which allows users to inspect and
962    /// pretty print them; otherwise returns `None`. If the grammar was not regenerated, this will
963    /// always return `None`, even if the grammar actually has conflicts.
964    ///
965    /// **Note: The conflicts feature is currently unstable and may change in the future.**
966    #[allow(private_interfaces)]
967    pub fn conflicts(
968        &self,
969        _: crate::unstable::UnstableApi,
970    ) -> Option<(
971        &YaccGrammar<StorageT>,
972        &StateGraph<StorageT>,
973        &StateTable<StorageT>,
974        &Conflicts<StorageT>,
975    )> {
976        if let Some((sgraph, stable)) = &self.conflicts {
977            return Some((
978                &self.yacc_grammar,
979                sgraph,
980                stable,
981                stable.conflicts().unwrap(),
982            ));
983        }
984        None
985    }
986
987    #[doc(hidden)]
988    pub fn yacc_grammar(&self) -> &YaccGrammar<StorageT> {
989        &self.yacc_grammar
990    }
991    #[doc(hidden)]
992    pub fn grammar_src(&self) -> &str {
993        &self.grammar_src
994    }
995    #[doc(hidden)]
996    pub fn grammar_path(&self) -> &Path {
997        self.grammar_path.as_path()
998    }
999}
1000
1001/// Indents a multi-line string and trims any trailing newline.
1002/// This currently assumes that indentation on blank lines does not matter.
1003///
1004/// The algorithm used by this function is:
1005/// 1. Prefix `s` with the indentation, indenting the first line.
1006/// 2. Trim any trailing newlines.
1007/// 3. Replace all newlines with `\n{indent}`` to indent all lines after the first.
1008///
1009/// It is plausible that we should a step 4, but currently do not:
1010/// 4. Replace all `\n{indent}\n` with `\n\n`
1011pub(crate) fn indent(indent: &str, s: &str) -> String {
1012    format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
1013}
1014
1015// Tests dealing with the filesystem not supported under wasm32
1016#[cfg(all(not(target_arch = "wasm32"), test))]
1017mod test {
1018    use std::{fs::File, io::Write, path::PathBuf};
1019
1020    use super::{CTConflictsError, CTParserBuilder};
1021    use crate::test_utils::TestLexerTypes;
1022    use cfgrammar::yacc::{YaccKind, YaccOriginalActionKind};
1023    use tempfile::TempDir;
1024
1025    #[test]
1026    fn test_conflicts() {
1027        let temp = TempDir::new().unwrap();
1028        let mut file_path = PathBuf::from(temp.as_ref());
1029        file_path.push("grm.y");
1030        let mut f = File::create(&file_path).unwrap();
1031        let _ = f.write_all(
1032            "%start A
1033%%
1034A : 'a' 'b' | B 'b';
1035B : 'a' | C;
1036C : 'a';"
1037                .as_bytes(),
1038        );
1039
1040        match CTParserBuilder::<TestLexerTypes>::new()
1041            .error_on_conflicts(false)
1042            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1043            .grammar_path(file_path.to_str().unwrap())
1044            .output_path(file_path.with_extension("ignored"))
1045            .build()
1046            .unwrap()
1047            .conflicts(crate::unstable::UnstableApi)
1048        {
1049            Some((_, _, _, conflicts)) => {
1050                assert_eq!(conflicts.sr_len(), 1);
1051                assert_eq!(conflicts.rr_len(), 1);
1052            }
1053            None => panic!("Expected error data"),
1054        }
1055    }
1056
1057    #[test]
1058    fn test_conflicts_error() {
1059        let temp = TempDir::new().unwrap();
1060        let mut file_path = PathBuf::from(temp.as_ref());
1061        file_path.push("grm.y");
1062        let mut f = File::create(&file_path).unwrap();
1063        let _ = f.write_all(
1064            "%start A
1065%%
1066A : 'a' 'b' | B 'b';
1067B : 'a' | C;
1068C : 'a';"
1069                .as_bytes(),
1070        );
1071
1072        match CTParserBuilder::<TestLexerTypes>::new()
1073            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1074            .grammar_path(file_path.to_str().unwrap())
1075            .output_path(file_path.with_extension("ignored"))
1076            .build()
1077        {
1078            Ok(_) => panic!("Expected error"),
1079            Err(e) => {
1080                let cs = e.downcast_ref::<CTConflictsError<u16>>();
1081                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1082                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1083            }
1084        }
1085    }
1086
1087    #[test]
1088    fn test_expect_error() {
1089        let temp = TempDir::new().unwrap();
1090        let mut file_path = PathBuf::from(temp.as_ref());
1091        file_path.push("grm.y");
1092        let mut f = File::create(&file_path).unwrap();
1093        let _ = f.write_all(
1094            "%start A
1095%expect 2
1096%%
1097A: 'a' 'b' | B 'b';
1098B: 'a';"
1099                .as_bytes(),
1100        );
1101
1102        match CTParserBuilder::<TestLexerTypes>::new()
1103            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1104            .grammar_path(file_path.to_str().unwrap())
1105            .output_path(file_path.with_extension("ignored"))
1106            .build()
1107        {
1108            Ok(_) => panic!("Expected error"),
1109            Err(e) => {
1110                let cs = e.downcast_ref::<CTConflictsError<u16>>();
1111                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 0);
1112                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1113            }
1114        }
1115    }
1116
1117    #[test]
1118    fn test_expectrr_error() {
1119        let temp = TempDir::new().unwrap();
1120        let mut file_path = PathBuf::from(temp.as_ref());
1121        file_path.push("grm.y");
1122        let mut f = File::create(&file_path).unwrap();
1123        let _ = f.write_all(
1124            "%start A
1125%expect 1
1126%expect-rr 2
1127%%
1128A : 'a' 'b' | B 'b';
1129B : 'a' | C;
1130C : 'a';"
1131                .as_bytes(),
1132        );
1133
1134        match CTParserBuilder::<TestLexerTypes>::new()
1135            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1136            .grammar_path(file_path.to_str().unwrap())
1137            .output_path(file_path.with_extension("ignored"))
1138            .build()
1139        {
1140            Ok(_) => panic!("Expected error"),
1141            Err(e) => {
1142                let cs = e.downcast_ref::<CTConflictsError<u16>>();
1143                assert_eq!(cs.unwrap().stable.conflicts().unwrap().rr_len(), 1);
1144                assert_eq!(cs.unwrap().stable.conflicts().unwrap().sr_len(), 1);
1145            }
1146        }
1147    }
1148
1149    #[test]
1150    /// Tests a yacc .y filename containing a dash character leading to an invalid rust identifier
1151    /// when that dash is subsequently used as the default `CTParserBuilder::mod_name`.
1152    fn test_invalid_identifier_in_derived_mod_name() {
1153        let temp = TempDir::new().unwrap();
1154        let mut file_path = PathBuf::from(temp.as_ref());
1155        file_path.push("contains-a-dash.y");
1156        let mut f = File::create(&file_path).unwrap();
1157        let _ = f.write_all(
1158            "%start A
1159%%
1160A : 'a';"
1161                .as_bytes(),
1162        );
1163        match CTParserBuilder::<TestLexerTypes>::new()
1164            .yacckind(YaccKind::Original(YaccOriginalActionKind::GenericParseTree))
1165            .grammar_path(file_path.to_str().unwrap())
1166            .output_path(file_path.with_extension("ignored"))
1167            .build()
1168        {
1169            Ok(_) => panic!("Expected error"),
1170            Err(e) => {
1171                let err_string = e.to_string();
1172                assert_eq!(
1173                    err_string,
1174                    "mod_name \'contains-a-dash_y\' is not a valid rust identifier due to 'unexpected token'"
1175                );
1176            }
1177        }
1178    }
1179
1180    #[cfg(test)]
1181    #[test]
1182    fn test_recoverer_header() -> Result<(), Box<dyn std::error::Error>> {
1183        use crate::RecoveryKind as RK;
1184        #[rustfmt::skip]
1185            let recovery_kinds = [
1186                //  Builder,          Header setting,     Expected result.
1187                // -----------       ------------------  -------------------
1188                (Some(RK::None),      Some(RK::None),     Some(RK::None)),
1189                (Some(RK::None),      Some(RK::CPCTPlus), Some(RK::None)),
1190                (Some(RK::CPCTPlus),  Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1191                (Some(RK::CPCTPlus),  Some(RK::None),     Some(RK::CPCTPlus)),
1192                (None,                Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1193                (None,                Some(RK::None),     Some(RK::None)),
1194                (None,                None,               Some(RK::CPCTPlus)),
1195                (Some(RK::None),      None,               Some(RK::None)),
1196                (Some(RK::CPCTPlus),  None,               Some(RK::CPCTPlus)),
1197            ];
1198
1199        for (i, (builder_arg, header_arg, expected_rk)) in
1200            recovery_kinds.iter().cloned().enumerate()
1201        {
1202            let y_src = if let Some(header_arg) = header_arg {
1203                format!(
1204                    "\
1205                    %grmtools{{yacckind: Original(NoAction), recoverer: {}}} \
1206                    %% \
1207                    start: ; \
1208                    ",
1209                    match header_arg {
1210                        RK::None => "RecoveryKind::None",
1211                        RK::CPCTPlus => "RecoveryKind::CPCTPlus",
1212                    }
1213                )
1214            } else {
1215                r#"
1216                    %grmtools{yacckind: Original(NoAction)}
1217                    %%
1218                    Start: ;
1219                    "#
1220                .to_string()
1221            };
1222            let out_dir = std::env::var("OUT_DIR").unwrap();
1223            let y_path = format!("{out_dir}/recoverykind_test_{i}.y");
1224            let y_out_path = format!("{y_path}.rs");
1225            std::fs::File::create(y_path.clone()).unwrap();
1226            std::fs::write(y_path.clone(), y_src).unwrap();
1227            let mut cp_builder = CTParserBuilder::<TestLexerTypes>::new();
1228            cp_builder = cp_builder
1229                .output_path(y_out_path.clone())
1230                .grammar_path(y_path.clone());
1231            cp_builder = if let Some(builder_arg) = builder_arg {
1232                cp_builder.recoverer(builder_arg)
1233            } else {
1234                cp_builder
1235            }
1236            .inspect_recoverer(Box::new(move |rk| {
1237                if matches!(
1238                    (rk, expected_rk),
1239                    (RK::None, Some(RK::None)) | (RK::CPCTPlus, Some(RK::CPCTPlus))
1240                ) {
1241                    Ok(())
1242                } else {
1243                    panic!("Unexpected recovery kind")
1244                }
1245            }));
1246            cp_builder.build()?;
1247        }
1248        Ok(())
1249    }
1250}