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. 7 use error::Error::*; 8 use error::{get_offset, Error, Result}; 9 use instruction::{Instruction, Path, PathStep}; 10 11 /// The end point of a branch or goto instruction is not known. 12 const 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. 17 enum 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. 24 static 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. 29 pub(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 } 39 impl<'template> TemplateCompiler<'template> { 40 /// Create a new template compiler to parse and compile the given template. new(text: &'template str) -> TemplateCompiler<'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. compile(mut self) -> Result<Vec<Instruction<'template>>>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. parse_path(&self, text: &'template str) -> Result<Path<'template>>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. parse_error(&self, location: &str, msg: String) -> Error204 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. expect_empty(&self, text: &str) -> Result<()>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. close_branch(&mut self, new_target: usize, discriminant: &str) -> Result<()>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. close_for(&mut self, new_target: usize, discriminant: &str) -> Result<usize>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. consume_text(&mut self, escaped: bool) -> &'template str268 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. consume_value(&mut self) -> Result<(Path<'template>, Option<&'template str>)>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. trim_last_whitespace(&mut self)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. trim_next_whitespace(&mut self)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. consume_block(&mut self) -> Result<(&'template str, &'template str)>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. consume_tag(&mut self, expected_close: &str) -> Result<&'template str>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. parse_with(&self, with_text: &'template str) -> Result<(Path<'template>, &'template str)>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. parse_for(&self, for_text: &'template str) -> Result<(Path<'template>, &'template str)>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. parse_call(&self, call_text: &'template str) -> Result<(&'template str, Path<'template>)>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)] 420 mod test { 421 use super::*; 422 use instruction::Instruction::*; 423 compile(text: &'static str) -> Result<Vec<Instruction<'static>>>424 fn compile(text: &'static str) -> Result<Vec<Instruction<'static>>> { 425 TemplateCompiler::new(text).compile() 426 } 427 428 #[test] test_compile_literal()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] test_compile_value()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] test_compile_value_with_formatter()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] test_dotted_path()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] test_indexed_path()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] test_mixture()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] test_if_endif()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] test_if_not_endif()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] test_if_else_endif()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] test_with()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] test_foreach()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] test_strip_whitespace_value()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] test_strip_whitespace_block()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] test_comment()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] test_strip_whitespace_comment()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] test_strip_whitespace_followed_by_another_tag()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] test_call()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] test_curly_brace_escaping()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] test_unclosed_tags()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] test_mismatched_blocks()655 fn test_mismatched_blocks() { 656 let text = "{{ if foo }}{{ with bar }}{{ endif }} {{ endwith }}"; 657 compile(text).unwrap_err(); 658 } 659 660 #[test] test_disallows_invalid_keywords()661 fn test_disallows_invalid_keywords() { 662 let text = "{ @foo }"; 663 compile(text).unwrap_err(); 664 } 665 666 #[test] test_diallows_unknown_block_type()667 fn test_diallows_unknown_block_type() { 668 let text = "{{ foobar }}"; 669 compile(text).unwrap_err(); 670 } 671 672 #[test] test_parse_error_line_column_num()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] test_parse_error_on_unclosed_if()685 fn test_parse_error_on_unclosed_if() { 686 let text = "{{ if foo }}"; 687 compile(text).unwrap_err(); 688 } 689 690 #[test] test_parse_escaped_open_curly_brace()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