Skip to content

Commit

Permalink
Use Cow<str> for map keys and template sources
Browse files Browse the repository at this point in the history
This allows you to store template sources computed at runtime in the
engine while preserving support for templates included in the binary.
  • Loading branch information
rossmacarthur committed Sep 24, 2022
1 parent 78419bc commit 270252b
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 43 deletions.
8 changes: 5 additions & 3 deletions src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ mod lex;
mod parse;
mod search;

use std::borrow::Cow;

pub use crate::compile::search::Searcher;

use crate::types::ast;
Expand All @@ -19,9 +21,9 @@ use crate::{Engine, Result};
/// Compile a template into a program.
pub fn template<'engine, 'source>(
engine: &'engine Engine<'engine>,
source: &'source str,
source: Cow<'source, str>,
) -> Result<Template<'source>> {
let ast = parse::Parser::new(engine, source).parse_template()?;
let ast = parse::Parser::new(engine, &source).parse_template()?;
Ok(Compiler::new().compile_template(source, ast))
}

Expand All @@ -36,7 +38,7 @@ impl Compiler {
Self { instrs: Vec::new() }
}

fn compile_template(mut self, source: &str, template: ast::Template) -> Template {
fn compile_template(mut self, source: Cow<'_, str>, template: ast::Template) -> Template<'_> {
let ast::Template { scope } = template;
self.compile_scope(scope);
Template {
Expand Down
2 changes: 1 addition & 1 deletion src/compile/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ impl<'engine, 'source> Parser<'engine, 'source> {
}
}

fn source(&self) -> &'source str {
fn source(&self) -> &str {
self.tokens.source
}

Expand Down
46 changes: 27 additions & 19 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ pub mod syntax;
mod types;
mod value;

use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt;
use std::io;
Expand All @@ -202,8 +203,8 @@ pub type Result<T> = std::result::Result<T, Error>;
pub struct Engine<'engine> {
searcher: Searcher,
default_formatter: &'engine FormatFn,
functions: BTreeMap<&'engine str, EngineFn>,
templates: BTreeMap<&'engine str, program::Template<'engine>>,
functions: BTreeMap<Cow<'engine, str>, EngineFn>,
templates: BTreeMap<Cow<'engine, str>, program::Template<'engine>>,
}

enum EngineFn {
Expand Down Expand Up @@ -278,39 +279,46 @@ impl<'engine> Engine<'engine> {
/// **Note:** filters and formatters share the same namespace.
#[cfg(feature = "filters")]
#[inline]
pub fn add_filter<F, R, A>(&mut self, name: &'engine str, f: F)
pub fn add_filter<N, F, R, A>(&mut self, name: N, f: F)
where
N: Into<Cow<'engine, str>>,
F: Filter<R, A> + Send + Sync + 'static,
R: FilterReturn,
A: for<'a> FilterArgs<'a>,
{
self.functions
.insert(name, EngineFn::Filter(filters::new(f)));
.insert(name.into(), EngineFn::Filter(filters::new(f)));
}

/// Add a new value formatter to the engine.
///
/// **Note:** filters and formatters share the same namespace.
#[inline]
pub fn add_formatter<F>(&mut self, name: &'engine str, f: F)
pub fn add_formatter<N, F>(&mut self, name: N, f: F)
where
N: Into<Cow<'engine, str>>,
F: Fn(&mut Formatter<'_>, &Value) -> Result<()> + Sync + Send + 'static,
{
self.functions
.insert(name, EngineFn::Formatter(Box::new(f)));
.insert(name.into(), EngineFn::Formatter(Box::new(f)));
}

/// Add a template to the engine.
///
/// The template will be compiled and stored under the given name.
///
/// When using this function over [`.compile(..)`][Engine::compile] the
/// template source lifetime needs to be as least as long as the engine
/// lifetime.
/// You can either pass a borrowed ([`&str`]) or owned ([`String`]) template
/// to this function. When passing a borrowed template, the lifetime needs
/// to be at least as long as the engine lifetime. For shorter template
/// lifetimes use [`.compile(..)`][Engine::compile].
#[inline]
pub fn add_template(&mut self, name: &'engine str, source: &'engine str) -> Result<()> {
let template = compile::template(self, source)?;
self.templates.insert(name, template);
pub fn add_template<N, S>(&mut self, name: N, source: S) -> Result<()>
where
N: Into<Cow<'engine, str>>,
S: Into<Cow<'engine, str>>,
{
let template = compile::template(self, source.into())?;
self.templates.insert(name.into(), template);
Ok(())
}

Expand All @@ -325,12 +333,12 @@ impl<'engine> Engine<'engine> {

/// Compile a template.
///
/// The template will not be stored in the engine. The advantage over
/// [`.add_template(..)`][Engine::add_template] here is that the lifetime of
/// the template source does not need to outlive the engine.
/// The template will not be stored in the engine. The advantage over using
/// [`.add_template(..)`][Engine::add_template] here is that the template
/// source does not need to outlive the engine.
#[inline]
pub fn compile<'source>(&self, source: &'source str) -> Result<Template<'_, 'source>> {
let template = compile::template(self, source)?;
let template = compile::template(self, Cow::Borrowed(source))?;
Ok(Template {
engine: self,
template,
Expand Down Expand Up @@ -397,8 +405,8 @@ impl<'engine, 'source> Template<'engine, 'source> {

/// Returns the original template source.
#[inline]
pub fn source(&self) -> &'source str {
self.template.source
pub fn source(&self) -> &str {
&self.template.source
}
}

Expand Down Expand Up @@ -455,7 +463,7 @@ impl<'engine> TemplateRef<'engine> {
/// Returns the original template source.
#[inline]
pub fn source(&self) -> &'engine str {
self.template.source
&self.template.source
}
}

Expand Down
34 changes: 17 additions & 17 deletions src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ impl<'engine, 'template> Renderer<'engine, 'template> {
}

Instr::JumpIfTrue(j, span) => {
if expr.take().unwrap().as_bool(t.source, *span)? {
if expr.take().unwrap().as_bool(&t.source, *span)? {
*pc = *j;
continue;
}
}

Instr::JumpIfFalse(j, span) => {
if !expr.take().unwrap().as_bool(t.source, *span)? {
if !expr.take().unwrap().as_bool(&t.source, *span)? {
*pc = *j;
continue;
}
Expand All @@ -146,16 +146,16 @@ impl<'engine, 'template> Renderer<'engine, 'template> {
Instr::Emit(span) => {
let value = expr.take().unwrap();
(self.engine.default_formatter)(f, &value)
.map_err(|err| err.with_span(t.source, *span))?;
.map_err(|err| err.with_span(&t.source, *span))?;
}

Instr::EmitRaw(span) => {
let raw = unsafe { index(t.source, *span) };
let raw = unsafe { index(&t.source, *span) };
f.write_str(raw)?;
}

Instr::EmitWith(name, span) => {
let name_raw = unsafe { index(t.source, name.span) };
let name_raw = unsafe { index(&t.source, name.span) };
match self.engine.functions.get(name_raw) {
// The referenced function is a filter, so we apply
// it and then emit the value using the default
Expand All @@ -165,26 +165,26 @@ impl<'engine, 'template> Renderer<'engine, 'template> {
let mut value = expr.take().unwrap();
let result = filter(FilterState {
stack,
source: t.source,
source: &t.source,
filter: name,
value: &mut value,
value_span: *span,
args: &[],
})?;
(self.engine.default_formatter)(f, &result)
.map_err(|err| err.with_span(t.source, *span))?;
.map_err(|err| err.with_span(&t.source, *span))?;
}
// The referenced function is a formatter so we simply
// emit the value with it.
Some(EngineFn::Formatter(formatter)) => {
let value = expr.take().unwrap();
formatter(f, &value).map_err(|err| err.with_span(t.source, *span))?;
formatter(f, &value).map_err(|err| err.with_span(&t.source, *span))?;
}
// No filter or formatter exists.
None => {
return Err(Error::new(
"unknown filter or formatter",
t.source,
&t.source,
name.span,
));
}
Expand All @@ -194,7 +194,7 @@ impl<'engine, 'template> Renderer<'engine, 'template> {
Instr::LoopStart(vars, span) => {
let iterable = expr.take().unwrap();
stack.push(State::Loop(LoopState::new(
t.source, vars, iterable, *span,
&t.source, vars, iterable, *span,
)?));
}

Expand All @@ -217,19 +217,19 @@ impl<'engine, 'template> Renderer<'engine, 'template> {

Instr::Include(name) => {
*pc += 1;
let template = self.get_template(t.source, name)?;
let template = self.get_template(&t.source, name)?;
return Ok(RenderState::Include { template });
}

Instr::IncludeWith(name) => {
*pc += 1;
let template = self.get_template(t.source, name)?;
let template = self.get_template(&t.source, name)?;
let globals = expr.take().unwrap();
return Ok(RenderState::IncludeWith { template, globals });
}

Instr::ExprStart(path) => {
let value = stack.lookup_path(t.source, path)?;
let value = stack.lookup_path(&t.source, path)?;
let prev = expr.replace(value);
debug_assert!(prev.is_none());
}
Expand All @@ -240,7 +240,7 @@ impl<'engine, 'template> Renderer<'engine, 'template> {
}

Instr::Apply(name, span, args) => {
let name_raw = unsafe { index(t.source, name.span) };
let name_raw = unsafe { index(&t.source, name.span) };
match self.engine.functions.get(name_raw) {
// The referenced function is a filter, so we apply it.
#[cfg(feature = "filters")]
Expand All @@ -252,7 +252,7 @@ impl<'engine, 'template> Renderer<'engine, 'template> {
.unwrap_or(&[]);
let result = filter(FilterState {
stack,
source: t.source,
source: &t.source,
filter: name,
value: &mut value,
value_span: *span,
Expand All @@ -265,13 +265,13 @@ impl<'engine, 'template> Renderer<'engine, 'template> {
Some(EngineFn::Formatter(_)) => {
return Err(Error::new(
"expected filter, found formatter",
t.source,
&t.source,
name.span,
));
}
// No filter or formatter exists.
None => {
return Err(Error::new("unknown filter", t.source, name.span));
return Err(Error::new("unknown filter", &t.source, name.span));
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/types/program.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Defines a compiled [`Template`] which is a sequence of [`Instr`] that can be
//! executed by the renderer.
use std::borrow::Cow;

use crate::types::ast;
use crate::types::span::Span;
use crate::Value;
Expand All @@ -9,7 +11,7 @@ pub const FIXME: usize = !0;

#[cfg_attr(test, derive(Debug))]
pub struct Template<'source> {
pub source: &'source str,
pub source: Cow<'source, str>,
pub instrs: Vec<Instr>,
}

Expand Down
17 changes: 15 additions & 2 deletions tests/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn engine_send_and_sync() {
}

#[test]
fn engine_compile_non_static_source() -> upon::Result<()> {
fn engine_compile_borrowed_source_non_static() -> upon::Result<()> {
let engine = Engine::new();
let source = String::from("{{ lorem }}");
let result = engine.compile(&source)?.render(value! { lorem: "ipsum" })?;
Expand All @@ -30,7 +30,7 @@ fn engine_compile_non_static_source() -> upon::Result<()> {
}

#[test]
fn engine_add_template_non_static_source() -> upon::Result<()> {
fn engine_add_template_borrowed_source_non_static() -> upon::Result<()> {
let mut engine = Engine::new();
let source = String::from("{{ lorem }}");
engine.add_template("test", &source)?;
Expand All @@ -41,3 +41,16 @@ fn engine_add_template_non_static_source() -> upon::Result<()> {
assert_eq!(result, "ipsum");
Ok(())
}

#[test]
fn engine_add_template_owned_source() -> upon::Result<()> {
let mut engine = Engine::new();
let source = String::from("{{ lorem }}");
engine.add_template("test", source)?;
let result = engine
.get_template("test")
.unwrap()
.render(value! { lorem: "ipsum" })?;
assert_eq!(result, "ipsum");
Ok(())
}

0 comments on commit 270252b

Please sign in to comment.