Skip to content

Commit 3b41f6f

Browse files
authored
Merge pull request #550 from ratmice/header_recoverykind
Header recoverykind
2 parents e64b15c + cf3cdaf commit 3b41f6f

8 files changed

Lines changed: 339 additions & 28 deletions

File tree

lrpar/cttests/src/calc_actiontype.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
name: Test basic user actions using the calculator grammar
22
yacckind: Original(YaccOriginalActionKind::UserAction)
3+
recoverer: RecoveryKind::None
34
grammar: |
45
%start Expr
56
%actiontype Result<u64, ()>

lrpar/cttests/src/calc_multitypes.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
name: Test basic user actions using the calculator grammar
22
yacckind: Grmtools
3+
recoverer: RecoveryKind::CPCTPlus
34
grammar: |
45
%start Expr
56
%avoid_insert "INT"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Test basic user actions using the calculator grammar
2+
grammar: |
3+
%grmtools {yacckind: Original(UserAction), recoverer: RecoveryKind::CPCTPlus}
4+
%start Expr
5+
%actiontype Result<u64, ()>
6+
%avoid_insert 'INT'
7+
%%
8+
Expr: Expr '+' Term { Ok($1? + $3?) }
9+
| Term { $1 }
10+
;
11+
12+
Term: Term '*' Factor { Ok($1? * $3?) }
13+
| Factor { $1 }
14+
;
15+
16+
Factor: '(' Expr ')' { $2 }
17+
| 'INT' {
18+
let l = $1.map_err(|_| ())?;
19+
match $lexer.span_str(l.span()).parse::<u64>() {
20+
Ok(v) => Ok(v),
21+
Err(_) => {
22+
let ((_, col), _) = $lexer.line_col(l.span());
23+
eprintln!("Error at column {}: '{}' cannot be represented as a u64",
24+
col,
25+
$lexer.span_str(l.span()));
26+
Err(())
27+
}
28+
}
29+
}
30+
;
31+
32+
lexer: |
33+
%%
34+
[0-9]+ "INT"
35+
\+ "+"
36+
\* "*"
37+
\( "("
38+
\) ")"
39+
[\t ]+ ;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
vname: Test basic user actions using the calculator grammar
2+
grammar: |
3+
%grmtools {yacckind: Original(UserAction), recoverer: RecoveryKind::None}
4+
%start Expr
5+
%actiontype Result<u64, ()>
6+
%avoid_insert 'INT'
7+
%%
8+
Expr: Expr '+' Term { Ok($1? + $3?) }
9+
| Term { $1 }
10+
;
11+
12+
Term: Term '*' Factor { Ok($1? * $3?) }
13+
| Factor { $1 }
14+
;
15+
16+
Factor: '(' Expr ')' { $2 }
17+
| 'INT' {
18+
let l = $1.map_err(|_| ())?;
19+
match $lexer.span_str(l.span()).parse::<u64>() {
20+
Ok(v) => Ok(v),
21+
Err(_) => {
22+
let ((_, col), _) = $lexer.line_col(l.span());
23+
eprintln!("Error at column {}: '{}' cannot be represented as a u64",
24+
col,
25+
$lexer.span_str(l.span()));
26+
Err(())
27+
}
28+
}
29+
}
30+
;
31+
32+
lexer: |
33+
%%
34+
[0-9]+ "INT"
35+
\+ "+"
36+
\* "*"
37+
\( "("
38+
\) ")"
39+
[\t ]+ ;

