1#![allow(deprecated)]
2
3use error::Error::*;
8use error::{get_offset, Error, Result};
9use instruction::{Instruction, Path, PathStep};
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 {
30 original_text: String,
31 remaining_text: String,
32 instructions: Vec<Instruction>,
33 block_stack: Vec<(String, Block)>,
34
35 trim_next: bool,
38}
39impl TemplateCompiler {
40 pub fn new(text: String) -> TemplateCompiler {
42 TemplateCompiler {
43 original_text: text.to_string(),
44 remaining_text: text.to_string(),
45 instructions: vec![],
46 block_stack: vec![],
47 trim_next: false,
48 }
49 }
50
51 pub fn compile(mut self) -> Result<Vec<Instruction>> {
53 while !self.remaining_text.is_empty() {
54 if self.remaining_text.starts_with("{#") {
56 self.trim_next = false;
57
58 let tag = match self.consume_tag("#}") {
59 Ok(it) => it,
60 Err(err) => {
61 self.instructions
62 .push(Instruction::Literal("{#".to_string()));
63 self.remaining_text = self.remaining_text[2..].to_string();
64 continue;
65 }
66 };
67 let comment = tag[2..(tag.len() - 2)].trim().to_string();
68 if comment.starts_with('-') {
69 self.trim_last_whitespace();
70 }
71 if comment.ends_with('-') {
72 self.trim_next_whitespace();
73 }
74 } else if self.remaining_text.starts_with("{{") {
77 self.trim_next = false;
78
79 let (discriminant, rest) = match self.consume_block() {
80 Ok(it) => it,
81 Err(err) => {
82 self.instructions
83 .push(Instruction::Literal("{#".to_string()));
84 self.remaining_text = self.remaining_text[2..].to_string();
85 continue;
86 }
87 };
88 match discriminant.as_str() {
89 "if" => {
90 let (path, negated) = if rest.starts_with("not") {
91 (self.parse_path(&rest[4..])?, true)
92 } else {
93 (self.parse_path(rest.as_str())?, false)
94 };
95 self.block_stack
96 .push((discriminant, Block::Branch(self.instructions.len())));
97 self.instructions
98 .push(Instruction::Branch(path, !negated, UNKNOWN));
99 }
100 "else" => {
101 self.expect_empty(rest.as_str())?;
102 let num_instructions = self.instructions.len() + 1;
103 self.close_branch(num_instructions, discriminant.as_str())?;
104 self.block_stack
105 .push((discriminant, Block::Branch(self.instructions.len())));
106 self.instructions.push(Instruction::Goto(UNKNOWN))
107 }
108 "endif" => {
109 self.expect_empty(rest.as_str())?;
110 let num_instructions = self.instructions.len();
111 self.close_branch(num_instructions, discriminant.as_str())?;
112 }
113 "with" => {
114 let (path, name) = self.parse_with(rest.as_str())?;
115 let instruction = Instruction::PushNamedContext(path, name);
116 self.instructions.push(instruction);
117 self.block_stack.push((discriminant, Block::With));
118 }
119 "endwith" => {
120 self.expect_empty(rest.as_str())?;
121 if let Some((_, Block::With)) = self.block_stack.pop() {
122 self.instructions.push(Instruction::PopContext)
123 } else {
124 return Err(self.parse_error(
125 discriminant.as_str(),
126 "Found a closing endwith that doesn't match with a preceeding with.".to_string()
127 ));
128 }
129 }
130 "for" => {
131 let (path, name) = self.parse_for(rest.as_str())?;
132 self.instructions
133 .push(Instruction::PushIterationContext(path, name));
134 self.block_stack
135 .push((discriminant, Block::For(self.instructions.len())));
136 self.instructions.push(Instruction::Iterate(UNKNOWN));
137 }
138 "endfor" => {
139 self.expect_empty(rest.as_str())?;
140 let num_instructions = self.instructions.len() + 1;
141 let goto_target =
142 self.close_for(num_instructions, discriminant.as_str())?;
143 self.instructions.push(Instruction::Goto(goto_target));
144 self.instructions.push(Instruction::PopContext);
145 }
146 "call" => {
147 let (name, path) = self.parse_call(rest.as_str())?;
148 self.instructions.push(Instruction::Call(name, path));
149 }
150 _ => {
151 return Err(self.parse_error(
152 discriminant.as_str(),
153 format!("Unknown block type '{}'", discriminant),
154 ));
155 }
156 }
157 } else if self.remaining_text.starts_with("\\{") {
159 let mut escaped = false;
160 loop {
161 let mut text = self.consume_text(escaped).clone();
162 if self.trim_next {
163 text = text.trim_left().to_string();
164 self.trim_next = false;
165 }
166 escaped = text.ends_with('\\');
167 if escaped {
168 text = text[..text.len() - 1].to_string();
169 }
170 self.instructions.push(Instruction::Literal(text.clone()));
171
172 if !escaped {
173 break;
174 }
175
176 if escaped && self.remaining_text.is_empty() {
177 return Err(self.parse_error(
178 text.as_str(),
179 "Found an escape that doesn't escape any character.".to_string(),
180 ));
181 }
182 }
183 } else if self.remaining_text.starts_with('{') {
185 self.trim_next = false;
186
187 match self.consume_value() {
188 Ok((path, name)) => {
189 let instruction = match name {
190 Some(name) => Instruction::FormattedValue(path, name),
191 None => Instruction::Value(path),
192 };
193 self.instructions.push(instruction);
194 }
195 Err(err) => {
196 if self.remaining_text.is_empty() {
197 continue;
198 }
199 self.instructions
200 .push(Instruction::Literal("{".to_string()));
201 self.remaining_text = self.remaining_text[1..].to_string();
202 }
203 };
204 } else {
206 let mut escaped = false;
207 loop {
208 let mut text = self.consume_text(escaped).clone();
209 if self.trim_next {
210 text = text.trim_left().to_string();
211 self.trim_next = false;
212 }
213 escaped = text.ends_with('\\');
214 if escaped {
215 text = text[..text.len() - 1].to_string();
216 }
217 self.instructions.push(Instruction::Literal(text.clone()));
218
219 if !escaped {
220 break;
221 }
222
223 if escaped && self.remaining_text.is_empty() {
224 return Err(self.parse_error(
225 text.as_str(),
226 "Found an escape that doesn't escape any character.".to_string(),
227 ));
228 }
229 }
230 }
231 }
232
233 if let Some((text, _)) = self.block_stack.pop() {
234 return Err(self.parse_error(
235 text.as_str(),
236 "Expected block-closing tag, but reached the end of input.".to_string(),
237 ));
238 }
239
240 Ok(self.instructions)
241 }
242
243 fn parse_path(&self, text: &str) -> Result<Path> {
246 if !text.starts_with('@') {
247 Ok(text
248 .split('.')
249 .map(|s| match s.parse::<usize>() {
250 Ok(n) => PathStep::Index(s.to_string(), n),
251 Err(_) => PathStep::Name(s.to_string()),
252 })
253 .collect::<Vec<_>>())
254 } else if KNOWN_KEYWORDS.iter().any(|k| *k == text) {
255 Ok(vec![PathStep::Name(text.to_string())])
256 } else {
257 Err(self.parse_error(text, format!("Invalid keyword name '{}'", text)))
258 }
259 }
260
261 fn parse_error(&self, location: &str, msg: String) -> Error {
264 let (line, column) = get_offset(self.original_text.as_str(), location);
265 ParseError { msg, line, column }
266 }
267
268 fn expect_empty(&self, text: &str) -> Result<()> {
271 if text.is_empty() {
272 Ok(())
273 } else {
274 Err(self.parse_error(text, format!("Unexpected text '{}'", text)))
275 }
276 }
277
278 fn close_branch(&mut self, new_target: usize, discriminant: &str) -> Result<()> {
282 let branch_block = self.block_stack.pop();
283 if let Some((_, Block::Branch(index))) = branch_block {
284 match &mut self.instructions[index] {
285 Instruction::Branch(_, _, target) => {
286 *target = new_target;
287 Ok(())
288 }
289 Instruction::Goto(target) => {
290 *target = new_target;
291 Ok(())
292 }
293 _ => panic!(),
294 }
295 } else {
296 Err(self.parse_error(
297 discriminant,
298 "Found a closing endif or else which doesn't match with a preceding if."
299 .to_string(),
300 ))
301 }
302 }
303
304 fn close_for(&mut self, new_target: usize, discriminant: &str) -> Result<usize> {
308 let branch_block = self.block_stack.pop();
309 if let Some((_, Block::For(index))) = branch_block {
310 match &mut self.instructions[index] {
311 Instruction::Iterate(target) => {
312 *target = new_target;
313 Ok(index)
314 }
315 _ => panic!(),
316 }
317 } else {
318 Err(self.parse_error(
319 discriminant,
320 "Found a closing endfor which doesn't match with a preceding for.".to_string(),
321 ))
322 }
323 }
324
325 fn consume_text(&mut self, escaped: bool) -> String {
328 let search_substr = if escaped {
329 &self.remaining_text[1..]
330 } else {
331 &self.remaining_text[..]
332 };
333
334 let mut position = search_substr
335 .find('{')
336 .unwrap_or_else(|| search_substr.len());
337 if escaped {
338 position += 1;
339 }
340
341 let remaining_text = self.remaining_text.clone();
342 let (text, remaining) = remaining_text.split_at(position);
343 self.remaining_text = remaining.to_string();
344 text.to_string()
345 }
346
347 fn consume_value(&mut self) -> Result<(Path, Option<String>)> {
350 let tag = self.consume_tag("}")?.to_string();
351 let mut tag = tag[1..(tag.len() - 1)].trim();
352 if tag.starts_with('-') {
353 tag = tag[1..].trim();
354 self.trim_last_whitespace();
355 }
356 if tag.ends_with('-') {
357 tag = tag[0..tag.len() - 1].trim();
358 self.trim_next_whitespace();
359 }
360
361 if let Some(index) = tag.find('|') {
362 let (path_str, name_str) = tag.split_at(index);
363 let name = name_str[1..].trim();
364 let path = self.parse_path(path_str.trim())?;
365 Ok((path, Some(name.to_string())))
366 } else {
367 Ok((self.parse_path(tag)?, None))
368 }
369 }
370
371 fn trim_last_whitespace(&mut self) {
373 if let Some(Instruction::Literal(text)) = self.instructions.last_mut() {
374 *text = text.trim_right().to_string();
375 }
376 }
377
378 fn trim_next_whitespace(&mut self) {
380 self.trim_next = true;
381 }
382
383 fn consume_block(&mut self) -> Result<(String, String)> {
386 let tag = self.consume_tag("}}")?.to_string();
387 let mut block = tag[2..(tag.len() - 2)].trim();
388 if block.starts_with('-') {
389 block = block[1..].trim();
390 self.trim_last_whitespace();
391 }
392 if block.ends_with('-') {
393 block = block[0..block.len() - 1].trim();
394 self.trim_next_whitespace();
395 }
396 let discriminant = block.split_whitespace().next().unwrap_or(block);
397 let rest = block[discriminant.len()..].trim();
398 Ok((discriminant.to_string(), rest.to_string()))
399 }
400
401 fn consume_tag(&mut self, expected_close: &str) -> Result<String> {
407 let start_len = expected_close.len();
411 let end_len = expected_close.len();
412 if let Some(line) = self.remaining_text.lines().next() {
413 if let Some(pos) = line[start_len..].find(expected_close) {
414 let remaining_text = self.remaining_text.to_string();
415 let (tag, remaining) = remaining_text.split_at(pos + start_len + end_len);
416 self.remaining_text = remaining.to_string();
417 Ok(tag.to_string())
418 } else {
419 Err(self.parse_error(
420 line,
421 format!(
422 "Expected a closing '{}' but found end-of-line instead.",
423 expected_close
424 ),
425 ))
426 }
427 } else {
428 Err(self.parse_error(
429 self.remaining_text.as_str(),
430 format!(
431 "Expected a closing '{}' but found end-of-text instead.",
432 expected_close
433 ),
434 ))
435 }
436 }
437
438 fn parse_with(&self, with_text: &str) -> Result<(Path, String)> {
440 if let Some(index) = with_text.find(" as ") {
441 let (path_str, name_str) = with_text.split_at(index);
442 let path = self.parse_path(path_str.trim())?;
443 let name = name_str[" as ".len()..].trim();
444 Ok((path, name.to_string()))
445 } else {
446 Err(self.parse_error(
447 with_text,
448 format!(
449 "Expected 'as <path>' in with block, but found \"{}\" instead",
450 with_text
451 ),
452 ))
453 }
454 }
455
456 fn parse_for(&self, for_text: &str) -> Result<(Path, String)> {
458 if let Some(index) = for_text.find(" in ") {
459 let (name_str, path_str) = for_text.split_at(index);
460 let name = name_str.trim();
461 let path = self.parse_path(path_str[" in ".len()..].trim())?;
462 Ok((path, name.to_string()))
463 } else {
464 Err(self.parse_error(
465 for_text,
466 format!("Unable to parse for block text '{}'", for_text),
467 ))
468 }
469 }
470
471 fn parse_call(&self, call_text: &str) -> Result<(String, Path)> {
473 if let Some(index) = call_text.find(" with ") {
474 let (name_str, path_str) = call_text.split_at(index);
475 let name = name_str.trim();
476 let path = self.parse_path(path_str[" with ".len()..].trim())?;
477 Ok((name.to_string(), path))
478 } else {
479 Err(self.parse_error(
480 call_text,
481 format!("Unable to parse call block text '{}'", call_text),
482 ))
483 }
484 }
485}
486
487#[cfg(test)]
488mod test {
489 use super::*;
490 use instruction::Instruction::*;
491 use std::io::Write;
492
493 fn compile(text: &'static str) -> Result<Vec<Instruction>> {
494 TemplateCompiler::new(text.to_string()).compile()
495 }
496
497 #[test]
498 fn test_compile_literal() {
499 let text = "Test String";
500 let instructions = compile(text).unwrap();
501 assert_eq!(1, instructions.len());
502 assert_eq!(&Literal(text.to_string()), &instructions[0]);
503 }
504
505 #[test]
506 fn test_compile_value() {
507 let text = "{ foobar }";
508 let instructions = compile(text).unwrap();
509 assert_eq!(1, instructions.len());
510 assert_eq!(
511 &Value(vec![PathStep::Name("foobar".to_string())]),
512 &instructions[0]
513 );
514 }
515
516 #[test]
517 fn test_compile_value_with_formatter() {
518 let text = "{ foobar | my_formatter }";
519 let instructions = compile(text).unwrap();
520 assert_eq!(1, instructions.len());
521 assert_eq!(
522 &FormattedValue(
523 vec![PathStep::Name("foobar".to_string())],
524 "my_formatter".to_string()
525 ),
526 &instructions[0]
527 );
528 }
529
530 #[test]
531 fn test_dotted_path() {
532 let text = "{ foo.bar }";
533 let instructions = compile(text).unwrap();
534 assert_eq!(1, instructions.len());
535 assert_eq!(
536 &Value(vec![
537 PathStep::Name("foo".to_string()),
538 PathStep::Name("bar".to_string())
539 ]),
540 &instructions[0]
541 );
542 }
543
544 #[test]
545 fn test_indexed_path() {
546 let text = "{ foo.0.bar }";
547 let instructions = compile(text).unwrap();
548 assert_eq!(1, instructions.len());
549 assert_eq!(
550 &Value(vec![
551 PathStep::Name("foo".to_string()),
552 PathStep::Index("0".to_string(), 0),
553 PathStep::Name("bar".to_string())
554 ]),
555 &instructions[0]
556 );
557 }
558
559 #[test]
560 fn test_mixture() {
561 let text = "Hello { name }, how are you?";
562 let instructions = compile(text).unwrap();
563 assert_eq!(3, instructions.len());
564 assert_eq!(&Literal("Hello ".to_string()), &instructions[0]);
565 assert_eq!(
566 &Value(vec![PathStep::Name("name".to_string())]),
567 &instructions[1]
568 );
569 assert_eq!(&Literal(", how are you?".to_string()), &instructions[2]);
570 }
571
572 #[test]
573 fn test_if_endif() {
574 let text = "{{ if foo }}Hello!{{ endif }}";
575 let instructions = compile(text).unwrap();
576 assert_eq!(2, instructions.len());
577 assert_eq!(
578 &Branch(vec![PathStep::Name("foo".to_string())], true, 2),
579 &instructions[0]
580 );
581 assert_eq!(&Literal("Hello!".to_string()), &instructions[1]);
582 }
583
584 #[test]
585 fn test_if_not_endif() {
586 let text = "{{ if not foo }}Hello!{{ endif }}";
587 let instructions = compile(text).unwrap();
588 assert_eq!(2, instructions.len());
589 assert_eq!(
590 &Branch(vec![PathStep::Name("foo".to_string())], false, 2),
591 &instructions[0]
592 );
593 assert_eq!(&Literal("Hello!".to_string()), &instructions[1]);
594 }
595
596 #[test]
597 fn test_if_else_endif() {
598 let text = "{{ if foo }}Hello!{{ else }}Goodbye!{{ endif }}";
599 let instructions = compile(text).unwrap();
600 assert_eq!(4, instructions.len());
601 assert_eq!(
602 &Branch(vec![PathStep::Name("foo".to_string())], true, 3),
603 &instructions[0]
604 );
605 assert_eq!(&Literal("Hello!".to_string()), &instructions[1]);
606 assert_eq!(&Goto(4), &instructions[2]);
607 assert_eq!(&Literal("Goodbye!".to_string()), &instructions[3]);
608 }
609
610 #[test]
611 fn test_with() {
612 let text = "{{ with foo as bar }}Hello!{{ endwith }}";
613 let instructions = compile(text).unwrap();
614 assert_eq!(3, instructions.len());
615 assert_eq!(
616 &PushNamedContext(vec![PathStep::Name("foo".to_string())], "bar".to_string()),
617 &instructions[0]
618 );
619 assert_eq!(&Literal("Hello!".to_string()), &instructions[1]);
620 assert_eq!(&PopContext, &instructions[2]);
621 }
622
623 #[test]
624 fn test_foreach() {
625 let text = "{{ for foo in bar.baz }}{ foo }{{ endfor }}";
626 let instructions = compile(text).unwrap();
627 assert_eq!(5, instructions.len());
628 assert_eq!(
629 &PushIterationContext(
630 vec![
631 PathStep::Name("bar".to_string()),
632 PathStep::Name("baz".to_string())
633 ],
634 "foo".to_string()
635 ),
636 &instructions[0]
637 );
638 assert_eq!(&Iterate(4), &instructions[1]);
639 assert_eq!(
640 &Value(vec![PathStep::Name("foo".to_string())]),
641 &instructions[2]
642 );
643 assert_eq!(&Goto(1), &instructions[3]);
644 assert_eq!(&PopContext, &instructions[4]);
645 }
646
647 #[test]
648 fn test_strip_whitespace_value() {
649 let text = "Hello, {- name -} , how are you?";
650 let instructions = compile(text).unwrap();
651 assert_eq!(3, instructions.len());
652 assert_eq!(&Literal("Hello,".to_string()), &instructions[0]);
653 assert_eq!(
654 &Value(vec![PathStep::Name("name".to_string())]),
655 &instructions[1]
656 );
657 assert_eq!(&Literal(", how are you?".to_string()), &instructions[2]);
658 }
659
660 #[test]
661 fn test_strip_whitespace_block() {
662 let text = "Hello, {{- if name -}} {name} {{- endif -}} , how are you?";
663 let instructions = compile(text).unwrap();
664 assert_eq!(6, instructions.len());
665 assert_eq!(&Literal("Hello,".to_string()), &instructions[0]);
666 assert_eq!(
667 &Branch(vec![PathStep::Name("name".to_string())], true, 5),
668 &instructions[1]
669 );
670 assert_eq!(&Literal("".to_string()), &instructions[2]);
671 assert_eq!(
672 &Value(vec![PathStep::Name("name".to_string())]),
673 &instructions[3]
674 );
675 assert_eq!(&Literal("".to_string()), &instructions[4]);
676 assert_eq!(&Literal(", how are you?".to_string()), &instructions[5]);
677 }
678
679 #[test]
680 fn test_comment() {
681 let text = "Hello, {# foo bar baz #} there!";
682 let instructions = compile(text).unwrap();
683 assert_eq!(2, instructions.len());
684 assert_eq!(&Literal("Hello, ".to_string()), &instructions[0]);
685 assert_eq!(&Literal(" there!".to_string()), &instructions[1]);
686 }
687
688 #[test]
689 fn test_strip_whitespace_comment() {
690 let text = "Hello, \t\n {#- foo bar baz -#} \t there!";
691 let instructions = compile(text).unwrap();
692 assert_eq!(2, instructions.len());
693 assert_eq!(&Literal("Hello,".to_string()), &instructions[0]);
694 assert_eq!(&Literal("there!".to_string()), &instructions[1]);
695 }
696
697 #[test]
698 fn test_strip_whitespace_followed_by_another_tag() {
699 let text = "{value -}{value} Hello";
700 let instructions = compile(text).unwrap();
701 assert_eq!(3, instructions.len());
702 assert_eq!(
703 &Value(vec![PathStep::Name("value".to_string())]),
704 &instructions[0]
705 );
706 assert_eq!(
707 &Value(vec![PathStep::Name("value".to_string())]),
708 &instructions[1]
709 );
710 assert_eq!(&Literal(" Hello".to_string()), &instructions[2]);
711 }
712
713 #[test]
714 fn test_call() {
715 let text = "{{ call my_macro with foo.bar }}";
716 let instructions = compile(text).unwrap();
717 assert_eq!(1, instructions.len());
718 assert_eq!(
719 &Call(
720 "my_macro".to_string(),
721 vec![
722 PathStep::Name("foo".to_string()),
723 PathStep::Name("bar".to_string())
724 ]
725 ),
726 &instructions[0]
727 );
728 }
729
730 #[test]
731 fn test_curly_brace_escaping() {
732 let text = "body \\{ \nfont-size: {fontsize} \n}";
733 let instructions = compile(text).unwrap();
734 assert_eq!(4, instructions.len());
735 assert_eq!(&Literal("body ".to_string()), &instructions[0]);
736 assert_eq!(&Literal("{ \nfont-size: ".to_string()), &instructions[1]);
737 assert_eq!(
738 &Value(vec![PathStep::Name("fontsize".to_string())]),
739 &instructions[2]
740 );
741 assert_eq!(&Literal(" \n}".to_string()), &instructions[3]);
742 }
743
744 #[test]
745 fn test_unclosed_tags() {
746 let tags = vec![
747 "{",
748 "{ foo.bar",
749 "{ foo.bar\n }",
750 "{{",
751 "{{ if foo.bar",
752 "{{ if foo.bar \n}}",
753 "{#",
754 "{# if foo.bar",
755 "{# if foo.bar \n#}",
756 ];
757 for tag in tags {
758 compile(tag).unwrap();
759 }
760 }
761
762 #[test]
763 fn test_mismatched_blocks() {
764 let text = "{{ if foo }}{{ with bar }}{{ endif }} {{ endwith }}";
765 compile(text).unwrap_err();
766 }
767
768 #[test]
769 fn test_disallows_invalid_keywords() {
770 let text = "{ @foo }";
771 compile(text).unwrap();
772 }
773
774 #[test]
775 fn test_diallows_unknown_block_type() {
776 let text = "{{ foobar }}";
777 compile(text).unwrap_err();
778 }
779
780 #[test]
781 fn test_parse_error_line_column_num() {
782 let text = "\n\n\n{{ foobar }}";
783 let err = compile(text).unwrap_err();
784 if let ParseError { line, column, .. } = err {
785 assert_eq!(4, line);
786 assert_eq!(3, column);
787 } else {
788 panic!("Should have returned a parse error");
789 }
790 }
791
792 #[test]
793 fn test_parse_error_on_unclosed_if() {
794 let text = "{{ if foo }}";
795 compile(text).unwrap_err();
796 }
797
798 #[test]
799 fn test_parse_escaped_open_curly_brace() {
800 let text: &str = r"hello \{world}";
801 let instructions = compile(text).unwrap();
802 assert_eq!(2, instructions.len());
803 assert_eq!(&Literal("hello ".to_string()), &instructions[0]);
804 assert_eq!(&Literal("{world}".to_string()), &instructions[1]);
805 }
806
807 #[test]
808 fn test_unmatched_escape() {
809 let text = r#"0\"#;
810 compile(text).unwrap_err();
811 }
812
813 #[test]
814 fn test_mismatched_closing_tag() {
815 let text = "{#}";
816 compile(text).unwrap();
817 }
818}