diff --git a/lrlex/src/lib/ctbuilder.rs b/lrlex/src/lib/ctbuilder.rs index db716e638..9b108f8e0 100644 --- a/lrlex/src/lib/ctbuilder.rs +++ b/lrlex/src/lib/ctbuilder.rs @@ -25,7 +25,8 @@ use regex::Regex; use serde::Serialize; use crate::{ - DefaultLexerTypes, LRNonStreamingLexerDef, LexerDef, RegexOptions, DEFAULT_REGEX_OPTIONS, + DefaultLexerTypes, LRNonStreamingLexerDef, LexBuildError, LexerDef, RegexOptions, + DEFAULT_REGEX_OPTIONS, }; const RUST_FILE_EXT: &str = "rs"; @@ -97,6 +98,7 @@ where allow_missing_terms_in_lexer: bool, allow_missing_tokens_in_parser: bool, regex_options: RegexOptions, + on_lex_build_errors_fn: Option<&'a dyn Fn(Vec) -> Box>, } impl<'a> CTLexerBuilder<'a, DefaultLexerTypes> { @@ -141,6 +143,7 @@ where allow_missing_terms_in_lexer: false, allow_missing_tokens_in_parser: true, regex_options: DEFAULT_REGEX_OPTIONS, + on_lex_build_errors_fn: None, } } @@ -329,27 +332,33 @@ where let lex_src = read_to_string(lexerp)?; let line_cache = NewlineCache::from_str(&lex_src).unwrap(); let mut lexerdef: Box> = match self.lexerkind { - LexerKind::LRNonStreamingLexer => Box::new( - LRNonStreamingLexerDef::::new_with_options( + LexerKind::LRNonStreamingLexer => { + let lexerdef = LRNonStreamingLexerDef::::new_with_options( &lex_src, self.regex_options.clone(), - ) - .map_err(|errs| { - errs.iter() - .map(|e| { - if let Some((line, column)) = line_cache.byte_to_line_num_and_col_num( - &lex_src, - e.spans().first().unwrap().start(), - ) { - format!("{} at line {line} column {column}", e) - } else { - format!("{}", e) - } - }) - .collect::>() - .join("\n") - })?, - ), + ); + Box::new(if let Some(on_err_fn) = self.on_lex_build_errors_fn { + lexerdef.map_err(on_err_fn)? + } else { + lexerdef.map_err(|errs| { + errs.iter() + .map(|e| { + if let Some((line, column)) = line_cache + .byte_to_line_num_and_col_num( + &lex_src, + e.spans().first().unwrap().start(), + ) + { + format!("{} at line {line} column {column}", e) + } else { + format!("{}", e) + } + }) + .collect::>() + .join("\n") + })? + }) + } }; let (missing_from_lexer, missing_from_parser) = match self.rule_ids_map { Some(ref rim) => { @@ -723,6 +732,14 @@ pub fn lexerdef() -> {lexerdef_type} {{ self.regex_options.nest_limit = Some(lim); self } + + pub fn on_lex_build_error( + mut self, + f: &'a dyn Fn(Vec) -> Box, + ) -> Self { + self.on_lex_build_errors_fn = Some(f); + self + } } /// An interface to the result of [CTLexerBuilder::build()]. diff --git a/lrpar/src/lib/ctbuilder.rs b/lrpar/src/lib/ctbuilder.rs index 76d53d7ed..9f8f83e06 100644 --- a/lrpar/src/lib/ctbuilder.rs +++ b/lrpar/src/lib/ctbuilder.rs @@ -19,7 +19,9 @@ use std::{ use bincode::{deserialize, serialize_into}; use cfgrammar::{ newlinecache::NewlineCache, - yacc::{ast::ASTWithValidityInfo, YaccGrammar, YaccKind, YaccOriginalActionKind}, + yacc::{ + ast::ASTWithValidityInfo, YaccGrammar, YaccGrammarError, YaccKind, YaccOriginalActionKind, + }, RIdx, Spanned, Symbol, }; use filetime::FileTime; @@ -166,6 +168,15 @@ where show_warnings: bool, visibility: Visibility, rust_edition: RustEdition, + on_grammar_error_fn: Option<&'a dyn Fn(Vec) -> Box>, + on_unexpected_conflicts_fn: Option< + &'a dyn Fn( + &YaccGrammar, + &StateGraph, + &StateTable, + &Conflicts, + ) -> Box, + >, phantom: PhantomData, } @@ -209,6 +220,8 @@ where show_warnings: true, visibility: Visibility::Private, rust_edition: RustEdition::Rust2021, + on_grammar_error_fn: None, + on_unexpected_conflicts_fn: None, phantom: PhantomData, } } @@ -339,6 +352,26 @@ where self } + pub fn on_grammar_error( + mut self, + f: &'a dyn Fn(Vec) -> Box, + ) -> Self { + self.on_grammar_error_fn = Some(f); + self + } + + pub fn on_unexpected_conflicts( + mut self, + f: &'a dyn Fn( + &YaccGrammar, + &StateGraph, + &StateTable, + &Conflicts, + ) -> Box, + ) -> Self { + self.on_unexpected_conflicts_fn = Some(f); + self + } /// Statically compile the Yacc file specified by [CTParserBuilder::grammar_path()] into Rust, /// placing the output into the file spec [CTParserBuilder::output_path()]. Note that three /// additional files will be created with the same name as specified in [self.output_path] but @@ -463,21 +496,25 @@ where grm } Err(errs) => { - let mut line_cache = NewlineCache::new(); - line_cache.feed(&inc); - return Err(ErrorString(if errs.len() + warnings.len() > 1 { - // Indent under the "Error:" prefix. - format!( - "\n\t{}", - errs.iter() - .map(|e| spanned_fmt(e, &inc, &line_cache)) - .chain(warnings.iter().map(|w| spanned_fmt(w, &inc, &line_cache))) - .collect::>() - .join("\n\t") - ) + if let Some(on_err_fn) = self.on_grammar_error_fn { + return Err(on_err_fn(errs)); } else { - spanned_fmt(errs.first().unwrap(), &inc, &line_cache) - }))?; + let mut line_cache = NewlineCache::new(); + line_cache.feed(&inc); + return Err(ErrorString(if errs.len() + warnings.len() > 1 { + // Indent under the "Error:" prefix. + format!( + "\n\t{}", + errs.iter() + .map(|e| spanned_fmt(e, &inc, &line_cache)) + .chain(warnings.iter().map(|w| spanned_fmt(w, &inc, &line_cache))) + .collect::>() + .join("\n\t") + ) + } else { + spanned_fmt(errs.first().unwrap(), &inc, &line_cache) + }))?; + } } }; @@ -529,7 +566,13 @@ where (Some(i), None) if i == c.sr_len() && 0 == c.rr_len() => (), (None, Some(j)) if 0 == c.sr_len() && j == c.rr_len() => (), (None, None) if 0 == c.rr_len() && 0 == c.sr_len() => (), - _ => return Err(Box::new(CTConflictsError { stable })), + _ => { + if let Some(on_conflicts) = self.on_unexpected_conflicts_fn { + return Err(on_conflicts(&grm, &sgraph, &stable, c)); + } else { + return Err(Box::new(CTConflictsError { stable })); + } + } } } } @@ -661,6 +704,8 @@ where show_warnings: self.show_warnings, visibility: self.visibility.clone(), rust_edition: self.rust_edition, + on_grammar_error_fn: None, + on_unexpected_conflicts_fn: None, phantom: PhantomData, }; Ok(cl.build()?.rule_ids)