Add further static validation of named precedences

This commit is contained in:
Max Brunsfeld 2021-02-25 11:54:21 -08:00
parent 344797c110
commit d8a235faa1

View file

@ -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<String>]) -> 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<String>]) -> 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) = &params.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::<HashSet<&String>>();
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'",
);
}
}