1 //! This module implements the bytecode interpreter that actually renders the templates.
3 use compiler
::TemplateCompiler
;
6 use instruction
::{Instruction, PathSlice, PathStep}
;
8 use std
::collections
::HashMap
;
13 /// Enum defining the different kinds of records on the context stack.
14 enum ContextElement
<'render
, 'template
> {
15 /// Object contexts shadow everything below them on the stack, because every name is looked up
17 Object(&'render Value
),
18 /// Named contexts shadow only one name. Any path that starts with that name is looked up in
19 /// this object, and all others are passed on down the stack.
20 Named(&'template
str, &'render Value
),
21 /// Iteration contexts shadow one name with the current value of the iteration. They also
22 /// store the iteration state. The two usizes are the index of the current value and the length
23 /// of the array that we're iterating over.
29 slice
::Iter
<'render
, Value
>,
33 /// Helper struct which mostly exists so that I have somewhere to put functions that access the
34 /// rendering context stack.
35 struct RenderContext
<'render
, 'template
> {
36 original_text
: &'template
str,
37 context_stack
: Vec
<ContextElement
<'render
, 'template
>>,
39 impl<'render
, 'template
> RenderContext
<'render
, 'template
> {
40 /// Look up the given path in the context stack and return the value (if found) or an error (if
42 fn lookup(&self, path
: PathSlice
) -> Result
<&'render Value
> {
43 for stack_layer
in self.context_stack
.iter().rev() {
45 ContextElement
::Object(obj
) => return self.lookup_in(path
, obj
),
46 ContextElement
::Named(name
, obj
) => {
47 if *name
== &*path
[0] {
48 return self.lookup_in(&path
[1..], obj
);
51 ContextElement
::Iteration(name
, obj
, _
, _
, _
) => {
52 if *name
== &*path
[0] {
53 return self.lookup_in(&path
[1..], obj
);
58 panic
!("Attempted to do a lookup with an empty context stack. That shouldn't be possible.")
61 /// Look up a path within a given value object and return the resulting value (if found) or
63 fn lookup_in(&self, path
: PathSlice
, object
: &'render Value
) -> Result
<&'render Value
> {
64 let mut current
= object
;
65 for step
in path
.iter() {
66 if let PathStep
::Index(_
, n
) = step
{
67 if let Some(next
) = current
.get(n
) {
73 let step
: &str = &*step
;
75 match current
.get(step
) {
76 Some(next
) => current
= next
,
77 None
=> return Err(lookup_error(self.original_text
, step
, path
, current
)),
83 /// Look up the index and length values for the top iteration context on the stack.
84 fn lookup_index(&self) -> Result
<(usize, usize)> {
85 for stack_layer
in self.context_stack
.iter().rev() {
87 ContextElement
::Iteration(_
, _
, index
, length
, _
) => return Ok((*index
, *length
)),
92 msg
: "Used @index outside of a foreach block.".to_string(),
96 /// Look up the root context object
97 fn lookup_root(&self) -> Result
<&'render Value
> {
98 match self.context_stack
.get(0) {
99 Some(ContextElement
::Object(obj
)) => Ok(obj
),
101 panic
!("Expected Object value at root of context stack, but was something else.")
104 "Attempted to do a lookup with an empty context stack. That shouldn't be possible."
110 /// Structure representing a parsed template. It holds the bytecode program for rendering the
111 /// template as well as the length of the original template string, which is used as a guess to
112 /// pre-size the output string buffer.
113 pub(crate) struct Template
<'template
> {
114 original_text
: &'template
str,
115 instructions
: Vec
<Instruction
<'template
>>,
118 impl<'template
> Template
<'template
> {
119 /// Create a Template from the given template string.
120 pub fn compile(text
: &'template
str) -> Result
<Template
> {
123 template_len
: text
.len(),
124 instructions
: TemplateCompiler
::new(text
).compile()?
,
128 /// Render this template into a string and return it (or any error if one is encountered).
132 template_registry
: &HashMap
<&str, Template
>,
133 formatter_registry
: &HashMap
<&str, Box
<ValueFormatter
>>,
134 default_formatter
: &ValueFormatter
,
135 ) -> Result
<String
> {
136 // The length of the original template seems like a reasonable guess at the length of the
138 let mut output
= String
::with_capacity(self.template_len
);
149 /// Render this template into a given string. Used for calling other templates.
153 template_registry
: &HashMap
<&str, Template
>,
154 formatter_registry
: &HashMap
<&str, Box
<ValueFormatter
>>,
155 default_formatter
: &ValueFormatter
,
158 let mut program_counter
= 0;
159 let mut render_context
= RenderContext
{
160 original_text
: self.original_text
,
161 context_stack
: vec
![ContextElement
::Object(context
)],
164 while program_counter
< self.instructions
.len() {
165 match &self.instructions
[program_counter
] {
166 Instruction
::Literal(text
) => {
167 output
.push_str(text
);
168 program_counter
+= 1;
170 Instruction
::Value(path
) => {
171 let first
= path
.first().unwrap();
172 if first
.starts_with('@'
) {
173 // Currently we just hard-code the special @-keywords and have special
174 // lookup functions to use them because there are lifetime complexities with
175 // looking up values that don't live for as long as the given context object.
176 let first
: &str = &*first
;
179 write
!(output
, "{}", render_context
.lookup_index()?
.0).unwrap()
182 write
!(output
, "{}", render_context
.lookup_index()?
.0 == 0).unwrap()
185 let (index
, length
) = render_context
.lookup_index()?
;
186 write
!(output
, "{}", index
== length
- 1).unwrap()
189 let value_to_render
= render_context
.lookup_root()?
;
190 default_formatter(value_to_render
, output
)?
;
192 _
=> panic
!(), // This should have been caught by the parser.
195 let value_to_render
= render_context
.lookup(path
)?
;
196 default_formatter(value_to_render
, output
)?
;
198 program_counter
+= 1;
200 Instruction
::FormattedValue(path
, name
) => {
201 // The @ keywords aren't supported for formatted values. Should they be?
202 let value_to_render
= render_context
.lookup(path
)?
;
203 match formatter_registry
.get(name
) {
205 let formatter_result
= formatter(value_to_render
, output
);
206 if let Err(err
) = formatter_result
{
207 return Err(called_formatter_error(self.original_text
, name
, err
));
210 None
=> return Err(unknown_formatter(self.original_text
, name
)),
212 program_counter
+= 1;
214 Instruction
::Branch(path
, negate
, target
) => {
215 let first
= path
.first().unwrap();
216 let mut truthy
= if first
.starts_with('@'
) {
217 let first
: &str = &*first
;
219 "@index" => render_context
.lookup_index()?
.0 != 0,
220 "@first" => render_context
.lookup_index()?
.0 == 0,
222 let (index
, length
) = render_context
.lookup_index()?
;
223 index
== (length
- 1)
225 "@root" => self.value_is_truthy(render_context
.lookup_root()?
, path
)?
,
226 other
=> panic
!("Unknown keyword {}", other
), // This should have been caught by the parser.
229 let value_to_render
= render_context
.lookup(path
)?
;
230 self.value_is_truthy(value_to_render
, path
)?
237 program_counter
= *target
;
239 program_counter
+= 1;
242 Instruction
::PushNamedContext(path
, name
) => {
243 let context_value
= render_context
.lookup(path
)?
;
246 .push(ContextElement
::Named(name
, context_value
));
247 program_counter
+= 1;
249 Instruction
::PushIterationContext(path
, name
) => {
250 // We push a context with an invalid index and no value and then wait for the
251 // following Iterate instruction to set the index and value properly.
252 let first
= path
.first().unwrap();
253 let context_value
= match first
{
254 PathStep
::Name("@root") => render_context
.lookup_root()?
,
255 PathStep
::Name(other
) if other
.starts_with('@'
) => {
256 return Err(not_iterable_error(self.original_text
, path
))
258 _
=> render_context
.lookup(path
)?
,
260 match context_value
{
261 Value
::Array(ref arr
) => {
262 render_context
.context_stack
.push(ContextElement
::Iteration(
270 _
=> return Err(not_iterable_error(self.original_text
, path
)),
272 program_counter
+= 1;
274 Instruction
::PopContext
=> {
275 render_context
.context_stack
.pop();
276 program_counter
+= 1;
278 Instruction
::Goto(target
) => {
279 program_counter
= *target
;
281 Instruction
::Iterate(target
) => {
282 match render_context
.context_stack
.last_mut() {
283 Some(ContextElement
::Iteration(_
, val
, index
, _
, iter
)) => {
287 // On the first iteration, this will be usize::MAX so it will
288 // wrap around to zero.
289 *index
= index
.wrapping_add(1);
290 program_counter
+= 1;
293 program_counter
= *target
;
297 _
=> panic
!("Malformed program."),
300 Instruction
::Call(template_name
, path
) => {
301 let context_value
= render_context
.lookup(path
)?
;
302 match template_registry
.get(template_name
) {
304 let called_templ_result
= templ
.render_into(
311 if let Err(err
) = called_templ_result
{
312 return Err(called_template_error(
319 None
=> return Err(unknown_template(self.original_text
, template_name
)),
321 program_counter
+= 1;
328 fn value_is_truthy(&self, value
: &Value
, path
: PathSlice
) -> Result
<bool
> {
329 let truthy
= match value
{
330 Value
::Null
=> false,
331 Value
::Bool(b
) => *b
,
332 Value
::Number(n
) => match n
.as_f64() {
333 Some(float
) => float
!= 0.0,
335 return Err(truthiness_error(self.original_text
, path
));
338 Value
::String(s
) => !s
.is_empty(),
339 Value
::Array(arr
) => !arr
.is_empty(),
340 Value
::Object(_
) => true,
349 use compiler
::TemplateCompiler
;
351 fn compile(text
: &'
static str) -> Template
<'
static> {
354 template_len
: text
.len(),
355 instructions
: TemplateCompiler
::new(text
).compile().unwrap(),
360 struct NestedContext
{
367 string
: &'
static str,
371 nested
: NestedContext
,
372 escapes
: &'
static str,
375 fn context() -> Value
{
376 let ctx
= TestContext
{
381 array
: vec
![1, 2, 3],
382 nested
: NestedContext { value: 10 }
,
383 escapes
: "1:< 2:> 3:& 4:' 5:\"",
385 ::serde_json
::to_value(&ctx
).unwrap()
388 fn other_templates() -> HashMap
<&'
static str, Template
<'
static>> {
389 let mut map
= HashMap
::new();
390 map
.insert("my_macro", compile("{value}"));
394 fn format(value
: &Value
, output
: &mut String
) -> Result
<()> {
395 output
.push_str("{");
396 ::format(value
, output
)?
;
397 output
.push_str("}");
401 fn formatters() -> HashMap
<&'
static str, Box
<ValueFormatter
>> {
402 let mut map
= HashMap
::<&'
static str, Box
<ValueFormatter
>>::new();
403 map
.insert("my_formatter", Box
::new(format
));
407 pub fn default_formatter() -> &'
static ValueFormatter
{
413 let template
= compile("Hello!");
414 let context
= context();
415 let template_registry
= other_templates();
416 let formatter_registry
= formatters();
417 let string
= template
422 &default_formatter(),
425 assert_eq
!("Hello!", &string
);
430 let template
= compile("{ number }");
431 let context
= context();
432 let template_registry
= other_templates();
433 let formatter_registry
= formatters();
434 let string
= template
439 &default_formatter(),
442 assert_eq
!("5", &string
);
447 let template
= compile("The number of the day is { nested.value }.");
448 let context
= context();
449 let template_registry
= other_templates();
450 let formatter_registry
= formatters();
451 let string
= template
456 &default_formatter(),
459 assert_eq
!("The number of the day is 10.", &string
);
464 let template
= compile("{{ if boolean }}Hello!{{ endif }}");
465 let context
= context();
466 let template_registry
= other_templates();
467 let formatter_registry
= formatters();
468 let string
= template
473 &default_formatter(),
476 assert_eq
!("Hello!", &string
);
480 fn test_if_untaken() {
481 let template
= compile("{{ if null }}Hello!{{ endif }}");
482 let context
= context();
483 let template_registry
= other_templates();
484 let formatter_registry
= formatters();
485 let string
= template
490 &default_formatter(),
493 assert_eq
!("", &string
);
497 fn test_if_else_taken() {
498 let template
= compile("{{ if boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
499 let context
= context();
500 let template_registry
= other_templates();
501 let formatter_registry
= formatters();
502 let string
= template
507 &default_formatter(),
510 assert_eq
!("Hello!", &string
);
514 fn test_if_else_untaken() {
515 let template
= compile("{{ if null }}Hello!{{ else }}Goodbye!{{ endif }}");
516 let context
= context();
517 let template_registry
= other_templates();
518 let formatter_registry
= formatters();
519 let string
= template
524 &default_formatter(),
527 assert_eq
!("Goodbye!", &string
);
531 fn test_ifnot_taken() {
532 let template
= compile("{{ if not boolean }}Hello!{{ endif }}");
533 let context
= context();
534 let template_registry
= other_templates();
535 let formatter_registry
= formatters();
536 let string
= template
541 &default_formatter(),
544 assert_eq
!("", &string
);
548 fn test_ifnot_untaken() {
549 let template
= compile("{{ if not null }}Hello!{{ endif }}");
550 let context
= context();
551 let template_registry
= other_templates();
552 let formatter_registry
= formatters();
553 let string
= template
558 &default_formatter(),
561 assert_eq
!("Hello!", &string
);
565 fn test_ifnot_else_taken() {
566 let template
= compile("{{ if not boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
567 let context
= context();
568 let template_registry
= other_templates();
569 let formatter_registry
= formatters();
570 let string
= template
575 &default_formatter(),
578 assert_eq
!("Goodbye!", &string
);
582 fn test_ifnot_else_untaken() {
583 let template
= compile("{{ if not null }}Hello!{{ else }}Goodbye!{{ endif }}");
584 let context
= context();
585 let template_registry
= other_templates();
586 let formatter_registry
= formatters();
587 let string
= template
592 &default_formatter(),
595 assert_eq
!("Hello!", &string
);
599 fn test_nested_ifs() {
600 let template
= compile(
601 "{{ if boolean }}Hi, {{ if null }}there!{{ else }}Hello!{{ endif }}{{ endif }}",
603 let context
= context();
604 let template_registry
= other_templates();
605 let formatter_registry
= formatters();
606 let string
= template
611 &default_formatter(),
614 assert_eq
!("Hi, Hello!", &string
);
619 let template
= compile("{{ with nested as n }}{ n.value } { number }{{endwith}}");
620 let context
= context();
621 let template_registry
= other_templates();
622 let formatter_registry
= formatters();
623 let string
= template
628 &default_formatter(),
631 assert_eq
!("10 5", &string
);
636 let template
= compile("{{ for a in array }}{ a }{{ endfor }}");
637 let context
= context();
638 let template_registry
= other_templates();
639 let formatter_registry
= formatters();
640 let string
= template
645 &default_formatter(),
648 assert_eq
!("123", &string
);
652 fn test_for_loop_index() {
653 let template
= compile("{{ for a in array }}{ @index }{{ endfor }}");
654 let context
= context();
655 let template_registry
= other_templates();
656 let formatter_registry
= formatters();
657 let string
= template
662 &default_formatter(),
665 assert_eq
!("012", &string
);
669 fn test_for_loop_first() {
671 compile("{{ for a in array }}{{if @first }}{ @index }{{ endif }}{{ endfor }}");
672 let context
= context();
673 let template_registry
= other_templates();
674 let formatter_registry
= formatters();
675 let string
= template
680 &default_formatter(),
683 assert_eq
!("0", &string
);
687 fn test_for_loop_last() {
689 compile("{{ for a in array }}{{ if @last}}{ @index }{{ endif }}{{ endfor }}");
690 let context
= context();
691 let template_registry
= other_templates();
692 let formatter_registry
= formatters();
693 let string
= template
698 &default_formatter(),
701 assert_eq
!("2", &string
);
705 fn test_whitespace_stripping_value() {
706 let template
= compile("1 \n\t {- number -} \n 1");
707 let context
= context();
708 let template_registry
= other_templates();
709 let formatter_registry
= formatters();
710 let string
= template
715 &default_formatter(),
718 assert_eq
!("151", &string
);
723 let template
= compile("{{ call my_macro with nested }}");
724 let context
= context();
725 let template_registry
= other_templates();
726 let formatter_registry
= formatters();
727 let string
= template
732 &default_formatter(),
735 assert_eq
!("10", &string
);
739 fn test_formatter() {
740 let template
= compile("{ nested.value | my_formatter }");
741 let context
= context();
742 let template_registry
= other_templates();
743 let formatter_registry
= formatters();
744 let string
= template
749 &default_formatter(),
752 assert_eq
!("{10}", &string
);
757 let template
= compile("{ foobar }");
758 let context
= context();
759 let template_registry
= other_templates();
760 let formatter_registry
= formatters();
766 &default_formatter(),
773 let template
= compile("{ escapes }");
774 let context
= context();
775 let template_registry
= other_templates();
776 let formatter_registry
= formatters();
777 let string
= template
782 &default_formatter(),
785 assert_eq
!("1:< 2:> 3:& 4:' 5:"", &string
);
789 fn test_unescaped() {
790 let template
= compile("{ escapes | unescaped }");
791 let context
= context();
792 let template_registry
= other_templates();
793 let mut formatter_registry
= formatters();
794 formatter_registry
.insert("unescaped", Box
::new(::format_unescaped
));
795 let string
= template
800 &default_formatter(),
803 assert_eq
!("1:< 2:> 3:& 4:' 5:\"", &string
);
807 fn test_root_print() {
808 let template
= compile("{ @root }");
809 let context
= "Hello World!";
810 let context
= ::serde_json
::to_value(&context
).unwrap();
811 let template_registry
= other_templates();
812 let formatter_registry
= formatters();
813 let string
= template
818 &default_formatter(),
821 assert_eq
!("Hello World!", &string
);
825 fn test_root_branch() {
826 let template
= compile("{{ if @root }}Hello World!{{ endif }}");
828 let context
= ::serde_json
::to_value(&context
).unwrap();
829 let template_registry
= other_templates();
830 let formatter_registry
= formatters();
831 let string
= template
836 &default_formatter(),
839 assert_eq
!("Hello World!", &string
);
843 fn test_root_iterate() {
844 let template
= compile("{{ for a in @root }}{ a }{{ endfor }}");
845 let context
= vec
!["foo", "bar"];
846 let context
= ::serde_json
::to_value(&context
).unwrap();
847 let template_registry
= other_templates();
848 let formatter_registry
= formatters();
849 let string
= template
854 &default_formatter(),
857 assert_eq
!("foobar", &string
);
861 fn test_number_truthiness_zero() {
862 let template
= compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
864 let context
= ::serde_json
::to_value(&context
).unwrap();
865 let template_registry
= other_templates();
866 let formatter_registry
= formatters();
867 let string
= template
872 &default_formatter(),
875 assert_eq
!("not truthy", &string
);
879 fn test_number_truthiness_one() {
880 let template
= compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
882 let context
= ::serde_json
::to_value(&context
).unwrap();
883 let template_registry
= other_templates();
884 let formatter_registry
= formatters();
885 let string
= template
890 &default_formatter(),
893 assert_eq
!("truthy", &string
);
897 fn test_indexed_paths() {
903 let template
= compile("{ foo.1 }{ foo.0 }");
904 let context
= Context { foo: (123, 456) }
;
905 let context
= ::serde_json
::to_value(&context
).unwrap();
906 let template_registry
= other_templates();
907 let formatter_registry
= formatters();
908 let string
= template
913 &default_formatter(),
916 assert_eq
!("456123", &string
);
920 fn test_indexed_paths_fall_back_to_string_lookup() {
923 foo
: HashMap
<&'
static str, usize>,
926 let template
= compile("{ foo.1 }{ foo.0 }");
927 let mut foo
= HashMap
::new();
928 foo
.insert("0", 123);
929 foo
.insert("1", 456);
930 let context
= Context { foo }
;
931 let context
= ::serde_json
::to_value(&context
).unwrap();
932 let template_registry
= other_templates();
933 let formatter_registry
= formatters();
934 let string
= template
939 &default_formatter(),
942 assert_eq
!("456123", &string
);