Skip to content

Commit 2f5092f

Browse files
committed
Add a process_header function to CTParserBuilder
This function, gets a mutable reference to the `Header`, the recovery kind, and a reference to the `YaccGrammar`. This occurrs after the YaccGrammar is built by `build()`, but before the `output_path` has been written. The `process_header` function can return an error result. Which will abort the build process.
1 parent 6b0a559 commit 2f5092f

3 files changed

Lines changed: 158 additions & 35 deletions

File tree

lrpar/cttests/build.rs

Lines changed: 105 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,111 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
2323
wasm32_unknown: { all(target_arch = "wasm32", target_os="unknown", target_vendor="unknown") },
2424
}
2525

26-
// Because we're modifying the `StorageT` this isn't something `run_test_path` can do,
27-
// Since it modifies the type of the builder.
28-
CTLexerBuilder::<DefaultLexerTypes<u8>>::new_with_lexemet()
29-
.rust_edition(lrlex::RustEdition::Rust2021)
30-
.output_path(format!(
31-
"{}/storaget.l.rs",
32-
std::env::var("OUT_DIR").unwrap()
33-
))
34-
.lrpar_config(|ctp| {
35-
ctp.rust_edition(lrpar::RustEdition::Rust2021)
36-
.output_path(format!(
37-
"{}/storaget.y.rs",
38-
std::env::var("OUT_DIR").unwrap()
39-
))
40-
.grammar_in_src_dir("storaget.y")
41-
.unwrap()
42-
})
43-
.lexer_in_src_dir("storaget.l")
44-
.unwrap()
45-
.build()
46-
.unwrap();
26+
{
27+
// Because we're modifying the `StorageT` this isn't something `run_test_path` can do,
28+
// Since it modifies the type of the builder.
29+
CTLexerBuilder::<DefaultLexerTypes<u8>>::new_with_lexemet()
30+
.rust_edition(lrlex::RustEdition::Rust2021)
31+
.output_path(format!(
32+
"{}/storaget.l.rs",
33+
std::env::var("OUT_DIR").unwrap()
34+
))
35+
.lrpar_config(|ctp| {
36+
ctp.rust_edition(lrpar::RustEdition::Rust2021)
37+
.output_path(format!(
38+
"{}/storaget.y.rs",
39+
std::env::var("OUT_DIR").unwrap()
40+
))
41+
.grammar_in_src_dir("storaget.y")
42+
.unwrap()
43+
})
44+
.lexer_in_src_dir("storaget.l")
45+
.unwrap()
46+
.build()
47+
.unwrap();
48+
}
49+
50+
{
51+
use lrpar::RecoveryKind as RK;
52+
#[rustfmt::skip]
53+
let recovery_kinds = vec![
54+
// Builder, Header setting, Expected result.
55+
// ----------- ------------------ -------------------
56+
(Some(RK::None), Some(RK::None), Some(RK::None)),
57+
(Some(RK::None), Some(RK::CPCTPlus), Some(RK::None)),
58+
(Some(RK::CPCTPlus), Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
59+
(Some(RK::CPCTPlus), Some(RK::None), Some(RK::CPCTPlus)),
60+
(None, Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
61+
(None, Some(RK::None), Some(RK::None)),
62+
(None, None, Some(RK::CPCTPlus)),
63+
(Some(RK::None), None, Some(RK::None)),
64+
(Some(RK::CPCTPlus), None, Some(RK::CPCTPlus)),
65+
];
66+
67+
for (i, (builder_arg, header_arg, expected_rk)) in
68+
recovery_kinds.iter().cloned().enumerate()
69+
{
70+
let y_src = if let Some(header_arg) = header_arg {
71+
format!(
72+
"\
73+
%grmtools{{yacckind: Original(NoAction), recoverer: {}}} \
74+
%% \
75+
start: ; \
76+
",
77+
match header_arg {
78+
RK::None => "RecoveryKind::None",
79+
RK::CPCTPlus => "RecoveryKind::CPCTPlus",
80+
_ => panic!("Unrecognized recoverykind"),
81+
}
82+
)
83+
} else {
84+
r#"
85+
%grmtools{yacckind: Original(NoAction)}
86+
%%
87+
Start: ;
88+
"#
89+
.to_string()
90+
};
91+
let out_dir = std::env::var("OUT_DIR").unwrap();
92+
let y_path = format!("{out_dir}/recoverykind_test_{i}.y");
93+
let y_out_path = format!("{y_path}.rs");
94+
let l_src = r#"
95+
%%
96+
. ;"#;
97+
let l_path = format!("{out_dir}/recoverykind_tests.l");
98+
let l_out_path = format!("{out_dir}/recoverykind_tests_{i}.l.rs");
99+
std::fs::File::create(l_path.clone()).unwrap();
100+
std::fs::write(l_path.clone(), l_src).unwrap();
101+
eprintln!("{:?} {:?}", y_path, l_path);
102+
std::fs::File::create(y_path.clone()).unwrap();
103+
std::fs::write(y_path.clone(), y_src).unwrap();
104+
let mut cl_builder = CTLexerBuilder::new()
105+
.output_path(l_out_path)
106+
.lexer_path(l_path);
107+
cl_builder = cl_builder.lrpar_config(move |mut cp_builder| {
108+
cp_builder = cp_builder
109+
.output_path(y_out_path.clone())
110+
.grammar_path(y_path.clone());
111+
if let Some(builder_arg) = builder_arg {
112+
cp_builder.recoverer(builder_arg.clone())
113+
} else {
114+
cp_builder
115+
}
116+
.process_header(Box::new(move |_, rk, _| {
117+
if match (rk, expected_rk) {
118+
(RK::None, Some(RK::None)) | (RK::CPCTPlus, Some(RK::CPCTPlus)) => true,
119+
_ => false,
120+
} {
121+
Ok(())
122+
} else {
123+
panic!("Unexpected recovery kind")
124+
}
125+
}))
126+
});
127+
128+
cl_builder.build()?;
129+
}
130+
}
47131
println!("cargo::rerun-if-changed=src/storaget.l");
48132
println!(
49133
"cargo::rerun-if-changed={}/storaget.l.rs",

lrpar/src/lib/ctbuilder.rs

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,16 @@ where
242242
show_warnings: bool,
243243
visibility: Visibility,
244244
rust_edition: RustEdition,
245+
// We want lifetimes that imply the callback can't capture the header or the grammar.
246+
header_callback: Option<
247+
Box<
248+
dyn for<'h, 'y> Fn(
249+
&'h mut Header,
250+
RecoveryKind,
251+
&'y YaccGrammar<LexerTypesT::StorageT>,
252+
) -> Result<(), Box<dyn Error>>,
253+
>,
254+
>,
245255
phantom: PhantomData<LexerTypesT>,
246256
}
247257

@@ -286,6 +296,7 @@ where
286296
show_warnings: true,
287297
visibility: Visibility::Private,
288298
rust_edition: RustEdition::Rust2021,
299+
header_callback: None,
289300
phantom: PhantomData,
290301
}
291302
}
@@ -416,6 +427,20 @@ where
416427
self
417428
}
418429

