Skip to content

Commit d8a235f

Browse files
committed
Add further static validation of named precedences
1 parent 344797c commit d8a235f

File tree

1 file changed

+133
-8
lines changed
  • cli/src/generate/prepare_grammar

1 file changed

+133
-8
lines changed

cli/src/generate/prepare_grammar/mod.rs

Lines changed: 133 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ mod flatten_grammar;
66
mod intern_symbols;
77
mod process_inlines;
88

9-
use super::Error;
9+
use super::{rules::Precedence, Error};
1010
use std::{
1111
cmp::Ordering,
12-
collections::{hash_map, HashMap},
12+
collections::{hash_map, HashMap, HashSet},
1313
mem,
1414
};
1515

@@ -57,7 +57,7 @@ pub(crate) fn prepare_grammar(
5757
InlinedProductionMap,
5858
AliasMap,
5959
)> {
60-
validate_precedence_orderings(&input_grammar.precedence_orderings)?;
60+
validate_named_precedences(input_grammar)?;
6161

6262
let interned_grammar = intern_symbols(input_grammar)?;
6363
let (syntax_grammar, lexical_grammar) = extract_tokens(interned_grammar)?;
@@ -69,12 +69,14 @@ pub(crate) fn prepare_grammar(
6969
Ok((syntax_grammar, lexical_grammar, inlines, default_aliases))
7070
}
7171

72-
/// Make sure that there are no conflicting orderings. For any two precedence
73-
/// names `a` and `b`, if `a` comes before `b` in some list, then it cannot come
74-
// *after* `b` in any list.
75-
fn validate_precedence_orderings(order_lists: &[Vec<String>]) -> Result<()> {
72+
/// Check that all of the named precedences used in the grammar are declared
73+
/// within the `precedences` lists, and also that there are no conflicting
74+
/// precedence orderings declared in those lists.
75+
fn validate_named_precedences(grammar: &InputGrammar) -> Result<()> {
76+
// For any two precedence names `a` and `b`, if `a` comes before `b`
77+
// in some list, then it cannot come *after* `b` in any list.
7678
let mut pairs = HashMap::new();
77-
for list in order_lists {
79+
for list in &grammar.precedence_orderings {
7880
for (i, mut name1) in list.iter().enumerate() {
7981
for mut name2 in list.iter().skip(i + 1) {
8082
if name2 == name1 {
@@ -101,5 +103,128 @@ fn validate_precedence_orderings(order_lists: &[Vec<String>]) -> Result<()> {
101103
}
102104
}
103105
}
106+
107+
// Check that no rule contains a named precedence that is not present in
108+
// any of the `precedences` lists.
109+
fn validate(rule_name: &str, rule: &Rule, names: &HashSet<&String>) -> Result<()> {
110+
match rule {
111+
Rule::Repeat(rule) => validate(rule_name, rule, names),
112+
Rule::Seq(elements) | Rule::Choice(elements) => elements
113+
.iter()
114+
.map(|e| validate(rule_name, e, names))
115+
.collect(),
116+
Rule::Metadata { rule, params } => {
117+
if let Precedence::Name(n) = &params.precedence {
118+
if !names.contains(n) {
119+
return Err(Error::new(format!(
120+
"Undeclared precedence '{}' in rule '{}'",
121+
n, rule_name
122+
)));
123+
}
124+
}
125+
validate(rule_name, rule, names)?;
126+
Ok(())
127+
}
128+
_ => Ok(()),
129+
}
130+
}
131+
132+
let precedence_names = grammar
133+
.precedence_orderings
134+
.iter()
135+
.flat_map(|l| l.iter())
136+
.collect::<HashSet<&String>>();
137+
for variable in &grammar.variables {
138+
validate(&variable.name, &variable.rule, &precedence_names)?;
139+
}
140+
104141
Ok(())
105142
}
143+
144+
#[cfg(test)]
145+
mod tests {
146+
use super::*;
147+
use crate::generate::grammars::{InputGrammar, Variable, VariableType};
148+
149+
#[test]
150+
fn test_validate_named_precedences_with_undeclared_precedence() {
151+
let grammar = InputGrammar {
152+
name: String::new(),
153+
word_token: None,
154+
extra_symbols: vec![],
155+
external_tokens: vec![],
156+
supertype_symbols: vec![],
157+
expected_conflicts: vec![],
158+
variables_to_inline: vec![],
159+
precedence_orderings: vec![
160+
vec!["a".to_string(), "b".to_string()],
161+
vec!["b".to_string(), "c".to_string(), "d".to_string()],
162+
],
163+
variables: vec![
164+
Variable {
165+
name: "v1".to_string(),
166+
kind: VariableType::Named,
167+
rule: Rule::Seq(vec![
168+
Rule::prec_left(Precedence::Name("b".to_string()), Rule::string("w")),
169+
Rule::prec(Precedence::Name("c".to_string()), Rule::string("x")),
170+
]),
171+
},
172+
Variable {
173+
name: "v2".to_string(),
174+
kind: VariableType::Named,
175+
rule: Rule::repeat(Rule::Choice(vec![
176+
Rule::prec_left(Precedence::Name("omg".to_string()), Rule::string("y")),
177+
Rule::prec(Precedence::Name("c".to_string()), Rule::string("z")),
178+
])),
179+
},
180+
],
181+
};
182+
183+
let result = validate_named_precedences(&grammar);
184+
assert_eq!(
185+
result.unwrap_err().message(),
186+
"Undeclared precedence 'omg' in rule 'v2'",
187+
);
188+
}
189+
190+
#[test]
191+
fn test_validate_named_precedences_with_conflicting_order() {
192+
let grammar = InputGrammar {
193+
name: String::new(),
194+
word_token: None,
195+
extra_symbols: vec![],
196+
external_tokens: vec![],
197+
supertype_symbols: vec![],
198+
expected_conflicts: vec![],
199+
variables_to_inline: vec![],
200+
precedence_orderings: vec![
201+
vec!["a".to_string(), "b".to_string()],
202+
vec!["b".to_string(), "c".to_string(), "a".to_string()],
203+
],
204+
variables: vec![
205+
Variable {
206+
name: "v1".to_string(),
207+
kind: VariableType::Named,
208+
rule: Rule::Seq(vec![
209+
Rule::prec_left(Precedence::Name("b".to_string()), Rule::string("w")),
210+
Rule::prec(Precedence::Name("c".to_string()), Rule::string("x")),
211+
]),
212+
},
213+
Variable {
214+
name: "v2".to_string(),
215+
kind: VariableType::Named,
216+
rule: Rule::repeat(Rule::Choice(vec![
217+
Rule::prec_left(Precedence::Name("a".to_string()), Rule::string("y")),
218+
Rule::prec(Precedence::Name("c".to_string()), Rule::string("z")),
219+
])),
220+
},
221+
],
222+
};
223+
224+
let result = validate_named_precedences(&grammar);
225+
assert_eq!(
226+
result.unwrap_err().message(),
227+
"Conflicting orderings for precedences 'a' and 'b'",
228+
);
229+
}
230+
}

0 commit comments

Comments
 (0)