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