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