use pest::{error::LineColLocation, iterators::Pair, Parser};
#[cfg(feature = "condition")]
use crate::template::condition::{
AndCondition, CompareCondition, CompareOperator, Condition, OrCondition,
};
#[cfg(feature = "assign")]
use crate::template::Assign;
#[cfg(feature = "conditional")]
use crate::template::Conditional;
#[cfg(feature = "loop")]
use crate::template::Loop;
use crate::{
template::{CalculatedValue, Statement, StorageMethod},
value::Value,
Template,
};
#[allow(clippy::upper_case_acronyms)]
#[derive(Parser)]
#[grammar = "template.pest"]
pub struct TemplateParser;
pub fn parse(input: String) -> Result<Template, ParseError> {
let mut compiled_template = Template {
tpl: Vec::new(),
tpl_str: input,
};
let template = match TemplateParser::parse(Rule::template, &compiled_template.tpl_str) {
Ok(t) => t,
Err(e) => match e.line_col {
LineColLocation::Pos(pos) => {
return Err(ParseError::Pos(pos));
}
LineColLocation::Span(start, end) => {
return Err(ParseError::Span(start, end));
}
},
}
.next()
.unwrap();
compiled_template.tpl = template
.into_inner()
.next()
.unwrap()
.into_inner()
.filter_map(parse_template_content)
.collect::<Result<Vec<_>, _>>()?;
Ok(compiled_template)
}
fn parse_template_content(item: Pair<Rule>) -> Option<Result<Statement, ParseError>> {
match item.as_rule() {
Rule::text => Some(Ok(Statement::Literal(item.as_str()))),
Rule::calculated => Some(Ok(parse_calculated(item))),
#[cfg(feature = "conditional")]
Rule::conditional => Some(parse_conditional(item)),
#[cfg(not(feature = "conditional"))]
Rule::conditional => Some(Err(ParseError::DisabledFeature(
UnsupportedFeature::Conditional,
))),
#[cfg(feature = "assign")]
Rule::assign => Some(Ok(Statement::Assign(parse_assign(item)))),
#[cfg(not(feature = "assign"))]
Rule::assign => Some(Err(ParseError::DisabledFeature(UnsupportedFeature::Assign))),
#[cfg(feature = "loop")]
Rule::while_loop => match parse_loop(item) {
Ok(l) => Some(Ok(Statement::Loop(l))),
Err(e) => Some(Err(e)),
},
#[cfg(not(feature = "loop"))]
Rule::while_loop => Some(Err(ParseError::DisabledFeature(UnsupportedFeature::Loop))),
Rule::EOI => None,
_ => unreachable!("Unexpected rule {:#?}", item.as_rule()),
}
}
#[cfg(feature = "conditional")]
fn parse_conditional(conditional: Pair<Rule>) -> Result<Statement, ParseError> {
assert_eq!(conditional.as_rule(), Rule::conditional);
let mut conditional = conditional.into_inner();
let condition = conditional.next().unwrap();
let condition = parse_condition(condition);
let then_case = conditional
.next()
.unwrap()
.into_inner()
.filter_map(parse_template_content)
.collect::<Result<Vec<_>, _>>()?;
let else_case = conditional.next().map(|else_case| {
else_case
.into_inner()
.filter_map(parse_template_content)
.collect::<Result<Vec<_>, _>>()
});
let else_case = if let Some(e) = else_case {
Some(e?)
} else {
None
};
Ok(Statement::Condition(Conditional {
condition,
then_case,
else_case,
}))
}
#[cfg(feature = "condition")]
fn parse_condition(condition: Pair<Rule>) -> Condition {
assert_eq!(condition.as_rule(), Rule::condition);
let mut inner = condition.into_inner();
let mut current_and = None;
let mut current_or = Vec::new();
let mut prev_operator = None;
while let Some(c) = inner.next() {
let c = match c.as_rule() {
Rule::condition => parse_condition(c),
Rule::compare_condition => Condition::Compare(parse_compare_condition(c)),
Rule::calculated_value => Condition::CalculatedValue(parse_calculated_value(c)),
_ => unreachable!(),
};
if let Some(operator) = inner.next() {
match (operator.as_rule(), prev_operator) {
(Rule::and_operator, _) => current_and.get_or_insert(Vec::default()).push(c),
(Rule::or_operator, Some(Rule::and_operator)) => {
current_and.get_or_insert(Vec::default()).push(c);
current_or.push(Condition::And(AndCondition::new(
current_and.take().unwrap(),
)))
}
(Rule::or_operator, _) => current_or.push(c),
_ => unreachable!(),
}
prev_operator = Some(operator.as_rule());
} else {
match prev_operator {
Some(Rule::and_operator) => {
current_and.get_or_insert(Vec::default()).push(c);
let and = Condition::And(AndCondition::new(current_and.take().unwrap()));
return if !current_or.is_empty() {
current_or.push(and);
Condition::Or(OrCondition::new(current_or))
} else {
and
};
}
Some(Rule::or_operator) => {
current_or.push(c);
return Condition::Or(OrCondition::new(current_or));
}
None => return c,
_ => unreachable!(),
}
}
}
unreachable!()
}
fn parse_calculated(calculated: Pair<Rule>) -> Statement {
assert_eq!(calculated.as_rule(), Rule::calculated);
let inner = calculated.into_inner().next().unwrap();
Statement::Calculated(parse_calculated_value(inner))
}
#[cfg(feature = "condition")]
fn parse_compare_condition(compare_condition: Pair<Rule>) -> CompareCondition {
assert_eq!(compare_condition.as_rule(), Rule::compare_condition);
let mut inner = compare_condition.into_inner();
let calc_val_l = parse_calculated_value(inner.next().unwrap());
let operator = parse_compare_operator(inner.next().unwrap());
let calc_val_r = parse_calculated_value(inner.next().unwrap());
CompareCondition {
left: calc_val_l,
operator,
right: calc_val_r,
}
}
#[cfg(feature = "condition")]
fn parse_compare_operator(compare_operator: Pair<Rule>) -> CompareOperator {
assert_eq!(compare_operator.as_rule(), Rule::compare_operator);
let inner = compare_operator.into_inner().next().unwrap();
match inner.as_rule() {
Rule::eq_operator => CompareOperator::EQ,
Rule::ne_operator => CompareOperator::NE,
Rule::lt_operator => CompareOperator::LT,
Rule::le_operator => CompareOperator::LE,
Rule::gt_operator => CompareOperator::GT,
Rule::ge_operator => CompareOperator::GE,
_ => unreachable!("Unknown compare operator: {}", inner.as_str()),
}
}
fn parse_calculated_value(calculated_value: Pair<Rule>) -> CalculatedValue {
assert_eq!(calculated_value.as_rule(), Rule::calculated_value);
let mut inner = calculated_value.into_inner();
let value = parse_value(inner.next().unwrap());
let modifiers = inner.into_iter().map(parse_modifier).collect::<Vec<_>>();
CalculatedValue::new(value, modifiers)
}
fn parse_modifier(item: Pair<Rule>) -> (*const str, Vec<StorageMethod>) {
assert_eq!(item.as_rule(), Rule::modifier);
let mut items = item.into_inner();
let name = items.next().unwrap().as_str();
(name, items.map(parse_argument).collect())
}
fn parse_argument(argument: Pair<Rule>) -> StorageMethod {
assert_eq!(argument.as_rule(), Rule::argument);
let value = argument.into_inner().next().unwrap();
parse_value(value)
}
fn parse_value(value: Pair<Rule>) -> StorageMethod {
assert_eq!(value.as_rule(), Rule::value);
let value = value.into_inner().next().unwrap();
match value.as_rule() {
Rule::identifier => StorageMethod::Variable(value.as_str()),
Rule::number => StorageMethod::Const(Value::Number(value.as_str().parse().unwrap())),
Rule::string => StorageMethod::Const(Value::String(
value.into_inner().next().unwrap().as_str().replace("\\\"", "\""),
)),
Rule::boolean => {
let value = match value.as_str() {
"true" => true,
"false" => false,
_ => unreachable!("boolean must be true or false"),
};
StorageMethod::Const(Value::Bool(value))
}
_ => unreachable!("Unexpected value {:#?}", value),
}
}
#[cfg(feature = "assign")]
fn parse_assign(assign: Pair<Rule>) -> Assign {
assert_eq!(assign.as_rule(), Rule::assign);
let mut inner = assign.into_inner();
let ident = inner.next().unwrap();
assert_eq!(ident.as_rule(), Rule::identifier);
let ident = ident.as_str();
let calc_val = parse_calculated_value(inner.next().unwrap());
Assign::new(ident, calc_val)
}
#[cfg(feature = "loop")]
fn parse_loop(l: Pair<Rule>) -> Result<Loop, ParseError> {
assert_eq!(l.as_rule(), Rule::while_loop);
let mut inner = l.into_inner();
let condition = parse_condition(inner.next().unwrap());
let template = inner
.next()
.unwrap()
.into_inner()
.filter_map(parse_template_content)
.collect::<Result<Vec<_>, _>>()?;
Ok(Loop::new(condition, template))
}
#[derive(Debug)]
pub enum ParseError {
Pos((usize, usize)),
Span((usize, usize), (usize, usize)),
DisabledFeature(UnsupportedFeature),
}
#[derive(Debug)]
pub enum UnsupportedFeature {
#[cfg(not(feature = "assign"))]
Assign,
#[cfg(not(feature = "conditional"))]
Conditional,
#[cfg(not(feature = "loop"))]
Loop,
}
#[cfg(test)]
mod tests {
use std::vec;
use super::*;
#[test]
fn parse_template_item_literal() {
let template = String::from("test literal");
let item = TemplateParser::parse(Rule::text, &template);
assert!(item.is_ok());
let item = item.unwrap().next();
assert!(item.is_some());
let item = item.unwrap();
let statement = parse_template_content(item).unwrap().unwrap();
assert_eq!(statement, Statement::Literal("test literal"))
}
#[test]
fn parse_template_single_literal() {
let template = String::from("test literal");
let template = parse(template);
assert!(template.is_ok());
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl: vec![Statement::Literal("test literal")],
tpl_str: String::from("test literal")
}
);
}
#[test]
fn parse_template_item_calculated() {
let template = String::from("{var}");
let item = TemplateParser::parse(Rule::calculated, &template);
assert!(item.is_ok());
let item = item.unwrap().next();
assert!(item.is_some());
let item = item.unwrap();
let statement = parse_calculated(item);
assert_eq!(
statement,
Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
Vec::new()
))
)
}
#[test]
fn parse_template_single_computed() {
let template = String::from("{var}");
let template = parse(template);
assert!(template.is_ok());
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl: vec![Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![],
))],
tpl_str: String::from("{var}")
}
);
}
#[test]
fn parse_template_single_computed_modifier() {
let template = String::from("{var|modifier}");
let template = parse(template);
assert!(template.is_ok());
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl_str: String::from("{var|modifier}"),
tpl: vec![Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![("modifier", vec![])]
))]
}
)
}
#[test]
fn parse_template_single_computed_multiple_modifier() {
let template = String::from("{var|modifier1|modifier2}");
let template = parse(template);
assert!(template.is_ok());
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl_str: String::from("{var|modifier1|modifier2}"),
tpl: vec![Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![("modifier1", vec![]), ("modifier2", vec![])]
))]
}
)
}
#[test]
fn parse_template_single_computed_modifier_var_param() {
let template = String::from("{var|modifier:var2}");
let template = parse(template);
assert!(template.is_ok());
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl_str: String::from("{var|modifier:var2}"),
tpl: vec![Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![("modifier", vec![StorageMethod::Variable("var2")])]
))]
}
)
}
#[test]
fn parse_template_single_computed_modifier_number_param() {
let template = String::from(r#"{var|modifier:-32.09}"#);
let template = parse(template);
assert!(template.is_ok());
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl_str: String::from(r#"{var|modifier:-32.09}"#),
tpl: vec![Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![(
"modifier",
vec![StorageMethod::Const(Value::Number(-32.09))]
)]
))]
}
)
}
#[test]
fn parse_template_single_computed_literal_before_modifier() {
let template = String::from(r#"{10|modifier:-32.09}"#);
let template = parse(template);
assert!(template.is_ok());
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl_str: String::from(r#"{10|modifier:-32.09}"#),
tpl: vec![Statement::Calculated(CalculatedValue::new(
StorageMethod::Const(Value::Number(10.0)),
vec![(
"modifier",
vec![StorageMethod::Const(Value::Number(-32.09))]
)]
))]
}
)
}
#[test]
fn parse_template_single_computed_modifier_multiple_args() {
let template = String::from(r#"{var|modifier:-32.09:"argument":var2:true}"#);
let template = parse(template);
assert!(template.is_ok(), "{:#?}", template);
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl_str: String::from(r#"{var|modifier:-32.09:"argument":var2:true}"#),
tpl: vec![Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![(
"modifier",
vec![
StorageMethod::Const(Value::Number(-32.09)),
StorageMethod::Const(Value::String(String::from("argument"))),
StorageMethod::Variable("var2"),
StorageMethod::Const(Value::Bool(true))
]
)]
))]
}
)
}
#[test]
fn parse_template_multi_line() {
let template = String::from("{var|modifier}\n{10|modifier:-32.09}");
let template = parse(template);
assert!(template.is_ok());
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl_str: String::from("{var|modifier}\n{10|modifier:-32.09}"),
tpl: vec![
Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![("modifier", vec![])]
)),
Statement::Literal("\n"),
Statement::Calculated(CalculatedValue::new(
StorageMethod::Const(Value::Number(10.0)),
vec![(
"modifier",
vec![StorageMethod::Const(Value::Number(-32.09))]
)]
))
]
}
)
}
#[cfg(feature = "assign")]
#[test]
fn parse_template_assign() {
let template = String::from("{var = 10|modifier:-32.09}");
let template = parse(template);
assert!(template.is_ok(), "{template:#?}");
let template = template.unwrap();
assert_eq!(
template,
Template {
tpl_str: String::from("{var = 10|modifier:-32.09}"),
tpl: vec![Statement::Assign(Assign::new(
"var",
CalculatedValue::new(
StorageMethod::Const(Value::Number(10.0)),
vec![(
"modifier",
vec![StorageMethod::Const(Value::Number(-32.09))]
)]
)
))]
}
)
}
#[cfg(feature = "conditional")]
mod conditional {
use super::*;
#[test]
fn parse_simple() {
let template = "{if i < 10}HI{endif}";
let conditional = TemplateParser::parse(Rule::conditional, template)
.unwrap()
.next()
.unwrap();
let conditional_statement = parse_conditional(conditional).unwrap();
if let Statement::Condition(conditional) = conditional_statement {
assert_eq!(
conditional,
Conditional {
condition: Condition::Compare(CompareCondition {
left: CalculatedValue::new(StorageMethod::Variable("i"), vec![]),
operator: CompareOperator::LT,
right: CalculatedValue::new(
StorageMethod::Const(Value::Number(10.)),
vec![]
)
}),
then_case: vec![Statement::Literal("HI")],
else_case: None
}
)
} else {
panic!("Unexpected statement")
}
}
#[test]
fn parse_complex_condition() {
let template = "{if (var1 || var2) && var3}HI{endif}";
let conditional = TemplateParser::parse(Rule::conditional, template)
.unwrap()
.next()
.unwrap();
let conditional_statement = parse_conditional(conditional).unwrap();
if let Statement::Condition(conditional) = conditional_statement {
assert_eq!(
conditional,
Conditional {
condition: Condition::and(vec![
Condition::or(vec![
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var1"),
vec![]
)),
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var2"),
vec![]
))
]),
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var3"),
vec![]
))
]),
then_case: vec![Statement::Literal("HI")],
else_case: None
}
)
} else {
panic!("Unexpected statement")
}
}
#[test]
fn parse_else() {
let template = "{if i < 10}HI{else}TEST{endif}";
let conditional = TemplateParser::parse(Rule::conditional, template)
.unwrap()
.next()
.unwrap();
let conditional_statement = parse_conditional(conditional).unwrap();
if let Statement::Condition(conditional) = conditional_statement {
assert_eq!(
conditional,
Conditional {
condition: Condition::Compare(CompareCondition {
left: CalculatedValue::new(StorageMethod::Variable("i"), vec![]),
operator: CompareOperator::LT,
right: CalculatedValue::new(
StorageMethod::Const(Value::Number(10.)),
vec![]
)
}),
then_case: vec![Statement::Literal("HI")],
else_case: Some(vec![Statement::Literal("TEST")])
}
)
} else {
panic!("Unexpected statement")
}
}
#[test]
fn parse_multiple() {
let template = "{if i < 10}HI{else}{if n == \"TEST\"}HI2{else}TEST{endif}{endif}";
let conditional = TemplateParser::parse(Rule::conditional, template)
.unwrap()
.next()
.unwrap();
let conditional_statement = parse_conditional(conditional).unwrap();
if let Statement::Condition(conditional) = conditional_statement {
assert_eq!(
conditional,
Conditional {
condition: Condition::Compare(CompareCondition {
left: CalculatedValue::new(StorageMethod::Variable("i"), vec![]),
operator: CompareOperator::LT,
right: CalculatedValue::new(
StorageMethod::Const(Value::Number(10.)),
vec![]
)
}),
then_case: vec![Statement::Literal("HI")],
else_case: Some(vec![Statement::Condition(Conditional {
condition: Condition::Compare(CompareCondition {
left: CalculatedValue::new(StorageMethod::Variable("n"), vec![]),
operator: CompareOperator::EQ,
right: CalculatedValue::new(
StorageMethod::Const(Value::String("TEST".to_owned())),
vec![]
)
}),
then_case: vec![Statement::Literal("HI2")],
else_case: Some(vec![Statement::Literal("TEST")])
})])
}
)
} else {
panic!("Unexpected statement")
}
}
}
#[cfg(feature = "conditional")]
mod condition {
use crate::template::condition::AndCondition;
use super::*;
#[test]
fn parse() {
let template = "bar";
let condition = TemplateParser::parse(Rule::condition, template)
.unwrap()
.next()
.unwrap();
let condition = super::parse_condition(condition);
assert_eq!(
condition,
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("bar"),
vec![]
)),
);
}
#[test]
fn parse_eq() {
let template = "bar == 10";
let condition = TemplateParser::parse(Rule::condition, template)
.unwrap()
.next()
.unwrap();
let condition = super::parse_condition(condition);
assert_eq!(
condition,
Condition::Compare(CompareCondition {
left: CalculatedValue::new(StorageMethod::Variable("bar"), vec![]),
operator: CompareOperator::EQ,
right: CalculatedValue::new(StorageMethod::Const(Value::Number(10.)), vec![])
})
);
}
#[test]
fn parse_2() {
let template = "(bar == 10)";
let condition = TemplateParser::parse(Rule::condition, template)
.unwrap()
.next()
.unwrap();
let condition = super::parse_condition(condition);
assert_eq!(
condition,
Condition::Compare(CompareCondition {
left: CalculatedValue::new(StorageMethod::Variable("bar"), vec![]),
operator: CompareOperator::EQ,
right: CalculatedValue::new(StorageMethod::Const(Value::Number(10.)), vec![])
})
);
}
#[test]
fn parse_complex() {
let tpl = "var1 || var2 && var3";
let condition = TemplateParser::parse(Rule::condition, tpl)
.unwrap()
.next()
.unwrap();
let condition = super::parse_condition(condition);
assert_eq!(
condition,
Condition::Or(OrCondition::new(vec![
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var1"),
vec![]
)),
Condition::And(AndCondition::new(vec![
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var2"),
vec![]
)),
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var3"),
vec![]
))
]))
]))
)
}
#[test]
fn parse_complex2() {
let tpl = "(var1 || var2)";
let condition = TemplateParser::parse(Rule::condition, tpl)
.unwrap()
.next()
.unwrap();
let condition = super::parse_condition(condition);
assert_eq!(
condition,
Condition::Or(OrCondition::new(vec![
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var1"),
vec![]
)),
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var2"),
vec![]
))
])),
)
}
#[test]
fn parse_complex3() {
let tpl = "(var1 && var2)";
let condition = TemplateParser::parse(Rule::condition, tpl)
.unwrap()
.next()
.unwrap();
let condition = super::parse_condition(condition);
assert_eq!(
condition,
Condition::And(AndCondition::new(vec![
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var1"),
vec![]
)),
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var2"),
vec![]
))
])),
)
}
#[test]
fn parse_complex4() {
let tpl = "((var1 || var2) && var3)";
let condition = TemplateParser::parse(Rule::condition, tpl)
.unwrap()
.next()
.unwrap();
let condition = super::parse_condition(condition);
assert_eq!(
condition,
Condition::And(AndCondition::new(vec![
Condition::Or(OrCondition::new(vec![
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var1"),
vec![]
)),
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var2"),
vec![]
))
])),
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var3"),
vec![]
))
]))
)
}
#[test]
fn parse_complex5() {
let tpl = "(var1 || (var2 && var3))";
let condition = TemplateParser::parse(Rule::condition, tpl)
.unwrap()
.next()
.unwrap();
let condition = super::parse_condition(condition);
assert_eq!(
condition,
Condition::or(vec![
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var1"),
vec![]
)),
Condition::and(vec![
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var2"),
vec![]
)),
Condition::CalculatedValue(CalculatedValue::new(
StorageMethod::Variable("var3"),
vec![]
))
])
])
)
}
}
#[cfg(feature = "assign")]
mod assign {
use crate::{
parser::{parse_assign, Parser, Rule, TemplateParser},
template::{Assign, CalculatedValue, StorageMethod},
value::Value,
};
#[test]
fn parse_assign_simple() {
let tpl = "{my_var=12}";
let assign = TemplateParser::parse(Rule::assign, tpl)
.unwrap()
.next()
.unwrap();
let assign = parse_assign(assign);
assert_eq!(
assign,
Assign::new(
"my_var",
CalculatedValue::new(StorageMethod::Const(Value::Number(12.)), vec![])
)
)
}
}
#[cfg(feature = "loop")]
mod while_loop {
use crate::{
parser::{Parser, Rule, TemplateParser},
template::{
condition::{CompareCondition, CompareOperator, Condition},
CalculatedValue, Loop, Statement, StorageMethod,
},
value::Value,
};
#[test]
fn parse_loop() {
let template = "{while var==0}Foo{endwhile}";
let l = TemplateParser::parse(Rule::while_loop, template)
.unwrap()
.next()
.unwrap();
let l = crate::parser::parse_loop(l).unwrap();
assert_eq!(
l,
Loop::new(
Condition::Compare(CompareCondition {
left: CalculatedValue::new(StorageMethod::Variable("var"), vec![]),
operator: CompareOperator::EQ,
right: CalculatedValue::new(
StorageMethod::Const(Value::Number(0.)),
vec![]
)
}),
vec![Statement::Literal("Foo")]
)
)
}
}
}
#[cfg(test)]
mod pest_tests {
use super::*;
const NUMBER_CASES: [&str; 5] = ["42", "42.0", "0.815", "-0.815", "+0.815"];
const IDENTIFIER_CASES: [&str; 3] = ["onlylowercase", "camelCase", "snail_case"];
const INNER_STRING_CASES: [&str; 4] = [
"Hello world",
"123Hello World!",
"!Hello World",
r#"Hello\"World"#,
];
macro_rules! test_cases {
($list: ident, $cases_var: ident, $format: tt) => {
let $cases_var = $list
.iter()
.map(|c| format!($format, c))
.collect::<Vec<_>>();
let $cases_var = $cases_var.iter().map(String::as_str).collect::<Vec<_>>();
};
}
#[test]
fn number() {
test_cases(&NUMBER_CASES, Rule::number)
}
#[test]
fn identifier() {
test_cases(&IDENTIFIER_CASES, Rule::identifier)
}
#[test]
fn argument() {
test_cases!(NUMBER_CASES, number_cases, ":{}");
test_cases(&number_cases, Rule::argument);
test_cases!(IDENTIFIER_CASES, ident_cases, ":{}");
test_cases(&ident_cases, Rule::argument)
}
#[test]
fn inner_string() {
test_cases(&INNER_STRING_CASES, Rule::inner_string)
}
#[test]
fn string() {
test_cases!(INNER_STRING_CASES, cases, r#""{}""#);
test_cases(&cases, Rule::string)
}
#[test]
fn string_before_modifier() {
test_cases(&[r#"{"test"|modifier:arg}"#], Rule::calculated)
}
#[test]
fn test_condition() {
test_cases(
&[
"bar",
"(bar)",
"var1 == var2",
"(var1 == var2)",
"var1 == var2 || var5 == var5",
"var1 == var2 || (var5 == var5)",
"var1 == var2 || var5 == var5 && var1 == \"foo\"",
"var1 == var2 || (var5 == var5 && var1 == \"foo\")",
],
Rule::condition,
);
}
#[test]
fn test_conditional() {
test_cases(
&[
"{if i < 10}HI{endif}",
"{if i < 10}HI{else}TEST{endif}",
"{if i < 10}HI{else}{if i < 10}HI{else}TEST{endif}{endif}",
"{if i}HI{endif}",
],
Rule::conditional,
);
}
#[test]
fn test_template() {
test_cases(
&[
"Hello world",
r#"{"test"|modifier:arg}"#,
"{if i < 10} HI {endif}",
"{if i < 10}HI{else}TEST{endif}",
"{if i < 10}HI{else}{if i < 10}HI{else}TEST{endif}{endif}",
"{if i}HI{endif}",
],
Rule::template,
);
}
#[test]
fn test_template_content() {
test_cases(
&[
"Hello world",
r#"{"test"|modifier:arg}"#,
"{if i < 10} HI {endif}",
"{if i < 10}HI{else}TEST{endif}",
"{if i < 10}HI{else}{if i < 10}HI{else}TEST{endif}{endif}",
"{if i}HI{endif}",
],
Rule::template_content,
)
}
#[test]
fn test_assign() {
test_cases(
&["{my_var=12}", r#"{my_var = "test"|modifier:arg}"#],
Rule::assign,
)
}
#[test]
fn test_while() {
test_cases(
&[
"{while var==0}1{endwhile}",
"{ while var == 0 } 1 { endwhile }",
"{while var==0}\n1\n{endwhile}",
],
Rule::while_loop,
)
}
fn test_cases(cases: &[&str], rule: Rule) {
cases.iter().for_each(|input| {
let parsed = TemplateParser::parse(rule, input);
assert!(parsed.is_ok(), "Failed to parse \"{input}\"\n{parsed:#?}");
let parsed = parsed.unwrap().next();
assert!(parsed.is_some(), "{:#?}", parsed);
let identifier = parsed.unwrap();
assert_eq!(identifier.as_str(), *input);
})
}
}
#[cfg(test)]
mod legacy_tests {
use super::*;
#[test]
fn simple_compile() {
let tpl = parse("Simple template string".to_owned()).unwrap();
assert_eq!(
vec![Statement::Literal("Simple template string" as *const _)],
tpl.tpl
);
}
#[test]
fn variable_value() {
let tpl = parse("Simple more {var} template {foo}".to_owned()).unwrap();
assert_eq!(
vec![
Statement::Literal("Simple more " as *const _),
Statement::Calculated(CalculatedValue::new(StorageMethod::Variable("var"), vec![])),
Statement::Literal(" template " as *const _),
Statement::Calculated(CalculatedValue::new(StorageMethod::Variable("foo"), vec![]))
],
tpl.tpl
)
}
#[test]
fn variable_value_simple_modifier() {
let tpl = parse("Simple {var|test} template".to_owned()).unwrap();
assert_eq!(
vec![
Statement::Literal("Simple " as *const _),
Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![("test" as *const _, vec![])]
)),
Statement::Literal(" template" as *const _)
],
tpl.tpl
);
}
#[test]
fn variable_value_modifier_string_value() {
let tpl = parse(r#"Simple {var|test:"test value"} template"#.to_owned()).unwrap();
assert_eq!(
vec![
Statement::Literal("Simple " as *const _),
Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![(
"test" as *const _,
vec![StorageMethod::Const(Value::String(
"test value".to_string()
))]
)]
)),
Statement::Literal(" template" as *const _)
],
tpl.tpl
);
}
#[test]
fn variable_value_modifier_num_value() {
let tpl = parse(r#"Simple {var|test:42} template"#.to_owned()).unwrap();
assert_eq!(
vec![
Statement::Literal("Simple " as *const _),
Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![(
"test" as *const _,
vec![StorageMethod::Const(Value::Number(42_f64))]
)]
)),
Statement::Literal(" template" as *const _)
],
tpl.tpl
);
}
#[test]
fn variable_value_modifier_var_value() {
let tpl = parse(r#"Simple {var|test:foobar} template"#.to_owned()).unwrap();
assert_eq!(
vec![
Statement::Literal("Simple " as *const _),
Statement::Calculated(CalculatedValue::new(
StorageMethod::Variable("var"),
vec![("test" as *const _, vec![StorageMethod::Variable("foobar")])]
)),
Statement::Literal(" template" as *const _)
],
tpl.tpl
);
}
}