|
1 | 1 | use crate::language::ast;
|
2 |
| -use crate::language::ast::Spanned; |
| 2 | +use crate::language::ast_visitor::{AstVisit, AstVisitAcceptor}; |
| 3 | +use std::borrow::Cow; |
3 | 4 | use std::collections::HashMap;
|
4 | 5 |
|
5 | 6 | #[derive(Default)]
|
6 | 7 | struct Runtime<'source> {
|
7 | 8 | // todo: maybe an `Ident` should be the key for variables
|
8 | 9 | vars: HashMap<&'source str, ast::Value>,
|
9 |
| - interpreted: Vec<u8>, |
| 10 | + output: Vec<u8>, |
10 | 11 | }
|
11 | 12 |
|
12 |
| -trait Visitable<'source> { |
13 |
| - fn accept<V: Visitor<'source>>(&self, visitor: &mut V); |
14 |
| -} |
15 |
| - |
16 |
| -trait Visitor<'source> { |
17 |
| - fn visit_stmt(&mut self, stmt: &ast::Stmt<'source>); |
18 |
| - fn visit_expr(&mut self, expr: &ast::Expr<'source>); |
19 |
| - fn visit_emit_raw(&mut self, raw: &ast::EmitRaw<'source>); |
20 |
| -} |
| 13 | +impl<'source> Runtime<'source> { |
| 14 | + /// registers a variable with a given `id` that is the variable identifier |
| 15 | + pub fn with_variable(mut self, id: &'source str, var: impl Into<ast::Value>) -> Self { |
| 16 | + self.vars.insert(id, var.into()); |
21 | 17 |
|
22 |
| -impl<'source> Visitable<'source> for ast::Stmt<'source> { |
23 |
| - fn accept<V: Visitor<'source>>(&self, visitor: &mut V) { |
24 |
| - visitor.visit_stmt(self); |
| 18 | + self |
25 | 19 | }
|
26 |
| -} |
27 | 20 |
|
28 |
| -impl<'source> Visitable<'source> for ast::Expr<'source> { |
29 |
| - fn accept<V: Visitor<'source>>(&self, visitor: &mut V) { |
30 |
| - visitor.visit_expr(self); |
| 21 | + /// returns the rendered template as a string in form of a `Cow<'_, str>` |
| 22 | + pub fn rendered(&mut self) -> Cow<'_, str> { |
| 23 | + String::from_utf8_lossy(self.output.as_slice()) |
31 | 24 | }
|
32 |
| -} |
33 | 25 |
|
34 |
| -impl<'source> Visitable<'source> for ast::EmitRaw<'source> { |
35 |
| - fn accept<V: Visitor<'source>>(&self, visitor: &mut V) { |
36 |
| - visitor.visit_emit_raw(self); |
| 26 | + #[cfg(test)] |
| 27 | + pub fn render(&mut self, source: &'source str) -> Cow<'_, str> { |
| 28 | + use crate::language::parser::Parser; |
| 29 | + |
| 30 | + let parsed = Parser::new(source).parse().unwrap(); |
| 31 | + parsed.accept(self); |
| 32 | + |
| 33 | + self.rendered() |
37 | 34 | }
|
38 | 35 | }
|
39 | 36 |
|
40 |
| -impl<'source> Visitor<'source> for Runtime<'source> { |
| 37 | +impl<'source> AstVisit<'source> for Runtime<'source> { |
41 | 38 | fn visit_stmt(&mut self, stmt: &ast::Stmt<'source>) {
|
| 39 | + use ast::Stmt::*; |
| 40 | + |
42 | 41 | match stmt {
|
43 |
| - ast::Stmt::Template(spanned) => { |
| 42 | + Template(spanned) => { |
44 | 43 | for s in spanned.node.children.as_slice() {
|
45 | 44 | s.accept(self);
|
46 | 45 | }
|
47 | 46 | }
|
48 |
| - ast::Stmt::EmitRaw(spanned) => spanned.node.accept(self), |
49 |
| - ast::Stmt::EmitExpr(spanned) => spanned.node.expr.accept(self), |
| 47 | + EmitRaw(spanned) => spanned.node.accept(self), |
| 48 | + EmitExpr(spanned) => spanned.node.expr.accept(self), |
50 | 49 | }
|
51 | 50 | }
|
52 | 51 |
|
53 | 52 | fn visit_expr(&mut self, expr: &ast::Expr<'source>) {
|
54 |
| - use ast::Expr; |
| 53 | + use ast::Expr::*; |
55 | 54 |
|
56 | 55 | match expr {
|
57 |
| - Expr::SysVar(var) => todo!(), |
58 |
| - Expr::Var(var) => { |
| 56 | + SysVar(var) => todo!(), |
| 57 | + Var(var) => { |
59 | 58 | if let Some(var) = self.vars.get(var.node.id) {
|
60 |
| - self.interpreted |
| 59 | + self.output |
61 | 60 | .extend_from_slice(var.as_str().unwrap().as_bytes());
|
62 | 61 | }
|
63 | 62 | }
|
64 |
| - Expr::Const(_) => { |
| 63 | + Const(_) => { |
65 | 64 | todo!()
|
66 | 65 | }
|
67 |
| - Expr::Call(_) => { |
| 66 | + Call(_) => { |
68 | 67 | todo!()
|
69 | 68 | }
|
70 | 69 | }
|
71 | 70 | }
|
72 | 71 |
|
73 | 72 | fn visit_emit_raw(&mut self, raw: &ast::EmitRaw<'source>) {
|
74 |
| - self.interpreted.extend_from_slice(raw.raw.as_bytes()); |
| 73 | + self.output.extend_from_slice(raw.raw.as_bytes()); |
75 | 74 | }
|
76 | 75 | }
|
77 | 76 |
|
78 | 77 | #[cfg(test)]
|
79 | 78 | mod tests {
|
80 | 79 | use super::*;
|
81 |
| - use crate::language::parser::Parser; |
| 80 | + use crate::language::ast::IntoSpanned; |
| 81 | + |
| 82 | + #[test] |
| 83 | + fn test_expr_var() { |
| 84 | + let mut runtime = Runtime::default().with_variable("foo", "John"); |
| 85 | + let expr = ast::Expr::Var(ast::Var { id: "foo" }.spanned()); |
| 86 | + |
| 87 | + expr.accept(&mut runtime); |
| 88 | + assert_eq!(runtime.rendered(), "John"); |
| 89 | + } |
| 90 | + |
| 91 | + #[test] |
| 92 | + fn test_expr_sys_var() { |
| 93 | + assert_eq!( |
| 94 | + Runtime::default().render("{{ $processEnv HOME }}"), |
| 95 | + env!("HOME") |
| 96 | + ); |
| 97 | + } |
| 98 | + |
| 99 | + #[test] |
| 100 | + fn test_whole_template() { |
| 101 | + assert_eq!( |
| 102 | + Runtime::default() |
| 103 | + .with_variable("world", "John") |
| 104 | + .render("hello {{ world }}"), |
| 105 | + "hello John" |
| 106 | + ); |
| 107 | + } |
82 | 108 |
|
83 | 109 | #[test]
|
84 |
| - fn test_x() { |
85 |
| - let mut p = Parser::new("hello {{ world }}"); |
86 |
| - let stmt = p.parse().unwrap(); |
87 |
| - let mut runtime = Runtime::default(); |
88 |
| - runtime |
89 |
| - .vars |
90 |
| - .insert("world", ast::Value::String("John".to_string())); |
91 |
| - |
92 |
| - stmt.accept(&mut runtime); |
93 |
| - |
94 |
| - let result = String::from_utf8_lossy(runtime.interpreted.as_slice()); |
95 |
| - assert_eq!(result, "hello John"); |
| 110 | + fn test_whole_template_unhappy() { |
| 111 | + assert_eq!(Runtime::default().render("hello {{ world }}"), "hello "); |
96 | 112 | }
|
97 | 113 | }
|
0 commit comments