diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs index da1e6e2..b543479 100644 --- a/fuzz/fuzz_targets/fuzz_target_1.rs +++ b/fuzz/fuzz_targets/fuzz_target_1.rs @@ -12,7 +12,7 @@ fuzz_target!(|data: String| -> Corpus { return Corpus::Keep; }; - let _instructions = nomo::emit::emit_machine(ast); + let _instructions = nomo::compiler::emit_machine(ast); Corpus::Keep }); diff --git a/src/compiler/lib.rs b/src/compiler/lib.rs new file mode 100644 index 0000000..8b4b35d --- /dev/null +++ b/src/compiler/lib.rs @@ -0,0 +1,206 @@ +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 parser; +pub mod compiler; +pub mod eval; +pub mod functions; +pub mod input; +pub mod lexer; +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") + } +} diff --git a/src/emit/mod.rs b/src/compiler/mod.rs similarity index 99% rename from src/emit/mod.rs rename to src/compiler/mod.rs index cf33cfa..eb06315 100644 --- a/src/emit/mod.rs +++ b/src/compiler/mod.rs @@ -494,7 +494,7 @@ fn emit_expr_load( #[cfg(test)] mod tests { - use crate::emit::emit_machine; + use crate::compiler::emit_machine; #[test] fn check_simple_variable_interpolation() { diff --git a/src/emit/snapshots/nomo__emit__tests__check_function_call.snap b/src/compiler/snapshots/nomo__compiler__tests__check_function_call.snap similarity index 98% rename from src/emit/snapshots/nomo__emit__tests__check_function_call.snap rename to src/compiler/snapshots/nomo__compiler__tests__check_function_call.snap index 3594efe..a4ee4f3 100644 --- a/src/emit/snapshots/nomo__emit__tests__check_function_call.snap +++ b/src/compiler/snapshots/nomo__compiler__tests__check_function_call.snap @@ -1,5 +1,5 @@ --- -source: src/emit/mod.rs +source: src/compiler/mod.rs expression: emit --- VMInstructions { diff --git a/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap b/src/compiler/snapshots/nomo__compiler__tests__check_if_else_if.snap similarity index 98% rename from src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap rename to src/compiler/snapshots/nomo__compiler__tests__check_if_else_if.snap index e178993..de57cc6 100644 --- a/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap +++ b/src/compiler/snapshots/nomo__compiler__tests__check_if_else_if.snap @@ -1,5 +1,5 @@ --- -source: src/emit/mod.rs +source: src/compiler/mod.rs expression: emit --- VMInstructions { diff --git a/src/eval/mod.rs b/src/eval/mod.rs index e0d864b..96ae66e 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -4,9 +4,9 @@ use displaydoc::Display; use thiserror::Error; use crate::Context; -use crate::emit::Instruction; -use crate::emit::VMInstructions; -use crate::emit::VariableSlot; +use crate::compiler::Instruction; +use crate::compiler::VMInstructions; +use crate::compiler::VariableSlot; use crate::functions::FunctionMap; use crate::input::NomoInput; use crate::value::NomoValue; @@ -247,7 +247,7 @@ mod tests { let ast = crate::parser::parse(parsed.tokens()).unwrap(); - let emit = crate::emit::emit_machine(ast); + let emit = crate::compiler::emit_machine(ast); let mut context = Context::new(); context.insert("world", "World"); @@ -268,7 +268,7 @@ mod tests { let ast = crate::parser::parse(parsed.tokens()).unwrap(); - let emit = crate::emit::emit_machine(ast); + let emit = crate::compiler::emit_machine(ast); let mut context = Context::new(); context.insert("world", "World"); diff --git a/src/lib.rs b/src/lib.rs index fbe8ecf..7874d4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,18 +3,18 @@ use std::collections::HashMap; use displaydoc::Display; use thiserror::Error; -use crate::emit::VMInstructions; +use crate::compiler::VMInstructions; use crate::functions::FunctionMap; use crate::input::NomoInput; use crate::value::NomoValue; use crate::value::NomoValueError; -pub mod parser; -pub mod emit; +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)] @@ -68,7 +68,7 @@ impl Nomo { let parse = lexer::parse(source.clone())?; let ast = parser::parse(parse.tokens())?; - let instructions = emit::emit_machine(ast); + let instructions = compiler::emit_machine(ast); self.templates .insert(name.into(), Template { instructions }); diff --git a/tests/checks.rs b/tests/checks.rs index 98480d8..200ac20 100644 --- a/tests/checks.rs +++ b/tests/checks.rs @@ -13,6 +13,6 @@ fn check_files() { continue; }; - let _emit = nomo::emit::emit_machine(ast); + let _emit = nomo::compiler::emit_machine(ast); } } diff --git a/tests/file_tests.rs b/tests/file_tests.rs index 6163922..b26c356 100644 --- a/tests/file_tests.rs +++ b/tests/file_tests.rs @@ -55,7 +55,7 @@ fn check_for_input([path]: [&Path; 1]) { insta::assert_debug_snapshot!(format!("{basename}.2-ast"), ast); - let emit = nomo::emit::emit_machine(ast); + let emit = nomo::compiler::emit_machine(ast); insta::assert_debug_snapshot!(format!("{basename}.3-instructions"), emit);