tinylang/
parser.rs

1use pest::iterators::{Pair, Pairs};
2use pest::pratt_parser::{Assoc, Op, PrattParser};
3use pest::Parser;
4use std::borrow::Cow;
5use std::sync::LazyLock;
6use std::vec::IntoIter;
7
8use crate::errors::{ParseError, RuntimeError, TinyLangError};
9use crate::types::{State, TinyLangType};
10
11#[derive(Parser)]
12#[grammar = "../grammar/template_lang.pest"]
13struct TemplateLangParser;
14
15const EMPTY_STRING_COW: Cow<str> = Cow::Borrowed("");
16
17// explanation on Pratt parsers in Rust:
18// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
19static PRATT_PARSER_OP_EXP: LazyLock<PrattParser<Rule>> = LazyLock::new(|| {
20    PrattParser::new()
21        .op(Op::infix(Rule::op_and, Assoc::Left) | Op::infix(Rule::op_or, Assoc::Left))
22        .op(Op::infix(Rule::op_eq, Assoc::Left)
23            | Op::infix(Rule::op_neq, Assoc::Left)
24            | Op::infix(Rule::op_eg, Assoc::Left)
25            | Op::infix(Rule::op_g, Assoc::Left)
26            | Op::infix(Rule::op_el, Assoc::Left)
27            | Op::infix(Rule::op_l, Assoc::Left))
28        .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left))
29        .op(Op::infix(Rule::mul, Assoc::Left) | Op::infix(Rule::div, Assoc::Left))
30        .op(Op::prefix(Rule::neg))
31});
32
33/// Since we parse and execute the flow control at same time
34/// we need to know if we are just parsing static data or
35/// if we are in a loop where we may need to replay later
36enum ParseState<'a> {
37    Static(Cow<'a, str>),
38    Dynamic(Loop<'a>),
39}
40/// Runtime of our language
41struct Runtime<'a> {
42    /// Everytime we encounter an if or a for loop we add a boolean here
43    /// if we should output the content or not
44    /// when we encounter an `end` we `pop` one element
45    should_output: Vec<bool>,
46    /// Everytime we encounter a flow control that requires an end we add it here
47    /// so we know if the user input is invalid
48    needs_end: Vec<Rule>,
49    /// we add the loops that are active here, so we can replay later
50    loops: Vec<Loop<'a>>,
51}
52
53impl Runtime<'_> {
54    pub fn new() -> Self {
55        Self {
56            should_output: Vec::new(),
57            needs_end: Vec::new(),
58            loops: Vec::new(),
59        }
60    }
61
62    pub fn is_output_enabled(&self) -> bool {
63        (!self.should_output.is_empty() && *(self.should_output.last().unwrap()))
64            || self.should_output.is_empty()
65    }
66}
67
68struct Loop<'a> {
69    variable_name: String,
70    vector: IntoIter<TinyLangType>,
71    was_empty: bool,
72    pairs: Vec<Pair<'a, Rule>>,
73    old_state_for_var: TinyLangType,
74}
75
76impl Loop<'_> {
77    fn new(
78        variable_name: String,
79        original_value: Vec<TinyLangType>,
80        old_state_for_var: TinyLangType,
81    ) -> Self {
82        let is_empty = original_value.is_empty();
83        Self {
84            was_empty: is_empty,
85            vector: original_value.into_iter(),
86            pairs: Vec::new(),
87            old_state_for_var,
88            variable_name,
89        }
90    }
91
92    fn next(&mut self) -> Option<TinyLangType> {
93        self.vector.next()
94    }
95
96    fn replay_loop<'a>(mut self, state: &mut State) -> Result<Cow<'a, str>, TinyLangError> {
97        let mut output = String::new();
98        self.pairs.remove(0);
99        let mut runtime = Runtime::new();
100
101        for item in self.vector {
102            state.insert(self.variable_name.to_string(), item);
103
104            for pair in &self.pairs {
105                output.push_str(&process_pair(pair.clone(), state, &mut runtime)?);
106            }
107        }
108        state.insert(self.variable_name.to_string(), self.old_state_for_var);
109        Ok(output.into())
110    }
111}
112
113/// eval receives the &str to parse and the State.
114/// You should put in the State any function or variable that your templates
115/// should have access to.
116/// Important to know that there is only one global scope. For loops may overwrite
117/// variables outside of it.
118/// For now there is no construct to assign new values to variables besides for loops.
119///
120/// Any char will be in the Result unless it's a "dynamic" or a "print".
121/// You can use `{{ 1 }}` to print any expression. Or you can use
122/// `{% if expression %} {% else %} {% end %}` to control the template display.
123/// You can use `{% for item in array %} {% end %}`.
124///
125/// Check `crate::types::TinyLangTypes` for details of all the types build-in the language.
126pub fn eval(input: &str, mut state: State) -> Result<String, TinyLangError> {
127    let pairs = parse(input)?.next().unwrap().into_inner();
128    let mut output = String::new();
129
130    let mut runtime = Runtime::new();
131
132    for pair in pairs {
133        output.push_str(&process_pair(pair, &mut state, &mut runtime)?);
134    }
135
136    Ok(output)
137}
138
139fn process_pair<'a>(
140    pair: Pair<'a, Rule>,
141    state: &mut State,
142    runtime: &mut Runtime<'a>,
143) -> Result<Cow<'a, str>, TinyLangError> {
144    let cloned_pair = pair.clone();
145
146    let current_output = visit_generic(pair, state, runtime)?;
147    let current_output = match current_output {
148        ParseState::Static(s) => s,
149        ParseState::Dynamic(l) => {
150            if !runtime.loops.is_empty() {
151                runtime
152                    .loops
153                    .last_mut()
154                    .unwrap()
155                    .pairs
156                    .extend(l.pairs.clone());
157            }
158            if runtime.is_output_enabled() {
159                l.replay_loop(state)?
160            } else {
161                EMPTY_STRING_COW
162            }
163        }
164    };
165    if !runtime.loops.is_empty() {
166        // we must put evey thing we cloned on the current loop
167        // on the previous one, since we may need to replay it again
168        runtime.loops.last_mut().unwrap().pairs.push(cloned_pair);
169    }
170    if runtime.is_output_enabled() {
171        Ok(current_output)
172    } else {
173        Ok(EMPTY_STRING_COW)
174    }
175}
176
177fn visit_generic<'a>(
178    pair: Pair<'a, Rule>,
179    state: &mut State,
180    runtime: &mut Runtime<'a>,
181) -> Result<ParseState<'a>, TinyLangError> {
182    // if we are currently not outputting the result
183    // we don't need to visit the Rule::html or Rule::print
184    // since we would just throw the result away
185    let current_output = match (pair.as_rule(), runtime.is_output_enabled()) {
186        (Rule::html, true) => pair.as_str().into(),
187        (Rule::print, true) => visit_print(pair.into_inner(), state)?.into(),
188        (Rule::dynamic, _) => {
189            let result = visit_dynamic(pair.into_inner(), state, runtime)?;
190            match result {
191                Some(l) => return Ok(ParseState::Dynamic(l)),
192                None => EMPTY_STRING_COW,
193            }
194        }
195        (Rule::invalid, _) => {
196            return Err(TinyLangError::ParserError(ParseError::InvalidNode(
197                format!("Invalid exp: {}", pair.as_span().as_str()),
198            )))
199        }
200        (_, false) => EMPTY_STRING_COW,
201        _ => unreachable!(),
202    };
203    Ok(ParseState::Static(current_output))
204}
205
206fn parse(input: &str) -> Result<Pairs<'_, Rule>, ParseError> {
207    // this is G which contains children which are
208    // either html chars, print statements or dynamic statements
209    TemplateLangParser::parse(Rule::g, input).map_err(|e| ParseError::Generic(e.to_string()))
210}
211
212fn visit_dynamic<'a>(
213    mut dynamic: Pairs<'a, Rule>,
214    state: &mut State,
215    runtime: &mut Runtime<'a>,
216) -> Result<Option<Loop<'a>>, TinyLangError> {
217    if let Some(dynamic) = dynamic.next() {
218        // if we are not outputting we don't need to process anything for the runtime
219        // but we should still visit flow_* rules since we need to check if they have
220        // their corresponding {%end%}
221        match (dynamic.as_rule(), runtime.is_output_enabled()) {
222            (Rule::exp, true) => {
223                let _ = visit_exp(dynamic.into_inner(), state)?;
224            }
225            (Rule::flow_if, true) => {
226                runtime.needs_end.push(Rule::flow_if);
227                runtime.should_output.push(visit_if(dynamic, state)?);
228            }
229            (Rule::flow_if, false) => {
230                runtime.needs_end.push(Rule::flow_if);
231                runtime.should_output.push(false);
232            }
233            (Rule::flow_else, _) => {
234                //handle case where there is a else without if
235                let last_if = match runtime.should_output.pop() {
236                    Some(b) => b,
237                    None => return Err(TinyLangError::ParserError(ParseError::NoMatchingIf)),
238                };
239                runtime.should_output.push(!last_if);
240            }
241            (Rule::flow_end, _) => {
242                let rule = match runtime.needs_end.pop() {
243                    Some(b) => b,
244                    None => return Err(TinyLangError::ParserError(ParseError::NoMatchingFor)),
245                };
246                if rule == Rule::flow_if {
247                    runtime.should_output.pop();
248                } else {
249                    runtime.should_output.pop();
250                    let loop_struct = runtime.loops.pop();
251                    return Ok(loop_struct);
252                }
253            }
254            (Rule::flow_for, true) => {
255                let for_loop = visit_for(dynamic.into_inner(), state, false)?;
256                runtime.needs_end.push(Rule::flow_for);
257                runtime.should_output.push(!for_loop.was_empty);
258                runtime.loops.push(for_loop);
259            }
260            (Rule::flow_for, false) => {
261                // if we are not outputting the value, it means we are inside a if false { HERE }
262                // if that is the case, we should not halt the execution due to errors on the type of the loop
263                // since it's likely the user did something like if myvar != Nil { for a in myvar {} }
264                let for_loop = visit_for(dynamic.into_inner(), state, true)?;
265                runtime.needs_end.push(Rule::flow_for);
266                // we should keep not outputting
267                runtime.should_output.push(false);
268                runtime.loops.push(for_loop);
269            }
270            (_, false) => (),
271            _ => {
272                return Err(ParseError::InvalidNode(format!(
273                    "dynamic statement does not accept {:?}",
274                    dynamic
275                ))
276                .into())
277            }
278        };
279    }
280
281    Ok(None)
282}
283
284fn visit_for<'a>(
285    mut node: Pairs<'a, Rule>,
286    state: &mut State,
287    ignore_error: bool,
288) -> Result<Loop<'a>, TinyLangError> {
289    let identifier_node = node.next().unwrap();
290    let identifier_name = identifier_node.as_span().as_str();
291    let identifier = visit_identifier(identifier_node, state)?;
292    let original_value = visit_exp(node.next().unwrap().into_inner(), state)?;
293    // we should ignore errors if we are not outputting anything
294    // because that means we should not execute dynamic statements
295    let original_value: Vec<TinyLangType> = match (original_value, ignore_error) {
296        (TinyLangType::Vec(v), _) => v,
297        (_, false) => return Err(TinyLangError::RuntimeError(RuntimeError::InvalidLangType)),
298        (_, true) => Vec::new(),
299    };
300
301    let mut loop_struct = Loop::new(identifier_name.into(), original_value, identifier);
302
303    state.insert(
304        identifier_name.to_string(),
305        loop_struct.next().unwrap_or(TinyLangType::Nil),
306    );
307
308    Ok(loop_struct)
309}
310
311fn visit_if(node_if: Pair<Rule>, state: &mut State) -> Result<bool, TinyLangError> {
312    let condition = match visit_exp(node_if.into_inner(), state)? {
313        TinyLangType::Bool(b) => b,
314        _ => return Err(TinyLangError::RuntimeError(RuntimeError::ExpectingBool)),
315    };
316
317    Ok(condition)
318}
319
320fn visit_print(mut node: Pairs<Rule>, state: &mut State) -> Result<String, TinyLangError> {
321    let child = node.next().unwrap();
322    let print_value = match child.as_rule() {
323        Rule::exp => visit_exp(child.into_inner(), state)?,
324        _ => {
325            return Err(ParseError::InvalidNode(format!(
326                "print statement does not accept {:?}",
327                child
328            ))
329            .into())
330        }
331    };
332
333    Ok(print_value.to_string())
334}
335
336fn visit_exp(node: Pairs<Rule>, state: &mut State) -> Result<TinyLangType, TinyLangError> {
337    let first_child = node.into_iter().next().unwrap();
338    match first_child.as_rule() {
339        Rule::dot => visit_dot(first_child.into_inner(), state),
340        Rule::literal => visit_literal(first_child.into_inner()),
341        Rule::op_exp => visit_op_exp(first_child.into_inner(), state),
342        Rule::identifier => visit_identifier(first_child, state),
343        Rule::function_call => visit_function_call(first_child.into_inner(), state),
344        Rule::exp => visit_exp(first_child.into_inner(), state),
345        _ => Err(ParseError::InvalidNode(format!(
346            "visit_exp was called with an invalid node {:?}",
347            first_child
348        ))
349        .into()),
350    }
351}
352
353fn visit_function_call(
354    mut nodes: Pairs<Rule>,
355    state: &mut State,
356) -> Result<TinyLangType, TinyLangError> {
357    let function = visit_identifier(nodes.next().unwrap(), state)?;
358    let mut params = Vec::new();
359
360    for node in nodes {
361        let param = visit_exp(node.into_inner(), state)?;
362        params.push(param);
363    }
364
365    match function {
366        TinyLangType::Function(f) => Ok(f(params, state)),
367        TinyLangType::Nil => Err(TinyLangError::RuntimeError(RuntimeError::IdentifierIsNil)),
368        _ => Err(TinyLangError::RuntimeError(RuntimeError::InvalidLangType)),
369    }
370}
371
372fn visit_identifier(node: Pair<Rule>, state: &mut State) -> Result<TinyLangType, TinyLangError> {
373    let key = node.as_span().as_str();
374    match state.get(key) {
375        Some(value) => Ok(value.clone()),
376        None => Ok(TinyLangType::Nil),
377    }
378}
379fn visit_dot(mut pairs: Pairs<Rule>, state: &mut State) -> Result<TinyLangType, TinyLangError> {
380    let mut object = visit_identifier(pairs.next().unwrap(), state)?;
381    for p in pairs {
382        object = match object {
383            TinyLangType::Object(ref mut s) => visit_identifier(p, s)?,
384            _ => return Err(TinyLangError::RuntimeError(RuntimeError::InvalidLangType)),
385        };
386    }
387    Ok(object)
388}
389
390fn visit_op_exp(pairs: Pairs<Rule>, state: &mut State) -> Result<TinyLangType, TinyLangError> {
391    PRATT_PARSER_OP_EXP
392        .map_primary(|primary| match primary.as_rule() {
393            Rule::dot => visit_dot(primary.into_inner(), state),
394            Rule::literal => visit_literal(primary.into_inner()),
395            Rule::identifier => visit_identifier(primary, state),
396            Rule::op_exp => visit_op_exp(primary.into_inner(), state), // from "(" ~ op_exp ~ ")"
397            _ => unreachable!(),
398        })
399        .map_prefix(|op, rhs| match op.as_rule() {
400            Rule::neg => Ok((-(rhs?))?),
401            _ => unreachable!(),
402        })
403        .map_infix(|lhs, op, rhs| {
404            let result = match op.as_rule() {
405                Rule::add => lhs? + rhs?,
406                Rule::sub => lhs? - rhs?,
407                Rule::mul => lhs? * rhs?,
408                Rule::div => lhs? / rhs?,
409                Rule::op_eq => Ok(TinyLangType::Bool(lhs? == rhs?)),
410                Rule::op_neq => Ok(TinyLangType::Bool(lhs? != rhs?)),
411                Rule::op_and | Rule::op_or => visit_logical_op(op.as_rule(), lhs?, rhs?),
412                Rule::op_eg => Ok(TinyLangType::Bool(lhs? >= rhs?)),
413                Rule::op_el => Ok(TinyLangType::Bool(lhs? <= rhs?)),
414                Rule::op_g => Ok(TinyLangType::Bool(lhs? > rhs?)),
415                Rule::op_l => Ok(TinyLangType::Bool(lhs? < rhs?)),
416                _ => unreachable!(),
417            };
418
419            result.map_err(TinyLangError::RuntimeError)
420        })
421        .parse(pairs)
422}
423
424fn visit_logical_op(
425    op: Rule,
426    lhs: TinyLangType,
427    rhs: TinyLangType,
428) -> Result<TinyLangType, RuntimeError> {
429    match (op, lhs, rhs) {
430        (Rule::op_and, TinyLangType::Bool(lhs), TinyLangType::Bool(rhs)) => {
431            Ok(TinyLangType::Bool(lhs && rhs))
432        }
433        (Rule::op_or, TinyLangType::Bool(lhs), TinyLangType::Bool(rhs)) => {
434            Ok(TinyLangType::Bool(lhs || rhs))
435        }
436        _ => Err(RuntimeError::InvalidLangType),
437    }
438}
439
440fn visit_literal(node: Pairs<Rule>) -> Result<TinyLangType, TinyLangError> {
441    let child = node.into_iter().next().ok_or(ParseError::InvalidNode(
442        "visit_lang_types was called with an empty node".to_string(),
443    ))?;
444
445    match child.as_rule() {
446        // cannot fail given our grammar (well, this can still fail
447        // because it could be a huge number, but let's ignore this for
448        // now)
449        Rule::integer | Rule::float => Ok(TinyLangType::Numeric(child.as_str().parse().unwrap())),
450        // cannot fail given our grammar
451        Rule::bool => Ok(TinyLangType::Bool(child.as_str().parse().unwrap())),
452        Rule::string => {
453            // we need to remove the ' from start and end of the string
454            let str_val = child.as_str();
455            let string = &str_val[1..str_val.len() - 1];
456            Ok(string.into())
457        }
458        Rule::nil => Ok(TinyLangType::Nil),
459        _ => Err(ParseError::InvalidNode(format!(
460            "visit_lang_types was called with an invalid node {:?}",
461            child
462        ))
463        .into()),
464    }
465}
466
467#[cfg(test)]
468mod test {
469    use super::*;
470    use std::collections::HashMap;
471
472    #[test]
473    fn test_dot_op() {
474        let mut a_object = State::new();
475        a_object.insert("b".into(), 3.into());
476        let result = eval(
477            "{{ a.b }}",
478            HashMap::from([("a".into(), TinyLangType::Object(a_object))]),
479        )
480        .unwrap();
481        assert_eq!("3", result.as_str());
482    }
483
484    #[test]
485    fn test_dot_op_math() {
486        let mut a_object = State::new();
487        a_object.insert("b".into(), 3.into());
488        let result = eval(
489            "{{ 3 + a.b }}",
490            HashMap::from([("a".into(), TinyLangType::Object(a_object))]),
491        )
492        .unwrap();
493        assert_eq!("6", result.as_str());
494    }
495
496    #[test]
497    fn test_dot_op_multiples() {
498        let mut a_object = State::new();
499        a_object.insert("b".into(), 3.into());
500        let mut b_object = State::new();
501        b_object.insert("b".into(), a_object.into());
502        let result = eval(
503            "{{ a.b.b }}",
504            HashMap::from([("a".into(), TinyLangType::Object(b_object))]),
505        )
506        .unwrap();
507        assert_eq!("3", result.as_str());
508    }
509
510    #[test]
511    fn test_dot_op_math_two_objects() {
512        let mut a_object = State::new();
513        a_object.insert("b".into(), 3.into());
514        let result = eval(
515            "{{ a.b + a.b }}",
516            HashMap::from([("a".into(), TinyLangType::Object(a_object))]),
517        )
518        .unwrap();
519        assert_eq!("6", result.as_str());
520    }
521
522    #[test]
523    fn test_basic_html() {
524        let result = eval("<html>", HashMap::default()).unwrap();
525        assert_eq!("<html>", result.as_str());
526    }
527
528    #[test]
529    fn test_empty_string() {
530        let result = eval("", HashMap::default()).unwrap();
531        assert_eq!("", result.as_str());
532    }
533    #[test]
534    fn test_html_with_spaces() {
535        let result = eval("something nice here", HashMap::default()).unwrap();
536        assert_eq!("something nice here", result.as_str());
537    }
538
539    #[test]
540    fn test_literal_print_stmt_with_html() {
541        let result = eval("the coolest number is {{ 12 }} :)", HashMap::default()).unwrap();
542        assert_eq!("the coolest number is 12 :)", result.as_str());
543    }
544
545    #[test]
546    fn test_literal_print_stmt() {
547        let result = eval("{{ 12 }}", HashMap::default()).unwrap();
548        assert_eq!("12", result.as_str());
549    }
550
551    #[test]
552    fn test_literal_float_print_stmt() {
553        let result = eval("{{ 12.4 }}", HashMap::default()).unwrap();
554        assert_eq!("12.4", result.as_str());
555    }
556
557    #[test]
558    fn test_literal_float_math_print_stmt() {
559        let result = eval("{{ 12.4 + 4.6 }}", HashMap::default()).unwrap();
560        assert_eq!("17", result.as_str());
561    }
562
563    #[test]
564    fn test_math_add_print_stmt() {
565        let result = eval("{{ 12 + 1 }}", HashMap::default()).unwrap();
566        assert_eq!("13", result.as_str());
567    }
568    #[test]
569    fn test_math_neg_print_stmt() {
570        let result = eval("{{ -12 + 1 }}", HashMap::default()).unwrap();
571        assert_eq!("-11", result.as_str());
572    }
573
574    #[test]
575    fn test_math_mul_print_stmt() {
576        let result = eval("{{ 12 * 2 }}", HashMap::default()).unwrap();
577        assert_eq!("24", result.as_str())
578    }
579
580    #[test]
581    fn test_right_assoc_math_print_stmt() {
582        let result = eval("{{ 1 + 12 / 2 - 2 }}", HashMap::default()).unwrap();
583        assert_eq!("5", result.as_str())
584    }
585
586    #[test]
587    fn test_identifier_print_stmt() {
588        let result = eval(
589            "{{ a }}",
590            HashMap::from([("a".into(), TinyLangType::Numeric(5_f64))]),
591        )
592        .unwrap();
593        assert_eq!("5", result.as_str())
594    }
595
596    #[test]
597    fn test_identifier_math_print_stmt() {
598        let result = eval(
599            "{{ a_a * 2 + a_a}}",
600            HashMap::from([("a_a".into(), TinyLangType::Numeric(5_f64))]),
601        )
602        .unwrap();
603        assert_eq!("15", result.as_str())
604    }
605
606    #[test]
607    fn test_bool_print_stmt() {
608        let result = eval("{{ true }}", HashMap::default()).unwrap();
609        assert_eq!("true", result.as_str())
610    }
611
612    #[test]
613    fn test_string_print_stmt() {
614        let result = eval("{{ 'something' }}", HashMap::default()).unwrap();
615        assert_eq!("something", result.as_str())
616    }
617
618    #[test]
619    fn test_invalid_stmt() {
620        let result = eval("abc {{ 1 2 3 }} {{1}}", HashMap::default());
621        assert_eq!(
622            Err(TinyLangError::ParserError(ParseError::InvalidNode(
623                "Invalid exp: {{ 1 2 3 }} {{1}}".into()
624            ))),
625            result
626        );
627    }
628
629    #[test]
630    fn test_invalid_math_stmt() {
631        let result = eval(
632            "{{ a * 2 + a}}",
633            HashMap::from([("a".into(), TinyLangType::String("abc".into()))]),
634        );
635        assert_eq!(
636            Err(TinyLangError::RuntimeError(RuntimeError::InvalidLangType)),
637            result
638        );
639    }
640
641    #[test]
642    fn test_comp_eq_stmt() {
643        let result = eval("{{ 1 == 1 }}", HashMap::default()).unwrap();
644        assert_eq!("true", result.as_str())
645    }
646
647    #[test]
648    fn test_comp_neq_stmt() {
649        let result = eval("{{ 1 != 1 }}", HashMap::default()).unwrap();
650        assert_eq!("false", result.as_str())
651    }
652
653    #[test]
654    fn test_comp_eq_str_stmt() {
655        let result = eval("{{ 'a' == 'a' }}", HashMap::default()).unwrap();
656        assert_eq!("true", result.as_str())
657    }
658
659    #[test]
660    fn test_comp_eq_str_ident_stmt() {
661        let result = eval(
662            "{{ 'a' == 'a' }}",
663            HashMap::from([("a".into(), TinyLangType::String("abc".into()))]),
664        )
665        .unwrap();
666        assert_eq!("true", result.as_str())
667    }
668
669    #[test]
670    fn test_comp_neq_and_stmt() {
671        let result = eval("{{ 1 != 3 and 1 != 2 }}", HashMap::default()).unwrap();
672        assert_eq!("true", result.as_str())
673    }
674
675    #[test]
676    fn test_comp_neq_or_stmt() {
677        let result = eval("{{ 1 != 1 or 1 != 2 }}", HashMap::default()).unwrap();
678        assert_eq!("true", result.as_str())
679    }
680
681    #[test]
682    fn test_undefined_var_stmt() {
683        let result = eval("{{ a }}", HashMap::default()).unwrap();
684        assert_eq!("Nil", result.as_str())
685    }
686
687    #[test]
688    fn test_nil_literal_stmt() {
689        let result = eval("{{ Nil }}", HashMap::default()).unwrap();
690        assert_eq!("Nil", result.as_str())
691    }
692
693    #[test]
694    fn test_eg_stmt() {
695        let result = eval("{{ 1 >= 1 }}", HashMap::default()).unwrap();
696        assert_eq!("true", result.as_str())
697    }
698
699    #[test]
700    fn test_g_stmt() {
701        let result = eval("{{ 4 > 1 }}", HashMap::default()).unwrap();
702        assert_eq!("true", result.as_str())
703    }
704
705    #[test]
706    fn test_function_call() {
707        // , 2, 3, 3 * 2
708        let result = eval(
709            "{{ f(1) }}",
710            HashMap::from([(
711                "f".into(),
712                TinyLangType::Function(|args, _state| args.get(0).unwrap().clone()),
713            )]),
714        )
715        .unwrap();
716        assert_eq!("1", result.as_str())
717    }
718
719    #[test]
720    fn test_function_dyn_call() {
721        // , 2, 3, 3 * 2
722        let result = eval(
723            "{% f(1) %}",
724            HashMap::from([(
725                "f".into(),
726                TinyLangType::Function(|args, _state| args.get(0).unwrap().clone()),
727            )]),
728        )
729        .unwrap();
730        assert_eq!("", result.as_str())
731    }
732
733    #[test]
734    fn test_if() {
735        let template = r#"
736        {% if 1 == 1 %}
737        this is true
738        .{% end %}
739        abc
740        "#;
741        let expected = r#"
742        this is true
743        .
744        abc
745        "#;
746        let result = eval(template, HashMap::default()).unwrap();
747        assert_eq!(expected.trim(), result.trim())
748    }
749
750    #[test]
751    fn test_if_else() {
752        let template = r#"
753        {% if 1 != 1 %}
754        this is true
755        {%else%}
756        over here
757        {% end %}
758        abc
759        "#;
760        let expected = r#"
761
762        over here
763
764        abc
765        "#;
766        let result = eval(template, HashMap::default()).unwrap();
767        assert_eq!(expected.replace(' ', ""), result.replace(' ', ""));
768    }
769
770    #[test]
771    fn test_else_without_if() {
772        let template = r#"
773        {%else%}
774        over here
775        {% end %}
776        abc
777        "#;
778        let result = eval(template, HashMap::default());
779        assert_eq!(
780            Err(TinyLangError::ParserError(ParseError::NoMatchingIf)),
781            result
782        );
783    }
784
785    #[test]
786    fn test_for() {
787        let template = r#"
788        {% for a in b %}
789        repeat
790        {{a}}
791        {% end %}
792        abc
793        "#;
794        let expected = r#"
795
796        repeat
797        1
798
799        repeat
800        2
801
802        repeat
803        3
804
805        abc
806        "#;
807        let result = eval(
808            template,
809            HashMap::from([(
810                "b".into(),
811                TinyLangType::Vec(vec![1.into(), 2.into(), 3.into()]),
812            )]),
813        )
814        .unwrap();
815        assert_eq!(expected.replace(' ', ""), result.replace(' ', ""))
816    }
817
818    #[test]
819    fn test_for_single_item() {
820        let template = r#"
821        {% for a in b %}
822        repeat
823        {{a}}
824        {% end %}
825        abc
826        "#;
827        let expected = r#"
828
829        repeat
830        1
831
832        abc
833        "#;
834        let result = eval(
835            template,
836            HashMap::from([("b".into(), TinyLangType::Vec(vec![1.into()]))]),
837        )
838        .unwrap();
839        assert_eq!(expected.replace(' ', ""), result.replace(' ', ""))
840    }
841
842    #[test]
843    fn test_for_single_empty() {
844        let template = r#"
845        {% for a in b %}
846        repeat
847        {{a}}
848        {% end %}
849        abc
850        "#;
851        let expected = r#"
852
853        abc
854        "#;
855        let result = eval(
856            template,
857            HashMap::from([("b".into(), TinyLangType::Vec(vec![]))]),
858        )
859        .unwrap();
860        assert_eq!(expected.replace(' ', ""), result.replace(' ', ""))
861    }
862}