murkdown/
compiler.rs

1pub(crate) mod lang;
2pub(crate) mod rule;
3pub(crate) mod rule_argument;
4
5use std::collections::HashSet;
6
7pub use lang::Lang;
8use rule::Context;
9pub(crate) use rule::Rule;
10
11use crate::ast::Node;
12use crate::parser;
13use crate::types::{Dependency, LibError, Pointer};
14
15/// Compile AST to string
16pub fn compile(node: &mut Node, lang: &Lang) -> Result<String, LibError> {
17    let mut ignored_deps = HashSet::new();
18    compile_recusive(
19        std::slice::from_mut(&mut *node),
20        &mut Context::default(),
21        &mut ignored_deps,
22        lang,
23        "",
24    )
25}
26
27fn compile_recusive<'a>(
28    nodes: &mut [Node],
29    ctx: &mut Context<'a>,
30    deps: &mut HashSet<Dependency>,
31    lang: &'a Lang,
32    base_path: &str,
33) -> Result<String, LibError> {
34    let mut out = String::new();
35    let mut nodes = nodes.iter_mut().peekable();
36    let mut idx = 0;
37
38    while let Some(node) = nodes.next() {
39        let path = node.build_path(base_path);
40        ctx.set_parent(node);
41        ctx.set_index(idx);
42
43        let rules = lang.get_rules("COMPILE", &path);
44        let mut rules_stack = Vec::new();
45
46        // Evaluate pre-yield
47        for rule in rules {
48            let mut instructions = rule.instructions.iter();
49            let settings = rule.settings;
50            let value = lang.evaluate(&mut instructions, &mut *ctx, deps, node, &settings)?;
51            out.push_str(&value);
52            rules_stack.push((instructions, settings));
53        }
54
55        if let Some(Pointer(weak)) = &node.pointer {
56            let mutex = weak.upgrade().unwrap();
57            if let parser::Rule::Ellipsis = node.rule {
58                // NOTE: skip block node
59                let mut block = mutex.lock().unwrap();
60                if let Some(children) = block.children.as_mut() {
61                    // NOTE: skip section node
62                    for section in children {
63                        assert_eq!(section.rule, parser::Rule::Section);
64                        if let Some(children) = section.children.as_mut() {
65                            // fall through Ellipsis and only render Section contents
66                            out.push_str(&compile_recusive(children, ctx, deps, lang, base_path)?);
67                            idx += 1;
68                        }
69                    }
70                }
71            } else {
72                let mut node = mutex.lock().expect("poisoned or deadlack");
73                if let Some(children) = node.children.as_mut() {
74                    out.push_str(&compile_recusive(children, ctx, deps, lang, &path)?);
75                    idx += 1;
76                }
77            }
78        } else if let Some(children) = node.children.as_mut() {
79            out.push_str(&compile_recusive(children, ctx, deps, lang, &path)?);
80            idx += 1;
81        }
82
83        // Evaluate post-yield
84        rules_stack.reverse();
85        for (mut instructions, settings) in rules_stack {
86            let value = lang.evaluate(&mut instructions, &mut *ctx, deps, node, &settings)?;
87            out.push_str(&value);
88        }
89
90        if nodes.peek().is_some() || matches!(node.rule, parser::Rule::RootA | parser::Rule::RootB)
91        {
92            if let Some(joins) = ctx.stacks.get("join") {
93                if let Some(last) = joins.last() {
94                    out.push_str(last);
95                }
96            }
97        }
98    }
99    Ok(out)
100}
101
102#[cfg(test)]
103mod tests {
104    use std::sync::{Arc, Mutex};
105
106    use indoc::indoc;
107    use pretty_assertions::assert_eq;
108
109    use super::*;
110    use crate::ast::NodeBuilder;
111
112    #[test]
113    fn test_compile() {
114        let lang = Lang::markdown();
115        let mut node = NodeBuilder::root()
116            .add_section(vec![NodeBuilder::block(">")
117                .add_prop(("src".into(), "bar".into()))
118                .add_section(vec![Node::line("foo")])
119                .done()])
120            .done();
121        let result = compile(&mut node, &lang).unwrap();
122
123        assert_eq!(&result, "> foo\n");
124    }
125
126    #[test]
127    fn test_compile_nested() {
128        let lang = Lang::markdown();
129        let mut node = NodeBuilder::root()
130            .add_section(vec![NodeBuilder::block(">")
131                .add_prop(("src".into(), "bar".into()))
132                .add_section(vec![
133                    Node::line("foo"),
134                    NodeBuilder::block(">")
135                        .add_prop(("src".into(), "bar".into()))
136                        .add_section(vec![Node::line("bar")])
137                        .done(),
138                    Node::line("baz"),
139                ])
140                .done()])
141            .done();
142        let result = compile(&mut node, &lang).unwrap();
143        assert_eq!(
144            result,
145            indoc! {
146            r#"
147            > foo
148            > > bar
149            > baz
150            "#
151            }
152        );
153    }
154
155    #[test]
156    fn test_compile_composable() {
157        let lang = Lang::new(indoc! {
158            r#"
159            RULES FOR test PRODUCE text/plain
160            COMPILE RULES:
161            [INDENTED] [SEC] LINE$
162              IS COMPOSABLE
163              WRITE "  "
164            [SEC...] LINE$
165              IS COMPOSABLE
166              WRITE "\v"
167            LINE$
168              WRITE "ish\n"
169            "#
170        })
171        .unwrap();
172        let mut node = NodeBuilder::root()
173            .add_section(vec![NodeBuilder::block(">")
174                .add_prop(("src".into(), "bar".into()))
175                .add_section(vec![
176                    Node::line("foo"),
177                    NodeBuilder::block(">")
178                        .headers(Some(vec!["INDENTED".into()]))
179                        .add_section(vec![Node::line("bar")])
180                        .done(),
181                    Node::line("baz"),
182                ])
183                .done()])
184            .done();
185        let result = compile(&mut node, &lang).unwrap();
186        assert_eq!(
187            result,
188            indoc! {
189            r#"
190            fooish
191              barish
192            bazish
193            "#
194            }
195        );
196    }
197
198    #[test]
199    fn test_compile_composable_with_yield() {
200        let lang = Lang::new(indoc! {
201            r#"
202            RULES FOR test PRODUCE text/plain
203            COMPILE RULES:
204            [...INDENTED...] [SEC]$
205              IS COMPOSABLE
206              PUSH indent "  "
207              YIELD
208              POP indent
209            [...DRAMATIC...] [SEC]$
210              IS COMPOSABLE
211              PUSH prefix "Wow! "
212              PUSH suffix "!"
213              YIELD
214              POP prefix
215              POP suffix
216            LINE$
217              WRITEALL indent
218              WRITEALL prefix
219              WRITE "\v"
220              WRITEALL suffix
221              WRITE "\n"
222            "#
223        })
224        .unwrap();
225        let mut node = NodeBuilder::root()
226            .add_section(vec![NodeBuilder::block(">")
227                .add_prop(("src".into(), "bar".into()))
228                .add_section(vec![
229                    Node::line("foo"),
230                    NodeBuilder::block(">")
231                        .headers(Some(vec!["INDENTED DRAMATIC".into()]))
232                        .add_section(vec![Node::line("bar")])
233                        .done(),
234                    Node::line("baz"),
235                ])
236                .done()])
237            .done();
238        let result = compile(&mut node, &lang).unwrap();
239        assert_eq!(
240            result,
241            indoc! {
242            r#"
243            foo
244              Wow! bar!
245            baz
246            "#
247            }
248        );
249    }
250
251    #[test]
252    fn test_compile_escapes_value_by_default() {
253        let lang = Lang::new(indoc! {
254            r#"
255            RULES FOR test PRODUCE text/plain
256            COMPILE RULES:
257            [SEC...] LINE$
258              WRITE "\v"
259            "#
260        })
261        .unwrap();
262        let unescaped_lang = Lang::new(indoc! {
263            r#"
264            RULES FOR test PRODUCE text/plain
265            COMPILE RULES:
266            [SEC...] LINE$
267              IS UNESCAPED_VALUE
268              WRITE "\v"
269            "#
270        })
271        .unwrap();
272        let mut node = NodeBuilder::root()
273            .add_section(vec![NodeBuilder::block(">")
274                .add_prop(("src".into(), "bar".into()))
275                .add_section(vec![Node::line("<br />")])
276                .done()])
277            .done();
278
279        let result = compile(&mut node, &unescaped_lang).unwrap();
280        assert_eq!(&result, "<br />");
281
282        let result = compile(&mut node, &lang).unwrap();
283        assert_eq!(&result, "&lt;br /&gt;");
284    }
285
286    #[test]
287    fn test_compile_skips_ellipsis_block_and_section_nodes() {
288        let lang = Lang::new(indoc! {
289            r#"
290            RULES FOR test PRODUCE text/plain
291            COMPILE RULES:
292            ^[]$
293              NOOP
294            ^[] [SEC]$
295              NOOP
296            [SEC]$
297              WRITE "  section start\n"
298              YIELD
299              WRITE "  section end\n"
300            [...]$
301              WRITE "block start\n"
302              YIELD
303              WRITE "block end\n"
304            [SEC] LINE$
305              WRITE "    \v\n"
306            "#
307        })
308        .unwrap();
309        let mutex = Mutex::new(
310            NodeBuilder::block(">")
311                .add_prop(("id".into(), "includeme".into()))
312                .add_section(vec![Node::line("hello")])
313                .done(),
314        );
315        let arc = Arc::new(mutex);
316        let pointer = Pointer(Arc::downgrade(&arc));
317        let mut node = NodeBuilder::root()
318            .add_section(vec![])
319            .add_section(vec![NodeBuilder::block(">")
320                .add_prop(("src".into(), "includeme".into()))
321                .add_section(vec![Node::ellipsis(Some(pointer)), Node::line("world")])
322                .done()])
323            .done();
324
325        let result = compile(&mut node, &lang).unwrap();
326        assert_eq!(
327            &result,
328            indoc! {r#"
329            block start
330              section start
331                hello
332                world
333              section end
334            block end
335            "#
336            }
337        );
338    }
339}