lrpar/cttests/src/cgen_helper.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use cfgrammar::yacc::{YaccKind, YaccOriginalActionKind};
22
use lrlex::{CTLexerBuilder, DefaultLexerTypes};
3-
use lrpar::CTParserBuilder;
3+
use lrpar::{CTParserBuilder, RecoveryKind};
44
use std::{
55
env, fs,
66
path::{Path, PathBuf},
@@ -32,6 +32,11 @@ pub(crate) fn run_test_path<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn std::
3232
Some(s) => panic!("YaccKind '{}' not supported", s),
3333
None => None,
3434
};
35+
let recoverer = match docs[0]["revoverer"].as_str() {
36+
Some("RecoveryKind::CPCTPlus") => Some(RecoveryKind::CPCTPlus),
37+
Some("RecoveryKind::None") => Some(RecoveryKind::None),
38+
_ => None,
39+
};
3540
let (negative_yacc_flags, positive_yacc_flags) = &docs[0]["yacc_flags"]
3641
.as_vec()
3742
.map(|flags_vec| {
@@ -93,6 +98,9 @@ pub(crate) fn run_test_path<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn std::
9398
if let Some(yacckind) = yacckind {
9499
cp_build = cp_build.yacckind(yacckind);
95100
}
101+
if let Some(recoverer) = recoverer {
102+
cp_build = cp_build.recoverer(recoverer)
103+
}
96104
cp_build = cp_build
97105
.grammar_path(pg.to_str().unwrap())
98106
.output_path(&outp);

lrpar/src/lib/ctbuilder.rs

Lines changed: 132 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,16 @@ where
235235
grammar_path: Option<PathBuf>,
236236
output_path: Option<PathBuf>,
237237
mod_name: Option<&'a str>,
238-
recoverer: RecoveryKind,
238+
recoverer: Option<RecoveryKind>,
239239
yacckind: Option<YaccKind>,
240240
error_on_conflicts: bool,
241241
warnings_are_errors: bool,
242242
show_warnings: bool,
243243
visibility: Visibility,
244244
rust_edition: RustEdition,
245+
// test function for inspecting private state
246+
#[cfg(test)]
247+
inspect_callback: Option<Box<dyn Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>>,
245248
phantom: PhantomData<LexerTypesT>,
246249
}
247250

@@ -279,13 +282,15 @@ where
279282
grammar_path: None,
280283
output_path: None,
281284
mod_name: None,
282-
recoverer: RecoveryKind::CPCTPlus,
285+
recoverer: None,
283286
yacckind: None,
284287
error_on_conflicts: true,
285288
warnings_are_errors: true,
286289
show_warnings: true,
287290
visibility: Visibility::Private,
288291
rust_edition: RustEdition::Rust2021,
292+
#[cfg(test)]
293+
inspect_callback: None,
289294
phantom: PhantomData,
290295
}
291296
}
@@ -378,7 +383,7 @@ where
378383

379384
/// Set the recoverer for this parser to `rk`. Defaults to `RecoveryKind::CPCTPlus`.
380385
pub fn recoverer(mut self, rk: RecoveryKind) -> Self {
381-
self.recoverer = rk;
386+
self.recoverer = Some(rk);
382387
self
383388
}
384389

@@ -416,6 +421,15 @@ where
416421
self
417422
}
418423

