@@ -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}
0 commit comments