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
17static 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
33enum ParseState<'a> {
37 Static(Cow<'a, str>),
38 Dynamic(Loop<'a>),
39}
40struct Runtime<'a> {
42 should_output: Vec<bool>,
46 needs_end: Vec<Rule>,
49 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
113pub 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 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 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 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 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 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 let for_loop = visit_for(dynamic.into_inner(), state, true)?;
265 runtime.needs_end.push(Rule::flow_for);
266 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 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), _ => 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 Rule::integer | Rule::float => Ok(TinyLangType::Numeric(child.as_str().parse().unwrap())),
450 Rule::bool => Ok(TinyLangType::Bool(child.as_str().parse().unwrap())),
452 Rule::string => {
453 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 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 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}