1#![allow(deprecated)]
2
3use error::Error::*;
8use error::{get_offset, Error, Result};
9use instruction::{Instruction, Path};
10
11const UNKNOWN: usize = ::std::usize::MAX;
13
14enum Block {
18 Branch(usize),
19 For(usize),
20 With,
21}
22
23static KNOWN_KEYWORDS: [&str; 4] = ["@index", "@first", "@last", "@root"];
25
26pub(crate) struct TemplateCompiler<'template> {
30 original_text: &'template str,
31 remaining_text: &'template str,
32 instructions: Vec<Instruction<'template>>,
33 block_stack: Vec<(&'template str, Block)>,
34
35 trim_next: bool,
38}
39impl<'template> TemplateCompiler<'template> {
40 pub fn new(text: &'template str) -> TemplateCompiler<'template> {
42 TemplateCompiler {
43 original_text: text,
44 remaining_text: text,
45 instructions: vec![],
46 block_stack: vec![],
47 trim_next: false,
48 }
49 }
50
51 pub fn compile(mut self) -> Result<Vec<Instruction<'template>>> {
53 while !self.remaining_text.is_empty() {
54 if self.remaining_text.starts_with("{#") {
56 self.trim_next = false;
57
58 let tag = self.consume_tag("#}")?;
59 let comment = tag[2..(tag.len() - 2)].trim();
60 if comment.starts_with('-') {
61 self.trim_last_whitespace();
62 }
63 if comment.ends_with('-') {
64 self.trim_next_whitespace();
65 }
66 } else if self.remaining_text.starts_with("{{") {
69 self.trim_next = false;
70
71 let (discriminant, rest) = self.consume_block()?;
72 match discriminant {
73 "if" => {
74 let (path, negated) = if rest.starts_with("not") {
75 (self.parse_path(&rest[4..])?, true)
76 } else {
77 (self.parse_path(rest)?, false)
78 };
79 self.block_stack
80 .push((discriminant, Block::Branch(self.instructions.len())));
81 self.instructions
82 .push(Instruction::Branch(path, !negated, UNKNOWN));
83 }
84 "else" => {
85 self.expect_empty(rest)?;
86 let num_instructions = self.instructions.len() + 1;
87 self.close_branch(num_instructions, discriminant)?;
88 self.block_stack
89 .push((discriminant, Block::Branch(self.instructions.len())));
90 self.instructions.push(Instruction::Goto(UNKNOWN))
91 }
92 "endif" => {
93 self.expect_empty(rest)?;
94 let num_instructions = self.instructions.len();
95 self.close_branch(num_instructions, discriminant)?;
96 }
97 "with" => {
98 let (path, name) = self.parse_with(rest)?;
99 let instruction = Instruction::PushNamedContext(path, name);
100 self.instructions.push(instruction);
101 self.block_stack.push((discriminant, Block::With));
102 }
103 "endwith" => {
104 self.expect_empty(rest)?;
105 if let Some((_, Block::With)) = self.block_stack.pop() {
106 self.instructions.push(Instruction::PopContext)
107 } else {
108 return Err(self.parse_error(
109 discriminant,
110 "Found a closing endwith that doesn't match with a preceeding with.".to_string()
111 ));
112 }
113 }
114 "for" => {
115 let (path, name) = self.parse_for(rest)?;
116 self.instructions
117 .push(Instruction::PushIterationContext(path, name));
118 self.block_stack
119 .push((discriminant, Block::For(self.instructions.len())));
120 self.instructions.push(Instruction::Iterate(UNKNOWN));
121 }
122 "endfor" => {
123 self.expect_empty(rest)?;
124 let num_instructions = self.instructions.len() + 1;
125 let goto_target = self.close_for(num_instructions, discriminant)?;
126 self.instructions.push(Instruction::Goto(goto_target));
127 self.instructions.push(Instruction::PopContext);
128 }
129 "call" => {
130 let (name, path) = self.parse_call(rest)?;
131 self.instructions.push(Instruction::Call(name, path));
132 }
133 _ => {
134 return Err(self.parse_error(
135 discriminant,
136 format!("Unknown block type '{}'", discriminant),
137 ));
138 }
139 }
140 } else if self.remaining_text.starts_with('{') {
144 self.trim_next = false;
145
146 let (path, name) = self.consume_value()?;
147 let instruction = match name {
148 Some(name) => Instruction::FormattedValue(path, name),
149 None => Instruction::Value(path),
150 };
151 self.instructions.push(instruction);
152 } else {
154 let mut escaped = false;
155 loop {
156 let mut text = self.consume_text(escaped);
157 if self.trim_next {
158 text = text.trim_left();
159 self.trim_next = false;
160 }
161 escaped = text.ends_with('\\');
162 if escaped {
163 text = &text[0..(text.len() - 1)];
164 }
165 self.instructions.push(Instruction::Literal(text));
166
167 if !escaped {
168 break;
169 }
170 }
171 }
172 }
173
174 if let Some((text, _)) = self.block_stack.pop() {
175 return Err(self.parse_error(
176 text,
177 "Expected block-closing tag, but reached the end of input.".to_string(),
178 ));
179 }
180
181 Ok(self.instructions)
182 }
183
184 fn parse_path(&self, text: &'template str) -> Result<Path<'template>> {
187 if !text.starts_with('@') {
188 Ok(text.split('.').collect::<Vec<_>>())
189 } else if KNOWN_KEYWORDS.iter().any(|k| *k == text) {
190 Ok(vec![text])
191 } else {
192 Err(self.parse_error(text, format!("Invalid keyword name '{}'", text)))
193 }
194 }
195
196 fn parse_error(&self, location: &str, msg: String) -> Error {
199 let (line, column) = get_offset(self.original_text, location);
200 ParseError { msg, line, column }
201 }
202
203 fn expect_empty(&self, text: &str) -> Result<()> {
206 if text.is_empty() {
207 Ok(())
208 } else {
209 Err(self.parse_error(text, format!("Unexpected text '{}'", text)))
210 }
211 }
212
213 fn close_branch(&mut self, new_target: usize, discriminant: &str) -> Result<()> {
217 let branch_block = self.block_stack.pop();
218 if let Some((_, Block::Branch(index))) = branch_block {
219 match &mut self.instructions[index] {
220 Instruction::Branch(_, _, target) => {
221 *target = new_target;
222 Ok(())
223 }
224 Instruction::Goto(target) => {
225 *target = new_target;
226 Ok(())
227 }
228 _ => panic!(),
229 }
230 } else {
231 Err(self.parse_error(
232 discriminant,
233 "Found a closing endif or else which doesn't match with a preceding if."
234 .to_string(),
235 ))
236 }
237 }
238
239 fn close_for(&mut self, new_target: usize, discriminant: &str) -> Result<usize> {
243 let branch_block = self.block_stack.pop();
244 if let Some((_, Block::For(index))) = branch_block {
245 match &mut self.instructions[index] {
246 Instruction::Iterate(target) => {
247 *target = new_target;
248 Ok(index)
249 }
250 _ => panic!(),
251 }
252 } else {
253 Err(self.parse_error(
254 discriminant,
255 "Found a closing endfor which doesn't match with a preceding for.".to_string(),
256 ))
257 }
258 }
259
260 fn consume_text(&mut self, escaped: bool) -> &'template str {
263 let search_substr = if escaped {
264 &self.remaining_text[1..]
265 } else {
266 self.remaining_text
267 };
268
269 let mut position = search_substr
270 .find('{')
271 .unwrap_or_else(|| search_substr.len());
272 if escaped {
273 position += 1;
274 }
275
276 let (text, remaining) = self.remaining_text.split_at(position);
277 self.remaining_text = remaining;
278 text
279 }
280
281 fn consume_value(&mut self) -> Result<(Path<'template>, Option<&'template str>)> {
284 let tag = self.consume_tag("}")?;
285 let mut tag = tag[1..(tag.len() - 1)].trim();
286 if tag.starts_with('-') {
287 tag = tag[1..].trim();
288 self.trim_last_whitespace();
289 }
290 if tag.ends_with('-') {
291 tag = tag[0..tag.len() - 1].trim();
292 self.trim_next_whitespace();
293 }
294
295 if let Some(index) = tag.find('|') {
296 let (path_str, name_str) = tag.split_at(index);
297 let name = name_str[1..].trim();
298 let path = self.parse_path(path_str.trim())?;
299 Ok((path, Some(name)))
300 } else {
301 Ok((self.parse_path(tag)?, None))
302 }
303 }
304
305 fn trim_last_whitespace(&mut self) {
307 if let Some(Instruction::Literal(text)) = self.instructions.last_mut() {
308 *text = text.trim_right();
309 }
310 }
311
312 fn trim_next_whitespace(&mut self) {
314 self.trim_next = true;
315 }
316
317 fn consume_block(&mut self) -> Result<(&'template str, &'template str)> {
320 let tag = self.consume_tag("}}")?;
321 let mut block = tag[2..(tag.len() - 2)].trim();
322 if block.starts_with('-') {
323 block = block[1..].trim();
324 self.trim_last_whitespace();
325 }
326 if block.ends_with('-') {
327 block = block[0..block.len() - 1].trim();
328 self.trim_next_whitespace();
329 }
330 let discriminant = block.split_whitespace().next().unwrap_or(block);
331 let rest = block[discriminant.len()..].trim();
332 Ok((discriminant, rest))
333 }
334
335 fn consume_tag(&mut self, expected_close: &str) -> Result<&'template str> {
339 if let Some(line) = self.remaining_text.lines().next() {
340 if let Some(pos) = line.find(expected_close) {
341 let (tag, remaining) = self.remaining_text.split_at(pos + expected_close.len());
342 self.remaining_text = remaining;
343 Ok(tag)
344 } else {
345 Err(self.parse_error(
346 line,
347 format!(
348 "Expected a closing '{}' but found end-of-line instead.",
349 expected_close
350 ),
351 ))
352 }
353 } else {
354 Err(self.parse_error(
355 self.remaining_text,
356 format!(
357 "Expected a closing '{}' but found end-of-text instead.",
358 expected_close
359 ),
360 ))
361 }
362 }
363
364 fn parse_with(&self, with_text: &'template str) -> Result<(Path<'template>, &'template str)> {
366 if let Some(index) = with_text.find(" as ") {
367 let (path_str, name_str) = with_text.split_at(index);
368 let path = self.parse_path(path_str.trim())?;
369 let name = name_str[" as ".len()..].trim();
370 Ok((path, name))
371 } else {
372 Err(self.parse_error(
373 with_text,
374 format!(
375 "Expected 'as <path>' in with block, but found \"{}\" instead",
376 with_text
377 ),
378 ))
379 }
380 }
381
382 fn parse_for(&self, for_text: &'template str) -> Result<(Path<'template>, &'template str)> {
384 if let Some(index) = for_text.find(" in ") {
385 let (name_str, path_str) = for_text.split_at(index);
386 let name = name_str.trim();
387 let path = self.parse_path(path_str[" in ".len()..].trim())?;
388 Ok((path, name))
389 } else {
390 Err(self.parse_error(
391 for_text,
392 format!("Unable to parse for block text '{}'", for_text),
393 ))
394 }
395 }
396
397 fn parse_call(&self, call_text: &'template str) -> Result<(&'template str, Path<'template>)> {
399 if let Some(index) = call_text.find(" with ") {
400 let (name_str, path_str) = call_text.split_at(index);
401 let name = name_str.trim();
402 let path = self.parse_path(path_str[" with ".len()..].trim())?;
403 Ok((name, path))
404 } else {
405 Err(self.parse_error(
406 call_text,
407 format!("Unable to parse call block text '{}'", call_text),
408 ))
409 }
410 }
411}
412
413#[cfg(test)]
414mod test {
415 use super::*;
416 use instruction::Instruction::*;
417
418 fn compile(text: &'static str) -> Result<Vec<Instruction<'static>>> {
419 TemplateCompiler::new(text).compile()
420 }
421
422 #[test]
423 fn test_compile_literal() {
424 let text = "Test String";
425 let instructions = compile(text).unwrap();
426 assert_eq!(1, instructions.len());
427 assert_eq!(&Literal(text), &instructions[0]);
428 }
429
430 #[test]
431 fn test_compile_value() {
432 let text = "{ foobar }";
433 let instructions = compile(text).unwrap();
434 assert_eq!(1, instructions.len());
435 assert_eq!(&Value(vec!["foobar"]), &instructions[0]);
436 }
437
438 #[test]
439 fn test_compile_value_with_formatter() {
440 let text = "{ foobar | my_formatter }";
441 let instructions = compile(text).unwrap();
442 assert_eq!(1, instructions.len());
443 assert_eq!(
444 &FormattedValue(vec!["foobar"], "my_formatter"),
445 &instructions[0]
446 );
447 }
448
449 #[test]
450 fn test_dotted_path() {
451 let text = "{ foo.bar }";
452 let instructions = compile(text).unwrap();
453 assert_eq!(1, instructions.len());
454 assert_eq!(&Value(vec!["foo", "bar"]), &instructions[0]);
455 }
456
457 #[test]
458 fn test_mixture() {
459 let text = "Hello { name }, how are you?";
460 let instructions = compile(text).unwrap();
461 assert_eq!(3, instructions.len());
462 assert_eq!(&Literal("Hello "), &instructions[0]);
463 assert_eq!(&Value(vec!["name"]), &instructions[1]);
464 assert_eq!(&Literal(", how are you?"), &instructions[2]);
465 }
466
467 #[test]
468 fn test_if_endif() {
469 let text = "{{ if foo }}Hello!{{ endif }}";
470 let instructions = compile(text).unwrap();
471 assert_eq!(2, instructions.len());
472 assert_eq!(&Branch(vec!["foo"], true, 2), &instructions[0]);
473 assert_eq!(&Literal("Hello!"), &instructions[1]);
474 }
475
476 #[test]
477 fn test_if_not_endif() {
478 let text = "{{ if not foo }}Hello!{{ endif }}";
479 let instructions = compile(text).unwrap();
480 assert_eq!(2, instructions.len());
481 assert_eq!(&Branch(vec!["foo"], false, 2), &instructions[0]);
482 assert_eq!(&Literal("Hello!"), &instructions[1]);
483 }
484
485 #[test]
486 fn test_if_else_endif() {
487 let text = "{{ if foo }}Hello!{{ else }}Goodbye!{{ endif }}";
488 let instructions = compile(text).unwrap();
489 assert_eq!(4, instructions.len());
490 assert_eq!(&Branch(vec!["foo"], true, 3), &instructions[0]);
491 assert_eq!(&Literal("Hello!"), &instructions[1]);
492 assert_eq!(&Goto(4), &instructions[2]);
493 assert_eq!(&Literal("Goodbye!"), &instructions[3]);
494 }
495
496 #[test]
497 fn test_with() {
498 let text = "{{ with foo as bar }}Hello!{{ endwith }}";
499 let instructions = compile(text).unwrap();
500 assert_eq!(3, instructions.len());
501 assert_eq!(&PushNamedContext(vec!["foo"], "bar"), &instructions[0]);
502 assert_eq!(&Literal("Hello!"), &instructions[1]);
503 assert_eq!(&PopContext, &instructions[2]);
504 }
505
506 #[test]
507 fn test_foreach() {
508 let text = "{{ for foo in bar.baz }}{ foo }{{ endfor }}";
509 let instructions = compile(text).unwrap();
510 assert_eq!(5, instructions.len());
511 assert_eq!(
512 &PushIterationContext(vec!["bar", "baz"], "foo"),
513 &instructions[0]
514 );
515 assert_eq!(&Iterate(4), &instructions[1]);
516 assert_eq!(&Value(vec!["foo"]), &instructions[2]);
517 assert_eq!(&Goto(1), &instructions[3]);
518 assert_eq!(&PopContext, &instructions[4]);
519 }
520
521 #[test]
522 fn test_strip_whitespace_value() {
523 let text = "Hello, {- name -} , how are you?";
524 let instructions = compile(text).unwrap();
525 assert_eq!(3, instructions.len());
526 assert_eq!(&Literal("Hello,"), &instructions[0]);
527 assert_eq!(&Value(vec!["name"]), &instructions[1]);
528 assert_eq!(&Literal(", how are you?"), &instructions[2]);
529 }
530
531 #[test]
532 fn test_strip_whitespace_block() {
533 let text = "Hello, {{- if name -}} {name} {{- endif -}} , how are you?";
534 let instructions = compile(text).unwrap();
535 assert_eq!(6, instructions.len());
536 assert_eq!(&Literal("Hello,"), &instructions[0]);
537 assert_eq!(&Branch(vec!["name"], true, 5), &instructions[1]);
538 assert_eq!(&Literal(""), &instructions[2]);
539 assert_eq!(&Value(vec!["name"]), &instructions[3]);
540 assert_eq!(&Literal(""), &instructions[4]);
541 assert_eq!(&Literal(", how are you?"), &instructions[5]);
542 }
543
544 #[test]
545 fn test_comment() {
546 let text = "Hello, {# foo bar baz #} there!";
547 let instructions = compile(text).unwrap();
548 assert_eq!(2, instructions.len());
549 assert_eq!(&Literal("Hello, "), &instructions[0]);
550 assert_eq!(&Literal(" there!"), &instructions[1]);
551 }
552
553 #[test]
554 fn test_strip_whitespace_comment() {
555 let text = "Hello, \t\n {#- foo bar baz -#} \t there!";
556 let instructions = compile(text).unwrap();
557 assert_eq!(2, instructions.len());
558 assert_eq!(&Literal("Hello,"), &instructions[0]);
559 assert_eq!(&Literal("there!"), &instructions[1]);
560 }
561
562 #[test]
563 fn test_strip_whitespace_followed_by_another_tag() {
564 let text = "{value -}{value} Hello";
565 let instructions = compile(text).unwrap();
566 assert_eq!(3, instructions.len());
567 assert_eq!(&Value(vec!["value"]), &instructions[0]);
568 assert_eq!(&Value(vec!["value"]), &instructions[1]);
569 assert_eq!(&Literal(" Hello"), &instructions[2]);
570 }
571
572 #[test]
573 fn test_call() {
574 let text = "{{ call my_macro with foo.bar }}";
575 let instructions = compile(text).unwrap();
576 assert_eq!(1, instructions.len());
577 assert_eq!(&Call("my_macro", vec!["foo", "bar"]), &instructions[0]);
578 }
579
580 #[test]
581 fn test_curly_brace_escaping() {
582 let text = "body \\{ \nfont-size: {fontsize} \n}";
583 let instructions = compile(text).unwrap();
584 assert_eq!(4, instructions.len());
585 assert_eq!(&Literal("body "), &instructions[0]);
586 assert_eq!(&Literal("{ \nfont-size: "), &instructions[1]);
587 assert_eq!(&Value(vec!["fontsize"]), &instructions[2]);
588 assert_eq!(&Literal(" \n}"), &instructions[3]);
589 }
590
591 #[test]
592 fn test_unclosed_tags() {
593 let tags = vec![
594 "{",
595 "{ foo.bar",
596 "{ foo.bar\n }",
597 "{{",
598 "{{ if foo.bar",
599 "{{ if foo.bar \n}}",
600 "{#",
601 "{# if foo.bar",
602 "{# if foo.bar \n#}",
603 ];
604 for tag in tags {
605 compile(tag).unwrap_err();
606 }
607 }
608
609 #[test]
610 fn test_mismatched_blocks() {
611 let text = "{{ if foo }}{{ with bar }}{{ endif }} {{ endwith }}";
612 compile(text).unwrap_err();
613 }
614
615 #[test]
616 fn test_disallows_invalid_keywords() {
617 let text = "{ @foo }";
618 compile(text).unwrap_err();
619 }
620
621 #[test]
622 fn test_diallows_unknown_block_type() {
623 let text = "{{ foobar }}";
624 compile(text).unwrap_err();
625 }
626
627 #[test]
628 fn test_parse_error_line_column_num() {
629 let text = "\n\n\n{{ foobar }}";
630 let err = compile(text).unwrap_err();
631 if let ParseError { line, column, .. } = err {
632 assert_eq!(4, line);
633 assert_eq!(3, column);
634 } else {
635 panic!("Should have returned a parse error");
636 }
637 }
638
639 #[test]
640 fn test_parse_error_on_unclosed_if() {
641 let text = "{{ if foo }}";
642 compile(text).unwrap_err();
643 }
644
645 #[test]
646 fn test_parse_escaped_open_curly_brace() {
647 let text: &str = r"hello \{world}";
648 let instructions = compile(text).unwrap();
649 assert_eq!(2, instructions.len());
650 assert_eq!(&Literal("hello "), &instructions[0]);
651 assert_eq!(&Literal("{world}"), &instructions[1]);
652 }
653}