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
15pub 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 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 let mut block = mutex.lock().unwrap();
60 if let Some(children) = block.children.as_mut() {
61 for section in children {
63 assert_eq!(section.rule, parser::Rule::Section);
64 if let Some(children) = section.children.as_mut() {
65 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 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, "<br />");
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}