Skip to main content

muffy_validation_macro/
lib.rs

1//! Macros for document validation.
2
3extern crate alloc;
4
5mod attribute;
6mod compiler;
7mod content;
8mod definition;
9mod error;
10mod name;
11mod pattern;
12
13use self::{
14    attribute::AttributeSet,
15    compiler::Compiler,
16    content::{children, generate_content},
17    definition::load_definitions,
18    error::MacroError,
19    name::class_names,
20    pattern::Pattern,
21};
22use alloc::collections::BTreeMap;
23use itertools::Itertools;
24use muffy_rnc::{NameClass, Pattern as RncPattern};
25use proc_macro::TokenStream;
26use proc_macro2::Span;
27use quote::{format_ident, quote};
28
29/// Generates HTML validation functions.
30#[proc_macro]
31pub fn html(_input: TokenStream) -> TokenStream {
32    generate_validation("html", &["html.rnc"]).unwrap_or_else(|error| {
33        syn::Error::new(Span::call_site(), error)
34            .to_compile_error()
35            .into()
36    })
37}
38
39fn generate_validation(language: &str, files: &[&str]) -> Result<TokenStream, MacroError> {
40    let definitions = load_definitions(files)?;
41    let mut compiler = Compiler::new(&definitions);
42    let mut element_rules = BTreeMap::<String, Vec<(Vec<AttributeSet>, Pattern)>>::new();
43
44    for definition in definitions.values() {
45        for (name_class, pattern) in collect_elements(definition) {
46            // Elements of any names constrain only content models of their parents.
47            // TODO Support position-aware validation to exempt descendants of
48            // elements with unconstrained content models from name validation.
49            let names = class_names(name_class)
50                .into_iter()
51                .filter(|name| name != "*")
52                .collect::<Vec<_>>();
53
54            if names.is_empty() {
55                continue;
56            }
57
58            for (attribute_sets, content_pattern) in compiler.compile(pattern)? {
59                for name in &names {
60                    let variants = element_rules.entry(name.clone()).or_default();
61                    let variant = (attribute_sets.clone(), content_pattern.clone());
62
63                    if !variants.contains(&variant) {
64                        variants.push(variant);
65                    }
66                }
67            }
68        }
69    }
70
71    let mut attribute_set_indexes = BTreeMap::<Vec<AttributeSet>, usize>::new();
72    let mut content_indexes = BTreeMap::<Pattern, usize>::new();
73    let mut element_matches = vec![];
74    let mut wildcard_matches = vec![];
75
76    for (name, variants) in &element_rules {
77        let attributes = variants
78            .iter()
79            .flat_map(|(sets, _)| sets)
80            .flat_map(|set| set.required.iter().chain(&set.optional))
81            .unique()
82            .sorted()
83            .map(|name| quote!(#name));
84        let children = variants
85            .iter()
86            .flat_map(|(_, content)| children(content))
87            .unique()
88            .sorted()
89            .map(|name| quote!(#name));
90
91        let variants = variants
92            .iter()
93            .map(|(sets, content)| {
94                let index = attribute_set_indexes.len();
95                let sets = format_ident!(
96                    "ATTRIBUTE_SETS_{}",
97                    *attribute_set_indexes.entry(sets.clone()).or_insert(index)
98                );
99
100                let index = content_indexes.len();
101                let content = format_ident!(
102                    "CONTENT_{}",
103                    *content_indexes.entry(content.clone()).or_insert(index)
104                );
105
106                quote!(Variant { attributes: #sets, content: &#content })
107            })
108            .collect::<Vec<_>>();
109
110        let rule = quote! {
111            {
112                const RULE: Rule = Rule {
113                    attributes: &[#(#attributes),*],
114                    children: &[#(#children),*],
115                    variants: &[#(#variants),*],
116                };
117
118                validate_rule(element, ignored_attributes, ignored_elements, &RULE)
119            }
120        };
121
122        if let Some(prefix) = name.strip_suffix('*') {
123            wildcard_matches.push(quote! { name if name.starts_with(#prefix) => #rule });
124        } else {
125            element_matches.push(quote! { #name => #rule });
126        }
127    }
128
129    let attribute_set_definitions = sort_by_index(attribute_set_indexes).map(|(sets, index)| {
130        let identifier = format_ident!("ATTRIBUTE_SETS_{index}");
131        let sets = sets.iter().map(|set| {
132            let required = set.required.iter().map(|name| quote!(#name));
133            let optional = set.optional.iter().map(|name| quote!(#name));
134
135            quote!(AttributeSet {
136                required: &[#(#required),*],
137                optional: &[#(#optional),*],
138            })
139        });
140
141        quote!(const #identifier: &[AttributeSet] = &[#(#sets),*];)
142    });
143    let content_definitions = sort_by_index(content_indexes)
144        .map(|(content, index)| {
145            let identifier = format_ident!("CONTENT_{index}");
146            let content = generate_content(&content)?;
147
148            Ok(quote!(const #identifier: Content = #content;))
149        })
150        .collect::<Result<Vec<_>, MacroError>>()?;
151
152    let function_name = format_ident!("validate_{language}_element");
153    let documentation = format!("Validates an {} element.", language.to_uppercase());
154
155    Ok(quote! {
156        #[doc = #documentation]
157        pub fn #function_name(
158            element: &Element,
159            ignored_attributes: &[::regex::Regex],
160            ignored_elements: &[::regex::Regex],
161        ) -> Result<(), MarkupError> {
162            #(#attribute_set_definitions)*
163            #(#content_definitions)*
164
165            match element.name() {
166                name if ignored_elements.iter().any(|pattern| pattern.is_match(name)) => Ok(()),
167                #(#element_matches)*
168                #(#wildcard_matches)*
169                _ => Err(MarkupError::UnknownTag(element.name().to_string())),
170            }
171        }
172    }
173    .into())
174}
175
176fn sort_by_index<T>(indexes: BTreeMap<T, usize>) -> impl Iterator<Item = (T, usize)> {
177    let mut entries = indexes.into_iter().collect::<Vec<_>>();
178
179    entries.sort_by_key(|(_, index)| *index);
180
181    entries.into_iter()
182}
183
184// TODO Skip element definitions gated by not-allowed flag conjuncts.
185fn collect_elements(pattern: &RncPattern) -> Vec<(&NameClass, &RncPattern)> {
186    match pattern {
187        RncPattern::Element {
188            name_class,
189            pattern,
190        } => vec![(name_class, pattern)],
191        RncPattern::Choice(patterns)
192        | RncPattern::Group(patterns)
193        | RncPattern::Interleave(patterns) => patterns.iter().flat_map(collect_elements).collect(),
194        RncPattern::Many0(pattern) | RncPattern::Many1(pattern) | RncPattern::Optional(pattern) => {
195            collect_elements(pattern)
196        }
197        RncPattern::Attribute { .. }
198        | RncPattern::Data { .. }
199        | RncPattern::Empty
200        | RncPattern::External(_)
201        | RncPattern::Grammar(_)
202        | RncPattern::List(_)
203        | RncPattern::Name(_)
204        | RncPattern::NotAllowed
205        | RncPattern::Text
206        | RncPattern::Value { .. } => vec![],
207    }
208}