use std::collections::HashMap; use displaydoc::Display; use thiserror::Error; use crate::compiler::VMInstructions; use crate::functions::FunctionMap; use crate::input::NomoInput; use crate::value::NomoValue; use crate::value::NomoValueError; pub mod compiler; pub mod eval; pub mod functions; pub mod input; pub mod lexer; pub mod parser; pub mod value; #[derive(Debug, Error, Display)] pub enum NomoError { /// Could not parse the given template ParseError { #[from] source: lexer::ParseFailure, }, /// Invalid Template AstError { #[from] source: parser::AstFailure, }, /// An error occurred while evaluating EvaluationError { #[from] source: eval::EvaluationError, }, /// The template '{0}' could not be found UnknownTemplate(String), } pub struct Nomo { templates: HashMap, function_map: FunctionMap, } impl Default for Nomo { fn default() -> Self { Self::new() } } impl Nomo { pub fn new() -> Nomo { Nomo { templates: HashMap::new(), function_map: FunctionMap::default(), } } pub fn add_template( &mut self, name: impl Into, value: impl Into, ) -> Result<(), NomoError> { let source = value.into(); let parse = lexer::parse(source.clone())?; let ast = parser::parse(parse.tokens())?; let instructions = compiler::emit_machine(ast); self.templates .insert(name.into(), Template { instructions }); Ok(()) } pub fn render(&self, name: &str, ctx: &Context) -> Result { let template = self .templates .get(name) .ok_or_else(|| NomoError::UnknownTemplate(name.to_string()))?; let res = eval::execute(&self.function_map, &template.instructions, ctx)?; Ok(res) } } struct Template { instructions: VMInstructions, } pub struct Context { values: HashMap, } impl Default for Context { fn default() -> Self { Context::new() } } impl Context { pub fn new() -> Context { Context { values: HashMap::new(), } } pub fn try_insert( &mut self, key: impl Into, value: impl TryInto, ) -> Result<(), NomoValueError> { self.values.insert(key.into(), value.try_into()?); Ok(()) } pub fn insert(&mut self, key: impl Into, value: impl Into) { self.values.insert(key.into(), value.into()); } pub fn values(&self) -> &HashMap { &self.values } } #[derive(Debug, Clone)] pub struct SourceSpan { pub range: std::ops::Range, } // This is just like the standard .resume_after(), except we only resume on Cut errors. fn resume_after_cut( mut parser: ParseNext, mut recover: ParseRecover, ) -> impl winnow::Parser, Error> where Input: winnow::stream::Stream + winnow::stream::Recover, Error: winnow::error::ParserError + winnow::error::FromRecoverableError, ParseNext: winnow::Parser, ParseRecover: winnow::Parser, { winnow::combinator::trace("resume_after_cut", move |input: &mut Input| { resume_after_cut_inner(&mut parser, &mut recover, input) }) } fn resume_after_cut_inner( parser: &mut P, recover: &mut R, i: &mut I, ) -> winnow::Result, E> where P: winnow::Parser, R: winnow::Parser, I: winnow::stream::Stream, I: winnow::stream::Recover, E: winnow::error::ParserError + winnow::error::FromRecoverableError, { let token_start = i.checkpoint(); let mut err = match parser.parse_next(i) { Ok(o) => { return Ok(Some(o)); } Err(e) if e.is_incomplete() || e.is_backtrack() => { return Err(e); } Err(err) => err, }; let err_start = i.checkpoint(); if recover.parse_next(i).is_ok() { if let Err(err_) = i.record_err(&token_start, &err_start, err) { err = err_; } else { return Ok(None); } } i.reset(&err_start); err = E::from_recoverable_error(&token_start, &err_start, i, err); Err(err) } #[cfg(test)] mod tests { use crate::Context; use crate::Nomo; #[test] fn check_simple_template() { let mut temp = Nomo::new(); temp.add_template("base", "Hello {{= name }}").unwrap(); let mut ctx = Context::new(); ctx.insert("name", "World"); let rendered = temp.render("base", &ctx).unwrap(); insta::assert_snapshot!(rendered, @"Hello World") } }