unidok_repr/into_ir/
macros.rs

1use std::{iter, mem};
2
3use detached_str::StrSlice;
4
5use crate::ast::macros::*;
6use crate::ast::AstData;
7use crate::config::HeadingAnchor;
8use crate::ir::blocks::{AnnBlock, Block};
9use crate::ir::html::HtmlNode;
10use crate::ir::macros::{Attr, AttrValue, Footnote, Macro};
11use crate::ir::segments::Segment;
12use crate::IntoIR;
13
14impl<'a> IntoIR<'a> for BlockMacro {
15    type IR = AnnBlock<'a>;
16
17    fn into_ir(self, text: &'a str, data: &mut AstData) -> Self::IR {
18        let mut block = self.content.into_ir(text, data);
19        let r#macro = MacroAst { name: self.name, args: self.args }.into_ir(text, data);
20
21        if r#macro.is_for_list() {
22            if let AnnBlock { block: Block::List(list), .. } = &mut block {
23                list.macros.push(r#macro);
24            }
25        } else {
26            block.macros.push(r#macro);
27        }
28
29        block
30    }
31}
32
33impl<'a> IntoIR<'a> for InlineMacroAst {
34    type IR = Segment<'a>;
35
36    fn into_ir(self, text: &'a str, data: &mut AstData) -> Self::IR {
37        let mut segment = (*self.segment).into_ir(text, data);
38        let r#macro = MacroAst { name: self.name, args: self.args };
39        match &mut segment {
40            Segment::Braces(b) => b.macros.push(r#macro.into_ir(text, data)),
41            Segment::Math(b) => b.macros.push(r#macro.into_ir(text, data)),
42            Segment::Link(b) => b.macros.push(r#macro.into_ir(text, data)),
43            Segment::Image(b) => b.macros.push(r#macro.into_ir(text, data)),
44            Segment::Code(b) => b.macros.push(r#macro.into_ir(text, data)),
45            Segment::InlineHtml(HtmlNode::Element(b)) => b.macros.push(r#macro.into_ir(text, data)),
46
47            _ => {}
48        }
49        segment
50    }
51}
52
53impl<'a> IntoIR<'a> for BlockMacroContent {
54    type IR = AnnBlock<'a>;
55
56    fn into_ir(self, text: &'a str, data: &mut AstData) -> Self::IR {
57        match self {
58            BlockMacroContent::Prefixed(p) => (*p).into_ir(text, data),
59            BlockMacroContent::Braces(b) => {
60                AnnBlock { macros: vec![], block: Block::Braces(b.into_ir(text, data)) }
61            }
62            BlockMacroContent::None => AnnBlock { macros: vec![], block: Block::Empty },
63        }
64    }
65}
66
67struct MacroAst {
68    name: StrSlice,
69    args: Option<MacroArgs>,
70}
71
72impl<'a> IntoIR<'a> for MacroAst {
73    type IR = Macro<'a>;
74
75    fn into_ir(self, text: &'a str, data: &mut AstData) -> Self::IR {
76        match self.name.to_str(text) {
77            "" => {
78                if let Some(MacroArgs::TokenTrees(tts)) = self.args {
79                    if tts.is_empty() {
80                        return Macro::Invalid;
81                    }
82                    let mut result = Vec::new();
83
84                    for tt in tts {
85                        match tt {
86                            TokenTree::Atom(TokenTreeAtom::Word(arg)) => {
87                                let arg = arg.to_str(text);
88                                if let Some(arg) = arg.strip_prefix('.') {
89                                    result.push(Attr {
90                                        key: "class",
91                                        value: Some(AttrValue::Word(arg)),
92                                    });
93                                } else if let Some(arg) = arg.strip_prefix('#') {
94                                    result.push(Attr {
95                                        key: "id",
96                                        value: Some(AttrValue::Word(arg)),
97                                    });
98                                } else {
99                                    result.push(Attr { key: arg, value: None })
100                                }
101                            }
102                            TokenTree::Atom(TokenTreeAtom::QuotedWord(word)) => result.push(Attr {
103                                key: "style",
104                                value: Some(AttrValue::QuotedWord(word)),
105                            }),
106                            TokenTree::KV(key, TokenTreeAtom::Word(word)) => {
107                                let key = key.to_str(text);
108                                let word = word.to_str(text);
109                                result.push(Attr { key, value: Some(AttrValue::Word(word)) })
110                            }
111                            TokenTree::KV(key, TokenTreeAtom::QuotedWord(word)) => {
112                                let key = key.to_str(text);
113                                result.push(Attr { key, value: Some(AttrValue::QuotedWord(word)) })
114                            }
115                            _ => return Macro::Invalid,
116                        }
117                    }
118
119                    Macro::HtmlAttrs(result)
120                } else {
121                    Macro::Invalid
122                }
123            }
124            "TOC" => {
125                if self.args.is_none() {
126                    Macro::Toc
127                } else {
128                    Macro::Invalid
129                }
130            }
131            "NOTOC" => {
132                if self.args.is_none() {
133                    Macro::NoToc
134                } else {
135                    Macro::Invalid
136                }
137            }
138            "NOTXT" => {
139                if self.args.is_none() {
140                    Macro::NoText
141                } else {
142                    Macro::Invalid
143                }
144            }
145            "LOOSE" => {
146                if self.args.is_none() {
147                    Macro::Loose
148                } else {
149                    Macro::Invalid
150                }
151            }
152            "BULLET" => {
153                if let Some(MacroArgs::TokenTrees(tts)) = self.args {
154                    if tts.is_empty() {
155                        return Macro::Invalid;
156                    }
157                    let mut style = String::new();
158
159                    for tt in tts {
160                        if let TokenTree::Atom(atom) = tt {
161                            match atom {
162                                TokenTreeAtom::Word(word) => {
163                                    style.push_str(word.to_str(text));
164                                    style.push(' ');
165                                }
166                                TokenTreeAtom::QuotedWord(word) => {
167                                    style.push('"');
168                                    style.extend(word.chars().flat_map(|c| {
169                                        iter::once('\\')
170                                            .filter(move |_| matches!(c, '"' | '\'' | '\\'))
171                                            .chain(iter::once(c))
172                                    }));
173                                    style.push_str("\" ");
174                                }
175                                _ => return Macro::Invalid,
176                            }
177                        } else {
178                            return Macro::Invalid;
179                        }
180                    }
181                    if style.ends_with(' ') {
182                        style.pop();
183                    }
184
185                    Macro::ListStyle(style)
186                } else {
187                    Macro::Invalid
188                }
189            }
190            "MATH_SCRIPT" => {
191                if self.args.is_none() {
192                    Macro::MathScript
193                } else {
194                    Macro::Invalid
195                }
196            }
197            "BLANK" => {
198                if self.args.is_none() {
199                    Macro::Blank
200                } else {
201                    Macro::Invalid
202                }
203            }
204            "FOOTNOTES" => {
205                if self.args.is_none() {
206                    if data.footnotes.is_empty() {
207                        Macro::Footnotes(vec![])
208                    } else {
209                        let links = mem::take(&mut data.footnotes);
210                        let footnotes = links
211                            .into_iter()
212                            .flat_map(|link| {
213                                let num = data.next_footnote_def;
214                                data.next_footnote_def += 1;
215
216                                link.text.map(|t| Footnote { num, text: t.into_ir(text, data) })
217                            })
218                            .collect();
219                        Macro::Footnotes(footnotes)
220                    }
221                } else {
222                    Macro::Invalid
223                }
224            }
225            "CONFIG" => {
226                if let Some(MacroArgs::TokenTrees(args)) = self.args {
227                    for arg in args {
228                        if let TokenTree::KV(key, value) = arg {
229                            match key.to_str(text) {
230                                "heading_anchor" => match value.as_str(text) {
231                                    Some("start" | "before") => {
232                                        data.config.heading_anchor = HeadingAnchor::Start
233                                    }
234                                    Some("end" | "after") => {
235                                        data.config.heading_anchor = HeadingAnchor::End
236                                    }
237                                    Some("none" | "no" | "false") => {
238                                        data.config.heading_anchor = HeadingAnchor::None
239                                    }
240                                    _ => {}
241                                },
242                                "lang" => {
243                                    if let Some(value) = value.as_str(text) {
244                                        if let Ok(quote_style) = value.parse() {
245                                            data.config.quote_style = quote_style;
246                                        } else {
247                                            return Macro::Invalid;
248                                        }
249                                    } else {
250                                        return Macro::Invalid;
251                                    }
252                                }
253                                _ => return Macro::Invalid,
254                            }
255                        }
256                    }
257                    Macro::Config
258                } else {
259                    Macro::Invalid
260                }
261            }
262            "INCLUDE" => {
263                if let Some(MacroArgs::Raw(path)) = self.args {
264                    Macro::Include(path.to_str(text))
265                } else {
266                    Macro::Invalid
267                }
268            }
269            _ => Macro::Invalid,
270        }
271    }
272}