1use 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#[derive(Clone, PartialEq, Eq, Debug)]
133#[non_exhaustive]
134pub enum Visibility {
135 Private,
137 Public,
139 PublicSuper,
141 PublicSelf,
143 PublicCrate,
145 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#[derive(Clone, Copy, PartialEq, Eq, Debug)]
169#[non_exhaustive]
170pub enum RustEdition {
171 Rust2015,
172 Rust2018,
173 Rust2021,
174}
175
176struct 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
192struct 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
203struct 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
213struct 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
229pub 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 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 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 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 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 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 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 pub fn lexerkind(mut self, lexerkind: LexerKind) -> Self {
423 self.lexerkind = Some(lexerkind);
424 self
425 }
426
427 pub fn mod_name(mut self, mod_name: &'a str) -> Self {
431 self.mod_name = Some(mod_name);
432 self
433 }
434
435 pub fn visibility(mut self, vis: Visibility) -> Self {
437 self.visibility = vis;
438 self
439 }
440
441 pub fn rust_edition(mut self, edition: RustEdition) -> Self {
444 self.rust_edition = edition;
445 self
446 }
447
448 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 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 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 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 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 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 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 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 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 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 let unformatted = out_tokens.to_string();
909 let mut outs = String::new();
910 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 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 #[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 #[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 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 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 pub fn warnings_are_errors(mut self, flag: bool) -> Self {
1030 self.warnings_are_errors = flag;
1031 self
1032 }
1033
1034 pub fn show_warnings(mut self, flag: bool) -> Self {
1037 self.show_warnings = flag;
1038 self
1039 }
1040
1041 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 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 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 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 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 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 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 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 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 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 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 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
1255pub 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#[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 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 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 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 pub fn build(&self) -> Result<(), Box<dyn Error>> {
1372 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 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 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 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 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#[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
1467fn indent(indent: &str, s: &str) -> String {
1478 format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
1479}
1480
1481#[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 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}