diff --git a/cli/src/generate/prepare_grammar/mod.rs b/cli/src/generate/prepare_grammar/mod.rs index 963727c4..92683c41 100644 --- a/cli/src/generate/prepare_grammar/mod.rs +++ b/cli/src/generate/prepare_grammar/mod.rs @@ -6,10 +6,10 @@ mod flatten_grammar; mod intern_symbols; mod process_inlines; -use super::Error; +use super::{rules::Precedence, Error}; use std::{ cmp::Ordering, - collections::{hash_map, HashMap}, + collections::{hash_map, HashMap, HashSet}, mem, }; @@ -57,7 +57,7 @@ pub(crate) fn prepare_grammar( InlinedProductionMap, AliasMap, )> { - validate_precedence_orderings(&input_grammar.precedence_orderings)?; + validate_named_precedences(input_grammar)?; let interned_grammar = intern_symbols(input_grammar)?; let (syntax_grammar, lexical_grammar) = extract_tokens(interned_grammar)?; @@ -69,12 +69,14 @@ pub(crate) fn prepare_grammar( Ok((syntax_grammar, lexical_grammar, inlines, default_aliases)) } -/// Make sure that there are no conflicting orderings. For any two precedence -/// names `a` and `b`, if `a` comes before `b` in some list, then it cannot come -// *after* `b` in any list. -fn validate_precedence_orderings(order_lists: &[Vec]) -> Result<()> { +/// Check that all of the named precedences used in the grammar are declared +/// within the `precedences` lists, and also that there are no conflicting +/// precedence orderings declared in those lists. +fn validate_named_precedences(grammar: &InputGrammar) -> Result<()> { + // For any two precedence names `a` and `b`, if `a` comes before `b` + // in some list, then it cannot come *after* `b` in any list. let mut pairs = HashMap::new(); - for list in order_lists { + for list in &grammar.precedence_orderings { for (i, mut name1) in list.iter().enumerate() { for mut name2 in list.iter().skip(i + 1) { if name2 == name1 { @@ -101,5 +103,128 @@ fn validate_precedence_orderings(order_lists: &[Vec]) -> Result<()> { } } } + + // Check that no rule contains a named precedence that is not present in + // any of the `precedences` lists. + fn validate(rule_name: &str, rule: &Rule, names: &HashSet<&String>) -> Result<()> { + match rule { + Rule::Repeat(rule) => validate(rule_name, rule, names), + Rule::Seq(elements) | Rule::Choice(elements) => elements + .iter() + .map(|e| validate(rule_name, e, names)) + .collect(), + Rule::Metadata { rule, params } => { + if let Precedence::Name(n) = ¶ms.precedence { + if !names.contains(n) { + return Err(Error::new(format!( + "Undeclared precedence '{}' in rule '{}'", + n, rule_name + ))); + } + } + validate(rule_name, rule, names)?; + Ok(()) + } + _ => Ok(()), + } + } + + let precedence_names = grammar + .precedence_orderings + .iter() + .flat_map(|l| l.iter()) + .collect::>(); + for variable in &grammar.variables { + validate(&variable.name, &variable.rule, &precedence_names)?; + } + Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::generate::grammars::{InputGrammar, Variable, VariableType}; + + #[test] + fn test_validate_named_precedences_with_undeclared_precedence() { + let grammar = InputGrammar { + name: String::new(), + word_token: None, + extra_symbols: vec![], + external_tokens: vec![], + supertype_symbols: vec![], + expected_conflicts: vec![], + variables_to_inline: vec![], + precedence_orderings: vec![ + vec!["a".to_string(), "b".to_string()], + vec!["b".to_string(), "c".to_string(), "d".to_string()], + ], + variables: vec![ + Variable { + name: "v1".to_string(), + kind: VariableType::Named, + rule: Rule::Seq(vec![ + Rule::prec_left(Precedence::Name("b".to_string()), Rule::string("w")), + Rule::prec(Precedence::Name("c".to_string()), Rule::string("x")), + ]), + }, + Variable { + name: "v2".to_string(), + kind: VariableType::Named, + rule: Rule::repeat(Rule::Choice(vec![ + Rule::prec_left(Precedence::Name("omg".to_string()), Rule::string("y")), + Rule::prec(Precedence::Name("c".to_string()), Rule::string("z")), + ])), + }, + ], + }; + + let result = validate_named_precedences(&grammar); + assert_eq!( + result.unwrap_err().message(), + "Undeclared precedence 'omg' in rule 'v2'", + ); + } + + #[test] + fn test_validate_named_precedences_with_conflicting_order() { + let grammar = InputGrammar { + name: String::new(), + word_token: None, + extra_symbols: vec![], + external_tokens: vec![], + supertype_symbols: vec![], + expected_conflicts: vec![], + variables_to_inline: vec![], + precedence_orderings: vec![ + vec!["a".to_string(), "b".to_string()], + vec!["b".to_string(), "c".to_string(), "a".to_string()], + ], + variables: vec![ + Variable { + name: "v1".to_string(), + kind: VariableType::Named, + rule: Rule::Seq(vec![ + Rule::prec_left(Precedence::Name("b".to_string()), Rule::string("w")), + Rule::prec(Precedence::Name("c".to_string()), Rule::string("x")), + ]), + }, + Variable { + name: "v2".to_string(), + kind: VariableType::Named, + rule: Rule::repeat(Rule::Choice(vec![ + Rule::prec_left(Precedence::Name("a".to_string()), Rule::string("y")), + Rule::prec(Precedence::Name("c".to_string()), Rule::string("z")), + ])), + }, + ], + }; + + let result = validate_named_precedences(&grammar); + assert_eq!( + result.unwrap_err().message(), + "Conflicting orderings for precedences 'a' and 'b'", + ); + } +}