use std::collections::HashMap;
use lexer::{Lexer, TokenType, Token};
use nodes::{Node, SpecificNode};
#[derive(Debug, Clone, PartialEq)]
enum InsideBlock {
If,
Elif,
Else,
For,
Block
}
#[derive(Debug)]
pub struct Parser {
name: String,
text: String,
lexer: Lexer,
pub root: Node,
current_token: usize,
currently_in: Vec<InsideBlock>,
tag_nodes: Vec<Node>, pub blocks: HashMap<String, Node>,
pub extends: Option<String>
}
impl Parser {
pub fn new(name: &str, text: &str) -> Parser {
let mut lexer = Lexer::new(name, text);
lexer.run();
let mut parser = Parser {
name: name.to_owned(),
text: text.to_owned(),
root: Node::new(0, SpecificNode::List(vec![])),
lexer: lexer,
current_token: 0,
currently_in: vec![],
tag_nodes: vec![],
blocks: HashMap::new(),
extends: None
};
parser.parse();
parser
}
pub fn parse(&mut self) {
loop {
match self.peek().kind {
TokenType::TagStart => self.parse_tag_block(),
TokenType::VariableStart => self.parse_variable_block(),
TokenType::Text => self.parse_text(),
TokenType::Eof => break,
_ => unreachable!()
};
}
}
fn peek(&self) -> Token {
self.lexer.tokens.get(self.current_token).unwrap().clone()
}
fn peek_non_space(&mut self) -> Token {
let mut token = self.next_token();
loop {
if token.kind != TokenType::Space {
break;
}
token = self.next_token();
}
self.current_token -= 1;
token
}
fn next_token(&mut self) -> Token {
let token = self.peek();
self.current_token += 1;
token
}
fn next_non_space(&mut self) -> Token {
let mut token = self.next_token();
loop {
if token.kind != TokenType::Space {
break;
}
token = self.next_token();
}
token
}
fn peek_tag_name(&mut self) -> TokenType {
let before_peeking = self.current_token;
self.next_token();
let tag_name = self.peek_non_space();
self.current_token = before_peeking;
tag_name.kind
}
fn expect(&mut self, kind: TokenType) -> Token {
let token = self.peek_non_space();
if token.kind != kind {
panic!("Unexpected token: {:?}, expected: {:?} at \
line {} of template {}", token, kind, token.line, self.name);
}
self.next_non_space()
}
fn throw_lexer_error(&self, token: &Token) -> ! {
panic!("Error: {} at line {} of template {}", token.value, token.line, self.name);
}
fn add_node(&mut self, node: Node) {
if self.tag_nodes.is_empty() {
match node.specific {
SpecificNode::Block {ref name, ..} => {
if self.blocks.contains_key(name) {
panic!("Block `{}` is duplicated in template `{}`", name, self.name)
}
self.blocks.insert(name.to_owned(), node.clone());
if self.extends.is_none() {
self.root.push(Box::new(node.clone()));
}
},
_ => { self.root.push(Box::new(node)); }
}
return;
}
let currently_in = self.currently_in.last().cloned().unwrap();
match currently_in {
InsideBlock::If | InsideBlock::Elif => {
self.tag_nodes.last_mut().unwrap().append_to_last_conditional(Box::new(node));
},
InsideBlock::Else => {
self.tag_nodes.last_mut().unwrap().push_to_else(Box::new(node));
},
InsideBlock::For | InsideBlock::Block => {
self.tag_nodes.last_mut().unwrap().push(Box::new(node));
}
};
}
fn parse_text(&mut self) {
let token = self.next_token();
let mut string = match self.tag_nodes.last() {
Some(n) => {
let currently_in = self.currently_in.last().cloned().unwrap_or(InsideBlock::For);
if n.is_empty() && currently_in != InsideBlock::Block {
token.value.trim_left().to_owned()
} else {
token.value
}
},
None => token.value
};
if self.peek_non_space().kind == TokenType::TagStart {
match self.peek_tag_name() {
TokenType::Endif | TokenType::Elif | TokenType::Else => {
string = string.trim_right().to_owned();
}
_ => ()
}
}
self.add_node(Node::new(token.position, SpecificNode::Text(string)));
}
fn parse_variable_block(&mut self) {
let token = self.expect(TokenType::VariableStart);
let contained = self.parse_whole_expression(None, TokenType::VariableEnd);
let node = Node::new(token.position, SpecificNode::VariableBlock(contained));
self.expect(TokenType::VariableEnd);
self.add_node(node);
}
fn parse_tag_block(&mut self) {
let token = self.expect(TokenType::TagStart);
match self.peek_non_space().kind {
TokenType::If => self.parse_if(token.position),
TokenType::Elif => self.parse_elif(),
TokenType::Else => self.parse_else(),
TokenType::For => self.parse_for(token.position),
TokenType::Block => self.parse_block(token.position),
TokenType::Extends => self.parse_extends(token.position),
TokenType::Endif | TokenType::Endfor | TokenType::Endblock => {
match self.peek_non_space().kind {
TokenType::Endif => { self.expect(TokenType::Endif); },
TokenType::Endfor => { self.expect(TokenType::Endfor); },
TokenType::Endblock => {
self.expect(TokenType::Endblock);
let next_node = self.peek_non_space();
match self.tag_nodes.last_mut().unwrap().specific {
SpecificNode::Block {ref name, ..} => {
if next_node.kind == TokenType::TagEnd {
panic!("Missing endblock name at line {} of \
template `{}`. It should be `{}`.",
next_node.line, self.name, name);
}
let end_name = next_node.value;
if end_name != name.clone() {
panic!("Found endblock `{}` while we were \
hoping for `{}` at line {} of template `{}`",
end_name, name, next_node.line, self.name);
}
},
_ => unreachable!()
}
self.next_non_space();
},
_ => unreachable!()
};
self.expect(TokenType::TagEnd);
let tag = self.tag_nodes.pop().unwrap();
self.currently_in.pop();
self.add_node(tag);
},
_ => unreachable!()
};
}
fn parse_extends(&mut self, start_position: usize) {
if start_position > 0 {
panic!("{{% extends %}} tag need to be the first thing \
in a template. It is not the case in `{}`", self.name);
}
self.expect(TokenType::Extends);
let name = self.next_non_space();
self.expect(TokenType::TagEnd);
self.extends = Some(name.value);
}
fn parse_block(&mut self, start_position: usize) {
self.currently_in.push(InsideBlock::Block);
self.expect(TokenType::Block);
let name = self.next_non_space();
self.expect(TokenType::TagEnd);
let body = Node::new(self.peek().position, SpecificNode::List(vec![]));
let block_node = Node::new(
start_position,
SpecificNode::Block {name: name.value, body: Box::new(body)}
);
self.tag_nodes.push(block_node);
}
fn parse_conditional_node(&mut self) -> Node {
match self.next_non_space().kind {
TokenType::If => self.currently_in.push(InsideBlock::If),
TokenType::Elif => self.currently_in.push(InsideBlock::Elif),
_ => unreachable!()
};
let condition = self.parse_whole_expression(None, TokenType::TagEnd);
self.expect(TokenType::TagEnd);
let body = Node::new(self.peek().position, SpecificNode::List(vec![]));
Node::new(
condition.position,
SpecificNode::Conditional {condition: condition, body: Box::new(body)}
)
}
fn parse_for(&mut self, start_position: usize) {
self.currently_in.push(InsideBlock::For);
self.expect(TokenType::For);
let local = self.parse_single_expression(&TokenType::TagEnd);
self.expect(TokenType::In);
let array = self.parse_single_expression(&TokenType::TagEnd);
self.expect(TokenType::TagEnd);
let body = Node::new(self.peek().position, SpecificNode::List(vec![]));
let for_node = Node::new(
start_position,
SpecificNode::For {local: local, array: array, body: Box::new(body)}
);
self.tag_nodes.push(for_node);
}
fn parse_if(&mut self, start_position: usize) {
let mut if_node = Node::new(
start_position,
SpecificNode::If {condition_nodes: vec![], else_node: None}
);
let node = self.parse_conditional_node();
if_node.push(Box::new(node));
self.tag_nodes.push(if_node);
}
fn parse_elif(&mut self) {
let currently_in = self.currently_in.last().cloned().unwrap();
match currently_in {
InsideBlock::If | InsideBlock::Elif => self.currently_in.pop(),
InsideBlock::Else | InsideBlock::For | InsideBlock::Block => {
panic!("Found a elif in a {:?} block at line {} of template `{}`, \
which is impossible.", currently_in, self.peek_non_space().line, self.name);
}
};
let node = Box::new(self.parse_conditional_node());
self.tag_nodes.last_mut().unwrap().push(node);
}
fn parse_else(&mut self) {
let currently_in = self.currently_in.last().cloned().unwrap();
match currently_in {
InsideBlock::If | InsideBlock::Elif => self.currently_in.pop(),
InsideBlock::Else | InsideBlock::For | InsideBlock::Block => {
panic!("Found a else in a {:?} block at line {} of template `{}`, \
which is impossible.", currently_in, self.peek_non_space().line, self.name);
}
};
self.expect(TokenType::Else);
self.expect(TokenType::TagEnd);
self.currently_in.push(InsideBlock::Else);
let if_node = self.tag_nodes.pop().unwrap();
let list = Node::new(self.peek().position, SpecificNode::List(vec![]));
self.tag_nodes.push(Node::new(
if_node.position,
SpecificNode::If {
condition_nodes: if_node.get_children(),
else_node: Some(Box::new(list))
}
));
}
fn parse_whole_expression(&mut self, stack: Option<Node>, terminator: TokenType) -> Box<Node> {
let token = self.peek_non_space();
let mut node_stack = stack.unwrap_or_else(||
Node::new(token.position, SpecificNode::List(vec![]))
);
let next = self.parse_single_expression(&terminator);
node_stack.push(next);
loop {
let token = self.peek_non_space();
if token.kind == terminator {
if node_stack.is_empty() {
panic!("Unexpected terminator {} at line {} in template {}",
token.value, token.line, self.name);
}
return node_stack.pop();
}
match token.kind {
TokenType::Error => self.throw_lexer_error(&token),
TokenType::Add | TokenType::Substract => {
self.next_non_space();
if node_stack.is_empty() {
continue;
}
let rhs = self.parse_whole_expression(Some(node_stack.clone()), terminator);
let next_token = self.peek_non_space();
if next_token.precedence() > token.precedence() {
node_stack.push(rhs);
return self.parse_whole_expression(Some(node_stack.clone()), terminator);
} else {
let lhs = node_stack.pop();
let node = Node::new(
lhs.position,
SpecificNode::Math{lhs: lhs, rhs: rhs, operator: token.kind}
);
node_stack.push(Box::new(node));
}
},
TokenType::Divide | TokenType::Multiply => {
self.next_non_space();
let rhs = self.parse_single_expression(&terminator);
let lhs = node_stack.pop();
let node = Node::new(
lhs.position,
SpecificNode::Math{lhs: lhs, rhs: rhs, operator: token.kind}
);
node_stack.push(Box::new(node));
},
TokenType::Equal | TokenType::NotEqual | TokenType::GreaterOrEqual
| TokenType::Greater | TokenType::Lower | TokenType::LowerOrEqual => {
if node_stack.len() > 1 {
return node_stack.pop();
}
self.next_non_space();
let rhs = self.parse_single_expression(&terminator);
let next_token = self.peek_non_space();
if next_token.precedence() > token.precedence() {
node_stack.push(rhs);
return self.parse_whole_expression(Some(node_stack.clone()), terminator);
} else {
let lhs = node_stack.pop();
let node = Node::new(
lhs.position,
SpecificNode::Logic{lhs: lhs, rhs: rhs, operator: token.kind}
);
node_stack.push(Box::new(node));
}
},
TokenType::And | TokenType::Or => {
self.next_non_space();
let lhs = node_stack.pop();
let rhs = self.parse_whole_expression(Some(node_stack.clone()), terminator);
let node = Node::new(
lhs.position,
SpecificNode::Logic{lhs: lhs, rhs: rhs, operator: token.kind}
);
node_stack.push(Box::new(node));
},
_ => unreachable!()
}
}
}
fn parse_single_expression(&mut self, terminator: &TokenType) -> Box<Node> {
let token = self.peek_non_space();
if token.kind == *terminator {
panic!("Terminator `{}` is too early at line {} in template {}",
token.value, token.line, self.name);
}
match token.kind {
TokenType::Error => self.throw_lexer_error(&token),
TokenType::Identifier => self.parse_identifier(),
TokenType::Float | TokenType::Int | TokenType::Bool => self.parse_literal(),
_ => unreachable!()
}
}
fn parse_identifier(&mut self) -> Box<Node> {
let ident = self.next_non_space();
Box::new(Node::new(ident.position, SpecificNode::Identifier(ident.value)))
}
fn parse_literal(&mut self) -> Box<Node> {
let literal = self.next_non_space();
match literal.kind {
TokenType::Int => {
let value = literal.value.parse::<i32>().unwrap();
Box::new(Node::new(literal.position, SpecificNode::Int(value)))
},
TokenType::Float => {
let value = literal.value.parse::<f32>().unwrap();
Box::new(Node::new(literal.position, SpecificNode::Float(value)))
},
TokenType::Bool => {
let value = literal.value == "true";
Box::new(Node::new(literal.position, SpecificNode::Bool(value)))
},
_ => unreachable!()
}
}
}
#[cfg(test)]
mod tests {
use super::{Parser};
use lexer::TokenType;
use nodes::{Node, SpecificNode};
fn compared_expected(expected: Vec<SpecificNode>, got: Vec<Box<Node>>) {
if expected.len() != got.len() {
println!("Got: {:#?}", got);
assert!(false);
}
for (i, node) in got.iter().enumerate() {
let expected_node = expected.get(i).unwrap().clone();
if expected_node != node.specific {
println!("Expected: {:#?}", expected_node);
println!("Got: {:#?}", node.specific);
}
assert_eq!(expected_node, node.specific);
}
}
fn test_parser(input: &str, expected: Vec<SpecificNode>) {
let parser = Parser::new("dummy", input);
let children = parser.root.get_children();
compared_expected(expected, children)
}
#[test]
fn test_empty() {
let parser = Parser::new("empty", "");
assert_eq!(0, parser.root.len());
}
#[test]
fn test_plain_string() {
test_parser(
"Hello world",
vec![SpecificNode::Text("Hello world".to_owned())]
);
}
#[test]
fn test_variable_block_and_text() {
test_parser(
"{{ greeting }} 世界",
vec![
SpecificNode::VariableBlock(
Box::new(Node::new(3, SpecificNode::Identifier("greeting".to_owned())))
),
SpecificNode::Text(" 世界".to_owned()),
]
);
}
#[test]
fn test_basic_math() {
test_parser(
"{{1+3.41}}{{1-42}}{{1*42}}{{1/42}}{{test+1}}",
vec![
SpecificNode::VariableBlock(
Box::new(Node::new(2, SpecificNode::Math {
lhs: Box::new(Node::new(2, SpecificNode::Int(1))),
rhs: Box::new(Node::new(4, SpecificNode::Float(3.41))),
operator: TokenType::Add
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(12, SpecificNode::Math {
lhs: Box::new(Node::new(12, SpecificNode::Int(1))),
rhs: Box::new(Node::new(14, SpecificNode::Int(42))),
operator: TokenType::Substract
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(20, SpecificNode::Math {
lhs: Box::new(Node::new(20, SpecificNode::Int(1))),
rhs: Box::new(Node::new(22, SpecificNode::Int(42))),
operator: TokenType::Multiply
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(28, SpecificNode::Math {
lhs: Box::new(Node::new(28, SpecificNode::Int(1))),
rhs: Box::new(Node::new(30, SpecificNode::Int(42))),
operator: TokenType::Divide
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(36, SpecificNode::Math {
lhs: Box::new(Node::new(36, SpecificNode::Identifier("test".to_owned()))),
rhs: Box::new(Node::new(41, SpecificNode::Int(1))),
operator: TokenType::Add
}))
),
]
);
}
#[test]
fn test_math_precedence_simple() {
test_parser(
"{{ 1 / 2 + 1 }}",
vec![
SpecificNode::VariableBlock(
Box::new(Node::new(3, SpecificNode::Math {
lhs: Box::new(Node::new(3, SpecificNode::Math {
lhs: Box::new(Node::new(3, SpecificNode::Int(1))),
rhs: Box::new(Node::new(7, SpecificNode::Int(2))),
operator: TokenType::Divide
})),
rhs: Box::new(Node::new(11, SpecificNode::Int(1))),
operator: TokenType::Add
}))
),
]
);
}
#[test]
fn test_math_precedence_complex() {
test_parser(
"{{ 1 / 2 + 3 * 2 + 42 }}",
vec![
SpecificNode::VariableBlock(
Box::new(Node::new(3, SpecificNode::Math {
lhs: Box::new(Node::new(3, SpecificNode::Math {
lhs: Box::new(Node::new(3, SpecificNode::Int(1))),
rhs: Box::new(Node::new(7, SpecificNode::Int(2))),
operator: TokenType::Divide
})),
rhs: Box::new(Node::new(11, SpecificNode::Math {
lhs: Box::new(Node::new(11, SpecificNode::Math {
lhs: Box::new(Node::new(11, SpecificNode::Int(3))),
rhs: Box::new(Node::new(15, SpecificNode::Int(2))),
operator: TokenType::Multiply
})),
rhs: Box::new(Node::new(19, SpecificNode::Int(42))),
operator: TokenType::Add
})),
operator: TokenType::Add
}))
),
]
);
}
#[test]
fn test_basic_logic() {
test_parser(
"{{1==1}}{{1>1}}{{1<1}}{{1>=1}}{{1<=1}}{{1&&1}}{{1||1}}",
vec![
SpecificNode::VariableBlock(
Box::new(Node::new(2, SpecificNode::Logic {
lhs: Box::new(Node::new(2, SpecificNode::Int(1))),
rhs: Box::new(Node::new(5, SpecificNode::Int(1))),
operator: TokenType::Equal
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(10, SpecificNode::Logic {
lhs: Box::new(Node::new(10, SpecificNode::Int(1))),
rhs: Box::new(Node::new(12, SpecificNode::Int(1))),
operator: TokenType::Greater
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(17, SpecificNode::Logic {
lhs: Box::new(Node::new(17, SpecificNode::Int(1))),
rhs: Box::new(Node::new(19, SpecificNode::Int(1))),
operator: TokenType::Lower
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(24, SpecificNode::Logic {
lhs: Box::new(Node::new(24, SpecificNode::Int(1))),
rhs: Box::new(Node::new(27, SpecificNode::Int(1))),
operator: TokenType::GreaterOrEqual
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(32, SpecificNode::Logic {
lhs: Box::new(Node::new(32, SpecificNode::Int(1))),
rhs: Box::new(Node::new(35, SpecificNode::Int(1))),
operator: TokenType::LowerOrEqual
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(40, SpecificNode::Logic {
lhs: Box::new(Node::new(40, SpecificNode::Int(1))),
rhs: Box::new(Node::new(43, SpecificNode::Int(1))),
operator: TokenType::And
}))
),
SpecificNode::VariableBlock(
Box::new(Node::new(48, SpecificNode::Logic {
lhs: Box::new(Node::new(48, SpecificNode::Int(1))),
rhs: Box::new(Node::new(51, SpecificNode::Int(1))),
operator: TokenType::Or
}))
),
]
);
}
#[test]
fn test_logic_precedence_complex() {
test_parser(
"{{1 > 2 || 3 == 4 && admin}}",
vec![
SpecificNode::VariableBlock(
Box::new(Node::new(2, SpecificNode::Logic {
lhs: Box::new(Node::new(2, SpecificNode::Logic {
lhs: Box::new(Node::new(2, SpecificNode::Int(1))),
rhs: Box::new(Node::new(6, SpecificNode::Int(2))),
operator: TokenType::Greater
})),
rhs: Box::new(Node::new(11, SpecificNode::Logic {
lhs: Box::new(Node::new(11, SpecificNode::Logic {
lhs: Box::new(Node::new(11, SpecificNode::Int(3))),
rhs: Box::new(Node::new(16, SpecificNode::Int(4))),
operator: TokenType::Equal
})),
rhs: Box::new(Node::new(21, SpecificNode::Identifier("admin".to_owned()))),
operator: TokenType::And
})),
operator: TokenType::Or
}))
),
]
)
}
#[test]
fn test_if() {
test_parser(
"{% if true %}1{% elif a %}2{% elif b %}3{% else %}4{% endif %}",
vec![
SpecificNode::If {
condition_nodes: vec![
Box::new(Node::new(6, SpecificNode::Conditional {
condition: Box::new(Node::new(6, SpecificNode::Bool(true))),
body: Box::new(Node::new(13, SpecificNode::List(vec![
Box::new(Node::new(13, SpecificNode::Text("1".to_owned()))),
])))
})),
Box::new(Node::new(22, SpecificNode::Conditional {
condition: Box::new(Node::new(22, SpecificNode::Identifier("a".to_owned()))),
body: Box::new(Node::new(26, SpecificNode::List(vec![
Box::new(Node::new(26, SpecificNode::Text("2".to_owned()))),
])))
})),
Box::new(Node::new(35, SpecificNode::Conditional {
condition: Box::new(Node::new(35, SpecificNode::Identifier("b".to_owned()))),
body: Box::new(Node::new(39, SpecificNode::List(vec![
Box::new(Node::new(39, SpecificNode::Text("3".to_owned()))),
])))
})),
],
else_node: Some(Box::new(Node::new(50, SpecificNode::List(vec![
Box::new(Node::new(50, SpecificNode::Text("4".to_owned()))),
]))))
},
]
);
}
#[test]
fn test_nested_if() {
test_parser(
"{% if true %}parent{% if a %}nested{% endif %}{% endif %} hey",
vec![
SpecificNode::If {
condition_nodes: vec![
Box::new(Node::new(6, SpecificNode::Conditional {
condition: Box::new(Node::new(6, SpecificNode::Bool(true))),
body: Box::new(Node::new(13, SpecificNode::List(vec![
Box::new(Node::new(13, SpecificNode::Text("parent".to_owned()))),
Box::new(Node::new(19, SpecificNode::If {
condition_nodes: vec![
Box::new(Node::new(25, SpecificNode::Conditional {
condition: Box::new(Node::new(25, SpecificNode::Identifier("a".to_owned()))),
body: Box::new(Node::new(29, SpecificNode::List(vec![
Box::new(Node::new(29, SpecificNode::Text("nested".to_owned()))),
])))
}))
],
else_node: None,
})),
])))
})),
],
else_node: None
},
SpecificNode::Text(" hey".to_owned())
]
);
}
#[test]
fn test_for() {
test_parser(
"{% for x in items %}{% if x.show %}{{x}}{% endif %}{% endfor %}",
vec![
SpecificNode::For {
local: Box::new(Node::new(7, SpecificNode::Identifier("x".to_owned()))),
array: Box::new(Node::new(12, SpecificNode::Identifier("items".to_owned()))),
body: Box::new(Node::new(20, SpecificNode::List(vec![
Box::new(Node::new(20, SpecificNode::If {
condition_nodes: vec![
Box::new(Node::new(26, SpecificNode::Conditional {
condition: Box::new(Node::new(26, SpecificNode::Identifier("x.show".to_owned()))),
body: Box::new(Node::new(35, SpecificNode::List(vec![
Box::new(Node::new(35, SpecificNode::VariableBlock(
Box::new(Node::new(37, SpecificNode::Identifier("x".to_owned())))
))),
])))
}))
],
else_node: None,
})),
]))),
}
]
);
}
#[test]
fn test_if_math_condition() {
test_parser(
"{% if age + 1 > 18 %}Adult{% endif %}",
vec![
SpecificNode::If {
condition_nodes: vec![
Box::new(Node::new(6, SpecificNode::Conditional {
condition: Box::new(Node::new(6, SpecificNode::Logic {
lhs: Box::new(Node::new(6, SpecificNode::Math {
lhs: Box::new(Node::new(6, SpecificNode::Identifier("age".to_owned()))),
rhs: Box::new(Node::new(12, SpecificNode::Int(1))),
operator: TokenType::Add
})),
rhs: Box::new(Node::new(16, SpecificNode::Int(18))),
operator: TokenType::Greater
})),
body: Box::new(Node::new(21, SpecificNode::List(vec![
Box::new(Node::new(21, SpecificNode::Text("Adult".to_owned()))),
])))
})),
],
else_node: None
},
]
);
}
#[test]
fn test_block() {
test_parser(
"{% block hello %}Hello{% endblock hello %}",
vec![
SpecificNode::Block {
name: "hello".to_owned(),
body: Box::new(Node::new(17, SpecificNode::List(vec![
Box::new(Node::new(17, SpecificNode::Text("Hello".to_owned()))),
])))
},
]
);
}
#[test]
fn test_extends() {
let parser = Parser::new("dummy", "{% extends \"main.html\" %}");
assert_eq!(parser.extends, Some("main.html".to_owned()));
}
}