#![recursion_limit = "500"]
#[macro_use]
extern crate pest;
use pest::prelude::*;
impl_rdp! {
grammar! {
op_or = { ["or"] }
op_wrong_or = { ["||"] }
op_and = { ["and"] }
op_wrong_and = { ["&&"] }
op_lte = { ["<="] }
op_gte = { [">="] }
op_lt = { ["<"] }
op_gt = { [">"] }
op_eq = { ["=="] }
op_ineq = { ["!="] }
op_plus = { ["+"] }
op_minus = { ["-"] }
op_times = { ["*"] }
op_slash = { ["/"] }
op_true = { ["true"] }
op_false = { ["false"] }
boolean = _{ op_true | op_false }
int = @{ ["-"]? ~ (["0"] | ['1'..'9'] ~ ['0'..'9']*) }
float = @{
["-"]? ~
["0"] ~ ["."] ~ ['0'..'9']+ |
['1'..'9'] ~ ['0'..'9']* ~ ["."] ~ ['0'..'9']+
}
identifier = @{
(['a'..'z'] | ['A'..'Z'] | ["_"]) ~
(['a'..'z'] | ['A'..'Z'] | ["_"] | ["."] | ['0'..'9'])*
}
string = @{ ["\""] ~ (!(["\""]) ~ any )* ~ ["\""]}
expression = _{
{ boolean | identifier | int | float }
or = { op_or | op_wrong_or }
and = { op_and | op_wrong_and }
comparison = { op_gt | op_lt | op_eq | op_ineq | op_lte | op_gte }
add_sub = { op_plus | op_minus }
mul_div = { op_times | op_slash }
}
variable_start = _{ ["{{"] }
variable_end = _{ ["}}"] }
tag_start = _{ ["{%"] }
tag_end = _{ ["%}"] }
comment_start = _{ ["{#"] }
comment_end = _{ ["#}"] }
block_start = _{ variable_start | tag_start | comment_start }
extends_tag = { tag_start ~ ["extends"] ~ string ~ tag_end }
variable_tag = { variable_start ~ expression ~ variable_end }
comment_tag = { comment_start ~ (!comment_end ~ any )* ~ comment_end }
block_tag = { tag_start ~ ["block"] ~ identifier ~ tag_end }
if_tag = { tag_start ~ ["if"] ~ expression ~ tag_end }
elif_tag = { tag_start ~ ["elif"] ~ expression ~ tag_end }
else_tag = { tag_start ~ ["else"] ~ tag_end }
for_tag = { tag_start ~ ["for"] ~ identifier ~ ["in"] ~ identifier ~ tag_end }
endblock_tag = { tag_start ~ ["endblock"] ~ identifier ~ tag_end }
endif_tag = { tag_start ~ ["endif"] ~ tag_end }
endfor_tag = { tag_start ~ ["endfor"] ~ tag_end }
text = { (!(block_start) ~ any )+ }
content = _{
variable_tag |
comment_tag |
block_tag ~ content* ~ endblock_tag |
if_tag ~ content* ~ (elif_tag ~ content?)* ~ (else_tag ~ content*)? ~ endif_tag |
for_tag ~ content* ~ endfor_tag |
text
}
template = _{ extends_tag? ~ content* ~ eoi }
whitespace = _{ [" "] | ["\t"] | ["\u{000C}"] | ["\r"] | ["\n"] }
}
}
fn expect_tokens<T: Input>(parser: &Rdp<T>, expected: Vec<Token<Rule>>) {
assert!(parser.end());
assert_eq!(parser.queue(), &expected);
}
#[test]
fn test_int() {
let mut parser = Rdp::new(StringInput::new("123"));
assert!(parser.int());
expect_tokens(&parser, vec![
Token { rule: Rule::int, start: 0, end: 3 },
]);
}
#[test]
fn test_float() {
let mut parser = Rdp::new(StringInput::new("123.5"));
assert!(parser.float());
expect_tokens(&parser, vec![
Token { rule: Rule::float, start: 0, end: 5 },
]);
}
#[test]
fn test_identifier() {
let mut parser = Rdp::new(StringInput::new("client.phone_number"));
assert!(parser.identifier());
expect_tokens(&parser, vec![
Token { rule: Rule::identifier, start: 0, end: 19 },
]);
}
#[test]
fn test_text() {
let mut parser = Rdp::new(StringInput::new("Hello\n 世界"));
assert!(parser.text());
expect_tokens(&parser, vec![
Token { rule: Rule::text, start: 0, end: 13 },
]);
}
#[test]
fn test_extends_tag() {
let mut parser = Rdp::new(StringInput::new("{% extends \"base.html\" %}"));
assert!(parser.extends_tag());
expect_tokens(&parser, vec![
Token { rule: Rule::extends_tag, start: 0, end: 25 },
Token { rule: Rule::string, start: 11, end: 22 },
]);
}
#[test]
fn test_comment_tag() {
let mut parser = Rdp::new(StringInput::new("{# some text {{}} #}"));
assert!(parser.comment_tag());
expect_tokens(&parser, vec![
Token { rule: Rule::comment_tag, start: 0, end: 20 },
]);
}
#[test]
fn test_block_tag() {
let mut parser = Rdp::new(StringInput::new("{% block hello %}"));
assert!(parser.block_tag());
expect_tokens(&parser, vec![
Token { rule: Rule::block_tag, start: 0, end: 17 },
Token { rule: Rule::identifier, start: 9, end: 14 },
]);
}
#[test]
fn test_endblock_tag() {
let mut parser = Rdp::new(StringInput::new("{% endblock hello %}"));
assert!(parser.endblock_tag());
expect_tokens(&parser, vec![
Token { rule: Rule::endblock_tag, start: 0, end: 20 },
Token { rule: Rule::identifier, start: 12, end: 17 },
]);
}
#[test]
fn test_for_tag() {
let mut parser = Rdp::new(StringInput::new("{% for client in clients %}"));
assert!(parser.for_tag());
expect_tokens(&parser, vec![
Token { rule: Rule::for_tag, start: 0, end: 27 },
Token { rule: Rule::identifier, start: 7, end: 13 },
Token { rule: Rule::identifier, start: 17, end: 24 },
]);
}
#[test]
fn test_endfor_tag() {
let mut parser = Rdp::new(StringInput::new("{% endfor %}"));
assert!(parser.endfor_tag());
expect_tokens(&parser, vec![
Token { rule: Rule::endfor_tag, start: 0, end: 12 },
]);
}
#[test]
fn test_expression_math() {
let mut parser = Rdp::new(StringInput::new("1 + 2 + 3 * 9/2 + 2"));
assert!(parser.expression());
expect_tokens(&parser, vec![
Token { rule: Rule::add_sub, start: 0, end: 19 },
Token { rule: Rule::add_sub, start: 0, end: 13 },
Token { rule: Rule::add_sub, start: 0, end: 5 },
Token { rule: Rule::int, start: 0, end: 1 },
Token { rule: Rule::op_plus, start: 2, end: 3 },
Token { rule: Rule::int, start: 4, end: 5 },
Token { rule: Rule::op_plus, start: 6, end: 7 },
Token { rule: Rule::mul_div, start: 8, end: 15 },
Token { rule: Rule::mul_div, start: 8, end: 13 },
Token { rule: Rule::int, start: 8, end: 9 },
Token { rule: Rule::op_times, start: 10, end: 11 },
Token { rule: Rule::int, start: 12, end: 13 },
Token { rule: Rule::op_slash, start: 13, end: 14 },
Token { rule: Rule::int, start: 14, end: 15 },
Token { rule: Rule::op_plus, start: 16, end: 17 },
Token { rule: Rule::int, start: 18, end: 19 }
]);
}
#[test]
fn test_expression_identifier_logic_simple() {
let mut parser = Rdp::new(StringInput::new("index + 1 > 1"));
assert!(parser.expression());
expect_tokens(&parser, vec![
Token { rule: Rule::comparison, start: 0, end: 13 },
Token { rule: Rule::add_sub, start: 0, end: 9 },
Token { rule: Rule::identifier, start: 0, end: 5 },
Token { rule: Rule::op_plus, start: 6, end: 7 },
Token { rule: Rule::int, start: 8, end: 9 },
Token { rule: Rule::op_gt, start: 10, end: 11 },
Token { rule: Rule::int, start: 12, end: 13 }
]);
}
#[test]
fn test_expression_identifier_logic_complex() {
let mut parser = Rdp::new(StringInput::new("1 > 2 or 3 == 4 and admin"));
assert!(parser.expression());
expect_tokens(&parser, vec![
Token { rule: Rule::or, start: 0, end: 25 },
Token { rule: Rule::comparison, start: 0, end: 5 },
Token { rule: Rule::int, start: 0, end: 1 },
Token { rule: Rule::op_gt, start: 2, end: 3 },
Token { rule: Rule::int, start: 4, end: 5 },
Token { rule: Rule::op_or, start: 6, end: 8 },
Token { rule: Rule::and, start: 9, end: 25 },
Token { rule: Rule::comparison, start: 9, end: 15 },
Token { rule: Rule::int, start: 9, end: 10 },
Token { rule: Rule::op_eq, start: 11, end: 13 },
Token { rule: Rule::int, start: 14, end: 15 },
Token { rule: Rule::op_and, start: 16, end: 19 },
Token { rule: Rule::identifier, start: 20, end: 25 }
]);
}
#[test]
fn test_if_tag() {
let mut parser = Rdp::new(StringInput::new("{% if true or show == false %}"));
assert!(parser.if_tag());
expect_tokens(&parser, vec![
Token { rule: Rule::if_tag, start: 0, end: 30 },
Token { rule: Rule::or, start: 6, end: 27 },
Token { rule: Rule::op_true, start: 6, end: 10 },
Token { rule: Rule::op_or, start: 11, end: 13 },
Token { rule: Rule::comparison, start: 14, end: 27 },
Token { rule: Rule::identifier, start: 14, end: 18 },
Token { rule: Rule::op_eq, start: 19, end: 21 },
Token { rule: Rule::op_false, start: 22, end: 27 }
]);
}
#[test]
fn test_variable_tag() {
let mut parser = Rdp::new(StringInput::new("{{loop.index + 1}}"));
assert!(parser.variable_tag());
expect_tokens(&parser, vec![
Token { rule: Rule::variable_tag, start: 0, end: 18 },
Token { rule: Rule::add_sub, start: 2, end: 16 },
Token { rule: Rule::identifier, start: 2, end: 12 },
Token { rule: Rule::op_plus, start: 13, end: 14 },
Token { rule: Rule::int, start: 15, end: 16 }
]);
}
#[test]
fn test_content() {
let mut parser = Rdp::new(StringInput::new("{% if i18n %}世界{% else %}world{% endif %}"));
assert!(parser.content());
expect_tokens(&parser, vec![
Token { rule: Rule::if_tag, start: 0, end: 13 },
Token { rule: Rule::identifier, start: 6, end: 10 },
Token { rule: Rule::text, start: 13, end: 19 },
Token { rule: Rule::else_tag, start: 19, end: 29 },
Token { rule: Rule::text, start: 29, end: 34 },
Token { rule: Rule::endif_tag, start: 34, end: 45 }
]);
}
#[test]
fn test_template() {
let mut parser = Rdp::new(StringInput::new("
{# Greeter template #}
Hello {% if i18n %}世界{% else %}world{% endif %}
{% for country in countries %}
{{ loop.index }}.{{ country }}
{% endfor %}
"));
assert!(parser.template());
assert!(parser.end());
}