diff --git a/cli/src/commands/config.rs b/cli/src/commands/config.rs index c0f958a91..d8272268a 100644 --- a/cli/src/commands/config.rs +++ b/cli/src/commands/config.rs @@ -26,7 +26,7 @@ use crate::command_error::{user_error, CommandError}; use crate::config::{AnnotatedValue, ConfigSource}; use crate::generic_templater::GenericTemplateLanguage; use crate::template_builder::TemplateLanguage as _; -use crate::templater::TemplatePropertyFn; +use crate::templater::TemplateFunction; use crate::ui::Ui; #[derive(clap::Args, Clone, Debug)] @@ -184,25 +184,28 @@ pub(crate) fn cmd_config( } } +// AnnotatedValue will be cloned internally in the templater. If the cloning +// cost matters, wrap it with Rc. fn config_template_language() -> GenericTemplateLanguage<'static, AnnotatedValue> { type L = GenericTemplateLanguage<'static, AnnotatedValue>; - fn prop_fn R>(f: F) -> TemplatePropertyFn { - TemplatePropertyFn(f) - } - let mut language = GenericTemplateLanguage::new(); + let mut language = L::new(); // "name" instead of "path" to avoid confusion with the source file path - language.add_keyword("name", || { - let property = prop_fn(|annotated| Ok(annotated.path.join("."))); - Ok(L::wrap_string(property)) + language.add_keyword("name", |self_property| { + let out_property = + TemplateFunction::new(self_property, |annotated| Ok(annotated.path.join("."))); + Ok(L::wrap_string(out_property)) }); - language.add_keyword("value", || { + language.add_keyword("value", |self_property| { // TODO: would be nice if we can provide raw dynamically-typed value - let property = prop_fn(|annotated| Ok(serialize_config_value(&annotated.value))); - Ok(L::wrap_string(property)) + let out_property = TemplateFunction::new(self_property, |annotated| { + Ok(serialize_config_value(&annotated.value)) + }); + Ok(L::wrap_string(out_property)) }); - language.add_keyword("overridden", || { - let property = prop_fn(|annotated| Ok(annotated.is_overridden)); - Ok(L::wrap_boolean(property)) + language.add_keyword("overridden", |self_property| { + let out_property = + TemplateFunction::new(self_property, |annotated| Ok(annotated.is_overridden)); + Ok(L::wrap_boolean(out_property)) }); language } diff --git a/cli/src/generic_templater.rs b/cli/src/generic_templater.rs index 78efd23d7..2130d3c46 100644 --- a/cli/src/generic_templater.rs +++ b/cli/src/generic_templater.rs @@ -19,19 +19,19 @@ use crate::template_builder::{ TemplateLanguage, }; use crate::template_parser::{self, FunctionCallNode, TemplateParseResult}; -use crate::templater::{Template, TemplateProperty}; +use crate::templater::{Template, TemplateProperty, TemplatePropertyFn}; /// General-purpose template language for basic value types. /// /// This template language only supports the core template property types (plus /// the context type `C`.) The context type `C` is usually a tuple or struct of -/// value types. Keyword functions need to be registered to extract properties -/// from the context object. -pub struct GenericTemplateLanguage<'a, C> { +/// value types. It's cloned several times internally. Keyword functions need to +/// be registered to extract properties from the context object. +pub struct GenericTemplateLanguage<'a, C: Clone> { build_fn_table: GenericTemplateBuildFnTable<'a, C>, } -impl<'a, C> GenericTemplateLanguage<'a, C> { +impl<'a, C: Clone> GenericTemplateLanguage<'a, C> { /// Sets up environment with no keywords. /// /// New keyword functions can be registered by `add_keyword()`. @@ -55,32 +55,33 @@ impl<'a, C> GenericTemplateLanguage<'a, C> { /// /// A keyword function returns `Self::Property`, which is basically a /// closure tagged by its return type. The inner closure is usually wrapped - /// by `TemplatePropertyFn`. + /// by `TemplateFunction`. /// /// ```ignore - /// language.add_keyword("name", || { - /// let property = TemplatePropertyFn(|v: &C| Ok(v.to_string())); - /// Ok(GenericTemplateLanguage::wrap_string(property)) + /// language.add_keyword("name", |self_property| { + /// let out_property = TemplateFunction::new(self_property, |v| Ok(v.to_string())); + /// Ok(GenericTemplateLanguage::wrap_string(out_property)) /// }); /// ``` pub fn add_keyword(&mut self, name: &'static str, build: F) where - F: Fn() -> TemplateParseResult> + 'a, + F: Fn( + Box + 'a>, + ) -> TemplateParseResult> + + 'a, { self.build_fn_table.keywords.insert(name, Box::new(build)); } } -impl<'a, C: 'a> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> { +impl<'a, C: Clone + 'a> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> { type Context = C; type Property = GenericTemplatePropertyKind<'a, C>; template_builder::impl_core_wrap_property_fns!('a, GenericTemplatePropertyKind::Core); fn build_self(&self) -> Self::Property { - // No need to clone the context object because there are no other - // objects of "Self" type, and the context is available everywhere. - GenericTemplatePropertyKind::Self_ + Self::wrap_self(TemplatePropertyFn(|context: &C| Ok(context.clone()))) } fn build_function( @@ -103,48 +104,56 @@ impl<'a, C: 'a> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> { let table = &self.build_fn_table.core; table.build_method(self, build_ctx, property, function) } - GenericTemplatePropertyKind::Self_ => { + GenericTemplatePropertyKind::Self_(property) => { let table = &self.build_fn_table.keywords; let build = template_parser::lookup_method("Self", table, function)?; // For simplicity, only 0-ary method is supported. template_parser::expect_no_arguments(function)?; - build() + build(property) } } } } +impl<'a, C: Clone> GenericTemplateLanguage<'a, C> { + pub fn wrap_self( + property: impl TemplateProperty + 'a, + ) -> GenericTemplatePropertyKind<'a, C> { + GenericTemplatePropertyKind::Self_(Box::new(property)) + } +} + pub enum GenericTemplatePropertyKind<'a, C> { Core(CoreTemplatePropertyKind<'a, C>), - Self_, + Self_(Box + 'a>), } impl<'a, C: 'a> IntoTemplateProperty<'a, C> for GenericTemplatePropertyKind<'a, C> { fn try_into_boolean(self) -> Option + 'a>> { match self { GenericTemplatePropertyKind::Core(property) => property.try_into_boolean(), - GenericTemplatePropertyKind::Self_ => None, + GenericTemplatePropertyKind::Self_(_) => None, } } fn try_into_integer(self) -> Option + 'a>> { match self { GenericTemplatePropertyKind::Core(property) => property.try_into_integer(), - GenericTemplatePropertyKind::Self_ => None, + GenericTemplatePropertyKind::Self_(_) => None, } } fn try_into_plain_text(self) -> Option + 'a>> { match self { GenericTemplatePropertyKind::Core(property) => property.try_into_plain_text(), - GenericTemplatePropertyKind::Self_ => None, + GenericTemplatePropertyKind::Self_(_) => None, } } fn try_into_template(self) -> Option + 'a>> { match self { GenericTemplatePropertyKind::Core(property) => property.try_into_template(), - GenericTemplatePropertyKind::Self_ => None, + GenericTemplatePropertyKind::Self_(_) => None, } } } @@ -154,15 +163,19 @@ impl<'a, C: 'a> IntoTemplateProperty<'a, C> for GenericTemplatePropertyKind<'a, /// /// Because the `GenericTemplateLanguage` doesn't provide a way to pass around /// global resources, the keyword function is allowed to capture resources. -pub type GenericTemplateBuildKeywordFn<'a, C> = - Box TemplateParseResult> + 'a>; +pub type GenericTemplateBuildKeywordFn<'a, C> = Box< + dyn Fn( + Box + 'a>, + ) -> TemplateParseResult> + + 'a, +>; /// Table of functions that translate keyword node. pub type GenericTemplateBuildKeywordFnMap<'a, C> = HashMap<&'static str, GenericTemplateBuildKeywordFn<'a, C>>; /// Symbol table of methods available in the general-purpose template. -struct GenericTemplateBuildFnTable<'a, C: 'a> { +struct GenericTemplateBuildFnTable<'a, C: Clone + 'a> { core: CoreTemplateBuildFnTable<'a, GenericTemplateLanguage<'a, C>>, keywords: GenericTemplateBuildKeywordFnMap<'a, C>, } diff --git a/cli/src/template_builder.rs b/cli/src/template_builder.rs index bf112b533..554c3aa7d 100644 --- a/cli/src/template_builder.rs +++ b/cli/src/template_builder.rs @@ -1206,7 +1206,7 @@ mod tests { where F: Fn() -> TestTemplatePropertyKind + 'static, { - self.language.add_keyword(name, move || Ok(build())); + self.language.add_keyword(name, move |_| Ok(build())); } fn add_alias(&mut self, decl: impl AsRef, defn: impl Into) {