Skip to main content

matugen_parser/
engine.rs

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}