1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use crate::template::content::{EvaluableMixedContent, EvaluableMixedContentIterator, TemplateSlice};
use crate::evaluator::{Evaluator, SyntaxError, Context};
use crate::template::Template;
use crate::evaluator::ast::{parse_ast, Position};
use std::error::Error;
use std::fmt::{Display, Formatter};
pub trait Compiler<T> {
type Item;
type ItemIterator: Iterator<Item = Self::Item>;
fn compile<C>(&self, content: C, context: Context) -> Result<String, CompilationError>
where C: EvaluableMixedContent<T, Item=Self::Item, IntoIter=Self::ItemIterator>;
}
#[derive(Debug, PartialEq)]
pub enum CompilationError {
EvaluationFailed {
error: SyntaxError,
position: Position,
source: String,
},
}
impl Error for CompilationError {}
impl Display for CompilationError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct TemplateCompiler<E: Evaluator> {
engine: E
}
impl<E> TemplateCompiler<E> where E: Evaluator {
pub fn new(engine: E) -> TemplateCompiler<E> {
TemplateCompiler {
engine
}
}
}
impl<'a, E> Compiler<&'a Template> for TemplateCompiler<E> where E: Evaluator {
type Item = TemplateSlice<'a>;
type ItemIterator = EvaluableMixedContentIterator<'a, Template>;
fn compile<C>(&self, content: C, context: Context) -> Result<String, CompilationError>
where C: EvaluableMixedContent<&'a Template, Item=Self::Item, IntoIter=Self::ItemIterator> {
let mut result = String::new();
let mut context = context;
let context = &mut context;
for item in content {
let compiled = match item {
TemplateSlice::Text { value, .. } => value.to_string(),
TemplateSlice::Code { value, start_position, .. } => self.engine
.evaluate(&parse_ast(value), context)
.map_err(|err| CompilationError::EvaluationFailed {
error: err,
position: Position::Absolute(start_position),
source: value.to_string(),
})?,
};
result.push_str(compiled.as_str());
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use crate::template::Template;
use crate::evaluator::engine::SimpleEvaluationEngine;
use std::collections::HashMap;
use crate::compiler::{TemplateCompiler, Compiler, CompilationError};
use crate::evaluator::{SyntaxError, EvaluationError, Context};
use crate::evaluator::ast::Position;
#[test]
fn should_compile_template() {
let template = Template::from("Some simple-template. {{ variable }} - or something".to_string());
let engine = SimpleEvaluationEngine::from(HashMap::new());
let compiler = TemplateCompiler::new(engine);
let mut variables = HashMap::new();
variables.insert("variable".to_string(), "Hello world".to_string());
let result = compiler.compile(&template, Context::with_variables(variables));
assert_eq!(result, Ok("Some simple-template. Hello world - or something".to_string()));
}
#[test]
fn should_return_error_during_evaluation() {
let template = Template::from("Should fail. {{ variable }}".to_string());
let engine = SimpleEvaluationEngine::from(HashMap::new());
let compiler = TemplateCompiler::new(engine);
let result = compiler.compile(&template, Context::empty());
assert_eq!(result, Err(CompilationError::EvaluationFailed {
error: SyntaxError::at_position(Position::RelativeToCodeStart(1), EvaluationError::UnknownSymbol {
symbol: "variable".to_string()
}
),
position: Position::Absolute(13),
source: "{{ variable }}".to_string()
}));
}
}