1extern crate alloc;
4
5mod error;
6
7use self::error::MacroError;
8use alloc::collections::{BTreeMap, BTreeSet};
9use core::mem::replace;
10use muffy_rnc::{
11 Combine, Grammar, GrammarContent, Identifier, NameClass, Pattern, SchemaBody, parse_schema,
12};
13use proc_macro::TokenStream;
14use proc_macro2::Span;
15use quote::quote;
16use std::{fs::read_to_string, path::Path};
17
18#[proc_macro]
20pub fn html(_input: TokenStream) -> TokenStream {
21 generate_html().unwrap_or_else(|error| {
22 syn::Error::new(Span::call_site(), error)
23 .to_compile_error()
24 .into()
25 })
26}
27
28fn generate_html() -> Result<TokenStream, MacroError> {
29 let mut definitions = Default::default();
30
31 for file in ["html5.rnc", "rdfa.rnc"] {
32 load_schema(
33 &Path::new(env!("CARGO_MANIFEST_DIR"))
34 .join("src")
35 .join("schema")
36 .join("html5")
37 .join(file),
38 &mut definitions,
39 )?;
40 }
41
42 let mut element_rules = BTreeMap::<String, (Vec<String>, Vec<String>)>::new();
44
45 for pattern in definitions.values() {
46 let Pattern::Element { name_class, .. } = pattern else {
47 continue;
48 };
49 let Some(element) = get_name(name_class) else {
50 continue;
51 };
52
53 if let Pattern::Element { pattern, .. } = pattern {
54 let (attributes, children) = element_rules
55 .entry(element)
56 .or_insert_with(|| (vec![], vec![]));
57
58 attributes.extend(collect_attributes(pattern, &definitions)?);
59 children.extend(collect_children(pattern, &definitions)?);
60 }
61 }
62
63 let mut element_matches = vec![];
64
65 for (element, (mut attributes, mut children)) in element_rules {
66 attributes.sort();
67 attributes.dedup();
68 children.sort();
69 children.dedup();
70
71 let attributes = attributes.iter().map(|attribute| quote!(#attribute));
72 let children = children.iter().map(|child| quote!(#child));
73
74 element_matches.push(quote! {
75 #element => {
76 let mut attributes = ::alloc::collections::BTreeMap::<
77 String,
78 ::alloc::collections::BTreeSet<AttributeError>,
79 >::new();
80
81 for (attribute, _) in element.attributes() {
82 match attribute {
83 #(#attributes |)* "_DUMMY_" => {}
84 _ => {
85 attributes
86 .entry(attribute.into())
87 .or_insert_with(Default::default)
88 .insert(AttributeError::NotAllowed);
89 }
90 }
91 }
92
93 let mut children = ::alloc::collections::BTreeMap::<
94 String,
95 ::alloc::collections::BTreeSet<ChildError>,
96 >::new();
97
98 for child in element.children() {
99 if let muffy_document::html::Node::Element(child_element) = child {
100 let child_name = child_element.name();
101
102 match child_name {
103 #(#children |)* "_DUMMY_" => {}
104 _ => {
105 children
106 .entry(child_name.into())
107 .or_insert_with(Default::default)
108 .insert(ChildError::NotAllowed);
109 }
110 }
111 }
112 }
113
114 if attributes.is_empty() && children.is_empty() {
115 Ok(())
116 } else {
117 Err(ValidationError::InvalidElement {
118 attributes,
119 children,
120 })
121 }
122 }
123 });
124 }
125
126 Ok(quote! {
127 pub fn validate_element(element: &Element) -> Result<(), ValidationError> {
129 match element.name() {
130 #(#element_matches)*
131 _ => Err(ValidationError::UnknownTag(element.name().to_string())),
132 }
133 }
134 }
135 .into())
136}
137
138fn load_schema(
139 path: &Path,
140 definitions: &mut BTreeMap<Identifier, Pattern>,
141) -> Result<(), MacroError> {
142 let schema = parse_schema(&read_to_string(path)?)?;
143
144 match schema.body {
147 SchemaBody::Grammar(grammar) => {
148 load_grammar(
149 &grammar,
150 definitions,
151 path.parent().ok_or(MacroError::NoParentDirectory)?,
152 )?;
153 }
154 SchemaBody::Pattern(_) => return Err(MacroError::RncSyntax("top-level pattern")),
155 }
156
157 Ok(())
158}
159
160fn load_grammar(
161 grammar: &Grammar,
162 definitions: &mut BTreeMap<Identifier, Pattern>,
163 directory: &Path,
164) -> Result<(), MacroError> {
165 for content in &grammar.contents {
166 match content {
167 GrammarContent::Definition(definition) => {
168 let name = definition.name.clone();
169 let pattern = definition.pattern.clone();
170
171 if let Some(combine) = definition.combine {
172 combine_patterns(
173 definitions.entry(name).or_insert(Pattern::NotAllowed),
174 pattern,
175 combine,
176 );
177 } else {
178 definitions.insert(name, pattern);
179 }
180 }
181 GrammarContent::Div(grammar) => load_grammar(grammar, definitions, directory)?,
182 GrammarContent::Include(include) => {
183 let include_path = directory.join(&include.uri);
184
185 load_schema(&include_path, definitions)?;
186
187 if let Some(grammar) = &include.grammar {
188 load_grammar(grammar, definitions, directory)?;
189 }
190 }
191 GrammarContent::Annotation(_) | GrammarContent::Start { .. } => {}
192 }
193 }
194
195 Ok(())
196}
197
198fn combine_patterns(existing: &mut Pattern, new: Pattern, combine: Combine) {
199 match combine {
200 Combine::Choice => match existing {
201 Pattern::Choice(choices) => choices.push(new),
202 Pattern::NotAllowed => *existing = new,
203 Pattern::Attribute { .. }
204 | Pattern::Data { .. }
205 | Pattern::Element { .. }
206 | Pattern::Empty
207 | Pattern::External(_)
208 | Pattern::Grammar(_)
209 | Pattern::Group(_)
210 | Pattern::Interleave(_)
211 | Pattern::List(_)
212 | Pattern::Many0(_)
213 | Pattern::Many1(_)
214 | Pattern::Name(_)
215 | Pattern::Optional(_)
216 | Pattern::Text
217 | Pattern::Value { .. } => {
218 let old = replace(existing, Pattern::Choice(vec![]));
219
220 if let Pattern::Choice(choices) = existing {
221 choices.push(old);
222 choices.push(new);
223 }
224 }
225 },
226 Combine::Interleave => match existing {
227 Pattern::Interleave(patterns) => patterns.push(new),
228 Pattern::NotAllowed => *existing = new,
229 Pattern::Attribute { .. }
230 | Pattern::Choice(_)
231 | Pattern::Data { .. }
232 | Pattern::Element { .. }
233 | Pattern::Empty
234 | Pattern::External(_)
235 | Pattern::Grammar(_)
236 | Pattern::Group(_)
237 | Pattern::List(_)
238 | Pattern::Many0(_)
239 | Pattern::Many1(_)
240 | Pattern::Name(_)
241 | Pattern::Optional(_)
242 | Pattern::Text
243 | Pattern::Value { .. } => {
244 let old = replace(existing, Pattern::Interleave(vec![]));
245
246 if let Pattern::Interleave(patterns) = existing {
247 patterns.push(old);
248 patterns.push(new);
249 }
250 }
251 },
252 }
253}
254
255fn get_name(name_class: &NameClass) -> Option<String> {
256 match name_class {
257 NameClass::Name(name) => Some(name.local.component.clone()),
258 NameClass::Choice(choices) => choices.iter().find_map(get_name),
259 NameClass::AnyName | NameClass::Except { .. } | NameClass::NamespaceName(_) => None,
260 }
261}
262
263fn collect_attributes(
264 pattern: &Pattern,
265 definitions: &BTreeMap<Identifier, Pattern>,
266) -> Result<BTreeSet<String>, MacroError> {
267 let mut attributes = Default::default();
268
269 collect_nested_attributes(
270 pattern,
271 definitions,
272 &mut attributes,
273 &mut Default::default(),
274 )?;
275
276 Ok(attributes)
277}
278
279fn collect_nested_attributes<'a>(
280 pattern: &'a Pattern,
281 definitions: &'a BTreeMap<Identifier, Pattern>,
282 attributes: &mut BTreeSet<String>,
283 visited: &mut BTreeSet<&'a Identifier>,
284) -> Result<(), MacroError> {
285 match pattern {
286 Pattern::Attribute { name_class, .. } => {
287 if let Some(name) = get_name(name_class) {
288 attributes.insert(name);
289 }
290 }
291 Pattern::Name(name) => {
292 if !visited.contains(&name.local) {
293 visited.insert(&name.local);
294
295 if let Some(pattern) = definitions.get(&name.local) {
296 collect_nested_attributes(pattern, definitions, attributes, visited)?;
297 }
298 }
299 }
300 Pattern::Choice(patterns) | Pattern::Group(patterns) | Pattern::Interleave(patterns) => {
301 for pattern in patterns {
302 collect_nested_attributes(pattern, definitions, attributes, visited)?;
303 }
304 }
305 Pattern::Many0(pattern) | Pattern::Many1(pattern) | Pattern::Optional(pattern) => {
306 collect_nested_attributes(pattern, definitions, attributes, visited)?;
307 }
308 Pattern::Data { .. } => return Err(MacroError::RncPattern("data")),
309 Pattern::External(_) => return Err(MacroError::RncPattern("external")),
310 Pattern::Grammar(_) => return Err(MacroError::RncPattern("grammar")),
311 Pattern::List { .. } => return Err(MacroError::RncPattern("list")),
312 Pattern::Value { .. } => return Err(MacroError::RncPattern("value")),
313 Pattern::Empty | Pattern::Element { .. } | Pattern::NotAllowed | Pattern::Text => {}
314 }
315
316 Ok(())
317}
318
319fn collect_children(
320 pattern: &Pattern,
321 definitions: &BTreeMap<Identifier, Pattern>,
322) -> Result<BTreeSet<String>, MacroError> {
323 let mut children = Default::default();
324
325 collect_nested_children(pattern, definitions, &mut children, &mut Default::default())?;
326
327 Ok(children)
328}
329
330fn collect_nested_children<'a>(
331 pattern: &'a Pattern,
332 definitions: &'a BTreeMap<Identifier, Pattern>,
333 children: &mut BTreeSet<String>,
334 visited: &mut BTreeSet<&'a Identifier>,
335) -> Result<(), MacroError> {
336 match pattern {
337 Pattern::Element { name_class, .. } => {
338 if let Some(name) = get_name(name_class) {
339 children.insert(name);
340 }
341 }
342 Pattern::Name(name) => {
343 if !visited.contains(&name.local) {
344 visited.insert(&name.local);
345
346 if let Some(pattern) = definitions.get(&name.local) {
347 collect_nested_children(pattern, definitions, children, visited)?;
348 }
349 }
350 }
351 Pattern::Choice(patterns) | Pattern::Group(patterns) | Pattern::Interleave(patterns) => {
352 for pattern in patterns {
353 collect_nested_children(pattern, definitions, children, visited)?;
354 }
355 }
356 Pattern::Many0(pattern) | Pattern::Many1(pattern) | Pattern::Optional(pattern) => {
357 collect_nested_children(pattern, definitions, children, visited)?;
358 }
359 Pattern::Data { .. } => return Err(MacroError::RncPattern("data")),
360 Pattern::External(_) => return Err(MacroError::RncPattern("external")),
361 Pattern::Grammar(_) => return Err(MacroError::RncPattern("grammar")),
362 Pattern::List { .. } => return Err(MacroError::RncPattern("list")),
363 Pattern::Value { .. } => return Err(MacroError::RncPattern("value")),
364 Pattern::Attribute { .. } | Pattern::Empty | Pattern::NotAllowed | Pattern::Text => {}
365 }
366
367 Ok(())
368}