1 //! This module implements the bytecode interpreter that actually renders the templates. 2 3 use compiler::TemplateCompiler; 4 use error::Error::*; 5 use error::*; 6 use format; 7 use instruction::{Instruction, PathSlice}; 8 use serde_json::Value; 9 use std::collections::HashMap; 10 use std::fmt::Write; 11 use std::slice; 12 use ValueFormatter; 13 14 /// Enum defining the different kinds of records on the context stack. 15 enum ContextElement<'render, 'template> { 16 /// Object contexts shadow everything below them on the stack, because every name is looked up 17 /// in this object. 18 Object(&'render Value), 19 /// Named contexts shadow only one name. Any path that starts with that name is looked up in 20 /// this object, and all others are passed on down the stack. 21 Named(&'template str, &'render Value), 22 /// Iteration contexts shadow one name with the current value of the iteration. They also 23 /// store the iteration state. The two usizes are the index of the current value and the length 24 /// of the array that we're iterating over. 25 Iteration( 26 &'template str, 27 &'render Value, 28 usize, 29 usize, 30 slice::Iter<'render, Value>, 31 ), 32 } 33 34 /// Helper struct which mostly exists so that I have somewhere to put functions that access the 35 /// rendering context stack. 36 struct RenderContext<'render, 'template> { 37 original_text: &'template str, 38 context_stack: Vec<ContextElement<'render, 'template>>, 39 } 40 impl<'render, 'template> RenderContext<'render, 'template> { 41 /// Look up the given path in the context stack and return the value (if found) or an error (if 42 /// not) lookup(&self, path: PathSlice) -> Result<&'render Value>43 fn lookup(&self, path: PathSlice) -> Result<&'render Value> { 44 for stack_layer in self.context_stack.iter().rev() { 45 match stack_layer { 46 ContextElement::Object(obj) => return self.lookup_in(path, obj), 47 ContextElement::Named(name, obj) => { 48 if *name == path[0] { 49 return self.lookup_in(&path[1..], obj); 50 } 51 } 52 ContextElement::Iteration(name, obj, _, _, _) => { 53 if *name == path[0] { 54 return self.lookup_in(&path[1..], obj); 55 } 56 } 57 } 58 } 59 panic!("Attempted to do a lookup with an empty context stack. That shouldn't be possible.") 60 } 61 62 /// Look up a path within a given value object and return the resulting value (if found) or 63 /// an error (if not) lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value>64 fn lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value> { 65 let mut current = object; 66 for step in path.iter() { 67 match current.get(step) { 68 Some(next) => current = next, 69 None => return Err(lookup_error(self.original_text, step, path, current)), 70 } 71 } 72 Ok(current) 73 } 74 75 /// Look up the index and length values for the top iteration context on the stack. lookup_index(&self) -> Result<(usize, usize)>76 fn lookup_index(&self) -> Result<(usize, usize)> { 77 for stack_layer in self.context_stack.iter().rev() { 78 match stack_layer { 79 ContextElement::Iteration(_, _, index, length, _) => return Ok((*index, *length)), 80 _ => continue, 81 } 82 } 83 Err(GenericError { 84 msg: "Used @index outside of a foreach block.".to_string(), 85 }) 86 } 87 } 88 89 /// Structure representing a parsed template. It holds the bytecode program for rendering the 90 /// template as well as the length of the original template string, which is used as a guess to 91 /// pre-size the output string buffer. 92 pub(crate) struct Template<'template> { 93 original_text: &'template str, 94 instructions: Vec<Instruction<'template>>, 95 template_len: usize, 96 } 97 impl<'template> Template<'template> { 98 /// Create a Template from the given template string. compile(text: &'template str) -> Result<Template>99 pub fn compile(text: &'template str) -> Result<Template> { 100 Ok(Template { 101 original_text: text, 102 template_len: text.len(), 103 instructions: TemplateCompiler::new(text).compile()?, 104 }) 105 } 106 107 /// Render this template into a string and return it (or any error if one is encountered). render( &self, context: &Value, template_registry: &HashMap<&str, Template>, formatter_registry: &HashMap<&str, Box<ValueFormatter>>, ) -> Result<String>108 pub fn render( 109 &self, 110 context: &Value, 111 template_registry: &HashMap<&str, Template>, 112 formatter_registry: &HashMap<&str, Box<ValueFormatter>>, 113 ) -> Result<String> { 114 // The length of the original template seems like a reasonable guess at the length of the 115 // output. 116 let mut output = String::with_capacity(self.template_len); 117 self.render_into(context, template_registry, formatter_registry, &mut output)?; 118 Ok(output) 119 } 120 121 /// Render this template into a given string. Used for calling other templates. render_into( &self, context: &Value, template_registry: &HashMap<&str, Template>, formatter_registry: &HashMap<&str, Box<ValueFormatter>>, output: &mut String, ) -> Result<()>122 pub fn render_into( 123 &self, 124 context: &Value, 125 template_registry: &HashMap<&str, Template>, 126 formatter_registry: &HashMap<&str, Box<ValueFormatter>>, 127 output: &mut String, 128 ) -> Result<()> { 129 let mut program_counter = 0; 130 let mut render_context = RenderContext { 131 original_text: self.original_text, 132 context_stack: vec![ContextElement::Object(context)], 133 }; 134 135 while program_counter < self.instructions.len() { 136 match &self.instructions[program_counter] { 137 Instruction::Literal(text) => { 138 output.push_str(text); 139 program_counter += 1; 140 } 141 Instruction::Value(path) => { 142 let first = *path.first().unwrap(); 143 if first.starts_with('@') { 144 // Currently we just hard-code the special @-keywords and have special 145 // lookup functions to use them because there are lifetime complexities with 146 // looking up values that don't live for as long as the given context object. 147 match first { 148 "@index" => { 149 write!(output, "{}", render_context.lookup_index()?.0).unwrap() 150 } 151 "@first" => { 152 write!(output, "{}", render_context.lookup_index()?.0 == 0).unwrap() 153 } 154 "@last" => { 155 let (index, length) = render_context.lookup_index()?; 156 write!(output, "{}", index == length - 1).unwrap() 157 } 158 _ => panic!(), // This should have been caught by the parser. 159 } 160 } else { 161 let value_to_render = render_context.lookup(path)?; 162 format(value_to_render, output)?; 163 } 164 program_counter += 1; 165 } 166 Instruction::FormattedValue(path, name) => { 167 // The @ keywords aren't supported for formatted values. Should they be? 168 let value_to_render = render_context.lookup(path)?; 169 match formatter_registry.get(name) { 170 Some(formatter) => { 171 let formatter_result = formatter(value_to_render, output); 172 if let Err(err) = formatter_result { 173 return Err(called_formatter_error(self.original_text, name, err)); 174 } 175 } 176 None => return Err(unknown_formatter(self.original_text, name)), 177 } 178 program_counter += 1; 179 } 180 Instruction::Branch(path, negate, target) => { 181 let first = *path.first().unwrap(); 182 let mut truthy = if first.starts_with('@') { 183 match first { 184 "@index" => render_context.lookup_index()?.0 != 0, 185 "@first" => render_context.lookup_index()?.0 == 0, 186 "@last" => { 187 let (index, length) = render_context.lookup_index()?; 188 index == (length - 1) 189 } 190 _ => panic!(), // This should have been caught by the parser. 191 } 192 } else { 193 let value_to_render = render_context.lookup(path)?; 194 match value_to_render { 195 Value::Null => false, 196 Value::Bool(b) => *b, 197 Value::Number(n) => match n.as_f64() { 198 Some(float) => float != 0.0, 199 None => { 200 return Err(truthiness_error(self.original_text, path)); 201 } 202 }, 203 Value::String(s) => !s.is_empty(), 204 Value::Array(arr) => !arr.is_empty(), 205 Value::Object(_) => true, 206 } 207 }; 208 if *negate { 209 truthy = !truthy; 210 } 211 212 if truthy { 213 program_counter = *target; 214 } else { 215 program_counter += 1; 216 } 217 } 218 Instruction::PushNamedContext(path, name) => { 219 let context_value = render_context.lookup(path)?; 220 render_context 221 .context_stack 222 .push(ContextElement::Named(name, context_value)); 223 program_counter += 1; 224 } 225 Instruction::PushIterationContext(path, name) => { 226 // We push a context with an invalid index and no value and then wait for the 227 // following Iterate instruction to set the index and value properly. 228 let context_value = render_context.lookup(path)?; 229 match context_value { 230 Value::Array(ref arr) => { 231 render_context.context_stack.push(ContextElement::Iteration( 232 name, 233 &Value::Null, 234 ::std::usize::MAX, 235 arr.len(), 236 arr.iter(), 237 )) 238 } 239 _ => return Err(not_iterable_error(self.original_text, path)), 240 }; 241 program_counter += 1; 242 } 243 Instruction::PopContext => { 244 render_context.context_stack.pop(); 245 program_counter += 1; 246 } 247 Instruction::Goto(target) => { 248 program_counter = *target; 249 } 250 Instruction::Iterate(target) => { 251 match render_context.context_stack.last_mut() { 252 Some(ContextElement::Iteration(_, val, index, _, iter)) => { 253 match iter.next() { 254 Some(new_val) => { 255 *val = new_val; 256 // On the first iteration, this will be usize::MAX so it will 257 // wrap around to zero. 258 *index = index.wrapping_add(1); 259 program_counter += 1; 260 } 261 None => { 262 program_counter = *target; 263 } 264 } 265 } 266 _ => panic!("Malformed program."), 267 }; 268 } 269 Instruction::Call(template_name, path) => { 270 let context_value = render_context.lookup(path)?; 271 match template_registry.get(template_name) { 272 Some(templ) => { 273 let called_templ_result = templ.render_into( 274 context_value, 275 template_registry, 276 formatter_registry, 277 output, 278 ); 279 if let Err(err) = called_templ_result { 280 return Err(called_template_error( 281 self.original_text, 282 template_name, 283 err, 284 )); 285 } 286 } 287 None => return Err(unknown_template(self.original_text, template_name)), 288 } 289 program_counter += 1; 290 } 291 } 292 } 293 Ok(()) 294 } 295 } 296 297 #[cfg(test)] 298 mod test { 299 use super::*; 300 use compiler::TemplateCompiler; 301 compile(text: &'static str) -> Template<'static>302 fn compile(text: &'static str) -> Template<'static> { 303 Template { 304 original_text: text, 305 template_len: text.len(), 306 instructions: TemplateCompiler::new(text).compile().unwrap(), 307 } 308 } 309 310 #[derive(Serialize)] 311 struct NestedContext { 312 value: usize, 313 } 314 315 #[derive(Serialize)] 316 struct TestContext { 317 number: usize, 318 string: &'static str, 319 boolean: bool, 320 null: Option<usize>, 321 array: Vec<usize>, 322 nested: NestedContext, 323 escapes: &'static str, 324 } 325 context() -> Value326 fn context() -> Value { 327 let ctx = TestContext { 328 number: 5, 329 string: "test", 330 boolean: true, 331 null: None, 332 array: vec![1, 2, 3], 333 nested: NestedContext { value: 10 }, 334 escapes: "1:< 2:> 3:& 4:' 5:\"", 335 }; 336 ::serde_json::to_value(&ctx).unwrap() 337 } 338 other_templates() -> HashMap<&'static str, Template<'static>>339 fn other_templates() -> HashMap<&'static str, Template<'static>> { 340 let mut map = HashMap::new(); 341 map.insert("my_macro", compile("{value}")); 342 map 343 } 344 format(value: &Value, output: &mut String) -> Result<()>345 fn format(value: &Value, output: &mut String) -> Result<()> { 346 output.push_str("{"); 347 ::format(value, output)?; 348 output.push_str("}"); 349 Ok(()) 350 } 351 formatters() -> HashMap<&'static str, Box<ValueFormatter>>352 fn formatters() -> HashMap<&'static str, Box<ValueFormatter>> { 353 let mut map = HashMap::<&'static str, Box<ValueFormatter>>::new(); 354 map.insert("my_formatter", Box::new(format)); 355 map 356 } 357 358 #[test] test_literal()359 fn test_literal() { 360 let template = compile("Hello!"); 361 let context = context(); 362 let template_registry = other_templates(); 363 let formatter_registry = formatters(); 364 let string = template 365 .render(&context, &template_registry, &formatter_registry) 366 .unwrap(); 367 assert_eq!("Hello!", &string); 368 } 369 370 #[test] test_value()371 fn test_value() { 372 let template = compile("{ number }"); 373 let context = context(); 374 let template_registry = other_templates(); 375 let formatter_registry = formatters(); 376 let string = template 377 .render(&context, &template_registry, &formatter_registry) 378 .unwrap(); 379 assert_eq!("5", &string); 380 } 381 382 #[test] test_path()383 fn test_path() { 384 let template = compile("The number of the day is { nested.value }."); 385 let context = context(); 386 let template_registry = other_templates(); 387 let formatter_registry = formatters(); 388 let string = template 389 .render(&context, &template_registry, &formatter_registry) 390 .unwrap(); 391 assert_eq!("The number of the day is 10.", &string); 392 } 393 394 #[test] test_if_taken()395 fn test_if_taken() { 396 let template = compile("{{ if boolean }}Hello!{{ endif }}"); 397 let context = context(); 398 let template_registry = other_templates(); 399 let formatter_registry = formatters(); 400 let string = template 401 .render(&context, &template_registry, &formatter_registry) 402 .unwrap(); 403 assert_eq!("Hello!", &string); 404 } 405 406 #[test] test_if_untaken()407 fn test_if_untaken() { 408 let template = compile("{{ if null }}Hello!{{ endif }}"); 409 let context = context(); 410 let template_registry = other_templates(); 411 let formatter_registry = formatters(); 412 let string = template 413 .render(&context, &template_registry, &formatter_registry) 414 .unwrap(); 415 assert_eq!("", &string); 416 } 417 418 #[test] test_if_else_taken()419 fn test_if_else_taken() { 420 let template = compile("{{ if boolean }}Hello!{{ else }}Goodbye!{{ endif }}"); 421 let context = context(); 422 let template_registry = other_templates(); 423 let formatter_registry = formatters(); 424 let string = template 425 .render(&context, &template_registry, &formatter_registry) 426 .unwrap(); 427 assert_eq!("Hello!", &string); 428 } 429 430 #[test] test_if_else_untaken()431 fn test_if_else_untaken() { 432 let template = compile("{{ if null }}Hello!{{ else }}Goodbye!{{ endif }}"); 433 let context = context(); 434 let template_registry = other_templates(); 435 let formatter_registry = formatters(); 436 let string = template 437 .render(&context, &template_registry, &formatter_registry) 438 .unwrap(); 439 assert_eq!("Goodbye!", &string); 440 } 441 442 #[test] test_ifnot_taken()443 fn test_ifnot_taken() { 444 let template = compile("{{ if not boolean }}Hello!{{ endif }}"); 445 let context = context(); 446 let template_registry = other_templates(); 447 let formatter_registry = formatters(); 448 let string = template 449 .render(&context, &template_registry, &formatter_registry) 450 .unwrap(); 451 assert_eq!("", &string); 452 } 453 454 #[test] test_ifnot_untaken()455 fn test_ifnot_untaken() { 456 let template = compile("{{ if not null }}Hello!{{ endif }}"); 457 let context = context(); 458 let template_registry = other_templates(); 459 let formatter_registry = formatters(); 460 let string = template 461 .render(&context, &template_registry, &formatter_registry) 462 .unwrap(); 463 assert_eq!("Hello!", &string); 464 } 465 466 #[test] test_ifnot_else_taken()467 fn test_ifnot_else_taken() { 468 let template = compile("{{ if not boolean }}Hello!{{ else }}Goodbye!{{ endif }}"); 469 let context = context(); 470 let template_registry = other_templates(); 471 let formatter_registry = formatters(); 472 let string = template 473 .render(&context, &template_registry, &formatter_registry) 474 .unwrap(); 475 assert_eq!("Goodbye!", &string); 476 } 477 478 #[test] test_ifnot_else_untaken()479 fn test_ifnot_else_untaken() { 480 let template = compile("{{ if not null }}Hello!{{ else }}Goodbye!{{ endif }}"); 481 let context = context(); 482 let template_registry = other_templates(); 483 let formatter_registry = formatters(); 484 let string = template 485 .render(&context, &template_registry, &formatter_registry) 486 .unwrap(); 487 assert_eq!("Hello!", &string); 488 } 489 490 #[test] test_nested_ifs()491 fn test_nested_ifs() { 492 let template = compile( 493 "{{ if boolean }}Hi, {{ if null }}there!{{ else }}Hello!{{ endif }}{{ endif }}", 494 ); 495 let context = context(); 496 let template_registry = other_templates(); 497 let formatter_registry = formatters(); 498 let string = template 499 .render(&context, &template_registry, &formatter_registry) 500 .unwrap(); 501 assert_eq!("Hi, Hello!", &string); 502 } 503 504 #[test] test_with()505 fn test_with() { 506 let template = compile("{{ with nested as n }}{ n.value } { number }{{endwith}}"); 507 let context = context(); 508 let template_registry = other_templates(); 509 let formatter_registry = formatters(); 510 let string = template 511 .render(&context, &template_registry, &formatter_registry) 512 .unwrap(); 513 assert_eq!("10 5", &string); 514 } 515 516 #[test] test_for_loop()517 fn test_for_loop() { 518 let template = compile("{{ for a in array }}{ a }{{ endfor }}"); 519 let context = context(); 520 let template_registry = other_templates(); 521 let formatter_registry = formatters(); 522 let string = template 523 .render(&context, &template_registry, &formatter_registry) 524 .unwrap(); 525 assert_eq!("123", &string); 526 } 527 528 #[test] test_for_loop_index()529 fn test_for_loop_index() { 530 let template = compile("{{ for a in array }}{ @index }{{ endfor }}"); 531 let context = context(); 532 let template_registry = other_templates(); 533 let formatter_registry = formatters(); 534 let string = template 535 .render(&context, &template_registry, &formatter_registry) 536 .unwrap(); 537 assert_eq!("012", &string); 538 } 539 540 #[test] test_for_loop_first()541 fn test_for_loop_first() { 542 let template = 543 compile("{{ for a in array }}{{if @first }}{ @index }{{ endif }}{{ endfor }}"); 544 let context = context(); 545 let template_registry = other_templates(); 546 let formatter_registry = formatters(); 547 let string = template 548 .render(&context, &template_registry, &formatter_registry) 549 .unwrap(); 550 assert_eq!("0", &string); 551 } 552 553 #[test] test_for_loop_last()554 fn test_for_loop_last() { 555 let template = 556 compile("{{ for a in array }}{{ if @last}}{ @index }{{ endif }}{{ endfor }}"); 557 let context = context(); 558 let template_registry = other_templates(); 559 let formatter_registry = formatters(); 560 let string = template 561 .render(&context, &template_registry, &formatter_registry) 562 .unwrap(); 563 assert_eq!("2", &string); 564 } 565 566 #[test] test_whitespace_stripping_value()567 fn test_whitespace_stripping_value() { 568 let template = compile("1 \n\t {- number -} \n 1"); 569 let context = context(); 570 let template_registry = other_templates(); 571 let formatter_registry = formatters(); 572 let string = template 573 .render(&context, &template_registry, &formatter_registry) 574 .unwrap(); 575 assert_eq!("151", &string); 576 } 577 578 #[test] test_call()579 fn test_call() { 580 let template = compile("{{ call my_macro with nested }}"); 581 let context = context(); 582 let template_registry = other_templates(); 583 let formatter_registry = formatters(); 584 let string = template 585 .render(&context, &template_registry, &formatter_registry) 586 .unwrap(); 587 assert_eq!("10", &string); 588 } 589 590 #[test] test_formatter()591 fn test_formatter() { 592 let template = compile("{ nested.value | my_formatter }"); 593 let context = context(); 594 let template_registry = other_templates(); 595 let formatter_registry = formatters(); 596 let string = template 597 .render(&context, &template_registry, &formatter_registry) 598 .unwrap(); 599 assert_eq!("{10}", &string); 600 } 601 602 #[test] test_unknown()603 fn test_unknown() { 604 let template = compile("{ foobar }"); 605 let context = context(); 606 let template_registry = other_templates(); 607 let formatter_registry = formatters(); 608 template 609 .render(&context, &template_registry, &formatter_registry) 610 .unwrap_err(); 611 } 612 613 #[test] test_escaping()614 fn test_escaping() { 615 let template = compile("{ escapes }"); 616 let context = context(); 617 let template_registry = other_templates(); 618 let formatter_registry = formatters(); 619 let string = template 620 .render(&context, &template_registry, &formatter_registry) 621 .unwrap(); 622 assert_eq!("1:< 2:> 3:& 4:' 5:"", &string); 623 } 624 625 #[test] test_unescaped()626 fn test_unescaped() { 627 let template = compile("{ escapes | unescaped }"); 628 let context = context(); 629 let template_registry = other_templates(); 630 let mut formatter_registry = formatters(); 631 formatter_registry.insert("unescaped", Box::new(::format_unescaped)); 632 let string = template 633 .render(&context, &template_registry, &formatter_registry) 634 .unwrap(); 635 assert_eq!("1:< 2:> 3:& 4:' 5:\"", &string); 636 } 637 } 638