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