Skip to content

Commit c334e52

Browse files
committed
Move action '$' variable checks to complete_and_validate
1 parent 27572b3 commit c334e52

3 files changed

Lines changed: 99 additions & 30 deletions

File tree

cfgrammar/src/lib/yacc/ast.rs

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ impl GrammarAST {
315315
/// 6) If `yacc_kind` is specified, perform any kind specific validation.
316316
/// * If the kind requires an action type, check that each rule has one
317317
/// * That each production has action code
318+
/// * That `$` variables referred to in action code are recognised.
318319
pub(crate) fn complete_and_validate(
319320
&mut self,
320321
yacc_kind: Option<YaccKind>,
@@ -349,12 +350,40 @@ impl GrammarAST {
349350
}
350351
for &pidx in &rule.pidxs {
351352
let prod = &self.prods[pidx];
352-
if kind_requires_action_checks && prod.action.is_none() {
353-
return Err(YaccGrammarError {
354-
kind: YaccGrammarErrorKind::MissingActionCode,
355-
spans: vec![prod.prod_span]
356-
});
353+
if kind_requires_action_checks {
354+
if let Some((action_code, action_span)) = prod.action.as_ref() {
355+
let mut last = 0;
356+
while let Some(off) = action_code[last..].find('$') {
357+
if !(action_code[last + off..].starts_with("$$")
358+
|| action_code[last + off..].starts_with("$lexer")
359+
|| action_code[last + off..].starts_with("$span")
360+
|| (last + off + 1 < action_code.len()
361+
&& action_code[last + off + 1..]
362+
.starts_with(|c: char| c.is_numeric())))
363+
{
364+
// Starting from the `$` find the end of a variable name, otherwise default to the span of the `$`
365+
let m = crate::yacc::parser::RE_NAME
366+
.find(&action_code[last + off + 1..]);
367+
let var_start_pos = action_span.start() + last + off;
368+
let var_end_pos = m
369+
.map(|m| var_start_pos + 1 + m.end())
370+
.unwrap_or(var_start_pos + 1);
371+
return Err(YaccGrammarError {
372+
kind: YaccGrammarErrorKind::UnrecognisedActionVariable,
373+
spans: vec![Span::new(var_start_pos, var_end_pos)],
374+
});
375+
} else {
376+
last = last + off + 2;
377+
}
378+
}
379+
} else {
380+
return Err(YaccGrammarError {
381+
kind: YaccGrammarErrorKind::MissingActionCode,
382+
spans: vec![prod.prod_span],
383+
});
384+
}
357385
}
386+
358387
if let Some(ref n) = prod.precedence {
359388
if !self.tokens.contains(n) {
360389
return Err(YaccGrammarError {
@@ -905,4 +934,56 @@ start: "a" { };
905934
})
906935
);
907936
}
937+
938+
#[test]
939+
fn test_unrecognized_action_variable() {
940+
use super::*;
941+
let ast_validity = ASTWithValidityInfo::new(
942+
YaccKind::Grmtools,
943+
r#"
944+
%token a
945+
%%
946+
start -> () : "a" { $foo; };
947+
"#,
948+
);
949+
assert_eq!(
950+
ast_validity.errors(),
951+
vec![YaccGrammarError {
952+
kind: YaccGrammarErrorKind::UnrecognisedActionVariable,
953+
spans: vec![Span::new(33, 37)],
954+
}]
955+
);
956+
957+
let ast_validity = ASTWithValidityInfo::new(
958+
YaccKind::Grmtools,
959+
r#"
960+
%token a
961+
%%
962+
start -> () : "a" {$};
963+
"#,
964+
);
965+
assert_eq!(
966+
ast_validity.errors(),
967+
vec![YaccGrammarError {
968+
kind: YaccGrammarErrorKind::UnrecognisedActionVariable,
969+
spans: vec![Span::new(32, 33)],
970+
}]
971+
);
972+
973+
let ast_validity = ASTWithValidityInfo::new(
974+
YaccKind::Grmtools,
975+
r#"
976+
%token a
977+
%%
978+
start -> () : "a" {$;;;; };
979+
"#,
980+
);
981+
assert_eq!(
982+
ast_validity.errors(),
983+
vec![YaccGrammarError {
984+
kind: YaccGrammarErrorKind::UnrecognisedActionVariable,
985+
spans: vec![Span::new(32, 33)],
986+
}]
987+
);
988+
}
908989
}

cfgrammar/src/lib/yacc/parser.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub enum YaccGrammarErrorKind {
5959
InvalidString,
6060
NoStartRule,
6161
UnknownSymbol,
62+
UnrecognisedActionVariable,
6263
InvalidStartRule(String),
6364
UnknownRuleRef(String),
6465
UnknownToken(String),
@@ -111,6 +112,9 @@ impl fmt::Display for YaccGrammarErrorKind {
111112
YaccGrammarErrorKind::UnknownDeclaration => "Unknown declaration",
112113
YaccGrammarErrorKind::DuplicatePrecedence => "Token has multiple precedences specified",
113114
YaccGrammarErrorKind::PrecNotFollowedByToken => "%prec not followed by token name",
115+
YaccGrammarErrorKind::UnrecognisedActionVariable => {
116+
"Unrecognised action variable following '$'"
117+
}
114118
YaccGrammarErrorKind::DuplicateAvoidInsertDeclaration => {
115119
"Duplicated %avoid_insert declaration"
116120
}
@@ -264,6 +268,7 @@ impl Spanned for YaccGrammarError {
264268
| YaccGrammarErrorKind::InvalidString
265269
| YaccGrammarErrorKind::NoStartRule
266270
| YaccGrammarErrorKind::UnknownSymbol
271+
| YaccGrammarErrorKind::UnrecognisedActionVariable
267272
| YaccGrammarErrorKind::InvalidStartRule(_)
268273
| YaccGrammarErrorKind::UnknownRuleRef(_)
269274
| YaccGrammarErrorKind::UnknownToken(_)
@@ -292,7 +297,7 @@ pub(crate) struct YaccParser<'a> {
292297
global_actiontype: Option<(String, Span)>,
293298
}
294299

295-
static RE_NAME: LazyLock<Regex> =
300+
pub(crate) static RE_NAME: LazyLock<Regex> =
296301
LazyLock::new(|| Regex::new(r"^[a-zA-Z_.][a-zA-Z0-9_.]*").unwrap());
297302
static RE_TOKEN: LazyLock<Regex> =
298303
LazyLock::new(|| Regex::new("^(?:(\".+?\")|('.+?')|([a-zA-Z_][a-zA-Z_0-9]*))").unwrap());
@@ -848,7 +853,7 @@ impl YaccParser<'_> {
848853
Err(self.mk_error(YaccGrammarErrorKind::IncompleteAction, i))
849854
} else {
850855
debug_assert!(self.lookahead_is("}", j).is_some());
851-
let s = self.src[i + '{'.len_utf8()..j].trim().to_string();
856+
let s = self.src[i + '{'.len_utf8()..j].to_string();
852857
Ok((j + '}'.len_utf8(), s))
853858
}
854859
}
@@ -2293,12 +2298,12 @@ x"
22932298
",
22942299
)
22952300
.unwrap();
2296-
let action_str = "println!(\"test\");".to_string();
2301+
let action_str = " println!(\"test\"); ".to_string();
22972302
assert_eq!(
22982303
grm.prods[grm.rules["A"].pidxs[0]].action,
22992304
Some((action_str.clone(), Span::new(34, 34 + action_str.len())))
23002305
);
2301-
let action_str = "add($1, $2);".to_string();
2306+
let action_str = " add($1, $2); ".to_string();
23022307
assert_eq!(
23032308
grm.prods[grm.rules["B"].pidxs[0]].action,
23042309
Some((action_str.clone(), Span::new(90, 90 + action_str.len())))

lrpar/src/lib/ctbuilder.rs

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::{
2323
use crate::unstable_api::UnstableApi;
2424

2525
use cfgrammar::{
26-
Location, RIdx, Span, Symbol,
26+
Location, RIdx, Symbol,
2727
header::{
2828
GrmtoolsSectionParser, Header, HeaderError, HeaderErrorKind, HeaderValue, Namespaced,
2929
Setting, Value,
@@ -954,7 +954,6 @@ where
954954
&derived_mod_name,
955955
outp,
956956
&format!("/* CACHE INFORMATION {} */\n", cache),
957-
&yacc_diag,
958957
)?;
959958
let conflicts = if stable.conflicts().is_some() {
960959
Some((sgraph, stable))
@@ -1083,14 +1082,13 @@ where
10831082
mod_name: &str,
10841083
outp_rs: P,
10851084
cache: &str,
1086-
diag: &SpannedDiagnosticFormatter,
10871085
) -> Result<(), Box<dyn Error>> {
10881086
let visibility = self.visibility.clone();
10891087
let user_actions = if let Some(
10901088
YaccKind::Original(YaccOriginalActionKind::UserAction) | YaccKind::Grmtools,
10911089
) = self.yacckind
10921090
{
1093-
Some(self.gen_user_actions(grm, diag)?)
1091+
Some(self.gen_user_actions(grm)?)
10941092
} else {
10951093
None
10961094
};
@@ -1611,11 +1609,7 @@ where
16111609
}
16121610

16131611
/// Generate the user action functions (if any).
1614-
fn gen_user_actions(
1615-
&self,
1616-
grm: &YaccGrammar<StorageT>,
1617-
diag: &SpannedDiagnosticFormatter,
1618-
) -> Result<TokenStream, Box<dyn Error>> {
1612+
fn gen_user_actions(&self, grm: &YaccGrammar<StorageT>) -> Result<TokenStream, Box<dyn Error>> {
16191613
let programs = grm
16201614
.programs()
16211615
.as_ref()
@@ -1714,18 +1708,7 @@ where
17141708
write!(outs, "{prefix}arg_", prefix = ACTION_PREFIX).ok();
17151709
last = last + off + "$".len();
17161710
} else {
1717-
let span = grm.action_span(pidx).unwrap();
1718-
let inner_span =
1719-
Span::new(span.start() + last + off + "$".len(), span.end());
1720-
let mut s = String::from("\n");
1721-
s.push_str(&diag.file_location_msg("Error", Some(inner_span)));
1722-
s.push('\n');
1723-
s.push_str(&diag.underline_span_with_text(
1724-
inner_span,
1725-
"Unknown text following '$'".to_string(),
1726-
'^',
1727-
));
1728-
return Err(ErrorString(s).into());
1711+
unreachable!("action variables checked during complete_and_validate");
17291712
}
17301713
}
17311714
None => {

0 commit comments

Comments
 (0)