1 use std::ops::Deref;
2 
3 /// TinyTemplate implements a simple bytecode interpreter for its template engine. Instructions
4 /// for this interpreter are represented by the Instruction enum and typically contain various
5 /// parameters such as the path to context values or name strings.
6 ///
7 /// In TinyTemplate, the template string itself is assumed to be statically available (or at least
8 /// longer-lived than the TinyTemplate instance) so paths and instructions simply borrow string
9 /// slices from the template text. These string slices can then be appended directly to the output
10 /// string.
11 
12 /// Enum for a step in a path which optionally contains a parsed index.
13 #[derive(Eq, PartialEq, Debug, Clone)]
14 pub(crate) enum PathStep<'template> {
15     Name(&'template str),
16     Index(&'template str, usize),
17 }
18 impl<'template> Deref for PathStep<'template> {
19     type Target = str;
20 
deref(&self) -> &Self::Target21     fn deref(&self) -> &Self::Target {
22         match self {
23             PathStep::Name(s) => s,
24             PathStep::Index(s, _) => s,
25         }
26     }
27 }
28 
29 /// Sequence of named steps used for looking up values in the context
30 pub(crate) type Path<'template> = Vec<PathStep<'template>>;
31 
32 /// Path, but as a slice.
33 pub(crate) type PathSlice<'a, 'template> = &'a [PathStep<'template>];
34 
35 /// Enum representing the bytecode instructions.
36 #[derive(Eq, PartialEq, Debug, Clone)]
37 pub(crate) enum Instruction<'template> {
38     /// Emit a literal string into the output buffer
39     Literal(&'template str),
40 
41     /// Look up the value for the given path and render it into the output buffer using the default
42     /// formatter
43     Value(Path<'template>),
44 
45     /// Look up the value for the given path and pass it to the formatter with the given name
46     FormattedValue(Path<'template>, &'template str),
47 
48     /// Look up the value at the given path and jump to the given instruction index if that value
49     /// is truthy (if the boolean is true) or falsy (if the boolean is false)
50     Branch(Path<'template>, bool, usize),
51 
52     /// Push a named context on the stack, shadowing only that name.
53     PushNamedContext(Path<'template>, &'template str),
54 
55     /// Push an iteration context on the stack, shadowing the given name with the current value from
56     /// the vec pointed to by the path. The current value will be updated by the Iterate instruction.
57     /// This is always generated before an Iterate instruction which actually starts the iterator.
58     PushIterationContext(Path<'template>, &'template str),
59 
60     /// Pop a context off the stack
61     PopContext,
62 
63     /// Advance the topmost iterator on the context stack by one and update that context. If the
64     /// iterator is empty, jump to the given instruction.
65     Iterate(usize),
66 
67     /// Unconditionally jump to the given instruction. Used to skip else blocks and repeat loops.
68     Goto(usize),
69 
70     /// Look up the named template and render it into the output buffer with the value pointed to
71     /// by the path as its context.
72     Call(&'template str, Path<'template>),
73 }
74 
75 /// Convert a path back into a dotted string.
path_to_str(path: PathSlice) -> String76 pub(crate) fn path_to_str(path: PathSlice) -> String {
77     let mut path_str = "".to_string();
78     for (i, step) in path.iter().enumerate() {
79         if i > 0 {
80             path_str.push('.');
81         }
82         path_str.push_str(step);
83     }
84     path_str
85 }
86