Skip to main content

jj_cli/
generic_templater.rs

1// Copyright 2024 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cmp::Ordering;
16use std::collections::HashMap;
17
18use jj_lib::settings::UserSettings;
19
20use crate::template_builder;
21use crate::template_builder::BuildContext;
22use crate::template_builder::CoreTemplateBuildFnTable;
23use crate::template_builder::CoreTemplatePropertyKind;
24use crate::template_builder::CoreTemplatePropertyVar;
25use crate::template_builder::TemplateLanguage;
26use crate::template_parser;
27use crate::template_parser::FunctionCallNode;
28use crate::template_parser::TemplateDiagnostics;
29use crate::template_parser::TemplateParseResult;
30use crate::templater::BoxedAnyProperty;
31use crate::templater::BoxedSerializeProperty;
32use crate::templater::BoxedTemplateProperty;
33use crate::templater::Template;
34use crate::templater::TemplatePropertyExt as _;
35
36/// General-purpose template language for basic value types.
37///
38/// This template language only supports the core template property types (plus
39/// the self type `C`.) The self type `C` is usually a tuple or struct of value
40/// types. It's cloned several times internally. Keyword functions need to be
41/// registered to extract properties from the self object.
42pub struct GenericTemplateLanguage<'a, C> {
43    settings: UserSettings,
44    build_fn_table: GenericTemplateBuildFnTable<'a, C>,
45}
46
47impl<'a, C> GenericTemplateLanguage<'a, C>
48where
49    C: serde::Serialize + 'a,
50{
51    /// Sets up environment with no keywords.
52    ///
53    /// New keyword functions can be registered by `add_keyword()`.
54    pub fn new(settings: &UserSettings) -> Self {
55        Self::with_keywords(HashMap::new(), settings)
56    }
57
58    /// Sets up environment with the given `keywords` table.
59    pub fn with_keywords(
60        keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
61        settings: &UserSettings,
62    ) -> Self {
63        Self {
64            // Clone settings to keep lifetime simple. It's cheap.
65            settings: settings.clone(),
66            build_fn_table: GenericTemplateBuildFnTable {
67                core: CoreTemplateBuildFnTable::builtin(),
68                keywords,
69            },
70        }
71    }
72
73    /// Registers new function that translates keyword to property.
74    ///
75    /// A keyword function returns `Self::Property`, which is basically a
76    /// closure tagged by its return type. The inner closure is usually wrapped
77    /// by `TemplateFunction`.
78    ///
79    /// ```ignore
80    /// language.add_keyword("name", |self_property| {
81    ///     let out_property = self_property.map(|v| v.to_string());
82    ///     Ok(out_property.into_dyn_wrapped())
83    /// });
84    /// ```
85    pub fn add_keyword<F>(&mut self, name: &'static str, build: F)
86    where
87        F: Fn(
88                BoxedTemplateProperty<'a, C>,
89            ) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
90            + 'a,
91    {
92        self.build_fn_table.keywords.insert(name, Box::new(build));
93    }
94}
95
96impl<'a, C> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C>
97where
98    C: serde::Serialize + 'a,
99{
100    type Property = GenericTemplatePropertyKind<'a, C>;
101
102    fn settings(&self) -> &UserSettings {
103        &self.settings
104    }
105
106    fn build_function(
107        &self,
108        diagnostics: &mut TemplateDiagnostics,
109        build_ctx: &BuildContext<Self::Property>,
110        function: &FunctionCallNode,
111    ) -> TemplateParseResult<Self::Property> {
112        let table = &self.build_fn_table.core;
113        table.build_function(self, diagnostics, build_ctx, function)
114    }
115
116    fn build_method(
117        &self,
118        diagnostics: &mut TemplateDiagnostics,
119        build_ctx: &BuildContext<Self::Property>,
120        property: Self::Property,
121        function: &FunctionCallNode,
122    ) -> TemplateParseResult<Self::Property> {
123        let type_name = property.type_name();
124        match property {
125            GenericTemplatePropertyKind::Core(property) => {
126                let table = &self.build_fn_table.core;
127                table.build_method(self, diagnostics, build_ctx, property, function)
128            }
129            GenericTemplatePropertyKind::Self_(property) => {
130                let table = &self.build_fn_table.keywords;
131                let build = template_parser::lookup_method(type_name, table, function)?;
132                // For simplicity, only 0-ary method is supported.
133                function.expect_no_arguments()?;
134                build(property)
135            }
136        }
137    }
138}
139
140pub enum GenericTemplatePropertyKind<'a, C> {
141    Core(CoreTemplatePropertyKind<'a>),
142    Self_(BoxedTemplateProperty<'a, C>),
143}
144
145template_builder::impl_core_property_wrappers!(<'a, C> GenericTemplatePropertyKind<'a, C> => Core);
146
147/// Implements conversion trait for the self property type.
148///
149/// Since we cannot guarantee that the generic type `C` does not conflict with
150/// the core template types, the conversion trait has to be implemented for each
151/// concrete type.
152macro_rules! impl_self_property_wrapper {
153    ($context:path) => {
154        $crate::template_builder::impl_property_wrappers!(
155            $crate::generic_templater::GenericTemplatePropertyKind<'static, $context> {
156                Self_($context),
157            }
158        );
159    };
160    (<$a:lifetime> $context:path) => {
161        $crate::template_builder::impl_property_wrappers!(
162            <$a> $crate::generic_templater::GenericTemplatePropertyKind<$a, $context> {
163                Self_($context),
164            }
165        );
166    };
167}
168
169pub(crate) use impl_self_property_wrapper;
170
171impl<'a, C> CoreTemplatePropertyVar<'a> for GenericTemplatePropertyKind<'a, C>
172where
173    C: serde::Serialize + 'a,
174{
175    fn wrap_template(template: Box<dyn Template + 'a>) -> Self {
176        Self::Core(CoreTemplatePropertyKind::wrap_template(template))
177    }
178
179    fn wrap_any(property: BoxedAnyProperty<'a>) -> Self {
180        Self::Core(CoreTemplatePropertyKind::wrap_any(property))
181    }
182
183    fn wrap_any_list(property: BoxedAnyProperty<'a>) -> Self {
184        Self::Core(CoreTemplatePropertyKind::wrap_any_list(property))
185    }
186
187    fn type_name(&self) -> &'static str {
188        match self {
189            Self::Core(property) => property.type_name(),
190            Self::Self_(_) => "Self",
191        }
192    }
193
194    fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>> {
195        match self {
196            Self::Core(property) => property.try_into_boolean(),
197            Self::Self_(_) => None,
198        }
199    }
200
201    fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>> {
202        match self {
203            Self::Core(property) => property.try_into_integer(),
204            Self::Self_(_) => None,
205        }
206    }
207
208    fn try_into_timestamp(self) -> Option<BoxedTemplateProperty<'a, jj_lib::backend::Timestamp>> {
209        match self {
210            Self::Core(property) => property.try_into_timestamp(),
211            Self::Self_(_) => None,
212        }
213    }
214
215    fn try_into_stringify(self) -> Option<BoxedTemplateProperty<'a, String>> {
216        match self {
217            Self::Core(property) => property.try_into_stringify(),
218            Self::Self_(_) => None,
219        }
220    }
221
222    fn try_into_serialize(self) -> Option<BoxedSerializeProperty<'a>> {
223        match self {
224            Self::Core(property) => property.try_into_serialize(),
225            Self::Self_(property) => Some(property.into_serialize()),
226        }
227    }
228
229    fn try_into_template(self) -> Option<Box<dyn Template + 'a>> {
230        match self {
231            Self::Core(property) => property.try_into_template(),
232            Self::Self_(_) => None,
233        }
234    }
235
236    fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> {
237        match (self, other) {
238            (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_eq(rhs),
239            (Self::Core(_), _) => None,
240            (Self::Self_(_), _) => None,
241        }
242    }
243
244    fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> {
245        match (self, other) {
246            (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_cmp(rhs),
247            (Self::Core(_), _) => None,
248            (Self::Self_(_), _) => None,
249        }
250    }
251}
252
253/// Function that translates keyword (or 0-ary method call node of the self type
254/// `C`.)
255///
256/// Because the `GenericTemplateLanguage` doesn't provide a way to pass around
257/// global resources, the keyword function is allowed to capture resources.
258pub type GenericTemplateBuildKeywordFn<'a, C> = Box<
259    dyn Fn(BoxedTemplateProperty<'a, C>) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
260        + 'a,
261>;
262
263/// Table of functions that translate keyword node.
264pub type GenericTemplateBuildKeywordFnMap<'a, C> =
265    HashMap<&'static str, GenericTemplateBuildKeywordFn<'a, C>>;
266
267/// Symbol table of methods available in the general-purpose template.
268struct GenericTemplateBuildFnTable<'a, C> {
269    core: CoreTemplateBuildFnTable<
270        'a,
271        GenericTemplateLanguage<'a, C>,
272        GenericTemplatePropertyKind<'a, C>,
273    >,
274    keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
275}