430+
pub fn process_header(
431+
mut self,
432+
cb: Box<
433+
dyn for<'h, 'y> Fn(
434+
&'h mut Header,
435+
RecoveryKind,
436+
&'y YaccGrammar<StorageT>,
437+
) -> Result<(), Box<dyn Error>>,
438+
>,
439+
) -> Self {
440+
self.header_callback = Some(cb);
441+
self
442+
}
443+
419444
/// Statically compile the Yacc file specified by [CTParserBuilder::grammar_path()] into Rust,
420445
/// placing the output into the file spec [CTParserBuilder::output_path()]. Note that three
421446
/// additional files will be created with the same name as specified in [self.output_path] but
@@ -527,18 +552,6 @@ where
527552
self.recoverer = Some(RecoveryKind::CPCTPlus);
528553
}
529554

530-
let unused_keys = header.unused();
531-
if !unused_keys.is_empty() {
532-
return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into());
533-
}
534-
let missing_keys = header.missing();
535-
if !missing_keys.is_empty() {
536-
return Err(format!(
537-
"Required values were missing from the header: {}",
538-
unused_keys.join(", ")
539-
)
540-
.into());
541-
}
542555
self.yacckind = ast_validation.yacc_kind();
543556
let warnings = ast_validation.ast().warnings();
544557
let loc_fmt = |err_str, loc, inc: &str, line_cache: &NewlineCache| match loc {
@@ -624,6 +637,27 @@ where
624637
}
625638
};
626639

640+
if let Some(cb) = &self.header_callback {
641+
cb(
642+
&mut header,
643+
self.recoverer.expect("has a default value"),
644+
&grm,
645+
)?;
646+
}
647+
648+
let unused_keys = header.unused();
649+
if !unused_keys.is_empty() {
650+
return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into());
651+
}
652+
let missing_keys = header.missing();
653+
if !missing_keys.is_empty() {
654+
return Err(format!(
655+
"Required values were missing from the header: {}",
656+
unused_keys.join(", ")
657+
)
658+
.into());
659+
}
660+
627661
let rule_ids = grm
628662
.tokens_map()
629663
.iter()
@@ -819,6 +853,7 @@ where
819853
show_warnings: self.show_warnings,
820854
visibility: self.visibility.clone(),
821855
rust_edition: self.rust_edition,
856+
header_callback: None,
822857
phantom: PhantomData,
823858
};
824859
Ok(cl.build()?.rule_ids)
@@ -895,7 +930,7 @@ where
895930
// rustc forces a recompile, this will change this value, causing anything which depends on
896931
// this build of lrpar to be recompiled too.
897932
let Self {
898-
// All variables except for `output_path` and `phantom` should
933+
// All variables except for `output_path`, `header_callback` and `phantom` should
899934
// be written into the cache.
900935
grammar_path,
901936
mod_name,
@@ -907,6 +942,7 @@ where
907942
show_warnings,
908943
visibility,
909944
rust_edition,
945+
header_callback: _,
910946
phantom: _,
911947
} = self;
912948
let build_time = env!("VERGEN_BUILD_TIMESTAMP");

nimbleparse/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ fn main() {
118118
match matches.opt_str("r") {
119119
None => (),
120120
Some(s) => {
121-
header.set_merge_behavior(&"recoverer".to_string(), cfgrammar::markmap::MergeBehavior::Ours);
121+
header.set_merge_behavior(
122+
&"recoverer".to_string(),
123+
cfgrammar::markmap::MergeBehavior::Ours,
124+
);
122125
header.insert(
123126
"recoverer".to_string(),
124127
(

0 commit comments

Comments
 (0)