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