Skip to main content

lrlex/
ctbuilder.rs

1//! Build grammars at run-time.
2
3use cfgrammar::{
4    header::{
5        GrmtoolsSectionParser, Header, HeaderError, HeaderErrorKind, HeaderValue, Namespaced,
6        Setting, Value,
7    },
8    markmap::MergeBehavior,
9    span::{Location, Span},
10};
11use glob::glob;
12use lrpar::{
13    CTParserBuilder, LexerTypes,
14    diagnostics::{DiagnosticFormatter, SpannedDiagnosticFormatter},
15};
16use num_traits::{AsPrimitive, PrimInt, Unsigned};
17use proc_macro2::{Ident, TokenStream};
18use quote::{ToTokens, TokenStreamExt, format_ident, quote};
19use regex::Regex;
20use std::marker::PhantomData;
21use std::{
22    any::type_name,
23    borrow::Borrow,
24    collections::{HashMap, HashSet},
25    env::{current_dir, var},
26    error::Error,
27    fmt::{self, Debug, Display, Write as _},
28    fs::{self, File, create_dir_all, read_to_string},
29    hash::Hash,
30    io::Write,
31    path::{Path, PathBuf},
32    sync::{LazyLock, Mutex},
33};
34use wincode::SchemaWrite;
35
36use crate::{DefaultLexerTypes, LRNonStreamingLexer, LRNonStreamingLexerDef, LexFlags, LexerDef};
37
38const RUST_FILE_EXT: &str = "rs";
39
40const ERROR: &str = "[Error]";
41const WARNING: &str = "[Warning]";
42
43static RE_TOKEN_ID: LazyLock<Regex> =
44    LazyLock::new(|| Regex::new(r"^[a-zA-Z_][a-zA-Z_0-9]*$").unwrap());
45
46static GENERATED_PATHS: LazyLock<Mutex<HashSet<PathBuf>>> =
47    LazyLock::new(|| Mutex::new(HashSet::new()));
48
49#[non_exhaustive]
50pub enum LexerKind {
51    LRNonStreamingLexer,
52}
53
54impl<T: Clone> TryFrom<&Value<T>> for LexerKind {
55    type Error = cfgrammar::header::HeaderError<T>;
56    fn try_from(it: &Value<T>) -> Result<LexerKind, Self::Error> {
57        match it {
58            Value::Flag(_, loc) => Err(HeaderError {
59                kind: HeaderErrorKind::ConversionError(
60                    "LexerKind",
61                    "Expected `LexerKind` found bool",
62                ),
63                locations: vec![loc.clone()],
64            }),
65            Value::Setting(Setting::Num(_, loc)) => Err(HeaderError {
66                kind: HeaderErrorKind::ConversionError(
67                    "LexerKind",
68                    "Expected `LexerKind` found numeric",
69                ),
70                locations: vec![loc.clone()],
71            }),
72            Value::Setting(Setting::String(_, loc)) => Err(HeaderError {
73                kind: HeaderErrorKind::ConversionError(
74                    "LexerKind",
75                    "Expected `LexerKind` found string",
76                ),
77                locations: vec![loc.clone()],
78            }),
79            Value::Setting(Setting::Constructor {
80                ctor:
81                    Namespaced {
82                        namespace: _,
83                        member: (_, loc),
84                    },
85                arg: _,
86            }) => Err(HeaderError {
87                kind: HeaderErrorKind::ConversionError(
88                    "LexerKind",
89                    "Expected `LexerKind` found constructor",
90                ),
91                locations: vec![loc.clone()],
92            }),
93            Value::Setting(Setting::Array(_, arr_loc, _)) => Err(HeaderError {
94                kind: HeaderErrorKind::ConversionError(
95                    "LexerKind",
96                    "Expected `LexerKind` found array",
97                ),
98                locations: vec![arr_loc.clone()],
99            }),
100            Value::Setting(Setting::Unitary(Namespaced {
101                namespace,
102                member: (member, member_loc),
103            })) => {
104                if let Some((ns, loc)) = namespace
105                    && ns.to_lowercase() != "lexerkind"
106                {
107                    return Err(HeaderError {
108                        kind: HeaderErrorKind::ConversionError(
109                            "LexerKind",
110                            "Expected namespace `LexerKind`",
111                        ),
112                        locations: vec![loc.clone()],
113                    });
114                }
115                if member.to_lowercase() != "lrnonstreaminglexer" {
116                    return Err(HeaderError {
117                        kind: HeaderErrorKind::ConversionError(
118                            "LexerKind",
119                            "Unknown `LexerKind` Variant",
120                        ),
121                        locations: vec![member_loc.clone()],
122                    });
123                }
124
125                Ok(LexerKind::LRNonStreamingLexer)
126            }
127        }
128    }
129}
130
131/// Specify the visibility of the module generated by [CTLexerBuilder].
132#[derive(Clone, PartialEq, Eq, Debug)]
133#[non_exhaustive]
134pub enum Visibility {
135    /// Module-level visibility only.
136    Private,
137    /// `pub`
138    Public,
139    /// `pub(super)`
140    PublicSuper,
141    /// `pub(self)`
142    PublicSelf,
143    /// `pub(crate)`
144    PublicCrate,
145    /// `pub(in {arg})`
146    PublicIn(String),
147}
148
149impl ToTokens for Visibility {
150    fn to_tokens(&self, tokens: &mut TokenStream) {
151        tokens.extend(match self {
152            Visibility::Private => quote!(),
153            Visibility::Public => quote! {pub},
154            Visibility::PublicSuper => quote! {pub(super)},
155            Visibility::PublicSelf => quote! {pub(self)},
156            Visibility::PublicCrate => quote! {pub(crate)},
157            Visibility::PublicIn(data) => {
158                let other = str::parse::<TokenStream>(data).unwrap();
159                quote! {pub(in #other)}
160            }
161        })
162    }
163}
164
165/// Specifies the [Rust Edition] that will be emitted during code generation.
166///
167/// [Rust Edition]: https://doc.rust-lang.org/edition-guide/rust-2021/index.html
168#[derive(Clone, Copy, PartialEq, Eq, Debug)]
169#[non_exhaustive]
170pub enum RustEdition {
171    Rust2015,
172    Rust2018,
173    Rust2021,
174}
175
176/// The quote impl of `ToTokens` for `Option` prints an empty string for `None`
177/// and the inner value for `Some(inner_value)`.
178///
179/// This wrapper instead emits both `Some` and `None` variants.
180/// See: [quote #20](https://github.com/dtolnay/quote/issues/20)
181struct QuoteOption<T>(Option<T>);
182
183impl<T: ToTokens> ToTokens for QuoteOption<T> {
184    fn to_tokens(&self, tokens: &mut TokenStream) {
185        tokens.append_all(match self.0 {
186            Some(ref t) => quote! { ::std::option::Option::Some(#t) },
187            None => quote! { ::std::option::Option::None },
188        });
189    }
190}
191
192/// This wrapper adds a missing impl of `ToTokens` for tuples.
193/// For a tuple `(a, b)` emits `(a.to_tokens(), b.to_tokens())`
194struct QuoteTuple<T>(T);
195
196impl<A: ToTokens, B: ToTokens> ToTokens for QuoteTuple<(A, B)> {
197    fn to_tokens(&self, tokens: &mut TokenStream) {
198        let (a, b) = &self.0;
199        tokens.append_all(quote!((#a, #b)));
200    }
201}
202
203/// The wrapped `&str` value will be emitted with a call to `to_string()`
204struct QuoteToString<'a>(&'a str);
205
206impl ToTokens for QuoteToString<'_> {
207    fn to_tokens(&self, tokens: &mut TokenStream) {
208        let x = &self.0;
209        tokens.append_all(quote! { #x.to_string() });
210    }
211}
212
213/// A string which uses `Display` for it's `Debug` impl.
214struct ErrorString(String);
215impl fmt::Display for ErrorString {
216    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
217        let ErrorString(s) = self;
218        write!(f, "{}", s)
219    }
220}
221impl fmt::Debug for ErrorString {
222    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
223        let ErrorString(s) = self;
224        write!(f, "{}", s)
225    }
226}
227impl Error for ErrorString {}
228
229/// A `CTLexerBuilder` allows one to specify the criteria for building a statically generated
230/// lexer.
231pub struct CTLexerBuilder<'a, LexerTypesT: LexerTypes = DefaultLexerTypes<u32>>
232where
233    LexerTypesT::StorageT: Debug + Eq + Hash + ToTokens,
234    usize: num_traits::AsPrimitive<LexerTypesT::StorageT>,
235{
236    lrpar_config:
237        Option<Box<dyn Fn(CTParserBuilder<LexerTypesT>) -> CTParserBuilder<LexerTypesT> + 'a>>,
238    lexer_path: Option<PathBuf>,
239    output_path: Option<PathBuf>,
240    lexerkind: Option<LexerKind>,
241    mod_name: Option<&'a str>,
242    visibility: Visibility,
243    rust_edition: RustEdition,
244    rule_ids_map: Option<HashMap<String, LexerTypesT::StorageT>>,
245    allow_missing_terms_in_lexer: bool,
246    allow_missing_tokens_in_parser: bool,
247    warnings_are_errors: bool,
248    show_warnings: bool,
249    header: Header<Location>,
250    #[cfg(test)]
251    inspect_lexerkind_cb: Option<Box<dyn Fn(&LexerKind) -> Result<(), Box<dyn Error>>>>,
252}
253
254impl CTLexerBuilder<'_, DefaultLexerTypes<u32>> {
255    /// Create a new [CTLexerBuilder].
256    pub fn new() -> Self {
257        CTLexerBuilder::<DefaultLexerTypes<u32>>::new_with_lexemet()
258    }
259}
260type FixIntConfig = wincode::config::Configuration;
261
262type VarIntConfig = wincode::config::Configuration<
263    true,
264    4194304,
265    wincode::len::BincodeLen,
266    wincode::int_encoding::LittleEndian,
267    wincode::int_encoding::VarInt,
268>;
269impl<'a, LexerTypesT: LexerTypes<LexErrorT = crate::LRLexError> + 'static>
270    CTLexerBuilder<'a, LexerTypesT>
271where
272    LexerTypesT::StorageT: 'static
273        + Debug
274        + Eq
275        + Hash
276        + PrimInt
277        + SchemaWrite<FixIntConfig, Src = LexerTypesT::StorageT>
278        + SchemaWrite<VarIntConfig, Src = LexerTypesT::StorageT>
279        + TryFrom<usize>
280        + Unsigned
281        + ToTokens,
282    usize: AsPrimitive<LexerTypesT::StorageT>,
283{
284    /// Create a new [CTLexerBuilder].
285    ///
286    /// `LexerTypesT::StorageT` must be an unsigned integer type (e.g. `u8`, `u16`) which is big enough
287    /// to index all the tokens, rules, and productions in the lexer and less than or equal in size
288    /// to `usize` (e.g. on a 64-bit machine `u128` would be too big). If you are lexing large
289    /// files, the additional storage requirements of larger integer types can be noticeable, and
290    /// in such cases it can be worth specifying a smaller type. `StorageT` defaults to `u32` if
291    /// unspecified.
292    ///
293    /// # Examples
294    ///
295    /// ```text
296    /// CTLexerBuilder::<DefaultLexerTypes<u8>>::new_with_lexemet()
297    ///     .lexer_in_src_dir("grm.l", None)?
298    ///     .build()?;
299    /// ```
300    pub fn new_with_lexemet() -> Self {
301        let mut header = Header::new();
302        header.set_default_merge_behavior(MergeBehavior::Ours);
303        CTLexerBuilder {
304            lrpar_config: None,
305            lexer_path: None,
306            output_path: None,
307            lexerkind: None,
308            mod_name: None,
309            visibility: Visibility::Private,
310            rust_edition: RustEdition::Rust2021,
311            rule_ids_map: None,
312            allow_missing_terms_in_lexer: false,
313            allow_missing_tokens_in_parser: false,
314            warnings_are_errors: false,
315            show_warnings: true,
316            header,
317            #[cfg(test)]
318            inspect_lexerkind_cb: None,
319        }
320    }
321
322    /// An optional convenience function to make it easier to create an (lrlex) lexer and (lrpar)
323    /// parser in one shot. The closure passed to this function will be called during
324    /// [CTLexerBuilder::build]: it will be passed an lrpar `CTParserBuilder` instance upon which
325    /// it can set whatever lrpar options are desired. [`CTLexerBuilder`] will then create both the
326    /// compiler and lexer and link them together as required.
327    ///
328    /// # Examples
329    ///
330    /// ```text
331    /// CTLexerBuilder:::new()
332    ///     .lrpar_config(|ctp| {
333    ///         ctp.yacckind(YaccKind::Grmtools)
334    ///             .grammar_in_src_dir("calc.y")
335    ///             .unwrap()
336    ///     })
337    ///     .lexer_in_src_dir("calc.l")?
338    ///     .build()?;
339    /// ```
340    pub fn lrpar_config<F>(mut self, config_func: F) -> Self
341    where
342        F: Fn(CTParserBuilder<LexerTypesT>) -> CTParserBuilder<LexerTypesT> + 'a,
343    {
344        self.lrpar_config = Some(Box::new(config_func));
345        self
346    }
347
348    /// Set the input lexer path to a file relative to this project's `src` directory. This will
349    /// also set the output path (i.e. you do not need to call [CTLexerBuilder::output_path]).
350    ///
351    /// For example if `a/b.l` is passed as `inp` then [CTLexerBuilder::build] will:
352    ///   * use `src/a/b.l` as the input file.
353    ///   * write output to a file which can then be imported by calling `lrlex_mod!("a/b.l")`.
354    ///   * create a module in that output file named `b_l`.
355    ///
356    /// You can override the output path and/or module name by calling
357    /// [CTLexerBuilder::output_path] and/or [CTLexerBuilder::mod_name], respectively, after
358    /// calling this function.
359    ///
360    /// This is a convenience function that makes it easier to compile lexer files stored in a
361    /// project's `src/` directory: please see [CTLexerBuilder::build] for additional constraints
362    /// and information about the generated files. Note also that each `.l` file can only be
363    /// processed once using this function: if you want to generate multiple lexers from a single
364    /// `.l` file, you will need to use [CTLexerBuilder::output_path].
365    pub fn lexer_in_src_dir<P>(mut self, srcp: P) -> Result<Self, Box<dyn Error>>
366    where
367        P: AsRef<Path>,
368    {
369        if !srcp.as_ref().is_relative() {
370            return Err(format!(
371                "Lexer path '{}' must be a relative path.",
372                srcp.as_ref().to_str().unwrap_or("<invalid UTF-8>")
373            )
374            .into());
375        }
376
377        let mut lexp = current_dir()?;
378        lexp.push("src");
379        lexp.push(srcp.as_ref());
380        self.lexer_path = Some(lexp);
381
382        let mut outp = PathBuf::new();
383        outp.push(var("OUT_DIR").unwrap());
384        outp.push(srcp.as_ref().parent().unwrap().to_str().unwrap());
385        create_dir_all(&outp)?;
386        let mut leaf = srcp
387            .as_ref()
388            .file_name()
389            .unwrap()
390            .to_str()
391            .unwrap()
392            .to_owned();
393        write!(leaf, ".{}", RUST_FILE_EXT).ok();
394        outp.push(leaf);
395        Ok(self.output_path(outp))
396    }
397
398    /// Set the input lexer path to `inp`. If specified, you must also call
399    /// [CTLexerBuilder::output_path]. In general it is easier to use
400    /// [CTLexerBuilder::lexer_in_src_dir].
401    pub fn lexer_path<P>(mut self, inp: P) -> Self
402    where
403        P: AsRef<Path>,
404    {
405        self.lexer_path = Some(inp.as_ref().to_owned());
406        self
407    }
408
409    /// Set the output lexer path to `outp`. Note that there are no requirements on `outp`: the
410    /// file can exist anywhere you can create a valid [Path] to. However, if you wish to use
411    /// [crate::lrlex_mod!] you will need to make sure that `outp` is in
412    /// [std::env::var]`("OUT_DIR")` or one of its subdirectories.
413    pub fn output_path<P>(mut self, outp: P) -> Self
414    where
415        P: AsRef<Path>,
416    {
417        self.output_path = Some(outp.as_ref().to_owned());
418        self
419    }
420
421    /// Set the type of lexer to be generated to `lexerkind`.
422    pub fn lexerkind(mut self, lexerkind: LexerKind) -> Self {
423        self.lexerkind = Some(lexerkind);
424        self
425    }
426
427    /// Set the generated module name to `mod_name`. If no module name is specified,
428    /// [`process_file`](#method.process_file) will attempt to create a sensible default based on
429    /// the input filename.
430    pub fn mod_name(mut self, mod_name: &'a str) -> Self {
431        self.mod_name = Some(mod_name);
432        self
433    }
434
435    /// Set the visibility of the generated module to `vis`. Defaults to `Visibility::Private`.
436    pub fn visibility(mut self, vis: Visibility) -> Self {
437        self.visibility = vis;
438        self
439    }
440
441    /// Sets the rust edition to be used for generated code. Defaults to the latest edition of
442    /// rust supported by grmtools.
443    pub fn rust_edition(mut self, edition: RustEdition) -> Self {
444        self.rust_edition = edition;
445        self
446    }
447
448    /// Set this lexer builder's map of rule IDs to `rule_ids_map`. By default, lexing rules have
449    /// arbitrary, but distinct, IDs. Setting the map of rule IDs (from rule names to `StorageT`)
450    /// allows users to synchronise a lexer and parser and to check that all rules are used by both
451    /// parts).
452    pub fn rule_ids_map<T: std::borrow::Borrow<HashMap<String, LexerTypesT::StorageT>> + Clone>(
453        mut self,
454        rule_ids_map: T,
455    ) -> Self {
456        self.rule_ids_map = Some(rule_ids_map.borrow().to_owned());
457        self
458    }
459
460    /// Statically compile the `.l` file specified by [CTLexerBuilder::lexer_path()] into Rust,
461    /// placing the output into the file specified by [CTLexerBuilder::output_path()].
462    ///
463    /// The generated module follows the form:
464    ///
465    /// ```text
466    ///    mod modname {
467    ///      pub fn lexerdef() -> LexerDef<LexerTypesT> { ... }
468    ///
469    ///      ...
470    ///    }
471    /// ```
472    ///
473    /// where:
474    ///  * `modname` is either:
475    ///    * the module name specified by [CTLexerBuilder::mod_name()]
476    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.l` the
477    ///      module name is `c_l` (i.e. the file's leaf name, minus its extension, with a prefix of
478    ///      `_l`).
479    pub fn build(mut self) -> Result<CTLexer, Box<dyn Error>> {
480        let lexerp = self
481            .lexer_path
482            .as_ref()
483            .expect("lexer_path must be specified before processing.");
484        let outp = self
485            .output_path
486            .as_ref()
487            .expect("output_path must be specified before processing.");
488
489        {
490            let mut lk = GENERATED_PATHS.lock().unwrap();
491            if lk.contains(outp.as_path()) {
492                return Err(format!("Generating two lexers to the same path ('{}') is not allowed: use CTLexerBuilder::output_path (and, optionally, CTLexerBuilder::mod_name) to differentiate them.", outp.to_str().unwrap()).into());
493            }
494            lk.insert(outp.clone());
495        }
496        let lex_src = read_to_string(lexerp)
497            .map_err(|e| format!("When reading '{}': {e}", lexerp.display()))?;
498        let lex_diag = SpannedDiagnosticFormatter::new(&lex_src, lexerp);
499        let mut header = self.header;
500        let (parsed_header, _) = GrmtoolsSectionParser::new(&lex_src, false)
501            .parse()
502            .map_err(|es| {
503                let mut out = String::new();
504                out.push_str(&format!(
505                    "\n{ERROR}{}\n",
506                    lex_diag.file_location_msg(" parsing the `%grmtools` section", None)
507                ));
508                for e in es {
509                    out.push_str(&indent("     ", &lex_diag.format_error(e).to_string()));
510                    out.push('\n');
511                }
512                ErrorString(out)
513            })?;
514        header.merge_from(parsed_header)?;
515        header.mark_used(&"lexerkind".to_string());
516        let lexerkind = match self.lexerkind {
517            Some(lexerkind) => lexerkind,
518            None => {
519                if let Some(HeaderValue(_, lk_val)) = header.get("lexerkind") {
520                    LexerKind::try_from(lk_val)?
521                } else {
522                    LexerKind::LRNonStreamingLexer
523                }
524            }
525        };
526        #[cfg(test)]
527        if let Some(inspect_lexerkind_cb) = self.inspect_lexerkind_cb {
528            inspect_lexerkind_cb(&lexerkind)?
529        }
530        let (lexerdef, lex_flags): (LRNonStreamingLexerDef<LexerTypesT>, LexFlags) =
531            match lexerkind {
532                LexerKind::LRNonStreamingLexer => {
533                    let lex_flags = LexFlags::try_from(&mut header)?;
534                    let lexerdef = LRNonStreamingLexerDef::<LexerTypesT>::new_with_options(
535                        &lex_src, lex_flags,
536                    )
537                    .map_err(|errs| {
538                        let mut out = String::new();
539                        out.push_str(&format!(
540                            "\n{ERROR}{}\n",
541                            lex_diag.file_location_msg("", None)
542                        ));
543                        for e in errs {
544                            out.push_str(&indent("     ", &lex_diag.format_error(e).to_string()));
545                            out.push('\n');
546                        }
547                        ErrorString(out)
548                    })?;
549                    let lex_flags = lexerdef.lex_flags().cloned();
550                    (lexerdef, lex_flags.unwrap())
551                }
552            };
553
554        let ct_parser = if let Some(ref lrcfg) = self.lrpar_config {
555            let mut closure_lexerdef = lexerdef.clone();
556            let mut ctp = CTParserBuilder::<LexerTypesT>::new().inspect_rt(Box::new(
557                move |yacc_header, rtpb, rule_ids_map, grm_path| {
558                    let owned_map = rule_ids_map
559                        .iter()
560                        .map(|(x, y)| (&**x, *y))
561                        .collect::<HashMap<_, _>>();
562                    closure_lexerdef.set_rule_ids(&owned_map);
563                    yacc_header.mark_used(&"test_files".to_string());
564                    let grammar = rtpb.grammar();
565                    let test_glob = yacc_header.get("test_files");
566                    let mut err_str = None;
567                    let add_error_line = |err_str: &mut Option<String>, line| {
568                        if let Some(err_str) = err_str {
569                            err_str.push_str(&format!("{}\n", line));
570                        } else {
571                            let _ = err_str.insert(format!("{}\n", line));
572                        }
573                    };
574                    match test_glob {
575                        Some(HeaderValue(_, Value::Setting(Setting::Array(test_globs, _, _)))) => {
576                            for setting in test_globs {
577                                match setting {
578                                    Setting::String(test_files, _) => {
579                                        let path_joined = grm_path.parent().unwrap().join(test_files);
580                                        let path_str = &path_joined.to_string_lossy();
581                                        let mut glob_paths = glob(path_str).map_err(|e| e.to_string())?.peekable();
582                                        if glob_paths.peek().is_none() {
583                                            return Err(format!("'test_files' glob '{}' matched no paths", path_str)
584                                                .to_string()
585                                                .into(),
586                                            );
587                                        }
588
589                                        for path in glob_paths {
590                                            let path = path?;
591                                            if let Some(ext) = path.extension()
592                                                && let Some(ext) = ext.to_str()
593                                                    && ext.starts_with("grm") {
594                                                        add_error_line(&mut err_str, "test_files extensions beginning with `grm` are reserved.".into());
595                                                    }
596                                            let input = fs::read_to_string(&path)?;
597                                            let l: LRNonStreamingLexer<LexerTypesT> =
598                                                closure_lexerdef.lexer(&input);
599                                            let errs = rtpb.parse_map(&l, &|_| (), &|_, _| ()).1;
600                                            if !errs.is_empty() {
601                                                add_error_line(&mut err_str, format!("While parsing {}:", path.display()));
602                                                for e in errs {
603                                                    let e_pp = e.pp(&l, &|t| grammar.token_epp(t));
604                                                    let e_lines = e_pp.split("\n");
605                                                    for e in e_lines {
606                                                        add_error_line(&mut err_str, format!("\t{}", e));
607                                                    }
608                                                }
609                                            }
610                                        }
611                                    }
612                                    _ => return Err("Invalid value for setting 'test_files'".into()),
613                                }
614                            }
615                            if let Some(err_str) = err_str {
616                                Err(ErrorString(err_str))?
617                            } else {
618                                Ok(())
619                            }
620
621                        }
622                        Some(_) => Err("Invalid value for setting 'test_files'".into()),
623                        None => Ok(()),
624                    }
625                },
626            ));
627            ctp = lrcfg(ctp);
628            let ct_parser = ctp.build()?;
629            self.rule_ids_map = Some(ct_parser.token_map().to_owned());
630            Some(ct_parser)
631        } else {
632            None
633        };
634
635        let mut lexerdef = Box::new(lexerdef);
636        let unused_header_values = header.unused();
637        if !unused_header_values.is_empty() {
638            return Err(
639                format!("Unused header values: {}", unused_header_values.join(", ")).into(),
640            );
641        }
642
643        let (mut missing_from_lexer, missing_from_parser) = match self.rule_ids_map {
644            Some(ref rim) => {
645                // Convert from HashMap<String, _> to HashMap<&str, _>
646                let owned_map = rim
647                    .iter()
648                    .map(|(x, y)| (&**x, *y))
649                    .collect::<HashMap<_, _>>();
650                let (x, y) = lexerdef.set_rule_ids_spanned(&owned_map);
651                (
652                    x.map(|a| a.iter().map(|&b| b.to_string()).collect::<HashSet<_>>()),
653                    y.map(|a| {
654                        a.iter()
655                            .map(|(b, span)| (b.to_string(), *span))
656                            .collect::<HashSet<_>>()
657                    }),
658                )
659            }
660            None => (None, None),
661        };
662
663        if let Some(mut mfl) = missing_from_lexer.take() {
664            for tok in &lexerdef.expected_missing_tokens {
665                mfl.remove(tok.as_str());
666            }
667            if mfl.is_empty() {
668                missing_from_lexer = None;
669            } else {
670                missing_from_lexer = Some(mfl);
671            }
672        }
673
674        let mut has_unallowed_missing = false;
675        let err_indent = " ".repeat(ERROR.len());
676        if !self.allow_missing_terms_in_lexer
677            && let Some(ref mfl) = missing_from_lexer
678        {
679            if let Some(ct_parser) = &ct_parser {
680                let grm = ct_parser.yacc_grammar();
681                let token_spans = mfl
682                    .iter()
683                    .map(|name| {
684                        ct_parser
685                            .yacc_grammar()
686                            .token_span(*grm.tokens_map().get(name.as_str()).unwrap())
687                            .expect("Given token should have a span")
688                    })
689                    .collect::<Vec<_>>();
690
691                let yacc_diag = SpannedDiagnosticFormatter::new(
692                    ct_parser.grammar_src(),
693                    ct_parser.grammar_path(),
694                );
695
696                eprintln!(
697                    "{ERROR} these tokens are not referenced in the lexer but defined as follows"
698                );
699                eprintln!(
700                    "{err_indent} {}",
701                    yacc_diag.file_location_msg("in the grammar", None)
702                );
703                for span in token_spans {
704                    eprintln!(
705                        "{}",
706                        yacc_diag.underline_span_with_text(
707                            span,
708                            "Missing from lexer".to_string(),
709                            '^'
710                        )
711                    );
712                }
713                eprintln!();
714            } else {
715                eprintln!(
716                    "{ERROR} the following tokens are used in the grammar but are not defined in the lexer:"
717                );
718                for n in mfl {
719                    eprintln!("    {}", n);
720                }
721            }
722            has_unallowed_missing = true;
723        }
724        if !self.allow_missing_tokens_in_parser
725            && self.show_warnings
726            && let Some(ref mfp) = missing_from_parser
727        {
728            let error_prefix = if self.warnings_are_errors {
729                ERROR
730            } else {
731                WARNING
732            };
733            let err_indent = " ".repeat(error_prefix.len());
734            let mut outs = Vec::new();
735            outs.push(format!("{error_prefix} these tokens are not referenced in the grammar but defined as follows"));
736            outs.push(format!(
737                "{err_indent} {}",
738                lex_diag.file_location_msg("in the lexer", None)
739            ));
740            for (_, span) in mfp {
741                let error_contents = lex_diag.underline_span_with_text(
742                    *span,
743                    "Missing from parser".to_string(),
744                    '^',
745                );
746                outs.extend(error_contents.lines().map(|s| s.to_string()));
747            }
748
749            for s in outs {
750                if !self.warnings_are_errors && std::env::var("OUT_DIR").is_ok() {
751                    println!("cargo:warning={}", s)
752                } else {
753                    eprintln!("{}", s);
754                }
755            }
756
757            has_unallowed_missing |= self.warnings_are_errors;
758        }
759        if has_unallowed_missing {
760            fs::remove_file(outp).ok();
761            panic!();
762        }
763
764        let mod_name = match self.mod_name {
765            Some(s) => s.to_owned(),
766            None => {
767                // The user hasn't specified a module name, so we create one automatically: what we
768                // do is strip off all the filename extensions (note that it's likely that inp ends
769                // with `l.rs`, so we potentially have to strip off more than one extension) and
770                // then add `_l` to the end.
771                let mut stem = lexerp.to_str().unwrap();
772                loop {
773                    let new_stem = Path::new(stem).file_stem().unwrap().to_str().unwrap();
774                    if stem == new_stem {
775                        break;
776                    }
777                    stem = new_stem;
778                }
779                format!("{}_l", stem)
780            }
781        };
782        let mod_name =
783            match syn::parse_str::<proc_macro2::Ident>(&mod_name) {
784                Ok(s) => s,
785                Err(e) => return Err(format!(
786                    "CTLexerBuilder::mod_name(\"{}\") is not a valid rust identifier due to '{}'",
787                    mod_name, e
788                )
789                .into()),
790            };
791        let mut lexerdef_func_impl = {
792            let LexFlags {
793                allow_wholeline_comments,
794                dot_matches_new_line,
795                multi_line,
796                octal,
797                posix_escapes,
798                case_insensitive,
799                unicode,
800                swap_greed,
801                ignore_whitespace,
802                size_limit,
803                dfa_size_limit,
804                nest_limit,
805            } = lex_flags;
806            let allow_wholeline_comments = QuoteOption(allow_wholeline_comments);
807            let dot_matches_new_line = QuoteOption(dot_matches_new_line);
808            let multi_line = QuoteOption(multi_line);
809            let octal = QuoteOption(octal);
810            let posix_escapes = QuoteOption(posix_escapes);
811            let case_insensitive = QuoteOption(case_insensitive);
812            let unicode = QuoteOption(unicode);
813            let swap_greed = QuoteOption(swap_greed);
814            let ignore_whitespace = QuoteOption(ignore_whitespace);
815            let size_limit = QuoteOption(size_limit);
816            let dfa_size_limit = QuoteOption(dfa_size_limit);
817            let nest_limit = QuoteOption(nest_limit);
818
819            // Code gen for the lexerdef() `lex_flags` variable.
820            quote! {
821                let mut lex_flags = ::lrlex::DEFAULT_LEX_FLAGS;
822                lex_flags.allow_wholeline_comments = #allow_wholeline_comments.or(::lrlex::DEFAULT_LEX_FLAGS.allow_wholeline_comments);
823                lex_flags.dot_matches_new_line = #dot_matches_new_line.or(::lrlex::DEFAULT_LEX_FLAGS.dot_matches_new_line);
824                lex_flags.multi_line = #multi_line.or(::lrlex::DEFAULT_LEX_FLAGS.multi_line);
825                lex_flags.octal = #octal.or(::lrlex::DEFAULT_LEX_FLAGS.octal);
826                lex_flags.posix_escapes = #posix_escapes.or(::lrlex::DEFAULT_LEX_FLAGS.posix_escapes);
827                lex_flags.case_insensitive = #case_insensitive.or(::lrlex::DEFAULT_LEX_FLAGS.case_insensitive);
828                lex_flags.unicode = #unicode.or(::lrlex::DEFAULT_LEX_FLAGS.unicode);
829                lex_flags.swap_greed = #swap_greed.or(::lrlex::DEFAULT_LEX_FLAGS.swap_greed);
830                lex_flags.ignore_whitespace = #ignore_whitespace.or(::lrlex::DEFAULT_LEX_FLAGS.ignore_whitespace);
831                lex_flags.size_limit = #size_limit.or(::lrlex::DEFAULT_LEX_FLAGS.size_limit);
832                lex_flags.dfa_size_limit = #dfa_size_limit.or(::lrlex::DEFAULT_LEX_FLAGS.dfa_size_limit);
833                lex_flags.nest_limit = #nest_limit.or(::lrlex::DEFAULT_LEX_FLAGS.nest_limit);
834                let lex_flags = lex_flags;
835            }
836        };
837        {
838            let start_states = lexerdef.iter_start_states();
839            let rules = lexerdef.iter_rules().map(|r| {
840                    let tok_id = QuoteOption(r.tok_id);
841                    let n = QuoteOption(r.name().map(QuoteToString));
842                    let target_state =
843                        QuoteOption(r.target_state().map(|(x, y)| QuoteTuple((x, y))));
844                    let n_span = r.name_span();
845                    let regex = QuoteToString(&r.re_str);
846                    let start_states = r.start_states();
847                    // Code gen to construct a rule.
848                    //
849                    // We cannot `impl ToToken for Rule` because `Rule` never stores `lex_flags`,
850                    // Thus we reference the local lex_flags variable bound earlier.
851                    quote! {
852                        Rule::new(::lrlex::unstable_api::InternalPublicApi, #tok_id, #n, #n_span, #regex,
853                                vec![#(#start_states),*], #target_state, &lex_flags).unwrap()
854                    }
855                });
856            // Code gen for `lexerdef()`s rules and the stack of `start_states`.
857            lexerdef_func_impl.append_all(quote! {
858                let start_states: Vec<StartState> = vec![#(#start_states),*];
859                let rules = vec![#(#rules),*];
860            });
861        }
862        let lexerdef_ty = match lexerkind {
863            LexerKind::LRNonStreamingLexer => {
864                quote!(::lrlex::LRNonStreamingLexerDef)
865            }
866        };
867        // Code gen for the lexerdef() return value referencing variables bound earlier.
868        lexerdef_func_impl.append_all(quote! {
869            #lexerdef_ty::from_rules(start_states, rules)
870        });
871
872        let mut token_consts = TokenStream::new();
873        if let Some(rim) = self.rule_ids_map {
874            let mut rim_sorted = Vec::from_iter(rim.iter());
875            rim_sorted.sort_by_key(|(k, _)| *k);
876            for (name, id) in rim_sorted {
877                if RE_TOKEN_ID.is_match(name) {
878                    let tok_ident = format_ident!("N_{}", name.to_ascii_uppercase());
879                    let storaget =
880                        str::parse::<TokenStream>(type_name::<LexerTypesT::StorageT>()).unwrap();
881                    // Code gen for the constant token values.
882                    let tok_const = quote! {
883                        #[allow(dead_code)]
884                        pub const #tok_ident: #storaget = #id;
885                    };
886                    token_consts.extend(tok_const)
887                }
888            }
889        }
890        let token_consts = token_consts.into_iter();
891        let out_tokens = {
892            let lexerdef_param = str::parse::<TokenStream>(type_name::<LexerTypesT>()).unwrap();
893            let mod_vis = self.visibility;
894            // Code gen for the generated module.
895            quote! {
896                #mod_vis mod #mod_name {
897                    use ::lrlex::{LexerDef, Rule, StartState};
898                    #[allow(dead_code)]
899                    pub fn lexerdef() -> #lexerdef_ty<#lexerdef_param> {
900                        #lexerdef_func_impl
901                    }
902
903                    #(#token_consts)*
904                }
905            }
906        };
907        // Try and run a code formatter on the generated code.
908        let unformatted = out_tokens.to_string();
909        let mut outs = String::new();
910        // Record the time that this version of lrlex was built. If the source code changes and rustc
911        // forces a recompile, this will change this value, causing anything which depends on this
912        // build of lrlex to be recompiled too.
913        let timestamp = env!("VERGEN_BUILD_TIMESTAMP");
914        write!(outs, "// lrlex build time: {}\n\n", quote!(#timestamp),).ok();
915        outs.push_str(
916            &syn::parse_str(&unformatted)
917                .map(|syntax_tree| prettyplease::unparse(&syntax_tree))
918                .unwrap_or(unformatted),
919        );
920        // If the file we're about to write out already exists with the same contents, then we
921        // don't overwrite it (since that will force a recompile of the file, and relinking of the
922        // binary etc).
923        if let Ok(curs) = read_to_string(outp)
924            && curs == outs
925        {
926            return Ok(CTLexer {
927                missing_from_lexer,
928                missing_from_parser,
929            });
930        }
931        let mut f = File::create(outp)?;
932        f.write_all(outs.as_bytes())?;
933        Ok(CTLexer {
934            missing_from_lexer,
935            missing_from_parser,
936        })
937    }
938
939    /// Given the filename `a/b.l` as input, statically compile the file `src/a/b.l` into a Rust
940    /// module which can then be imported using `lrlex_mod!("a/b.l")`. This is a convenience
941    /// function around [`process_file`](struct.CTLexerBuilder.html#method.process_file) which makes
942    /// it easier to compile `.l` files stored in a project's `src/` directory: please see
943    /// [`process_file`](#method.process_file) for additional constraints and information about the
944    /// generated files.
945    #[deprecated(
946        since = "0.11.0",
947        note = "Please use lexer_in_src_dir() and build() instead"
948    )]
949    #[allow(deprecated)]
950    pub fn process_file_in_src(
951        self,
952        srcp: &str,
953    ) -> Result<(Option<HashSet<String>>, Option<HashSet<String>>), Box<dyn Error>> {
954        let mut inp = current_dir()?;
955        inp.push("src");
956        inp.push(srcp);
957        let mut outp = PathBuf::new();
958        outp.push(var("OUT_DIR").unwrap());
959        outp.push(Path::new(srcp).parent().unwrap().to_str().unwrap());
960        create_dir_all(&outp)?;
961        let mut leaf = Path::new(srcp)
962            .file_name()
963            .unwrap()
964            .to_str()
965            .unwrap()
966            .to_owned();
967        write!(leaf, ".{}", RUST_FILE_EXT).ok();
968        outp.push(leaf);
969        self.process_file(inp, outp)
970    }
971
972    /// Statically compile the `.l` file `inp` into Rust, placing the output into the file `outp`.
973    /// The latter defines a module as follows:
974    ///
975    /// ```text
976    ///    mod modname {
977    ///      pub fn lexerdef() -> LexerDef<LexerTypesT::StorageT> { ... }
978    ///
979    ///      ...
980    ///    }
981    /// ```
982    ///
983    /// where:
984    ///  * `modname` is either:
985    ///    * the module name specified [`mod_name`](#method.mod_name)
986    ///    * or, if no module name was explicitly specified, then for the file `/a/b/c.l` the
987    ///      module name is `c_l` (i.e. the file's leaf name, minus its extension, with a prefix of
988    ///      `_l`).
989    #[deprecated(
990        since = "0.11.0",
991        note = "Please use lexer_in_src_dir() and build() instead"
992    )]
993    pub fn process_file<P, Q>(
994        mut self,
995        inp: P,
996        outp: Q,
997    ) -> Result<(Option<HashSet<String>>, Option<HashSet<String>>), Box<dyn Error>>
998    where
999        P: AsRef<Path>,
1000        Q: AsRef<Path>,
1001    {
1002        self.lexer_path = Some(inp.as_ref().to_owned());
1003        self.output_path = Some(outp.as_ref().to_owned());
1004        let cl = self.build()?;
1005        Ok((
1006            cl.missing_from_lexer().map(|x| x.to_owned()),
1007            cl.missing_from_parser()
1008                .map(|x| x.iter().map(|(n, _)| n.to_owned()).collect::<HashSet<_>>()),
1009        ))
1010    }
1011
1012    /// If passed false, tokens used in the grammar but not defined in the lexer will cause a
1013    /// panic at lexer generation time. Defaults to false.
1014    pub fn allow_missing_terms_in_lexer(mut self, allow: bool) -> Self {
1015        self.allow_missing_terms_in_lexer = allow;
1016        self
1017    }
1018
1019    /// If passed false, tokens defined in the lexer but not used in the grammar will cause a
1020    /// warning at lexer generation time. Defaults to false (since lexers sometimes define tokens such
1021    /// as reserved words, which are intentionally not in the grammar).
1022    pub fn allow_missing_tokens_in_parser(mut self, allow: bool) -> Self {
1023        self.allow_missing_tokens_in_parser = allow;
1024        self
1025    }
1026
1027    /// If set to true, [CTLexerBuilder::build] will return an error if the given lexer contains
1028    /// any warnings. Defaults to `true`.
1029    pub fn warnings_are_errors(mut self, flag: bool) -> Self {
1030        self.warnings_are_errors = flag;
1031        self
1032    }
1033
1034    /// If set to true, [CTParserBuilder::build] will print warnings to stderr, or via cargo when
1035    /// running under cargo. Defaults to `true`.
1036    pub fn show_warnings(mut self, flag: bool) -> Self {
1037        self.show_warnings = flag;
1038        self
1039    }
1040
1041    /// Enables `// comment` style parsing according to `flag``.
1042    /// When enabled comments can appear at the beginning of a line,
1043    /// and regular expressions with the `/` character should be escaped via `\/`.
1044    ///
1045    /// The default value is `false`.
1046    ///
1047    /// Setting this flag will override the same flag within a `%grmtools` section.
1048    pub fn allow_wholeline_comments(mut self, flag: bool) -> Self {
1049        let key = "allow_wholeline_comments".to_string();
1050        self.header.insert(
1051            key,
1052            HeaderValue(
1053                Location::Other("CTLexerBuilder".to_string()),
1054                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1055            ),
1056        );
1057        self
1058    }
1059
1060    /// Sets the `regex::RegexBuilder` option of the same name.
1061    /// The default value is `true`.
1062    ///
1063    /// Setting this flag will override the same flag within a `%grmtools` section.
1064    pub fn dot_matches_new_line(mut self, flag: bool) -> Self {
1065        let key = "dot_matches_new_line".to_string();
1066        self.header.insert(
1067            key,
1068            HeaderValue(
1069                Location::Other("CTLexerBuilder".to_string()),
1070                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1071            ),
1072        );
1073        self
1074    }
1075
1076    /// Sets the `regex::RegexBuilder` option of the same name.
1077    /// The default value is `true`.
1078    ///
1079    /// Setting this flag will override the same flag within a `%grmtools` section.
1080    pub fn multi_line(mut self, flag: bool) -> Self {
1081        let key = "multi_line".to_string();
1082        self.header.insert(
1083            key,
1084            HeaderValue(
1085                Location::Other("CTLexerBuilder".to_string()),
1086                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1087            ),
1088        );
1089        self
1090    }
1091
1092    /// Enables posix lex compatible escape sequences according to `flag`.
1093    /// The default value is `false`.
1094    ///
1095    /// Setting this flag will override the same flag within a `%grmtools` section.
1096    pub fn posix_escapes(mut self, flag: bool) -> Self {
1097        let key = "posix_escapes".to_string();
1098        self.header.insert(
1099            key,
1100            HeaderValue(
1101                Location::Other("CTLexerBuilder".to_string()),
1102                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1103            ),
1104        );
1105        self
1106    }
1107
1108    /// Sets the `regex::RegexBuilder` option of the same name.
1109    /// The default value is `true`.
1110    ///
1111    /// Setting this flag will override the same flag within a `%grmtools` section.
1112    pub fn octal(mut self, flag: bool) -> Self {
1113        let key = "octal".to_string();
1114        self.header.insert(
1115            key,
1116            HeaderValue(
1117                Location::Other("CTLexerBuilder".to_string()),
1118                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1119            ),
1120        );
1121        self
1122    }
1123
1124    /// Sets the `regex::RegexBuilder` option of the same name.
1125    /// Default value is specified by regex.
1126    ///
1127    /// Setting this flag will override the same flag within a `%grmtools` section.
1128    pub fn swap_greed(mut self, flag: bool) -> Self {
1129        let key = "swap_greed".to_string();
1130        self.header.insert(
1131            key,
1132            HeaderValue(
1133                Location::Other("CTLexerBuilder".to_string()),
1134                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1135            ),
1136        );
1137        self
1138    }
1139
1140    /// Sets the `regex::RegexBuilder` option of the same name.
1141    /// Default value is specified by regex.
1142    ///
1143    /// Setting this flag will override the same flag within a `%grmtools` section.
1144    pub fn ignore_whitespace(mut self, flag: bool) -> Self {
1145        let key = "ignore_whitespace".to_string();
1146        self.header.insert(
1147            key,
1148            HeaderValue(
1149                Location::Other("CTLexerBuilder".to_string()),
1150                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1151            ),
1152        );
1153        self
1154    }
1155
1156    /// Sets the `regex::RegexBuilder` option of the same name.
1157    /// Default value is specified by regex.
1158    ///
1159    /// Setting this flag will override the same flag within a `%grmtools` section.
1160    pub fn unicode(mut self, flag: bool) -> Self {
1161        let key = "unicode".to_string();
1162        self.header.insert(
1163            key,
1164            HeaderValue(
1165                Location::Other("CTLexerBuilder".to_string()),
1166                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1167            ),
1168        );
1169        self
1170    }
1171
1172    /// Sets the `regex::RegexBuilder` option of the same name.
1173    /// Default value is specified by regex.
1174    ///
1175    /// Setting this flag will override the same flag within a `%grmtools` section.
1176    pub fn case_insensitive(mut self, flag: bool) -> Self {
1177        let key = "case_insensitive".to_string();
1178        self.header.insert(
1179            key,
1180            HeaderValue(
1181                Location::Other("CTLexerBuilder".to_string()),
1182                Value::Flag(flag, Location::Other("CTLexerBuilder".to_string())),
1183            ),
1184        );
1185        self
1186    }
1187
1188    /// Sets the `regex::RegexBuilder` option of the same name.
1189    /// Default value is specified by regex.
1190    ///
1191    /// Setting this flag will override the same flag within a `%grmtools` section.
1192    pub fn size_limit(mut self, sz: usize) -> Self {
1193        let key = "size_limit".to_string();
1194        self.header.insert(
1195            key,
1196            HeaderValue(
1197                Location::Other("CTLexerBuilder".to_string()),
1198                Value::Setting(Setting::Num(
1199                    sz as u64,
1200                    Location::Other("CTLexerBuilder".to_string()),
1201                )),
1202            ),
1203        );
1204        self
1205    }
1206
1207    /// Sets the `regex::RegexBuilder` option of the same name.
1208    /// Default value is specified by regex.
1209    ///
1210    /// Setting this flag will override the same flag within a `%grmtools` section.
1211    pub fn dfa_size_limit(mut self, sz: usize) -> Self {
1212        let key = "dfa_size_limit".to_string();
1213        self.header.insert(
1214            key,
1215            HeaderValue(
1216                Location::Other("CTLexerBuilder".to_string()),
1217                Value::Setting(Setting::Num(
1218                    sz as u64,
1219                    Location::Other("CTLexerBuilder".to_string()),
1220                )),
1221            ),
1222        );
1223        self
1224    }
1225
1226    /// Sets the `regex::RegexBuilder` option of the same name.
1227    /// Default value is specified by regex.
1228    ///
1229    /// Setting this flag will override the same flag within a `%grmtools` section.
1230    pub fn nest_limit(mut self, lim: u32) -> Self {
1231        let key = "nest_limit".to_string();
1232        self.header.insert(
1233            key,
1234            HeaderValue(
1235                Location::Other("CTLexerBuilder".to_string()),
1236                Value::Setting(Setting::Num(
1237                    lim as u64,
1238                    Location::Other("CTLexerBuilder".to_string()),
1239                )),
1240            ),
1241        );
1242        self
1243    }
1244
1245    #[cfg(test)]
1246    pub fn inspect_lexerkind(
1247        mut self,
1248        cb: Box<dyn Fn(&LexerKind) -> Result<(), Box<dyn Error>>>,
1249    ) -> Self {
1250        self.inspect_lexerkind_cb = Some(cb);
1251        self
1252    }
1253}
1254
1255/// An interface to the result of [CTLexerBuilder::build()].
1256pub struct CTLexer {
1257    missing_from_lexer: Option<HashSet<String>>,
1258    missing_from_parser: Option<HashSet<(String, Span)>>,
1259}
1260
1261impl CTLexer {
1262    fn missing_from_lexer(&self) -> Option<&HashSet<String>> {
1263        self.missing_from_lexer.as_ref()
1264    }
1265
1266    fn missing_from_parser(&self) -> Option<&HashSet<(String, Span)>> {
1267        self.missing_from_parser.as_ref()
1268    }
1269}
1270
1271/// Exports all token IDs used by a parser as a separate Rust module.
1272///
1273/// This builder will create a Rust module named `mod_name`
1274/// that can be imported with [`lrlex_mod!(mod_name)`](crate::lrlex_mod).
1275/// The module will contain one `const` `StorageT` per token in `token_map`,
1276/// with the token prefixed by `T_`. In addition, it will contain
1277/// an array of all token IDs `TOK_IDS`.
1278///
1279/// For example, if `StorageT` is `u8`, `mod_name` is `x`, and `token_map` is
1280/// `HashMap{"ID": 0, "INT": 1}` the generated module will look roughly as follows:
1281///
1282/// ```rust,ignore
1283/// mod x {
1284///   pub const T_ID: u8 = 0;
1285///   pub const T_INT: u8 = 1;
1286///   pub const TOK_IDS: &[u8] = &[T_ID, T_INT];
1287/// }
1288/// ```
1289///
1290/// See the [custom lexer example] for more usage details.
1291///
1292/// [custom lexer example]: https://github.com/softdevteam/grmtools/tree/master/lrlex/examples/calc_manual_lex
1293#[derive(Debug, Clone)]
1294pub struct CTTokenMapBuilder<StorageT: Display + ToTokens> {
1295    mod_name: String,
1296    token_map: Vec<(String, TokenStream)>,
1297    rename_map: Option<HashMap<String, String>>,
1298    allow_dead_code: bool,
1299    _marker: PhantomData<StorageT>,
1300}
1301
1302impl<StorageT: Display + ToTokens> CTTokenMapBuilder<StorageT> {
1303    /// Create a new token map builder.
1304    ///
1305    /// See the [builder documentation] for more info.
1306    ///
1307    /// [builder documentation]: CTTokenMapBuilder
1308    pub fn new(
1309        mod_name: impl Into<String>,
1310        token_map: impl Borrow<HashMap<String, StorageT>>,
1311    ) -> Self {
1312        Self {
1313            mod_name: mod_name.into(),
1314            token_map: token_map
1315                .borrow()
1316                .iter()
1317                .map(|(tok_name, tok_value)| (tok_name.clone(), tok_value.to_token_stream()))
1318                .collect(),
1319            rename_map: None,
1320            allow_dead_code: false,
1321            _marker: PhantomData,
1322        }
1323    }
1324
1325    /// Set a token rename map.
1326    ///
1327    /// Rename map is used to specify identifier names for tokens whose names
1328    /// are not valid Rust identifiers. For example, if `token_map`
1329    /// is `HashMap{"+": 0, "ID": 1}` and `rename_map` is `HashMap{"+": "PLUS"}`
1330    /// then the generated module will look roughly as follows:
1331    ///
1332    /// ```rust,ignore
1333    /// mod x {
1334    ///   pub const T_PLUS: u8 = 0;
1335    ///   pub const T_ID: u8 = 1;
1336    /// }
1337    /// ```
1338    pub fn rename_map<M, I, K, V>(mut self, rename_map: Option<M>) -> Self
1339    where
1340        M: IntoIterator<Item = I>,
1341        I: Borrow<(K, V)>,
1342        K: AsRef<str>,
1343        V: AsRef<str>,
1344    {
1345        self.rename_map = rename_map.map(|rename_map| {
1346            rename_map
1347                .into_iter()
1348                .map(|it| {
1349                    let (k, v) = it.borrow();
1350                    let k = k.as_ref().into();
1351                    let v = v.as_ref().into();
1352                    (k, v)
1353                })
1354                .collect()
1355        });
1356        self
1357    }
1358
1359    /// Control whether the builder will add `#[allow(dead_code)]`
1360    /// to the generated module.
1361    ///
1362    /// By default, all tokens are `#[deny(dead_code)]`, meaning that you'll
1363    /// get a warning if your custom lexer doesn't use any of them.
1364    /// This function can be used to disable this behavior.
1365    pub fn allow_dead_code(mut self, allow_dead_code: bool) -> Self {
1366        self.allow_dead_code = allow_dead_code;
1367        self
1368    }
1369
1370    /// Build the token map module.
1371    pub fn build(&self) -> Result<(), Box<dyn Error>> {
1372        // Record the time that this version of lrlex was built. If the source code changes and rustc
1373        // forces a recompile, this will change this value, causing anything which depends on this
1374        // build of lrlex to be recompiled too.
1375        let mut outs = String::new();
1376        let timestamp = env!("VERGEN_BUILD_TIMESTAMP");
1377        let mod_ident = format_ident!("{}", self.mod_name);
1378        write!(outs, "// lrlex build time: {}\n\n", quote!(#timestamp),).ok();
1379        let storaget = str::parse::<TokenStream>(type_name::<StorageT>()).unwrap();
1380        // Sort the tokens so that they're always in the same order.
1381        // This will prevent unneeded rebuilds.
1382        let mut token_map_sorted = self.token_map.clone();
1383        token_map_sorted.sort_by(|(l, _), (r, _)| l.cmp(r));
1384        let (token_array, tokens) = token_map_sorted
1385            .iter()
1386            .map(|(k, id)| {
1387                let name = match &self.rename_map {
1388                    Some(rmap) => rmap.get(k).unwrap_or(k),
1389                    _ => k,
1390                };
1391                let tok_ident: Ident = syn::parse_str(&format!("T_{}", name.to_ascii_uppercase()))
1392                    .map_err(|e| {
1393                        format!(
1394                            "token name {:?} is not a valid Rust identifier: {}; \
1395                            consider renaming it via `CTTokenMapBuilder::rename_map`.",
1396                            name, e
1397                        )
1398                    })?;
1399                Ok((
1400                    // Note: the array of all tokens can't use `tok_ident` because
1401                    // it will confuse the dead code checker. For this reason,
1402                    // we use `id` here.
1403                    quote! {
1404                        #id,
1405                    },
1406                    quote! {
1407                        pub const #tok_ident: #storaget = #id;
1408                    },
1409                ))
1410            })
1411            .collect::<Result<(TokenStream, TokenStream), Box<dyn Error>>>()?;
1412        let unused_annotation = if self.allow_dead_code {
1413            quote! {#[allow(dead_code)]}
1414        } else {
1415            quote! {}
1416        };
1417        // Since the formatter doesn't preserve comments and we don't want to lose build time,
1418        // just format the module contents.
1419        let unformatted = quote! {
1420            #unused_annotation
1421            mod #mod_ident {
1422                #tokens
1423                #[allow(dead_code)]
1424                pub const TOK_IDS: &[#storaget] = &[#token_array];
1425            }
1426        }
1427        .to_string();
1428        let out_mod = syn::parse_str(&unformatted)
1429            .map(|syntax_tree| prettyplease::unparse(&syntax_tree))
1430            .unwrap_or(unformatted);
1431        outs.push_str(&out_mod);
1432        let mut outp = PathBuf::from(var("OUT_DIR")?);
1433        outp.push(&self.mod_name);
1434        outp.set_extension("rs");
1435
1436        // If the file we're about to write out already exists with the same contents, then we
1437        // don't overwrite it (since that will force a recompile of the file, and relinking of the
1438        // binary etc).
1439        if let Ok(curs) = read_to_string(&outp)
1440            && curs == outs
1441        {
1442            return Ok(());
1443        }
1444
1445        let mut f = File::create(outp)?;
1446        f.write_all(outs.as_bytes())?;
1447        Ok(())
1448    }
1449}
1450
1451/// Create a Rust module named `mod_name` that can be imported with
1452/// [`lrlex_mod!(mod_name)`](crate::lrlex_mod).
1453///
1454/// This function is deprecated in favour of [`CTTokenMapBuilder`].
1455#[deprecated(since = "0.14.0", note = "use `lrlex::CTTokenMapBuilder` instead")]
1456pub fn ct_token_map<StorageT: Display + ToTokens>(
1457    mod_name: &str,
1458    token_map: impl Borrow<HashMap<String, StorageT>>,
1459    rename_map: Option<&HashMap<&str, &str>>,
1460) -> Result<(), Box<dyn Error>> {
1461    CTTokenMapBuilder::new(mod_name, token_map)
1462        .rename_map(rename_map)
1463        .allow_dead_code(true)
1464        .build()
1465}
1466
1467/// Indents a multi-line string and trims any trailing newline.
1468/// This currently assumes that indentation on blank lines does not matter.
1469///
1470/// The algorithm used by this function is:
1471/// 1. Prefix `s` with the indentation, indenting the first line.
1472/// 2. Trim any trailing newlines.
1473/// 3. Replace all newlines with `\n{indent}`` to indent all lines after the first.
1474///
1475/// It is plausible that we should a step 4, but currently do not:
1476/// 4. Replace all `\n{indent}\n` with `\n\n`
1477fn indent(indent: &str, s: &str) -> String {
1478    format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
1479}
1480
1481// It isn't clear to me why this test isn't working on wasm32,
1482// as the `workspace_runner` should allow access to `OUT_DIR`
1483// perhaps it is related to absolute paths
1484#[cfg(all(not(target_arch = "wasm32"), test))]
1485mod test {
1486    use std::fs::File;
1487    use std::io::Write;
1488
1489    use super::{CTLexerBuilder, LexerKind};
1490    #[test]
1491    fn test_grmtools_section_lexerkind() {
1492        let lexerkinds = [
1493            "LRNonStreamingLexer",
1494            "lrnonstreaminglexer",
1495            "LexerKind::lrnonstreaminglexer",
1496            "lexerkind::LRNonStreamingLexer",
1497        ];
1498        for (i, kind) in lexerkinds.iter().enumerate() {
1499            let lex_src = format!(
1500                "
1501%grmtools{{lexerkind: {}}}
1502%%
1503. ;
1504",
1505                kind
1506            );
1507            let lex_path = format!(
1508                "{}/test_grmtools_section_lexerkind_{}.l",
1509                env!("OUT_DIR"),
1510                i
1511            );
1512            let mut l_file = File::create(lex_path.clone()).unwrap();
1513            l_file.write_all(lex_src.as_bytes()).unwrap();
1514            CTLexerBuilder::new()
1515                .output_path(format!("{}.rs", lex_path.clone()))
1516                .lexer_path(lex_path.clone())
1517                .inspect_lexerkind(Box::new(move |lexerkind| {
1518                    assert!(matches!(lexerkind, &LexerKind::LRNonStreamingLexer));
1519                    Ok(())
1520                }))
1521                .build()
1522                .unwrap();
1523        }
1524    }
1525
1526    #[test]
1527    /// Tests a yacc .y filename containing a dash character leading to an invalid rust identifier
1528    /// when that dash is subsequently used as the default `CTParserBuilder::mod_name`.
1529    fn test_invalid_identifier_in_derived_mod_name() {
1530        let mut lex_path = std::path::PathBuf::from(env!("OUT_DIR"));
1531        lex_path.push("contains-a-dash.l");
1532        let mut f = File::create(&lex_path).unwrap();
1533        let _ = f.write_all(
1534            r#"
1535%%
1536A  "A"
1537"#
1538            .as_bytes(),
1539        );
1540        match CTLexerBuilder::new()
1541            .output_path(format!("{}.rs", lex_path.display()))
1542            .lexer_path(lex_path.clone())
1543            .build()
1544        {
1545            Ok(_) => panic!("Expected error"),
1546            Err(e) => {
1547                let err_string = e.to_string();
1548                assert_eq!(
1549                    err_string,
1550                    "CTLexerBuilder::mod_name(\"contains-a-dash_l\") is not a valid rust identifier due to 'unexpected token'"
1551                );
1552            }
1553        }
1554    }
1555}