//! This module implements the bytecode interpreter that actually renders the templates. use compiler::TemplateCompiler; use error::Error::*; use error::*; use instruction::{Instruction, PathSlice, PathStep}; use serde_json::Value; use std::collections::HashMap; use std::fmt::Write; use std::slice; use ValueFormatter; /// Enum defining the different kinds of records on the context stack. enum ContextElement<'render, 'template> { /// Object contexts shadow everything below them on the stack, because every name is looked up /// in this object. Object(&'render Value), /// Named contexts shadow only one name. Any path that starts with that name is looked up in /// this object, and all others are passed on down the stack. Named(&'template str, &'render Value), /// Iteration contexts shadow one name with the current value of the iteration. They also /// store the iteration state. The two usizes are the index of the current value and the length /// of the array that we're iterating over. Iteration( &'template str, &'render Value, usize, usize, slice::Iter<'render, Value>, ), } /// Helper struct which mostly exists so that I have somewhere to put functions that access the /// rendering context stack. struct RenderContext<'render, 'template> { original_text: &'template str, context_stack: Vec>, } impl<'render, 'template> RenderContext<'render, 'template> { /// Look up the given path in the context stack and return the value (if found) or an error (if /// not) fn lookup(&self, path: PathSlice) -> Result<&'render Value> { for stack_layer in self.context_stack.iter().rev() { match stack_layer { ContextElement::Object(obj) => return self.lookup_in(path, obj), ContextElement::Named(name, obj) => { if *name == &*path[0] { return self.lookup_in(&path[1..], obj); } } ContextElement::Iteration(name, obj, _, _, _) => { if *name == &*path[0] { return self.lookup_in(&path[1..], obj); } } } } panic!("Attempted to do a lookup with an empty context stack. That shouldn't be possible.") } /// Look up a path within a given value object and return the resulting value (if found) or /// an error (if not) fn lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value> { let mut current = object; for step in path.iter() { if let PathStep::Index(_, n) = step { if let Some(next) = current.get(n) { current = next; continue; } } let step: &str = &*step; match current.get(step) { Some(next) => current = next, None => return Err(lookup_error(self.original_text, step, path, current)), } } Ok(current) } /// Look up the index and length values for the top iteration context on the stack. fn lookup_index(&self) -> Result<(usize, usize)> { for stack_layer in self.context_stack.iter().rev() { match stack_layer { ContextElement::Iteration(_, _, index, length, _) => return Ok((*index, *length)), _ => continue, } } Err(GenericError { msg: "Used @index outside of a foreach block.".to_string(), }) } /// Look up the root context object fn lookup_root(&self) -> Result<&'render Value> { match self.context_stack.get(0) { Some(ContextElement::Object(obj)) => Ok(obj), Some(_) => { panic!("Expected Object value at root of context stack, but was something else.") } None => panic!( "Attempted to do a lookup with an empty context stack. That shouldn't be possible." ), } } } /// Structure representing a parsed template. It holds the bytecode program for rendering the /// template as well as the length of the original template string, which is used as a guess to /// pre-size the output string buffer. pub(crate) struct Template<'template> { original_text: &'template str, instructions: Vec>, template_len: usize, } impl<'template> Template<'template> { /// Create a Template from the given template string. pub fn compile(text: &'template str) -> Result