424+
#[cfg(test)]
425+
pub fn inspect_recoverer(
426+
mut self,
427+
cb: Box<dyn for<'h, 'y> Fn(RecoveryKind) -> Result<(), Box<dyn Error>>>,
428+
) -> Self {
429+
self.inspect_callback = Some(cb);
430+
self
431+
}
432+
419433
/// Statically compile the Yacc file specified by [CTParserBuilder::grammar_path()] into Rust,
420434
/// placing the output into the file spec [CTParserBuilder::output_path()]. Note that three
421435
/// additional files will be created with the same name as specified in [self.output_path] but
@@ -494,6 +508,18 @@ where
494508
}
495509
},
496510
}
511+
if let Some(recoverer) = self.recoverer {
512+
match header.entry("recoverer".to_string()) {
513+
Entry::Occupied(_) => unreachable!(),
514+
Entry::Vacant(v) => {
515+
let rk_value: Value = Value::try_from(recoverer)?;
516+
let mut o =
517+
v.insert_entry((Location::Other("CTParserBuilder".to_string()), rk_value));
518+
o.set_merge_behavior(MergeBehavior::Ours);
519+
}
520+
}
521+
}
522+
497523
{
498524
let mut lk = GENERATED_PATHS.lock().unwrap();
499525
if lk.contains(outp.as_path()) {
@@ -505,18 +531,16 @@ where
505531
let inc =
506532
read_to_string(grmp).map_err(|e| format!("When reading '{}': {e}", grmp.display()))?;
507533
let ast_validation = ASTWithValidityInfo::new(&mut header, &inc);
508-
let unused_keys = header.unused();
509-
if !unused_keys.is_empty() {
510-
return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into());
511-
}
512-
let missing_keys = header.missing();
513-
if !missing_keys.is_empty() {
514-
return Err(format!(
515-
"Required values were missing from the header: {}",
516-
unused_keys.join(", ")
517-
)
518-
.into());
534+
header.mark_used(&"recoverer".to_string());
535+
let rk_val = header.get("recoverer").map(|(_, rk_val)| rk_val);
536+
537+
if let Some(rk_val) = rk_val {
538+
self.recoverer = Some(RecoveryKind::try_from(rk_val)?);
539+
} else {
540+
// Fallback to the default recoverykind.
541+
self.recoverer = Some(RecoveryKind::CPCTPlus);
519542
}
543+
520544
self.yacckind = ast_validation.yacc_kind();
521545
let warnings = ast_validation.ast().warnings();
522546
let loc_fmt = |err_str, loc, inc: &str, line_cache: &NewlineCache| match loc {
@@ -602,6 +626,24 @@ where
602626
}
603627
};
604628

629+
#[cfg(test)]
630+
if let Some(cb) = &self.inspect_callback {
631+
cb(self.recoverer.expect("has a default value"))?;
632+
}
633+
634+
let unused_keys = header.unused();
635+
if !unused_keys.is_empty() {
636+
return Err(format!("Unused keys in header: {}", unused_keys.join(", ")).into());
637+
}
638+
let missing_keys = header.missing();
639+
if !missing_keys.is_empty() {
640+
return Err(format!(
641+
"Required values were missing from the header: {}",
642+
unused_keys.join(", ")
643+
)
644+
.into());
645+
}
646+
605647
let rule_ids = grm
606648
.tokens_map()
607649
.iter()
@@ -797,6 +839,8 @@ where
797839
show_warnings: self.show_warnings,
798840
visibility: self.visibility.clone(),
799841
rust_edition: self.rust_edition,
842+
#[cfg(test)]
843+
inspect_callback: None,
800844
phantom: PhantomData,
801845
};
802846
Ok(cl.build()?.rule_ids)
@@ -873,7 +917,7 @@ where
873917
// rustc forces a recompile, this will change this value, causing anything which depends on
874918
// this build of lrpar to be recompiled too.
875919
let Self {
876-
// All variables except for `output_path` and `phantom` should
920+
// All variables except for `output_path`, `inspect_callback` and `phantom` should
877921
// be written into the cache.
878922
grammar_path,
879923
mod_name,
@@ -885,6 +929,8 @@ where
885929
show_warnings,
886930
visibility,
887931
rust_edition,
932+
#[cfg(test)]
933+
inspect_callback: _,
888934
phantom: _,
889935
} = self;
890936
let build_time = env!("VERGEN_BUILD_TIMESTAMP");
@@ -1573,4 +1619,75 @@ C : 'a';"
15731619
}
15741620
}
15751621
}
1622+
1623+
#[cfg(test)]
1624+
#[test]
1625+
fn test_recoverer_header() -> Result<(), Box<dyn std::error::Error>> {
1626+
use crate::RecoveryKind as RK;
1627+
#[rustfmt::skip]
1628+
let recovery_kinds = [
1629+
// Builder, Header setting, Expected result.
1630+
// ----------- ------------------ -------------------
1631+
(Some(RK::None), Some(RK::None), Some(RK::None)),
1632+
(Some(RK::None), Some(RK::CPCTPlus), Some(RK::None)),
1633+
(Some(RK::CPCTPlus), Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1634+
(Some(RK::CPCTPlus), Some(RK::None), Some(RK::CPCTPlus)),
1635+
(None, Some(RK::CPCTPlus), Some(RK::CPCTPlus)),
1636+
(None, Some(RK::None), Some(RK::None)),
1637+
(None, None, Some(RK::CPCTPlus)),
1638+
(Some(RK::None), None, Some(RK::None)),
1639+
(Some(RK::CPCTPlus), None, Some(RK::CPCTPlus)),
1640+
];
1641+
1642+
for (i, (builder_arg, header_arg, expected_rk)) in
1643+
recovery_kinds.iter().cloned().enumerate()
1644+
{
1645+
let y_src = if let Some(header_arg) = header_arg {
1646+
format!(
1647+
"\
1648+
%grmtools{{yacckind: Original(NoAction), recoverer: {}}} \
1649+
%% \
1650+
start: ; \
1651+
",
1652+
match header_arg {
1653+
RK::None => "RecoveryKind::None",
1654+
RK::CPCTPlus => "RecoveryKind::CPCTPlus",
1655+
}
1656+
)
1657+
} else {
1658+
r#"
1659+
%grmtools{yacckind: Original(NoAction)}
1660+
%%
1661+
Start: ;
1662+
"#
1663+
.to_string()
1664+
};
1665+
let out_dir = std::env::var("OUT_DIR").unwrap();
1666+
let y_path = format!("{out_dir}/recoverykind_test_{i}.y");
1667+
let y_out_path = format!("{y_path}.rs");
1668+
std::fs::File::create(y_path.clone()).unwrap();
1669+
std::fs::write(y_path.clone(), y_src).unwrap();
1670+
let mut cp_builder = CTParserBuilder::<TestLexerTypes>::new();
1671+
cp_builder = cp_builder
1672+
.output_path(y_out_path.clone())
1673+
.grammar_path(y_path.clone());
1674+
cp_builder = if let Some(builder_arg) = builder_arg {
1675+
cp_builder.recoverer(builder_arg)
1676+
} else {
1677+
cp_builder
1678+
}
1679+
.inspect_recoverer(Box::new(move |rk| {
1680+
if matches!(
1681+
(rk, expected_rk),
1682+
(RK::None, Some(RK::None)) | (RK::CPCTPlus, Some(RK::CPCTPlus))
1683+
) {
1684+
Ok(())
1685+
} else {
1686+
panic!("Unexpected recovery kind")
1687+
}
1688+
}));
1689+
cp_builder.build()?;
1690+
}
1691+
Ok(())
1692+
}
15761693
}

0 commit comments

Comments
 (0)