1use std::{cell::RefCell, collections::HashMap};
2
3use ariadne::{Color, Label, Report, ReportKind, Source};
4use chumsky::{error::Rich, prelude::*, span::SimpleSpan};
5
6use crate::{Error, ErrorCollector, SpannedValue, context::RuntimeContext, filtertype::FilterFn};
7
8use super::context::Context;
9
10pub mod replace;
11pub(crate) use replace::*;
12mod parser;
13
14mod resolve;
15
16#[derive(Debug, Clone)]
17enum Expression {
18 Keyword {
19 keywords: Box<SpannedExpr>,
20 },
21 Filter {
22 name: SimpleSpan,
23 args: Vec<Box<SpannedExpr>>,
24 },
25 KeywordWithFilters {
26 keyword: Box<SpannedExpr>,
27 filters: Vec<SpannedExpr>,
28 },
29 ForLoop {
30 var: Vec<SpannedValue>,
31 iter: Box<SpannedExpr>,
32 body: Vec<Box<SpannedExpr>>,
33 },
34 Raw {
35 value: SimpleSpan,
36 },
37 Include {
38 name: SpannedValue,
39 },
40 If {
41 condition: Box<SpannedExpr>,
42 then_branch: Vec<Box<SpannedExpr>>,
43 else_branch: Option<Vec<Box<SpannedExpr>>>,
44 negated: bool,
45 },
46 Range {
47 start: i64,
48 end: i64,
49 },
50 LiteralValue {
51 value: SpannedValue,
52 },
53 BinaryOp {
54 lhs: Box<SpannedExpr>,
55 op: SpannedBinaryOperator,
56 rhs: Box<SpannedExpr>,
57 },
58 Access {
59 keywords: Vec<SimpleSpan>,
60 },
61}
62
63#[allow(dead_code)]
64#[derive(Debug, Clone, Copy)]
65struct SpannedBinaryOperator {
66 op: BinaryOperator,
67 span: SimpleSpan,
68}
69
70#[derive(Debug, Clone, Copy)]
71enum BinaryOperator {
72 Add,
73 Sub,
74 Mul,
75 Div,
76}
77
78impl std::fmt::Display for BinaryOperator {
79 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
80 match self {
81 BinaryOperator::Add => write!(f, "+"),
82 BinaryOperator::Sub => write!(f, "-"),
83 BinaryOperator::Mul => write!(f, "*"),
84 BinaryOperator::Div => write!(f, "/"),
85 }
86 }
87}
88
89impl Expression {
90 pub fn as_keywords<'a>(&self, source: &'a str) -> Option<Vec<&'a str>> {
91 match self {
92 Expression::Keyword { keywords } => keywords.expr.as_keywords(source),
93 Expression::Access { keywords } => Some(get_str_vec(source, keywords)),
94 _ => None,
95 }
96 }
97 pub fn as_spans<'a>(&self) -> Option<&Vec<SimpleSpan>> {
98 if let Expression::Access { keywords } = self {
99 Some(keywords)
100 } else {
101 None
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
107pub struct SpannedExpr {
108 expr: Expression,
109 span: SimpleSpan,
110}
111
112pub struct Engine {
113 filters: HashMap<&'static str, FilterFn>,
114 syntax: EngineSyntax,
115 context: Context,
116 runtime: RefCell<RuntimeContext>,
117 templates: HashMap<String, Template>,
118 sources: Vec<String>,
119 errors: ErrorCollector,
120}
121
122pub struct Template {
123 pub name: String,
124 pub source_id: usize,
125 pub ast: Vec<Box<SpannedExpr>>,
126}
127
128pub struct EngineSyntax {
129 pub keyword_left: String,
130 pub keyword_right: String,
131 pub block_left: String,
132 pub block_right: String,
133}
134
135impl Default for EngineSyntax {
136 fn default() -> Self {
137 Self {
138 keyword_left: String::from("{{"),
139 keyword_right: String::from("}}"),
140 block_left: String::from("<*"),
141 block_right: String::from("*>"),
142 }
143 }
144}
145
146impl EngineSyntax {
147 pub fn new(
148 keyword_left: String,
149 keyword_right: String,
150 block_left: String,
151 block_right: String,
152 ) -> Self {
153 Self {
154 keyword_left,
155 keyword_right,
156 block_left,
157 block_right,
158 }
159 }
160}
161
162impl Engine {
163 pub fn new() -> Self {
164 let filters: HashMap<&str, FilterFn> = HashMap::new();
165
166 let ctx = Context::new();
167
168 Self {
169 filters,
170 syntax: EngineSyntax::default(),
171 context: ctx.clone(),
172 runtime: RuntimeContext::new(ctx.clone()).into(),
173 templates: HashMap::new(),
174 sources: vec![],
175 errors: ErrorCollector::new(),
176 }
177 }
178
179 pub fn set_syntax(&mut self, syntax: EngineSyntax) -> EngineSyntax {
180 std::mem::replace(&mut self.syntax, syntax)
181 }
182
183 pub fn add_filter(&mut self, name: &'static str, function: FilterFn) -> Option<FilterFn> {
184 self.filters.insert(name, function)
185 }
186 pub fn remove_filter(&mut self, name: &'static str) -> Option<FilterFn> {
187 self.filters.remove(name)
188 }
189
190 pub fn add_template(&mut self, name: String, source: String) {
191 self.sources.push(source);
192 let source_id = self.sources.len() - 1;
193 let source_ref = &self.sources[source_id];
194
195 let parser = Self::parser(&self.syntax);
196
197 let (ast, errs) = parser.parse(source_ref).into_output_errors();
198
199 self.templates.insert(
200 name.clone(),
201 Template {
202 name,
203 source_id,
204 ast: ast.unwrap_or_else(|| {
205 self.show_errors(errs, source_ref);
206 std::process::exit(1);
207 }),
208 },
209 );
210 }
211
212 pub fn remove_template(&mut self, name: &String) -> bool {
213 self.templates.remove(name).is_some()
214 }
215
216 pub fn add_context(&mut self, context: serde_json::Value) {
217 self.context.merge_json(context);
218 }
219
220 pub fn remove_key_from_context(&mut self, key: &str) {
221 self.context.delete_key(key);
222 }
223
224 pub fn get_source(&self, name: &str) -> Result<&String, color_eyre::Report> {
225 let template = self
226 .templates
227 .get(name)
228 .ok_or(color_eyre::Report::msg(format!(
229 "Failed to get template: {}",
230 name
231 )))?;
232 self.sources
233 .get(template.source_id)
234 .ok_or(color_eyre::Report::msg(format!(
235 "Failed to get source of template: {}",
236 name
237 )))
238 }
239
240 pub fn compile(&mut self, source: String) -> Result<String, Vec<Error>> {
241 self.add_template(String::from("temporary"), source.clone());
242 let res = self.render("temporary");
243 self.remove_template(&String::from("temporary"));
244 res
245 }
246
247 pub fn render(&self, name: &str) -> Result<String, Vec<Error>> {
248 match self.templates.get(name) {
249 Some(template) => {
250 let res = self.generate_template(template, name.to_string());
251 if !self.errors.is_empty() {
252 return Err(self.errors.take());
253 }
254 Ok(res)
255 }
256 None => {
257 self.errors.add(Error::TemplateNotFound {
258 template: name.to_owned(),
259 name: "none".to_string(),
260 });
261 Err(self.errors.take())
262 }
263 }
264 }
265
266 fn show_errors(&self, errs: Vec<Rich<'_, char>>, source: &str) {
267 errs.into_iter().for_each(|e| {
268 Report::build(ReportKind::Error, ((), e.span().into_range()))
269 .with_config(ariadne::Config::default().with_index_type(ariadne::IndexType::Byte))
270 .with_message(e.to_string())
271 .with_label(
272 Label::new(((), e.span().into_range()))
273 .with_message(e.reason().to_string())
274 .with_color(Color::Red),
275 )
276 .finish()
277 .print(Source::from(source))
278 .unwrap();
279 });
280 }
281}