Skip to content

Commit 05f25cd

Browse files
authored
Merge pull request #1852 from ehuss/fix-root-check
Fix recursive root-accessible grammar check
2 parents 5238a8f + 1113d80 commit 05f25cd

File tree

3 files changed

+30
-8
lines changed

3 files changed

+30
-8
lines changed

mdbook-spec/src/grammar.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,31 @@ fn check_undefined_nt(grammar: &Grammar, diag: &mut Diagnostics) {
195195
/// This is intended to help catch any unexpected misspellings, orphaned
196196
/// productions, or general mistakes.
197197
fn check_unexpected_roots(grammar: &Grammar, diag: &mut Diagnostics) {
198+
// `set` starts with every production name.
198199
let mut set: HashSet<_> = grammar.name_order.iter().map(|s| s.as_str()).collect();
199-
grammar.visit_nt(&mut |nt| {
200-
set.remove(nt);
201-
});
200+
fn remove(set: &mut HashSet<&str>, grammar: &Grammar, prod: &Production, root_name: &str) {
201+
prod.expression.visit_nt(&mut |nt| {
202+
// Leave the root name in the set if we find it recursively.
203+
if nt == root_name {
204+
return;
205+
}
206+
if !set.remove(nt) {
207+
return;
208+
}
209+
if let Some(nt_prod) = grammar.productions.get(nt) {
210+
remove(set, grammar, nt_prod, root_name);
211+
}
212+
});
213+
}
214+
// Walk the productions starting from the root nodes, and remove every
215+
// non-terminal from `set`. What's left must be the set of roots.
216+
grammar
217+
.productions
218+
.values()
219+
.filter(|prod| prod.is_root)
220+
.for_each(|root| {
221+
remove(&mut set, grammar, root, &root.name);
222+
});
202223
let expected: HashSet<_> = grammar
203224
.productions
204225
.values()
@@ -210,17 +231,18 @@ fn check_unexpected_roots(grammar: &Grammar, diag: &mut Diagnostics) {
210231
if !new.is_empty() {
211232
warn_or_err!(
212233
diag,
213-
"New grammar production detected that is not used in any other\n\
234+
"New grammar production detected that is not used in any root-accessible\n\
214235
production. If this is expected, mark the production with\n\
215236
`@root`. If not, make sure it is spelled correctly and used in\n\
216-
another production.\n\
237+
another root-accessible production.\n\
217238
\n\
218239
The new names are: {new:?}\n"
219240
);
220241
} else if !removed.is_empty() {
221242
warn_or_err!(
222243
diag,
223-
"Old grammar production root seems to have been removed.\n\
244+
"Old grammar production root seems to have been removed\n\
245+
(it is used in some other production that is root-accessible).\n\
224246
If this is expected, remove `@root` from the production.\n\
225247
\n\
226248
The removed names are: {removed:?}\n"

src/attributes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ attributes]. It has the following grammar:
115115

116116
r[attributes.meta.syntax]
117117
```grammar,attributes
118-
MetaItem ->
118+
@root MetaItem ->
119119
SimplePath
120120
| SimplePath `=` Expression
121121
| SimplePath `(` MetaSeq? `)`

src/comments.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ OUTER_BLOCK_DOC ->
3030
( BlockCommentOrDoc | ~[`*/` CR] )*
3131
`*/`
3232
33-
BlockCommentOrDoc ->
33+
@root BlockCommentOrDoc ->
3434
BLOCK_COMMENT
3535
| OUTER_BLOCK_DOC
3636
| INNER_BLOCK_DOC

0 commit comments

Comments
 (0)