1#![allow(deprecated)]
2
3/// The compiler module houses the code which parses and compiles templates. TinyTemplate implements
4/// a simple bytecode interpreter (see the [instruction] module for more details) to render templates.
5/// The [`TemplateCompiler`](struct.TemplateCompiler.html) struct is responsible for parsing the
6/// template strings and generating the appropriate bytecode instructions.
7use error::Error::*;
8use error::{get_offset, Error, Result};
9use instruction::{Instruction, Path, PathStep};
10
11/// The end point of a branch or goto instruction is not known.
12const UNKNOWN: usize = ::std::usize::MAX;
13
14/// The compiler keeps a stack of the open blocks so that it can ensure that blocks are closed in
15/// the right order. The Block type is a simple enumeration of the kinds of blocks that could be
16/// open. It may contain the instruction index corresponding to the start of the block.
17enum Block {
18 Branch(usize),
19 For(usize),
20 With,
21}
22
23/// List of the known @-keywords so that we can error if the user spells them wrong.
24static KNOWN_KEYWORDS: [&str; 4] = ["@index", "@first", "@last", "@root"];
25
26/// The TemplateCompiler struct is responsible for parsing a template string and generating bytecode
27/// instructions based on it. The parser is a simple hand-written pattern-matching parser with no
28/// recursion, which makes it relatively easy to read.
29pub(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 /// When we see a `{foo -}` or similar, we need to remember to left-trim the next text block we
36 /// encounter.
37 trim_next: bool,
38}
39impl<'template> TemplateCompiler<'template> {
40 /// Create a new template compiler to parse and compile the given template.
41 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 /// Consume the template compiler to parse the template and return the generated bytecode.
52 pub fn compile(mut self) -> Result<Vec<Instruction<'template>>> {
53 while !self.remaining_text.is_empty() {
54 // Comment, denoted by {# comment text #}
55 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 // Block tag. Block tags are wrapped in {{ }} and always have one word at the start
67 // to identify which kind of tag it is. Depending on the tag type there may be more.
68 } 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 // Values, of the form { dotted.path.to.value.in.context }
141 // Note that it is not (currently) possible to escape curly braces in the templates to
142 // prevent them from being interpreted as values.
143 } 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 // All other text - just consume characters until we see a {
153 } 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 /// Splits a string into a list of named segments which can later be used to look up values in the
185 /// context.
186 fn parse_path(&self, text: &'template str) -> Result<Path<'template>> {
187 if !text.starts_with('@') {
188 Ok(text
189 .split('.')
190 .map(|s| match s.parse::<usize>() {
191 Ok(n) => PathStep::Index(s, n),
192 Err(_) => PathStep::Name(s),
193 })
194 .collect::<Vec<_>>())
195 } else if KNOWN_KEYWORDS.iter().any(|k| *k == text) {
196 Ok(vec![PathStep::Name(text)])
197 } else {
198 Err(self.parse_error(text, format!("Invalid keyword name '{}'", text)))
199 }
200 }
201
202 /// Finds the line number and column where an error occurred. Location is the substring of
203 /// self.original_text where the error was found, and msg is the error message.
204 fn parse_error(&self, location: &str, msg: String) -> Error {
205 let (line, column) = get_offset(self.original_text, location);
206 ParseError { msg, line, column }
207 }
208
209 /// Tags which should have no text after the discriminant use this to raise an error if
210 /// text is found.
211 fn expect_empty(&self, text: &str) -> Result<()> {
212 if text.is_empty() {
213 Ok(())
214 } else {
215 Err(self.parse_error(text, format!("Unexpected text '{}'", text)))
216 }
217 }
218
219 /// Close the branch that is on top of the block stack by setting its target instruction
220 /// and popping it from the stack. Returns an error if the top of the block stack is not a
221 /// branch.
222 fn close_branch(&mut self, new_target: usize, discriminant: &str) -> Result<()> {
223 let branch_block = self.block_stack.pop();
224 if let Some((_, Block::Branch(index))) = branch_block {
225 match &mut self.instructions[index] {
226 Instruction::Branch(_, _, target) => {
227 *target = new_target;
228 Ok(())
229 }
230 Instruction::Goto(target) => {
231 *target = new_target;
232 Ok(())
233 }
234 _ => panic!(),
235 }
236 } else {
237 Err(self.parse_error(
238 discriminant,
239 "Found a closing endif or else which doesn't match with a preceding if."
240 .to_string(),
241 ))
242 }
243 }
244
245 /// Close the for loop that is on top of the block stack by setting its target instruction and
246 /// popping it from the stack. Returns an error if the top of the stack is not a for loop.
247 /// Returns the index of the loop's Iterate instruction for further processing.
248 fn close_for(&mut self, new_target: usize, discriminant: &str) -> Result<usize> {
249 let branch_block = self.block_stack.pop();
250 if let Some((_, Block::For(index))) = branch_block {
251 match &mut self.instructions[index] {
252 Instruction::Iterate(target) => {
253 *target = new_target;
254 Ok(index)
255 }
256 _ => panic!(),
257 }
258 } else {
259 Err(self.parse_error(
260 discriminant,
261 "Found a closing endfor which doesn't match with a preceding for.".to_string(),
262 ))
263 }
264 }
265
266 /// Advance the cursor to the next { and return the consumed text. If `escaped` is true, skips
267 /// a { at the start of the text.
268 fn consume_text(&mut self, escaped: bool) -> &'template str {
269 let search_substr = if escaped {
270 &self.remaining_text[1..]
271 } else {
272 self.remaining_text
273 };
274
275 let mut position = search_substr
276 .find('{')
277 .unwrap_or_else(|| search_substr.len());
278 if escaped {
279 position += 1;
280 }
281
282 let (text, remaining) = self.remaining_text.split_at(position);
283 self.remaining_text = remaining;
284 text
285 }
286
287 /// Advance the cursor to the end of the value tag and return the value's path and optional
288 /// formatter name.
289 fn consume_value(&mut self) -> Result<(Path<'template>, Option<&'template str>)> {
290 let tag = self.consume_tag("}")?;
291 let mut tag = tag[1..(tag.len() - 1)].trim();
292 if tag.starts_with('-') {
293 tag = tag[1..].trim();
294 self.trim_last_whitespace();
295 }
296 if tag.ends_with('-') {
297 tag = tag[0..tag.len() - 1].trim();
298 self.trim_next_whitespace();
299 }
300
301 if let Some(index) = tag.find('|') {
302 let (path_str, name_str) = tag.split_at(index);
303 let name = name_str[1..].trim();
304 let path = self.parse_path(path_str.trim())?;
305 Ok((path, Some(name)))
306 } else {
307 Ok((self.parse_path(tag)?, None))
308 }
309 }
310
311 /// Right-trim whitespace from the last text block we parsed.
312 fn trim_last_whitespace(&mut self) {
313 if let Some(Instruction::Literal(text)) = self.instructions.last_mut() {
314 *text = text.trim_right();
315 }
316 }
317
318 /// Make a note to left-trim whitespace from the next text block we parse.
319 fn trim_next_whitespace(&mut self) {
320 self.trim_next = true;
321 }
322
323 /// Advance the cursor to the end of the current block tag and return the discriminant substring
324 /// and the rest of the text in the tag. Also handles trimming whitespace where needed.
325 fn consume_block(&mut self) -> Result<(&'template str, &'template str)> {
326 let tag = self.consume_tag("}}")?;
327 let mut block = tag[2..(tag.len() - 2)].trim();
328 if block.starts_with('-') {
329 block = block[1..].trim();
330 self.trim_last_whitespace();
331 }
332 if block.ends_with('-') {
333 block = block[0..block.len() - 1].trim();
334 self.trim_next_whitespace();
335 }
336 let discriminant = block.split_whitespace().next().unwrap_or(block);
337 let rest = block[discriminant.len()..].trim();
338 Ok((discriminant, rest))
339 }
340
341 /// Advance the cursor to after the given expected_close string and return the text in between
342 /// (including the expected_close characters), or return an error message if we reach the end
343 /// of a line of text without finding it.
344 fn consume_tag(&mut self, expected_close: &str) -> Result<&'template str> {
345 if let Some(line) = self.remaining_text.lines().next() {
346 if let Some(pos) = line.find(expected_close) {
347 let (tag, remaining) = self.remaining_text.split_at(pos + expected_close.len());
348 self.remaining_text = remaining;
349 Ok(tag)
350 } else {
351 Err(self.parse_error(
352 line,
353 format!(
354 "Expected a closing '{}' but found end-of-line instead.",
355 expected_close
356 ),
357 ))
358 }
359 } else {
360 Err(self.parse_error(
361 self.remaining_text,
362 format!(
363 "Expected a closing '{}' but found end-of-text instead.",
364 expected_close
365 ),
366 ))
367 }
368 }
369
370 /// Parse a with tag to separate the value path from the (optional) name.
371 fn parse_with(&self, with_text: &'template str) -> Result<(Path<'template>, &'template str)> {
372 if let Some(index) = with_text.find(" as ") {
373 let (path_str, name_str) = with_text.split_at(index);
374 let path = self.parse_path(path_str.trim())?;
375 let name = name_str[" as ".len()..].trim();
376 Ok((path, name))
377 } else {
378 Err(self.parse_error(
379 with_text,
380 format!(
381 "Expected 'as <path>' in with block, but found \"{}\" instead",
382 with_text
383 ),
384 ))
385 }
386 }
387
388 /// Parse a for tag to separate the value path from the name.
389 fn parse_for(&self, for_text: &'template str) -> Result<(Path<'template>, &'template str)> {
390 if let Some(index) = for_text.find(" in ") {
391 let (name_str, path_str) = for_text.split_at(index);
392 let name = name_str.trim();
393 let path = self.parse_path(path_str[" in ".len()..].trim())?;
394 Ok((path, name))
395 } else {
396 Err(self.parse_error(
397 for_text,
398 format!("Unable to parse for block text '{}'", for_text),
399 ))
400 }
401 }
402
403 /// Parse a call tag to separate the template name and context value.
404 fn parse_call(&self, call_text: &'template str) -> Result<(&'template str, Path<'template>)> {
405 if let Some(index) = call_text.find(" with ") {
406 let (name_str, path_str) = call_text.split_at(index);
407 let name = name_str.trim();
408 let path = self.parse_path(path_str[" with ".len()..].trim())?;
409 Ok((name, path))
410 } else {
411 Err(self.parse_error(
412 call_text,
413 format!("Unable to parse call block text '{}'", call_text),
414 ))
415 }
416 }
417}
418
419#[cfg(test)]
420mod test {
421 use super::*;
422 use instruction::Instruction::*;
423
424 fn compile(text: &'static str) -> Result<Vec<Instruction<'static>>> {
425 TemplateCompiler::new(text).compile()
426 }
427
428 #[test]
429 fn test_compile_literal() {
430 let text = "Test String";
431 let instructions = compile(text).unwrap();
432 assert_eq!(1, instructions.len());
433 assert_eq!(&Literal(text), &instructions[0]);
434 }
435
436 #[test]
437 fn test_compile_value() {
438 let text = "{ foobar }";
439 let instructions = compile(text).unwrap();
440 assert_eq!(1, instructions.len());
441 assert_eq!(&Value(vec![PathStep::Name("foobar")]), &instructions[0]);
442 }
443
444 #[test]
445 fn test_compile_value_with_formatter() {
446 let text = "{ foobar | my_formatter }";
447 let instructions = compile(text).unwrap();
448 assert_eq!(1, instructions.len());
449 assert_eq!(
450 &FormattedValue(vec![PathStep::Name("foobar")], "my_formatter"),
451 &instructions[0]
452 );
453 }
454
455 #[test]
456 fn test_dotted_path() {
457 let text = "{ foo.bar }";
458 let instructions = compile(text).unwrap();
459 assert_eq!(1, instructions.len());
460 assert_eq!(
461 &Value(vec![PathStep::Name("foo"), PathStep::Name("bar")]),
462 &instructions[0]
463 );
464 }
465
466 #[test]
467 fn test_indexed_path() {
468 let text = "{ foo.0.bar }";
469 let instructions = compile(text).unwrap();
470 assert_eq!(1, instructions.len());
471 assert_eq!(
472 &Value(vec![
473 PathStep::Name("foo"),
474 PathStep::Index("0", 0),
475 PathStep::Name("bar")
476 ]),
477 &instructions[0]
478 );
479 }
480
481 #[test]
482 fn test_mixture() {
483 let text = "Hello { name }, how are you?";
484 let instructions = compile(text).unwrap();
485 assert_eq!(3, instructions.len());
486 assert_eq!(&Literal("Hello "), &instructions[0]);
487 assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[1]);
488 assert_eq!(&Literal(", how are you?"), &instructions[2]);
489 }
490
491 #[test]
492 fn test_if_endif() {
493 let text = "{{ if foo }}Hello!{{ endif }}";
494 let instructions = compile(text).unwrap();
495 assert_eq!(2, instructions.len());
496 assert_eq!(
497 &Branch(vec![PathStep::Name("foo")], true, 2),
498 &instructions[0]
499 );
500 assert_eq!(&Literal("Hello!"), &instructions[1]);
501 }
502
503 #[test]
504 fn test_if_not_endif() {
505 let text = "{{ if not foo }}Hello!{{ endif }}";
506 let instructions = compile(text).unwrap();
507 assert_eq!(2, instructions.len());
508 assert_eq!(
509 &Branch(vec![PathStep::Name("foo")], false, 2),
510 &instructions[0]
511 );
512 assert_eq!(&Literal("Hello!"), &instructions[1]);
513 }
514
515 #[test]
516 fn test_if_else_endif() {
517 let text = "{{ if foo }}Hello!{{ else }}Goodbye!{{ endif }}";
518 let instructions = compile(text).unwrap();
519 assert_eq!(4, instructions.len());
520 assert_eq!(
521 &Branch(vec![PathStep::Name("foo")], true, 3),
522 &instructions[0]
523 );
524 assert_eq!(&Literal("Hello!"), &instructions[1]);
525 assert_eq!(&Goto(4), &instructions[2]);
526 assert_eq!(&Literal("Goodbye!"), &instructions[3]);
527 }
528
529 #[test]
530 fn test_with() {
531 let text = "{{ with foo as bar }}Hello!{{ endwith }}";
532 let instructions = compile(text).unwrap();
533 assert_eq!(3, instructions.len());
534 assert_eq!(
535 &PushNamedContext(vec![PathStep::Name("foo")], "bar"),
536 &instructions[0]
537 );
538 assert_eq!(&Literal("Hello!"), &instructions[1]);
539 assert_eq!(&PopContext, &instructions[2]);
540 }
541
542 #[test]
543 fn test_foreach() {
544 let text = "{{ for foo in bar.baz }}{ foo }{{ endfor }}";
545 let instructions = compile(text).unwrap();
546 assert_eq!(5, instructions.len());
547 assert_eq!(
548 &PushIterationContext(vec![PathStep::Name("bar"), PathStep::Name("baz")], "foo"),
549 &instructions[0]
550 );
551 assert_eq!(&Iterate(4), &instructions[1]);
552 assert_eq!(&Value(vec![PathStep::Name("foo")]), &instructions[2]);
553 assert_eq!(&Goto(1), &instructions[3]);
554 assert_eq!(&PopContext, &instructions[4]);
555 }
556
557 #[test]
558 fn test_strip_whitespace_value() {
559 let text = "Hello, {- name -} , how are you?";
560 let instructions = compile(text).unwrap();
561 assert_eq!(3, instructions.len());
562 assert_eq!(&Literal("Hello,"), &instructions[0]);
563 assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[1]);
564 assert_eq!(&Literal(", how are you?"), &instructions[2]);
565 }
566
567 #[test]
568 fn test_strip_whitespace_block() {
569 let text = "Hello, {{- if name -}} {name} {{- endif -}} , how are you?";
570 let instructions = compile(text).unwrap();
571 assert_eq!(6, instructions.len());
572 assert_eq!(&Literal("Hello,"), &instructions[0]);
573 assert_eq!(
574 &Branch(vec![PathStep::Name("name")], true, 5),
575 &instructions[1]
576 );
577 assert_eq!(&Literal(""), &instructions[2]);
578 assert_eq!(&Value(vec![PathStep::Name("name")]), &instructions[3]);
579 assert_eq!(&Literal(""), &instructions[4]);
580 assert_eq!(&Literal(", how are you?"), &instructions[5]);
581 }
582
583 #[test]
584 fn test_comment() {
585 let text = "Hello, {# foo bar baz #} there!";
586 let instructions = compile(text).unwrap();
587 assert_eq!(2, instructions.len());
588 assert_eq!(&Literal("Hello, "), &instructions[0]);
589 assert_eq!(&Literal(" there!"), &instructions[1]);
590 }
591
592 #[test]
593 fn test_strip_whitespace_comment() {
594 let text = "Hello, \t\n {#- foo bar baz -#} \t there!";
595 let instructions = compile(text).unwrap();
596 assert_eq!(2, instructions.len());
597 assert_eq!(&Literal("Hello,"), &instructions[0]);
598 assert_eq!(&Literal("there!"), &instructions[1]);
599 }
600
601 #[test]
602 fn test_strip_whitespace_followed_by_another_tag() {
603 let text = "{value -}{value} Hello";
604 let instructions = compile(text).unwrap();
605 assert_eq!(3, instructions.len());
606 assert_eq!(&Value(vec![PathStep::Name("value")]), &instructions[0]);
607 assert_eq!(&Value(vec![PathStep::Name("value")]), &instructions[1]);
608 assert_eq!(&Literal(" Hello"), &instructions[2]);
609 }
610
611 #[test]
612 fn test_call() {
613 let text = "{{ call my_macro with foo.bar }}";
614 let instructions = compile(text).unwrap();
615 assert_eq!(1, instructions.len());
616 assert_eq!(
617 &Call(
618 "my_macro",
619 vec![PathStep::Name("foo"), PathStep::Name("bar")]
620 ),
621 &instructions[0]
622 );
623 }
624
625 #[test]
626 fn test_curly_brace_escaping() {
627 let text = "body \\{ \nfont-size: {fontsize} \n}";
628 let instructions = compile(text).unwrap();
629 assert_eq!(4, instructions.len());
630 assert_eq!(&Literal("body "), &instructions[0]);
631 assert_eq!(&Literal("{ \nfont-size: "), &instructions[1]);
632 assert_eq!(&Value(vec![PathStep::Name("fontsize")]), &instructions[2]);
633 assert_eq!(&Literal(" \n}"), &instructions[3]);
634 }
635
636 #[test]
637 fn test_unclosed_tags() {
638 let tags = vec![
639 "{",
640 "{ foo.bar",
641 "{ foo.bar\n }",
642 "{{",
643 "{{ if foo.bar",
644 "{{ if foo.bar \n}}",
645 "{#",
646 "{# if foo.bar",
647 "{# if foo.bar \n#}",
648 ];
649 for tag in tags {
650 compile(tag).unwrap_err();
651 }
652 }
653
654 #[test]
655 fn test_mismatched_blocks() {
656 let text = "{{ if foo }}{{ with bar }}{{ endif }} {{ endwith }}";
657 compile(text).unwrap_err();
658 }
659
660 #[test]
661 fn test_disallows_invalid_keywords() {
662 let text = "{ @foo }";
663 compile(text).unwrap_err();
664 }
665
666 #[test]
667 fn test_diallows_unknown_block_type() {
668 let text = "{{ foobar }}";
669 compile(text).unwrap_err();
670 }
671
672 #[test]
673 fn test_parse_error_line_column_num() {
674 let text = "\n\n\n{{ foobar }}";
675 let err = compile(text).unwrap_err();
676 if let ParseError { line, column, .. } = err {
677 assert_eq!(4, line);
678 assert_eq!(3, column);
679 } else {
680 panic!("Should have returned a parse error");
681 }
682 }
683
684 #[test]
685 fn test_parse_error_on_unclosed_if() {
686 let text = "{{ if foo }}";
687 compile(text).unwrap_err();
688 }
689
690 #[test]
691 fn test_parse_escaped_open_curly_brace() {
692 let text: &str = r"hello \{world}";
693 let instructions = compile(text).unwrap();
694 assert_eq!(2, instructions.len());
695 assert_eq!(&Literal("hello "), &instructions[0]);
696 assert_eq!(&Literal("{world}"), &instructions[1]);
697 }
698}
699