// 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::collections::HashMap;use std::io; use itertools::Itertools as _;use jj_lib::backend::Signature;use jj_lib::backend::Timestamp;use jj_lib::dsl_util::AliasExpandError as _;use jj_lib::time_util::DatePattern; 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::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::CoalesceTemplate;use crate::templater::ConcatTemplate;use crate::templater::ConditionalTemplate;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::TimestampRange;use crate::text_util;use crate::time_util; /// Callbacks to build language-specific evaluation objects from AST nodes.pub trait TemplateLanguage<'a> { type Property: IntoTemplateProperty<'a>; fn wrap_string(property: impl TemplateProperty<Output = String> + 'a) -> Self::Property; fn wrap_string_list( property: impl TemplateProperty<Output = Vec<String>> + 'a, ) -> Self::Property; fn wrap_boolean(property: impl TemplateProperty<Output = bool> + 'a) -> Self::Property; fn wrap_integer(property: impl TemplateProperty<Output = i64> + 'a) -> Self::Property; fn wrap_integer_opt( property: impl TemplateProperty<Output = Option<i64>> + 'a, ) -> Self::Property; fn wrap_signature(property: impl TemplateProperty<Output = Signature> + 'a) -> Self::Property; fn wrap_size_hint(property: impl TemplateProperty<Output = SizeHint> + 'a) -> Self::Property; fn wrap_timestamp(property: impl TemplateProperty<Output = Timestamp> + 'a) -> Self::Property; fn wrap_timestamp_range( property: impl TemplateProperty<Output = TimestampRange> + 'a, ) -> Self::Property; fn wrap_template(template: Box<dyn Template + 'a>) -> Self::Property; fn wrap_list_template(template: Box<dyn ListTemplate + 'a>) -> Self::Property; /// 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 `TemplateLanguage::wrap_<type>()` functions.////// - `impl_core_wrap_property_fns('a)` for `CoreTemplatePropertyKind`,/// - `impl_core_wrap_property_fns('a, MyKind::Core)` for `MyKind::Core(..)`.macro_rules! impl_core_wrap_property_fns { ($a:lifetime) => { $crate::template_builder::impl_core_wrap_property_fns!($a, std::convert::identity); }; ($a:lifetime, $outer:path) => { $crate::template_builder::impl_wrap_property_fns!( $a, $crate::template_builder::CoreTemplatePropertyKind, $outer, { wrap_string(String) => String, wrap_string_list(Vec<String>) => StringList, wrap_boolean(bool) => Boolean, wrap_integer(i64) => Integer, wrap_integer_opt(Option<i64>) => IntegerOpt, wrap_signature(jj_lib::backend::Signature) => Signature, wrap_size_hint($crate::templater::SizeHint) => SizeHint, wrap_timestamp(jj_lib::backend::Timestamp) => Timestamp, wrap_timestamp_range($crate::templater::TimestampRange) => TimestampRange, } ); fn wrap_template( template: Box<dyn $crate::templater::Template + $a>, ) -> Self::Property { use $crate::template_builder::CoreTemplatePropertyKind as Kind; $outer(Kind::Template(template)) } fn wrap_list_template( template: Box<dyn $crate::templater::ListTemplate + $a>, ) -> Self::Property { use $crate::template_builder::CoreTemplatePropertyKind as Kind; $outer(Kind::ListTemplate(template)) } };} macro_rules! impl_wrap_property_fns { ($a:lifetime, $kind:path, $outer:path, { $( $func:ident($ty:ty) => $var:ident, )+ }) => { $( fn $func( property: impl $crate::templater::TemplateProperty<Output = $ty> + $a, ) -> Self::Property { use $kind as Kind; // https://github.com/rust-lang/rust/issues/48067 $outer(Kind::$var(Box::new(property))) } )+ };} pub(crate) use impl_core_wrap_property_fns;pub(crate) use impl_wrap_property_fns; /// Provides access to basic template property types.pub trait IntoTemplateProperty<'a> { /// Type name of the property output. fn type_name(&self) -> &'static str; fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'a>>; fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Output = i64> + 'a>>; fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<Output = String> + '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<Box<dyn TemplateProperty<Output = bool> + 'a>>;} pub enum CoreTemplatePropertyKind<'a> { String(Box<dyn TemplateProperty<Output = String> + 'a>), StringList(Box<dyn TemplateProperty<Output = Vec<String>> + 'a>), Boolean(Box<dyn TemplateProperty<Output = bool> + 'a>), Integer(Box<dyn TemplateProperty<Output = i64> + 'a>), IntegerOpt(Box<dyn TemplateProperty<Output = Option<i64>> + 'a>), Signature(Box<dyn TemplateProperty<Output = Signature> + 'a>), SizeHint(Box<dyn TemplateProperty<Output = SizeHint> + 'a>), Timestamp(Box<dyn TemplateProperty<Output = Timestamp> + 'a>), TimestampRange(Box<dyn TemplateProperty<Output = TimestampRange> + 'a>), // 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>),} impl<'a> IntoTemplateProperty<'a> for CoreTemplatePropertyKind<'a> { fn type_name(&self) -> &'static str { match self { CoreTemplatePropertyKind::String(_) => "String", CoreTemplatePropertyKind::StringList(_) => "List<String>", CoreTemplatePropertyKind::Boolean(_) => "Boolean", CoreTemplatePropertyKind::Integer(_) => "Integer", CoreTemplatePropertyKind::IntegerOpt(_) => "Option<Integer>", CoreTemplatePropertyKind::Signature(_) => "Signature", CoreTemplatePropertyKind::SizeHint(_) => "SizeHint", CoreTemplatePropertyKind::Timestamp(_) => "Timestamp", CoreTemplatePropertyKind::TimestampRange(_) => "TimestampRange", CoreTemplatePropertyKind::Template(_) => "Template", CoreTemplatePropertyKind::ListTemplate(_) => "ListTemplate", } } fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'a>> { match self { CoreTemplatePropertyKind::String(property) => { Some(Box::new(property.map(|s| !s.is_empty()))) } CoreTemplatePropertyKind::StringList(property) => { Some(Box::new(property.map(|l| !l.is_empty()))) } CoreTemplatePropertyKind::Boolean(property) => Some(property), CoreTemplatePropertyKind::Integer(_) => None, CoreTemplatePropertyKind::IntegerOpt(property) => { Some(Box::new(property.map(|opt| opt.is_some()))) } CoreTemplatePropertyKind::Signature(_) => None, CoreTemplatePropertyKind::SizeHint(_) => None, CoreTemplatePropertyKind::Timestamp(_) => None, CoreTemplatePropertyKind::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". CoreTemplatePropertyKind::Template(_) => None, CoreTemplatePropertyKind::ListTemplate(_) => None, } } fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Output = i64> + 'a>> { match self { CoreTemplatePropertyKind::Integer(property) => Some(property), CoreTemplatePropertyKind::IntegerOpt(property) => { Some(Box::new(property.try_unwrap("Integer"))) } _ => None, } } fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<Output = String> + 'a>> { match self { CoreTemplatePropertyKind::String(property) => Some(property), _ => { let template = self.try_into_template()?; Some(Box::new(PlainTextFormattedProperty::new(template))) } } } fn try_into_template(self) -> Option<Box<dyn Template + 'a>> { match self { CoreTemplatePropertyKind::String(property) => Some(property.into_template()), CoreTemplatePropertyKind::StringList(property) => Some(property.into_template()), CoreTemplatePropertyKind::Boolean(property) => Some(property.into_template()), CoreTemplatePropertyKind::Integer(property) => Some(property.into_template()), CoreTemplatePropertyKind::IntegerOpt(property) => Some(property.into_template()), CoreTemplatePropertyKind::Signature(property) => Some(property.into_template()), CoreTemplatePropertyKind::SizeHint(_) => None, CoreTemplatePropertyKind::Timestamp(property) => Some(property.into_template()), CoreTemplatePropertyKind::TimestampRange(property) => Some(property.into_template()), CoreTemplatePropertyKind::Template(template) => Some(template), CoreTemplatePropertyKind::ListTemplate(template) => Some(template.into_template()), } } fn try_into_eq(self, other: Self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'a>> { match (self, other) { (CoreTemplatePropertyKind::String(lhs), CoreTemplatePropertyKind::String(rhs)) => { Some(Box::new((lhs, rhs).map(|(l, r)| l == r))) } (CoreTemplatePropertyKind::Boolean(lhs), CoreTemplatePropertyKind::Boolean(rhs)) => { Some(Box::new((lhs, rhs).map(|(l, r)| l == r))) } (CoreTemplatePropertyKind::Integer(lhs), CoreTemplatePropertyKind::Integer(rhs)) => { Some(Box::new((lhs, rhs).map(|(l, r)| l == r))) } (CoreTemplatePropertyKind::String(_), _) => None, (CoreTemplatePropertyKind::StringList(_), _) => None, (CoreTemplatePropertyKind::Boolean(_), _) => None, (CoreTemplatePropertyKind::Integer(_), _) => None, (CoreTemplatePropertyKind::IntegerOpt(_), _) => None, (CoreTemplatePropertyKind::Signature(_), _) => None, (CoreTemplatePropertyKind::SizeHint(_), _) => None, (CoreTemplatePropertyKind::Timestamp(_), _) => None, (CoreTemplatePropertyKind::TimestampRange(_), _) => None, (CoreTemplatePropertyKind::Template(_), _) => None, (CoreTemplatePropertyKind::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>; /// Function that translates method call node of self type `T`.pub type TemplateBuildMethodFn<'a, L, T> = fn( &L, &mut TemplateDiagnostics, &BuildContext<<L as TemplateLanguage<'a>>::Property>, Box<dyn TemplateProperty<Output = T> + 'a>, &FunctionCallNode, ) -> TemplateParseResult<<L as TemplateLanguage<'a>>::Property>; /// 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>>; /// 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 boolean_methods: TemplateBuildMethodFnMap<'a, L, bool>, pub integer_methods: TemplateBuildMethodFnMap<'a, L, i64>, 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 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(), boolean_methods: HashMap::new(), integer_methods: HashMap::new(), signature_methods: builtin_signature_methods(), size_hint_methods: builtin_size_hint_methods(), timestamp_methods: builtin_timestamp_methods(), timestamp_range_methods: builtin_timestamp_range_methods(), } } pub fn empty() -> Self { CoreTemplateBuildFnTable { functions: HashMap::new(), string_methods: HashMap::new(), boolean_methods: HashMap::new(), integer_methods: HashMap::new(), signature_methods: HashMap::new(), size_hint_methods: HashMap::new(), timestamp_methods: HashMap::new(), timestamp_range_methods: HashMap::new(), } } pub fn merge(&mut self, extension: CoreTemplateBuildFnTable<'a, L>) { let CoreTemplateBuildFnTable { functions, string_methods, boolean_methods, integer_methods, signature_methods, size_hint_methods, timestamp_methods, timestamp_range_methods, } = extension; merge_fn_map(&mut self.functions, functions); merge_fn_map(&mut self.string_methods, string_methods); merge_fn_map(&mut self.boolean_methods, boolean_methods); merge_fn_map(&mut self.integer_methods, integer_methods); merge_fn_map(&mut self.signature_methods, signature_methods); merge_fn_map(&mut self.size_hint_methods, size_hint_methods); merge_fn_map(&mut self.timestamp_methods, timestamp_methods); merge_fn_map(&mut self.timestamp_range_methods, timestamp_range_methods); } /// Translates the function call node `function` by using this symbol table. pub fn build_function( &self, language: &L, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<L::Property>, function: &FunctionCallNode, ) -> TemplateParseResult<L::Property> { let table = &self.functions; let build = template_parser::lookup_function(table, function)?; build(language, diagnostics, build_ctx, function) } /// Applies the method call node `function` to the given `property` by using /// this symbol table. pub fn build_method( &self, language: &L, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<L::Property>, property: CoreTemplatePropertyKind<'a>, function: &FunctionCallNode, ) -> TemplateParseResult<L::Property> { let type_name = property.type_name(); match property { CoreTemplatePropertyKind::String(property) => { let table = &self.string_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::StringList(property) => { // TODO: migrate to table? build_formattable_list_method( language, diagnostics, build_ctx, property, function, L::wrap_string, ) } CoreTemplatePropertyKind::Boolean(property) => { let table = &self.boolean_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::Integer(property) => { let table = &self.integer_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::IntegerOpt(property) => { let type_name = "Integer"; let table = &self.integer_methods; let build = template_parser::lookup_method(type_name, table, function)?; let inner_property = property.try_unwrap(type_name); build( language, diagnostics, build_ctx, Box::new(inner_property), function, ) } CoreTemplatePropertyKind::Signature(property) => { let table = &self.signature_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::SizeHint(property) => { let table = &self.size_hint_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::Timestamp(property) => { let table = &self.timestamp_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::TimestampRange(property) => { let table = &self.timestamp_range_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::Template(_) => { // TODO: migrate to table? Err(TemplateParseError::no_such_method(type_name, function)) } CoreTemplatePropertyKind::ListTemplate(template) => { // TODO: migrate to table? build_list_template_method(language, diagnostics, build_ctx, template, function) } } }} /// Opaque struct that represents a template value.pub struct Expression<P> { property: P, labels: Vec<String>,}Lines 1–500 of 2816. 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::collections::HashMap; use std::io; use itertools::Itertools as _; use jj_lib::backend::Signature; use jj_lib::backend::Timestamp; use jj_lib::dsl_util::AliasExpandError as _; use jj_lib::time_util::DatePattern; 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::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::CoalesceTemplate; use crate::templater::ConcatTemplate; use crate::templater::ConditionalTemplate; 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::TimestampRange; use crate::text_util; use crate::time_util; /// Callbacks to build language-specific evaluation objects from AST nodes. pub trait TemplateLanguage<'a> { type Property: IntoTemplateProperty<'a>; fn wrap_string(property: impl TemplateProperty<Output = String> + 'a) -> Self::Property; fn wrap_string_list( property: impl TemplateProperty<Output = Vec<String>> + 'a, ) -> Self::Property; fn wrap_boolean(property: impl TemplateProperty<Output = bool> + 'a) -> Self::Property; fn wrap_integer(property: impl TemplateProperty<Output = i64> + 'a) -> Self::Property; fn wrap_integer_opt( property: impl TemplateProperty<Output = Option<i64>> + 'a, ) -> Self::Property; fn wrap_signature(property: impl TemplateProperty<Output = Signature> + 'a) -> Self::Property; fn wrap_size_hint(property: impl TemplateProperty<Output = SizeHint> + 'a) -> Self::Property; fn wrap_timestamp(property: impl TemplateProperty<Output = Timestamp> + 'a) -> Self::Property; fn wrap_timestamp_range( property: impl TemplateProperty<Output = TimestampRange> + 'a, ) -> Self::Property; fn wrap_template(template: Box<dyn Template + 'a>) -> Self::Property; fn wrap_list_template(template: Box<dyn ListTemplate + 'a>) -> Self::Property; /// 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 `TemplateLanguage::wrap_<type>()` functions. /// /// - `impl_core_wrap_property_fns('a)` for `CoreTemplatePropertyKind`, /// - `impl_core_wrap_property_fns('a, MyKind::Core)` for `MyKind::Core(..)`. macro_rules! impl_core_wrap_property_fns { ($a:lifetime) => { $crate::template_builder::impl_core_wrap_property_fns!($a, std::convert::identity); }; ($a:lifetime, $outer:path) => { $crate::template_builder::impl_wrap_property_fns!( $a, $crate::template_builder::CoreTemplatePropertyKind, $outer, { wrap_string(String) => String, wrap_string_list(Vec<String>) => StringList, wrap_boolean(bool) => Boolean, wrap_integer(i64) => Integer, wrap_integer_opt(Option<i64>) => IntegerOpt, wrap_signature(jj_lib::backend::Signature) => Signature, wrap_size_hint($crate::templater::SizeHint) => SizeHint, wrap_timestamp(jj_lib::backend::Timestamp) => Timestamp, wrap_timestamp_range($crate::templater::TimestampRange) => TimestampRange, } ); fn wrap_template( template: Box<dyn $crate::templater::Template + $a>, ) -> Self::Property { use $crate::template_builder::CoreTemplatePropertyKind as Kind; $outer(Kind::Template(template)) } fn wrap_list_template( template: Box<dyn $crate::templater::ListTemplate + $a>, ) -> Self::Property { use $crate::template_builder::CoreTemplatePropertyKind as Kind; $outer(Kind::ListTemplate(template)) } }; } macro_rules! impl_wrap_property_fns { ($a:lifetime, $kind:path, $outer:path, { $( $func:ident($ty:ty) => $var:ident, )+ }) => { $( fn $func( property: impl $crate::templater::TemplateProperty<Output = $ty> + $a, ) -> Self::Property { use $kind as Kind; // https://github.com/rust-lang/rust/issues/48067 $outer(Kind::$var(Box::new(property))) } )+ }; } pub(crate) use impl_core_wrap_property_fns; pub(crate) use impl_wrap_property_fns; /// Provides access to basic template property types. pub trait IntoTemplateProperty<'a> { /// Type name of the property output. fn type_name(&self) -> &'static str; fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'a>>; fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Output = i64> + 'a>>; fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<Output = String> + '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<Box<dyn TemplateProperty<Output = bool> + 'a>>; } pub enum CoreTemplatePropertyKind<'a> { String(Box<dyn TemplateProperty<Output = String> + 'a>), StringList(Box<dyn TemplateProperty<Output = Vec<String>> + 'a>), Boolean(Box<dyn TemplateProperty<Output = bool> + 'a>), Integer(Box<dyn TemplateProperty<Output = i64> + 'a>), IntegerOpt(Box<dyn TemplateProperty<Output = Option<i64>> + 'a>), Signature(Box<dyn TemplateProperty<Output = Signature> + 'a>), SizeHint(Box<dyn TemplateProperty<Output = SizeHint> + 'a>), Timestamp(Box<dyn TemplateProperty<Output = Timestamp> + 'a>), TimestampRange(Box<dyn TemplateProperty<Output = TimestampRange> + 'a>), // 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>), } impl<'a> IntoTemplateProperty<'a> for CoreTemplatePropertyKind<'a> { fn type_name(&self) -> &'static str { match self { CoreTemplatePropertyKind::String(_) => "String", CoreTemplatePropertyKind::StringList(_) => "List<String>", CoreTemplatePropertyKind::Boolean(_) => "Boolean", CoreTemplatePropertyKind::Integer(_) => "Integer", CoreTemplatePropertyKind::IntegerOpt(_) => "Option<Integer>", CoreTemplatePropertyKind::Signature(_) => "Signature", CoreTemplatePropertyKind::SizeHint(_) => "SizeHint", CoreTemplatePropertyKind::Timestamp(_) => "Timestamp", CoreTemplatePropertyKind::TimestampRange(_) => "TimestampRange", CoreTemplatePropertyKind::Template(_) => "Template", CoreTemplatePropertyKind::ListTemplate(_) => "ListTemplate", } } fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'a>> { match self { CoreTemplatePropertyKind::String(property) => { Some(Box::new(property.map(|s| !s.is_empty()))) } CoreTemplatePropertyKind::StringList(property) => { Some(Box::new(property.map(|l| !l.is_empty()))) } CoreTemplatePropertyKind::Boolean(property) => Some(property), CoreTemplatePropertyKind::Integer(_) => None, CoreTemplatePropertyKind::IntegerOpt(property) => { Some(Box::new(property.map(|opt| opt.is_some()))) } CoreTemplatePropertyKind::Signature(_) => None, CoreTemplatePropertyKind::SizeHint(_) => None, CoreTemplatePropertyKind::Timestamp(_) => None, CoreTemplatePropertyKind::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". CoreTemplatePropertyKind::Template(_) => None, CoreTemplatePropertyKind::ListTemplate(_) => None, } } fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<Output = i64> + 'a>> { match self { CoreTemplatePropertyKind::Integer(property) => Some(property), CoreTemplatePropertyKind::IntegerOpt(property) => { Some(Box::new(property.try_unwrap("Integer"))) } _ => None, } } fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<Output = String> + 'a>> { match self { CoreTemplatePropertyKind::String(property) => Some(property), _ => { let template = self.try_into_template()?; Some(Box::new(PlainTextFormattedProperty::new(template))) } } } fn try_into_template(self) -> Option<Box<dyn Template + 'a>> { match self { CoreTemplatePropertyKind::String(property) => Some(property.into_template()), CoreTemplatePropertyKind::StringList(property) => Some(property.into_template()), CoreTemplatePropertyKind::Boolean(property) => Some(property.into_template()), CoreTemplatePropertyKind::Integer(property) => Some(property.into_template()), CoreTemplatePropertyKind::IntegerOpt(property) => Some(property.into_template()), CoreTemplatePropertyKind::Signature(property) => Some(property.into_template()), CoreTemplatePropertyKind::SizeHint(_) => None, CoreTemplatePropertyKind::Timestamp(property) => Some(property.into_template()), CoreTemplatePropertyKind::TimestampRange(property) => Some(property.into_template()), CoreTemplatePropertyKind::Template(template) => Some(template), CoreTemplatePropertyKind::ListTemplate(template) => Some(template.into_template()), } } fn try_into_eq(self, other: Self) -> Option<Box<dyn TemplateProperty<Output = bool> + 'a>> { match (self, other) { (CoreTemplatePropertyKind::String(lhs), CoreTemplatePropertyKind::String(rhs)) => { Some(Box::new((lhs, rhs).map(|(l, r)| l == r))) } (CoreTemplatePropertyKind::Boolean(lhs), CoreTemplatePropertyKind::Boolean(rhs)) => { Some(Box::new((lhs, rhs).map(|(l, r)| l == r))) } (CoreTemplatePropertyKind::Integer(lhs), CoreTemplatePropertyKind::Integer(rhs)) => { Some(Box::new((lhs, rhs).map(|(l, r)| l == r))) } (CoreTemplatePropertyKind::String(_), _) => None, (CoreTemplatePropertyKind::StringList(_), _) => None, (CoreTemplatePropertyKind::Boolean(_), _) => None, (CoreTemplatePropertyKind::Integer(_), _) => None, (CoreTemplatePropertyKind::IntegerOpt(_), _) => None, (CoreTemplatePropertyKind::Signature(_), _) => None, (CoreTemplatePropertyKind::SizeHint(_), _) => None, (CoreTemplatePropertyKind::Timestamp(_), _) => None, (CoreTemplatePropertyKind::TimestampRange(_), _) => None, (CoreTemplatePropertyKind::Template(_), _) => None, (CoreTemplatePropertyKind::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>; /// Function that translates method call node of self type `T`. pub type TemplateBuildMethodFn<'a, L, T> = fn( &L, &mut TemplateDiagnostics, &BuildContext<<L as TemplateLanguage<'a>>::Property>, Box<dyn TemplateProperty<Output = T> + 'a>, &FunctionCallNode, ) -> TemplateParseResult<<L as TemplateLanguage<'a>>::Property>; /// 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>>; /// 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 boolean_methods: TemplateBuildMethodFnMap<'a, L, bool>, pub integer_methods: TemplateBuildMethodFnMap<'a, L, i64>, 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 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(), boolean_methods: HashMap::new(), integer_methods: HashMap::new(), signature_methods: builtin_signature_methods(), size_hint_methods: builtin_size_hint_methods(), timestamp_methods: builtin_timestamp_methods(), timestamp_range_methods: builtin_timestamp_range_methods(), } } pub fn empty() -> Self { CoreTemplateBuildFnTable { functions: HashMap::new(), string_methods: HashMap::new(), boolean_methods: HashMap::new(), integer_methods: HashMap::new(), signature_methods: HashMap::new(), size_hint_methods: HashMap::new(), timestamp_methods: HashMap::new(), timestamp_range_methods: HashMap::new(), } } pub fn merge(&mut self, extension: CoreTemplateBuildFnTable<'a, L>) { let CoreTemplateBuildFnTable { functions, string_methods, boolean_methods, integer_methods, signature_methods, size_hint_methods, timestamp_methods, timestamp_range_methods, } = extension; merge_fn_map(&mut self.functions, functions); merge_fn_map(&mut self.string_methods, string_methods); merge_fn_map(&mut self.boolean_methods, boolean_methods); merge_fn_map(&mut self.integer_methods, integer_methods); merge_fn_map(&mut self.signature_methods, signature_methods); merge_fn_map(&mut self.size_hint_methods, size_hint_methods); merge_fn_map(&mut self.timestamp_methods, timestamp_methods); merge_fn_map(&mut self.timestamp_range_methods, timestamp_range_methods); } /// Translates the function call node `function` by using this symbol table. pub fn build_function( &self, language: &L, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<L::Property>, function: &FunctionCallNode, ) -> TemplateParseResult<L::Property> { let table = &self.functions; let build = template_parser::lookup_function(table, function)?; build(language, diagnostics, build_ctx, function) } /// Applies the method call node `function` to the given `property` by using /// this symbol table. pub fn build_method( &self, language: &L, diagnostics: &mut TemplateDiagnostics, build_ctx: &BuildContext<L::Property>, property: CoreTemplatePropertyKind<'a>, function: &FunctionCallNode, ) -> TemplateParseResult<L::Property> { let type_name = property.type_name(); match property { CoreTemplatePropertyKind::String(property) => { let table = &self.string_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::StringList(property) => { // TODO: migrate to table? build_formattable_list_method( language, diagnostics, build_ctx, property, function, L::wrap_string, ) } CoreTemplatePropertyKind::Boolean(property) => { let table = &self.boolean_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::Integer(property) => { let table = &self.integer_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::IntegerOpt(property) => { let type_name = "Integer"; let table = &self.integer_methods; let build = template_parser::lookup_method(type_name, table, function)?; let inner_property = property.try_unwrap(type_name); build( language, diagnostics, build_ctx, Box::new(inner_property), function, ) } CoreTemplatePropertyKind::Signature(property) => { let table = &self.signature_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::SizeHint(property) => { let table = &self.size_hint_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::Timestamp(property) => { let table = &self.timestamp_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::TimestampRange(property) => { let table = &self.timestamp_range_methods; let build = template_parser::lookup_method(type_name, table, function)?; build(language, diagnostics, build_ctx, property, function) } CoreTemplatePropertyKind::Template(_) => { // TODO: migrate to table? Err(TemplateParseError::no_such_method(type_name, function)) } CoreTemplatePropertyKind::ListTemplate(template) => { // TODO: migrate to table? build_list_template_method(language, diagnostics, build_ctx, template, function) } } } } /// Opaque struct that represents a template value. pub struct Expression<P> { property: P, labels: Vec<String>, }