pest 0.2.1

Elegant, efficient grammars
Documentation
#![recursion_limit = "500"]

#[macro_use]
extern crate pest;

use pest::prelude::*;

impl_rdp! {
    grammar! {
        // basic blocks of the language
        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'])*
        }
        // matches anything between 2 double quotes
        string = @{ ["\""] ~ (!(["\""]) ~ any )* ~ ["\""]}

        // Precedence climbing
        expression = _{
            // boolean first so they are not caught as identifiers
            { 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 }
        }

        // Tera specific things

        // different types of blocks
        variable_start = _{ ["{{"] }
        variable_end   = _{ ["}}"] }
        tag_start      = _{ ["{%"] }
        tag_end        = _{ ["%}"] }
        comment_start  = _{ ["{#"] }
        comment_end    = _{ ["#}"] }
        block_start    = _{ variable_start | tag_start | comment_start }

        // Actual tags
        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
        }

        // top level node
        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());
}