// Copyright 2020-2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::cmp::Ordering;use std::collections::HashMap;use std::io;use std::iter; use itertools::Itertools as _;use jj_lib::backend::Signature;use jj_lib::backend::Timestamp;use jj_lib::config::ConfigNamePathBuf;use jj_lib::config::ConfigValue;use jj_lib::op_store::TimestampRange;use jj_lib::settings::UserSettings;use jj_lib::time_util::DatePattern;use serde::de::IntoDeserializer as _;use serde::Deserialize; use crate::formatter::FormatRecorder;use crate::formatter::Formatter;use crate::template_parser;use crate::template_parser::BinaryOp;use crate::template_parser::ExpressionKind;use crate::template_parser::ExpressionNode;use crate::template_parser::FunctionCallNode;use crate::template_parser::LambdaNode;use crate::template_parser::TemplateAliasesMap;use crate::template_parser::TemplateDiagnostics;use crate::template_parser::TemplateParseError;use crate::template_parser::TemplateParseErrorKind;use crate::template_parser::TemplateParseResult;use crate::template_parser::UnaryOp;use crate::templater::BoxedSerializeProperty;use crate::templater::BoxedTemplateProperty;use crate::templater::CoalesceTemplate;use crate::templater::ConcatTemplate;use crate::templater::ConditionalTemplate;use crate::templater::Email;use crate::templater::LabelTemplate;use crate::templater::ListPropertyTemplate;use crate::templater::ListTemplate;use crate::templater::Literal;use crate::templater::PlainTextFormattedProperty;use crate::templater::PropertyPlaceholder;use crate::templater::RawEscapeSequenceTemplate;use crate::templater::ReformatTemplate;use crate::templater::SeparateTemplate;use crate::templater::SizeHint;use crate::templater::Template;use crate::templater::TemplateProperty;use crate::templater::TemplatePropertyError;use crate::templater::TemplatePropertyExt as _;use crate::templater::TemplateRenderer;use crate::templater::WrapTemplateProperty;use crate::text_util;use crate::time_util; /// Callbacks to build language-specific evaluation objects from AST nodes.pub trait TemplateLanguage<'a> { type Property: CoreTemplatePropertyVar<'a>; fn settings(&self) -> &UserSettings; /// Translates the given global `function` call to a property. /// /// This should be delegated to /// `CoreTemplateBuildFnTable::build_function()`. fn build_function( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property>; fn build_method( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property>;} /// Implements [`WrapTemplateProperty<'a, O>`] for property types.////// - `impl_property_wrappers!(Kind { Foo(Foo), FooList(Vec<Foo>), .. });` to/// implement conversion from types `Foo`, `Vec<Foo>`, .../// - `impl_property_wrappers!(<'a> Kind<'a> { .. });` for types with lifetime./// - `impl_property_wrappers!(Kind => Core { .. });` to forward conversion to/// `Kind::Core(_)`.macro_rules! impl_property_wrappers { ($kind:path $(=> $var:ident)? { $($body:tt)* }) => { $crate::template_builder::_impl_property_wrappers_many!( [], 'static, $kind $(=> $var)?, { $($body)* }); }; // capture the first lifetime as the lifetime of template objects. (<$a:lifetime $(, $p:lifetime)* $(, $q:ident)*> $kind:path $(=> $var:ident)? { $($body:tt)* }) => { $crate::template_builder::_impl_property_wrappers_many!( [$a, $($p,)* $($q,)*], $a, $kind $(=> $var)?, { $($body)* }); };} macro_rules! _impl_property_wrappers_many { // lifetime/type parameters are packed in order to disable zipping. // https://github.com/rust-lang/rust/issues/96184#issuecomment-1294999418 ($ps:tt, $a:lifetime, $kind:path, { $( $var:ident($ty:ty), )* }) => { $( $crate::template_builder::_impl_property_wrappers_one!( $ps, $a, $kind, $var, $ty, std::convert::identity); )* }; // variant part in body is ignored so the same body can be reused for // implementing forwarding conversion. ($ps:tt, $a:lifetime, $kind:path => $var:ident, { $( $ignored_var:ident($ty:ty), )* }) => { $( $crate::template_builder::_impl_property_wrappers_one!( $ps, $a, $kind, $var, $ty, $crate::templater::WrapTemplateProperty::wrap_property); )* };} macro_rules! _impl_property_wrappers_one { ([$($p:tt)*], $a:lifetime, $kind:path, $var:ident, $ty:ty, $inner:path) => { impl<$($p)*> $crate::templater::WrapTemplateProperty<$a, $ty> for $kind { fn wrap_property(property: $crate::templater::BoxedTemplateProperty<$a, $ty>) -> Self { Self::$var($inner(property)) } } };} pub(crate) use _impl_property_wrappers_many;pub(crate) use _impl_property_wrappers_one;pub(crate) use impl_property_wrappers; /// Wrapper for the core template property types.pub trait CoreTemplatePropertyVar<'a>where Self: WrapTemplateProperty<'a, String>, Self: WrapTemplateProperty<'a, Vec<String>>, Self: WrapTemplateProperty<'a, bool>, Self: WrapTemplateProperty<'a, i64>, Self: WrapTemplateProperty<'a, Option<i64>>, Self: WrapTemplateProperty<'a, ConfigValue>, Self: WrapTemplateProperty<'a, Signature>, Self: WrapTemplateProperty<'a, Email>, Self: WrapTemplateProperty<'a, SizeHint>, Self: WrapTemplateProperty<'a, Timestamp>, Self: WrapTemplateProperty<'a, TimestampRange>,{ fn wrap_template(template: Box<dyn Template + 'a>) -> Self; fn wrap_list_template(template: Box<dyn ListTemplate + 'a>) -> Self; /// Type name of the property output. fn type_name(&self) -> &'static str; fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>>; fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>>; /// Transforms into a string property by formatting the value if needed. fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>>; fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>>; fn try_into_template(self) -> Option<Box<dyn Template + 'a>>; /// Transforms into a property that will evaluate to `self == other`. fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>>; /// Transforms into a property that will evaluate to an [`Ordering`]. fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>>;} pub enum CoreTemplatePropertyKind<'a> { String(BoxedTemplateProperty<'a, String>), StringList(BoxedTemplateProperty<'a, Vec<String>>), Boolean(BoxedTemplateProperty<'a, bool>), Integer(BoxedTemplateProperty<'a, i64>), IntegerOpt(BoxedTemplateProperty<'a, Option<i64>>), ConfigValue(BoxedTemplateProperty<'a, ConfigValue>), Signature(BoxedTemplateProperty<'a, Signature>), Email(BoxedTemplateProperty<'a, Email>), SizeHint(BoxedTemplateProperty<'a, SizeHint>), Timestamp(BoxedTemplateProperty<'a, Timestamp>), TimestampRange(BoxedTemplateProperty<'a, TimestampRange>), // Both TemplateProperty and Template can represent a value to be evaluated // dynamically, which suggests that `Box<dyn Template + 'a>` could be // composed as `Box<dyn TemplateProperty<Output = Box<dyn Template ..`. // However, there's a subtle difference: TemplateProperty is strict on // error, whereas Template is usually lax and prints an error inline. If // `concat(x, y)` were a property returning Template, and if `y` failed to // evaluate, the whole expression would fail. In this example, a partial // evaluation output is more useful. That's one reason why Template isn't // wrapped in a TemplateProperty. Another reason is that the outermost // caller expects a Template, not a TemplateProperty of Template output. Template(Box<dyn Template + 'a>), ListTemplate(Box<dyn ListTemplate + 'a>),} /// Implements `WrapTemplateProperty<type>` for core property types.////// Use `impl_core_property_wrappers!(<'a> Kind<'a> => Core);` to implement/// forwarding conversion.macro_rules! impl_core_property_wrappers { ($($head:tt)+) => { $crate::template_builder::impl_property_wrappers!($($head)+ { String(String), StringList(Vec<String>), Boolean(bool), Integer(i64), IntegerOpt(Option<i64>), ConfigValue(jj_lib::config::ConfigValue), Signature(jj_lib::backend::Signature), Email($crate::templater::Email), SizeHint($crate::templater::SizeHint), Timestamp(jj_lib::backend::Timestamp), TimestampRange(jj_lib::op_store::TimestampRange), }); };} pub(crate) use impl_core_property_wrappers; impl_core_property_wrappers!(<'a> CoreTemplatePropertyKind<'a>); impl<'a> CoreTemplatePropertyVar<'a> for CoreTemplatePropertyKind<'a> { fn wrap_template(template: Box<dyn Template + 'a>) -> Self { Self::Template(template) } fn wrap_list_template(template: Box<dyn ListTemplate + 'a>) -> Self { Self::ListTemplate(template) } fn type_name(&self) -> &'static str { match self { Self::String(_) => "String", Self::StringList(_) => "List<String>", Self::Boolean(_) => "Boolean", Self::Integer(_) => "Integer", Self::IntegerOpt(_) => "Option<Integer>", Self::ConfigValue(_) => "ConfigValue", Self::Signature(_) => "Signature", Self::Email(_) => "Email", Self::SizeHint(_) => "SizeHint", Self::Timestamp(_) => "Timestamp", Self::TimestampRange(_) => "TimestampRange", Self::Template(_) => "Template", Self::ListTemplate(_) => "ListTemplate", } } fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>> { match self { Self::String(property) => Some(property.map(|s| !s.is_empty()).into_dyn()), Self::StringList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()), Self::Boolean(property) => Some(property), Self::Integer(_) => None, Self::IntegerOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()), Self::ConfigValue(_) => None, Self::Signature(_) => None, Self::Email(property) => Some(property.map(|e| !e.0.is_empty()).into_dyn()), Self::SizeHint(_) => None, Self::Timestamp(_) => None, Self::TimestampRange(_) => None, // Template types could also be evaluated to boolean, but it's less likely // to apply label() or .map() and use the result as conditional. It's also // unclear whether ListTemplate should behave as a "list" or a "template". Self::Template(_) => None, Self::ListTemplate(_) => None, } } fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>> { match self { Self::Integer(property) => Some(property), Self::IntegerOpt(property) => Some(property.try_unwrap("Integer").into_dyn()), _ => None, } } fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>> { match self { Self::String(property) => Some(property), _ => { let template = self.try_into_template()?; Some(PlainTextFormattedProperty::new(template).into_dyn()) } } } fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> { match self { Self::String(property) => Some(property.into_serialize()), Self::StringList(property) => Some(property.into_serialize()), Self::Boolean(property) => Some(property.into_serialize()), Self::Integer(property) => Some(property.into_serialize()), Self::IntegerOpt(property) => Some(property.into_serialize()), Self::ConfigValue(_) => None, Self::Signature(property) => Some(property.into_serialize()), Self::Email(property) => Some(property.into_serialize()), Self::SizeHint(property) => Some(property.into_serialize()), Self::Timestamp(property) => Some(property.into_serialize()), Self::TimestampRange(property) => Some(property.into_serialize()), Self::Template(_) => None, Self::ListTemplate(_) => None, } } fn try_into_template(self) -> Option<Box<dyn Template + 'a>> { match self { Self::String(property) => Some(property.into_template()), Self::StringList(property) => Some(property.into_template()), Self::Boolean(property) => Some(property.into_template()), Self::Integer(property) => Some(property.into_template()), Self::IntegerOpt(property) => Some(property.into_template()), Self::ConfigValue(property) => Some(property.into_template()), Self::Signature(property) => Some(property.into_template()), Self::Email(property) => Some(property.into_template()), Self::SizeHint(_) => None, Self::Timestamp(property) => Some(property.into_template()), Self::TimestampRange(property) => Some(property.into_template()), Self::Template(template) => Some(template), Self::ListTemplate(template) => Some(template.into_template()), } } fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> { match (self, other) { (Self::String(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::String(lhs), Self::Email(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r.0).into_dyn()) } (Self::Boolean(lhs), Self::Boolean(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Integer(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Integer(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| Some(l) == r).into_dyn()) } (Self::IntegerOpt(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == Some(r)).into_dyn()) } (Self::IntegerOpt(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Email(lhs), Self::Email(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Email(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.0 == r).into_dyn()) } (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::Timestamp(_), _) => None, (Self::TimestampRange(_), _) => None, (Self::Template(_), _) => None, (Self::ListTemplate(_), _) => None, } } fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> { match (self, other) { (Self::Integer(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&r)).into_dyn()) } (Self::Integer(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| Some(l).cmp(&r)).into_dyn()) } (Self::IntegerOpt(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&Some(r))).into_dyn()) } (Self::IntegerOpt(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&r)).into_dyn()) } (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::Timestamp(_), _) => None, (Self::TimestampRange(_), _) => None, (Self::Template(_), _) => None, (Self::ListTemplate(_), _) => None, } }} /// Function that translates global function call node.// The lifetime parameter 'a could be replaced with for<'a> to keep the method// table away from a certain lifetime. That's technically more correct, but I// couldn't find an easy way to expand that to the core template methods, which// are defined for L: TemplateLanguage<'a>. That's why the build fn table is// bound to a named lifetime, and therefore can't be cached statically.pub type TemplateBuildFunctionFn<'a, L> = fn( &L, &mut TemplateDiagnostics, &BuildContext<<L as TemplateLanguage<'a>>::Property>, &FunctionCallNode, ) -> TemplateParseResult<<L as TemplateLanguage<'a>>::Property>; type BuildMethodFn<'a, L, T> = fn( &L, &mut TemplateDiagnostics, &BuildContext<<L as TemplateLanguage<'a>>::Property>, T, &FunctionCallNode,) -> TemplateParseResult<<L as TemplateLanguage<'a>>::Property>; /// Function that translates method call node of self type `T`.pub type TemplateBuildMethodFn<'a, L, T> = BuildMethodFn<'a, L, BoxedTemplateProperty<'a, T>>; /// Function that translates method call node of `Template`.pub type BuildTemplateMethodFn<'a, L> = BuildMethodFn<'a, L, Box<dyn Template + 'a>>; /// Function that translates method call node of `ListTemplate`.pub type BuildListTemplateMethodFn<'a, L> = BuildMethodFn<'a, L, Box<dyn ListTemplate + 'a>>; /// Table of functions that translate global function call node.pub type TemplateBuildFunctionFnMap<'a, L> = HashMap<&'static str, TemplateBuildFunctionFn<'a, L>>; /// Table of functions that translate method call node of self type `T`.pub type TemplateBuildMethodFnMap<'a, L, T> = HashMap<&'static str, TemplateBuildMethodFn<'a, L, T>>; /// Table of functions that translate method call node of `Template`.pub type BuildTemplateMethodFnMap<'a, L> = HashMap<&'static str, BuildTemplateMethodFn<'a, L>>; /// Table of functions that translate method call node of `ListTemplate`.pub type BuildListTemplateMethodFnMap<'a, L> = HashMap<&'static str, BuildListTemplateMethodFn<'a, L>>; /// Symbol table of functions and methods available in the core template.pub struct CoreTemplateBuildFnTable<'a, L: TemplateLanguage<'a> + ?Sized> { pub functions: TemplateBuildFunctionFnMap<'a, L>, pub string_methods: TemplateBuildMethodFnMap<'a, L, String>, pub string_list_methods: TemplateBuildMethodFnMap<'a, L, Vec<String>>, pub boolean_methods: TemplateBuildMethodFnMap<'a, L, bool>, pub integer_methods: TemplateBuildMethodFnMap<'a, L, i64>, pub config_value_methods: TemplateBuildMethodFnMap<'a, L, ConfigValue>, pub email_methods: TemplateBuildMethodFnMap<'a, L, Email>, pub signature_methods: TemplateBuildMethodFnMap<'a, L, Signature>, pub size_hint_methods: TemplateBuildMethodFnMap<'a, L, SizeHint>, pub timestamp_methods: TemplateBuildMethodFnMap<'a, L, Timestamp>, pub timestamp_range_methods: TemplateBuildMethodFnMap<'a, L, TimestampRange>, pub template_methods: BuildTemplateMethodFnMap<'a, L>, pub list_template_methods: BuildListTemplateMethodFnMap<'a, L>,} pub fn merge_fn_map<'s, F>(base: &mut HashMap<&'s str, F>, extension: HashMap<&'s str, F>) { for (name, function) in extension { if base.insert(name, function).is_some() { panic!("Conflicting template definitions for '{name}' function"); } }} impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> { /// Creates new symbol table containing the builtin functions and methods. pub fn builtin() -> Self { CoreTemplateBuildFnTable { functions: builtin_functions(), string_methods: builtin_string_methods(), string_list_methods: builtin_formattable_list_methods(), boolean_methods: HashMap::new(), integer_methods: HashMap::new(), config_value_methods: builtin_config_value_methods(), signature_methods: builtin_signature_methods(), email_methods: builtin_email_methods(), size_hint_methods: builtin_size_hint_methods(), timestamp_methods: builtin_timestamp_methods(), timestamp_range_methods: builtin_timestamp_range_methods(),Lines 1–500 of 3501. Load more
// Copyright 2020-2023 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::cmp::Ordering; use std::collections::HashMap; use std::io; use std::iter; use itertools::Itertools as _; use jj_lib::backend::Signature; use jj_lib::backend::Timestamp; use jj_lib::config::ConfigNamePathBuf; use jj_lib::config::ConfigValue; use jj_lib::op_store::TimestampRange; use jj_lib::settings::UserSettings; use jj_lib::time_util::DatePattern; use serde::de::IntoDeserializer as _; use serde::Deserialize; use crate::formatter::FormatRecorder; use crate::formatter::Formatter; use crate::template_parser; use crate::template_parser::BinaryOp; use crate::template_parser::ExpressionKind; use crate::template_parser::ExpressionNode; use crate::template_parser::FunctionCallNode; use crate::template_parser::LambdaNode; use crate::template_parser::TemplateAliasesMap; use crate::template_parser::TemplateDiagnostics; use crate::template_parser::TemplateParseError; use crate::template_parser::TemplateParseErrorKind; use crate::template_parser::TemplateParseResult; use crate::template_parser::UnaryOp; use crate::templater::BoxedSerializeProperty; use crate::templater::BoxedTemplateProperty; use crate::templater::CoalesceTemplate; use crate::templater::ConcatTemplate; use crate::templater::ConditionalTemplate; use crate::templater::Email; use crate::templater::LabelTemplate; use crate::templater::ListPropertyTemplate; use crate::templater::ListTemplate; use crate::templater::Literal; use crate::templater::PlainTextFormattedProperty; use crate::templater::PropertyPlaceholder; use crate::templater::RawEscapeSequenceTemplate; use crate::templater::ReformatTemplate; use crate::templater::SeparateTemplate; use crate::templater::SizeHint; use crate::templater::Template; use crate::templater::TemplateProperty; use crate::templater::TemplatePropertyError; use crate::templater::TemplatePropertyExt as _; use crate::templater::TemplateRenderer; use crate::templater::WrapTemplateProperty; use crate::text_util; use crate::time_util; /// Callbacks to build language-specific evaluation objects from AST nodes. pub trait TemplateLanguage<'a> { type Property: CoreTemplatePropertyVar<'a>; fn settings(&self) -> &UserSettings; /// Translates the given global `function` call to a property. /// /// This should be delegated to /// `CoreTemplateBuildFnTable::build_function()`. fn build_function( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property>; fn build_method( &self, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<Self::Property>, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult<Self::Property>; } /// Implements [`WrapTemplateProperty<'a, O>`] for property types. /// /// - `impl_property_wrappers!(Kind { Foo(Foo), FooList(Vec<Foo>), .. });` to /// implement conversion from types `Foo`, `Vec<Foo>`, ... /// - `impl_property_wrappers!(<'a> Kind<'a> { .. });` for types with lifetime. /// - `impl_property_wrappers!(Kind => Core { .. });` to forward conversion to /// `Kind::Core(_)`. macro_rules! impl_property_wrappers { ($kind:path $(=> $var:ident)? { $($body:tt)* }) => { $crate::template_builder::_impl_property_wrappers_many!( [], 'static, $kind $(=> $var)?, { $($body)* }); }; // capture the first lifetime as the lifetime of template objects. (<$a:lifetime $(, $p:lifetime)* $(, $q:ident)*> $kind:path $(=> $var:ident)? { $($body:tt)* }) => { $crate::template_builder::_impl_property_wrappers_many!( [$a, $($p,)* $($q,)*], $a, $kind $(=> $var)?, { $($body)* }); }; } macro_rules! _impl_property_wrappers_many { // lifetime/type parameters are packed in order to disable zipping. // https://github.com/rust-lang/rust/issues/96184#issuecomment-1294999418 ($ps:tt, $a:lifetime, $kind:path, { $( $var:ident($ty:ty), )* }) => { $( $crate::template_builder::_impl_property_wrappers_one!( $ps, $a, $kind, $var, $ty, std::convert::identity); )* }; // variant part in body is ignored so the same body can be reused for // implementing forwarding conversion. ($ps:tt, $a:lifetime, $kind:path => $var:ident, { $( $ignored_var:ident($ty:ty), )* }) => { $( $crate::template_builder::_impl_property_wrappers_one!( $ps, $a, $kind, $var, $ty, $crate::templater::WrapTemplateProperty::wrap_property); )* }; } macro_rules! _impl_property_wrappers_one { ([$($p:tt)*], $a:lifetime, $kind:path, $var:ident, $ty:ty, $inner:path) => { impl<$($p)*> $crate::templater::WrapTemplateProperty<$a, $ty> for $kind { fn wrap_property(property: $crate::templater::BoxedTemplateProperty<$a, $ty>) -> Self { Self::$var($inner(property)) } } }; } pub(crate) use _impl_property_wrappers_many; pub(crate) use _impl_property_wrappers_one; pub(crate) use impl_property_wrappers; /// Wrapper for the core template property types. pub trait CoreTemplatePropertyVar<'a> where Self: WrapTemplateProperty<'a, String>, Self: WrapTemplateProperty<'a, Vec<String>>, Self: WrapTemplateProperty<'a, bool>, Self: WrapTemplateProperty<'a, i64>, Self: WrapTemplateProperty<'a, Option<i64>>, Self: WrapTemplateProperty<'a, ConfigValue>, Self: WrapTemplateProperty<'a, Signature>, Self: WrapTemplateProperty<'a, Email>, Self: WrapTemplateProperty<'a, SizeHint>, Self: WrapTemplateProperty<'a, Timestamp>, Self: WrapTemplateProperty<'a, TimestampRange>, { fn wrap_template(template: Box<dyn Template + 'a>) -> Self; fn wrap_list_template(template: Box<dyn ListTemplate + 'a>) -> Self; /// Type name of the property output. fn type_name(&self) -> &'static str; fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>>; fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>>; /// Transforms into a string property by formatting the value if needed. fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>>; fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>>; fn try_into_template(self) -> Option<Box<dyn Template + 'a>>; /// Transforms into a property that will evaluate to `self == other`. fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>>; /// Transforms into a property that will evaluate to an [`Ordering`]. fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>>; } pub enum CoreTemplatePropertyKind<'a> { String(BoxedTemplateProperty<'a, String>), StringList(BoxedTemplateProperty<'a, Vec<String>>), Boolean(BoxedTemplateProperty<'a, bool>), Integer(BoxedTemplateProperty<'a, i64>), IntegerOpt(BoxedTemplateProperty<'a, Option<i64>>), ConfigValue(BoxedTemplateProperty<'a, ConfigValue>), Signature(BoxedTemplateProperty<'a, Signature>), Email(BoxedTemplateProperty<'a, Email>), SizeHint(BoxedTemplateProperty<'a, SizeHint>), Timestamp(BoxedTemplateProperty<'a, Timestamp>), TimestampRange(BoxedTemplateProperty<'a, TimestampRange>), // Both TemplateProperty and Template can represent a value to be evaluated // dynamically, which suggests that `Box<dyn Template + 'a>` could be // composed as `Box<dyn TemplateProperty<Output = Box<dyn Template ..`. // However, there's a subtle difference: TemplateProperty is strict on // error, whereas Template is usually lax and prints an error inline. If // `concat(x, y)` were a property returning Template, and if `y` failed to // evaluate, the whole expression would fail. In this example, a partial // evaluation output is more useful. That's one reason why Template isn't // wrapped in a TemplateProperty. Another reason is that the outermost // caller expects a Template, not a TemplateProperty of Template output. Template(Box<dyn Template + 'a>), ListTemplate(Box<dyn ListTemplate + 'a>), } /// Implements `WrapTemplateProperty<type>` for core property types. /// /// Use `impl_core_property_wrappers!(<'a> Kind<'a> => Core);` to implement /// forwarding conversion. macro_rules! impl_core_property_wrappers { ($($head:tt)+) => { $crate::template_builder::impl_property_wrappers!($($head)+ { String(String), StringList(Vec<String>), Boolean(bool), Integer(i64), IntegerOpt(Option<i64>), ConfigValue(jj_lib::config::ConfigValue), Signature(jj_lib::backend::Signature), Email($crate::templater::Email), SizeHint($crate::templater::SizeHint), Timestamp(jj_lib::backend::Timestamp), TimestampRange(jj_lib::op_store::TimestampRange), }); }; } pub(crate) use impl_core_property_wrappers; impl_core_property_wrappers!(<'a> CoreTemplatePropertyKind<'a>); impl<'a> CoreTemplatePropertyVar<'a> for CoreTemplatePropertyKind<'a> { fn wrap_template(template: Box<dyn Template + 'a>) -> Self { Self::Template(template) } fn wrap_list_template(template: Box<dyn ListTemplate + 'a>) -> Self { Self::ListTemplate(template) } fn type_name(&self) -> &'static str { match self { Self::String(_) => "String", Self::StringList(_) => "List<String>", Self::Boolean(_) => "Boolean", Self::Integer(_) => "Integer", Self::IntegerOpt(_) => "Option<Integer>", Self::ConfigValue(_) => "ConfigValue", Self::Signature(_) => "Signature", Self::Email(_) => "Email", Self::SizeHint(_) => "SizeHint", Self::Timestamp(_) => "Timestamp", Self::TimestampRange(_) => "TimestampRange", Self::Template(_) => "Template", Self::ListTemplate(_) => "ListTemplate", } } fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>> { match self { Self::String(property) => Some(property.map(|s| !s.is_empty()).into_dyn()), Self::StringList(property) => Some(property.map(|l| !l.is_empty()).into_dyn()), Self::Boolean(property) => Some(property), Self::Integer(_) => None, Self::IntegerOpt(property) => Some(property.map(|opt| opt.is_some()).into_dyn()), Self::ConfigValue(_) => None, Self::Signature(_) => None, Self::Email(property) => Some(property.map(|e| !e.0.is_empty()).into_dyn()), Self::SizeHint(_) => None, Self::Timestamp(_) => None, Self::TimestampRange(_) => None, // Template types could also be evaluated to boolean, but it's less likely // to apply label() or .map() and use the result as conditional. It's also // unclear whether ListTemplate should behave as a "list" or a "template". Self::Template(_) => None, Self::ListTemplate(_) => None, } } fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>> { match self { Self::Integer(property) => Some(property), Self::IntegerOpt(property) => Some(property.try_unwrap("Integer").into_dyn()), _ => None, } } fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>> { match self { Self::String(property) => Some(property), _ => { let template = self.try_into_template()?; Some(PlainTextFormattedProperty::new(template).into_dyn()) } } } fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> { match self { Self::String(property) => Some(property.into_serialize()), Self::StringList(property) => Some(property.into_serialize()), Self::Boolean(property) => Some(property.into_serialize()), Self::Integer(property) => Some(property.into_serialize()), Self::IntegerOpt(property) => Some(property.into_serialize()), Self::ConfigValue(_) => None, Self::Signature(property) => Some(property.into_serialize()), Self::Email(property) => Some(property.into_serialize()), Self::SizeHint(property) => Some(property.into_serialize()), Self::Timestamp(property) => Some(property.into_serialize()), Self::TimestampRange(property) => Some(property.into_serialize()), Self::Template(_) => None, Self::ListTemplate(_) => None, } } fn try_into_template(self) -> Option<Box<dyn Template + 'a>> { match self { Self::String(property) => Some(property.into_template()), Self::StringList(property) => Some(property.into_template()), Self::Boolean(property) => Some(property.into_template()), Self::Integer(property) => Some(property.into_template()), Self::IntegerOpt(property) => Some(property.into_template()), Self::ConfigValue(property) => Some(property.into_template()), Self::Signature(property) => Some(property.into_template()), Self::Email(property) => Some(property.into_template()), Self::SizeHint(_) => None, Self::Timestamp(property) => Some(property.into_template()), Self::TimestampRange(property) => Some(property.into_template()), Self::Template(template) => Some(template), Self::ListTemplate(template) => Some(template.into_template()), } } fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> { match (self, other) { (Self::String(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::String(lhs), Self::Email(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r.0).into_dyn()) } (Self::Boolean(lhs), Self::Boolean(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Integer(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Integer(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| Some(l) == r).into_dyn()) } (Self::IntegerOpt(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == Some(r)).into_dyn()) } (Self::IntegerOpt(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Email(lhs), Self::Email(rhs)) => { Some((lhs, rhs).map(|(l, r)| l == r).into_dyn()) } (Self::Email(lhs), Self::String(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.0 == r).into_dyn()) } (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::Timestamp(_), _) => None, (Self::TimestampRange(_), _) => None, (Self::Template(_), _) => None, (Self::ListTemplate(_), _) => None, } } fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> { match (self, other) { (Self::Integer(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&r)).into_dyn()) } (Self::Integer(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| Some(l).cmp(&r)).into_dyn()) } (Self::IntegerOpt(lhs), Self::Integer(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&Some(r))).into_dyn()) } (Self::IntegerOpt(lhs), Self::IntegerOpt(rhs)) => { Some((lhs, rhs).map(|(l, r)| l.cmp(&r)).into_dyn()) } (Self::String(_), _) => None, (Self::StringList(_), _) => None, (Self::Boolean(_), _) => None, (Self::Integer(_), _) => None, (Self::IntegerOpt(_), _) => None, (Self::ConfigValue(_), _) => None, (Self::Signature(_), _) => None, (Self::Email(_), _) => None, (Self::SizeHint(_), _) => None, (Self::Timestamp(_), _) => None, (Self::TimestampRange(_), _) => None, (Self::Template(_), _) => None, (Self::ListTemplate(_), _) => None, } } } /// Function that translates global function call node. // The lifetime parameter 'a could be replaced with for<'a> to keep the method // table away from a certain lifetime. That's technically more correct, but I // couldn't find an easy way to expand that to the core template methods, which // are defined for L: TemplateLanguage<'a>. That's why the build fn table is // bound to a named lifetime, and therefore can't be cached statically. pub type TemplateBuildFunctionFn<'a, L> = fn( &L, &mut TemplateDiagnostics, &BuildContext<<L as TemplateLanguage<'a>>::Property>, &FunctionCallNode, ) -> TemplateParseResult<<L as TemplateLanguage<'a>>::Property>; type BuildMethodFn<'a, L, T> = fn( &L, &mut TemplateDiagnostics, &BuildContext<<L as TemplateLanguage<'a>>::Property>, T, &FunctionCallNode, ) -> TemplateParseResult<<L as TemplateLanguage<'a>>::Property>; /// Function that translates method call node of self type `T`. pub type TemplateBuildMethodFn<'a, L, T> = BuildMethodFn<'a, L, BoxedTemplateProperty<'a, T>>; /// Function that translates method call node of `Template`. pub type BuildTemplateMethodFn<'a, L> = BuildMethodFn<'a, L, Box<dyn Template + 'a>>; /// Function that translates method call node of `ListTemplate`. pub type BuildListTemplateMethodFn<'a, L> = BuildMethodFn<'a, L, Box<dyn ListTemplate + 'a>>; /// Table of functions that translate global function call node. pub type TemplateBuildFunctionFnMap<'a, L> = HashMap<&'static str, TemplateBuildFunctionFn<'a, L>>; /// Table of functions that translate method call node of self type `T`. pub type TemplateBuildMethodFnMap<'a, L, T> = HashMap<&'static str, TemplateBuildMethodFn<'a, L, T>>; /// Table of functions that translate method call node of `Template`. pub type BuildTemplateMethodFnMap<'a, L> = HashMap<&'static str, BuildTemplateMethodFn<'a, L>>; /// Table of functions that translate method call node of `ListTemplate`. pub type BuildListTemplateMethodFnMap<'a, L> = HashMap<&'static str, BuildListTemplateMethodFn<'a, L>>; /// Symbol table of functions and methods available in the core template. pub struct CoreTemplateBuildFnTable<'a, L: TemplateLanguage<'a> + ?Sized> { pub functions: TemplateBuildFunctionFnMap<'a, L>, pub string_methods: TemplateBuildMethodFnMap<'a, L, String>, pub string_list_methods: TemplateBuildMethodFnMap<'a, L, Vec<String>>, pub boolean_methods: TemplateBuildMethodFnMap<'a, L, bool>, pub integer_methods: TemplateBuildMethodFnMap<'a, L, i64>, pub config_value_methods: TemplateBuildMethodFnMap<'a, L, ConfigValue>, pub email_methods: TemplateBuildMethodFnMap<'a, L, Email>, pub signature_methods: TemplateBuildMethodFnMap<'a, L, Signature>, pub size_hint_methods: TemplateBuildMethodFnMap<'a, L, SizeHint>, pub timestamp_methods: TemplateBuildMethodFnMap<'a, L, Timestamp>, pub timestamp_range_methods: TemplateBuildMethodFnMap<'a, L, TimestampRange>, pub template_methods: BuildTemplateMethodFnMap<'a, L>, pub list_template_methods: BuildListTemplateMethodFnMap<'a, L>, } pub fn merge_fn_map<'s, F>(base: &mut HashMap<&'s str, F>, extension: HashMap<&'s str, F>) { for (name, function) in extension { if base.insert(name, function).is_some() { panic!("Conflicting template definitions for '{name}' function"); } } } impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> { /// Creates new symbol table containing the builtin functions and methods. pub fn builtin() -> Self { CoreTemplateBuildFnTable { functions: builtin_functions(), string_methods: builtin_string_methods(), string_list_methods: builtin_formattable_list_methods(), boolean_methods: HashMap::new(), integer_methods: HashMap::new(), config_value_methods: builtin_config_value_methods(), signature_methods: builtin_signature_methods(), email_methods: builtin_email_methods(), size_hint_methods: builtin_size_hint_methods(), timestamp_methods: builtin_timestamp_methods(), timestamp_range_methods: builtin_timestamp_range_methods(),