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::BoxedTemplateProperty;
31use crate::templater::ListTemplate;
32use crate::templater::Template;
33
34/// General-purpose template language for basic value types.
35///
36/// This template language only supports the core template property types (plus
37/// the self type `C`.) The self type `C` is usually a tuple or struct of value
38/// types. It's cloned several times internally. Keyword functions need to be
39/// registered to extract properties from the self object.
40pub struct GenericTemplateLanguage<'a, C> {
41    settings: UserSettings,
42    build_fn_table: GenericTemplateBuildFnTable<'a, C>,
43}
44
45impl<'a, C> GenericTemplateLanguage<'a, C> {
46    /// Sets up environment with no keywords.
47    ///
48    /// New keyword functions can be registered by `add_keyword()`.
49    pub fn new(settings: &UserSettings) -> Self {
50        Self::with_keywords(HashMap::new(), settings)
51    }
52
53    /// Sets up environment with the given `keywords` table.
54    pub fn with_keywords(
55        keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
56        settings: &UserSettings,
57    ) -> Self {
58        GenericTemplateLanguage {
59            // Clone settings to keep lifetime simple. It's cheap.
60            settings: settings.clone(),
61            build_fn_table: GenericTemplateBuildFnTable {
62                core: CoreTemplateBuildFnTable::builtin(),
63                keywords,
64            },
65        }
66    }
67
68    /// Registers new function that translates keyword to property.
69    ///
70    /// A keyword function returns `Self::Property`, which is basically a
71    /// closure tagged by its return type. The inner closure is usually wrapped
72    /// by `TemplateFunction`.
73    ///
74    /// ```ignore
75    /// language.add_keyword("name", |self_property| {
76    ///     let out_property = self_property.map(|v| v.to_string());
77    ///     Ok(out_property.into_dyn_wrapped())
78    /// });
79    /// ```
80    pub fn add_keyword<F>(&mut self, name: &'static str, build: F)
81    where
82        F: Fn(
83                BoxedTemplateProperty<'a, C>,
84            ) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
85            + 'a,
86    {
87        self.build_fn_table.keywords.insert(name, Box::new(build));
88    }
89}
90
91impl<'a, C> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> {
92    type Property = GenericTemplatePropertyKind<'a, C>;
93
94    fn settings(&self) -> &UserSettings {
95        &self.settings
96    }
97
98    fn build_function(
99        &self,
100        diagnostics: &mut TemplateDiagnostics,
101        build_ctx: &BuildContext<Self::Property>,
102        function: &FunctionCallNode,
103    ) -> TemplateParseResult<Self::Property> {
104        let table = &self.build_fn_table.core;
105        table.build_function(self, diagnostics, build_ctx, function)
106    }
107
108    fn build_method(
109        &self,
110        diagnostics: &mut TemplateDiagnostics,
111        build_ctx: &BuildContext<Self::Property>,
112        property: Self::Property,
113        function: &FunctionCallNode,
114    ) -> TemplateParseResult<Self::Property> {
115        let type_name = property.type_name();
116        match property {
117            GenericTemplatePropertyKind::Core(property) => {
118                let table = &self.build_fn_table.core;
119                table.build_method(self, diagnostics, build_ctx, property, function)
120            }
121            GenericTemplatePropertyKind::Self_(property) => {
122                let table = &self.build_fn_table.keywords;
123                let build = template_parser::lookup_method(type_name, table, function)?;
124                // For simplicity, only 0-ary method is supported.
125                function.expect_no_arguments()?;
126                build(property)
127            }
128        }
129    }
130}
131
132pub enum GenericTemplatePropertyKind<'a, C> {
133    Core(CoreTemplatePropertyKind<'a>),
134    Self_(BoxedTemplateProperty<'a, C>),
135}
136
137template_builder::impl_core_property_wrappers!(<'a, C> GenericTemplatePropertyKind<'a, C> => Core);
138
139/// Implements conversion trait for the self property type.
140///
141/// Since we cannot guarantee that the generic type `C` does not conflict with
142/// the core template types, the conversion trait has to be implemented for each
143/// concrete type.
144macro_rules! impl_self_property_wrapper {
145    ($context:path) => {
146        $crate::template_builder::impl_property_wrappers!(
147            $crate::generic_templater::GenericTemplatePropertyKind<'static, $context> {
148                Self_($context),
149            }
150        );
151    };
152    (<$a:lifetime> $context:path) => {
153        $crate::template_builder::impl_property_wrappers!(
154            <$a> $crate::generic_templater::GenericTemplatePropertyKind<$a, $context> {
155                Self_($context),
156            }
157        );
158    };
159}
160
161pub(crate) use impl_self_property_wrapper;
162
163impl<'a, C> CoreTemplatePropertyVar<'a> for GenericTemplatePropertyKind<'a, C> {
164    fn wrap_template(template: Box<dyn Template + 'a>) -> Self {
165        Self::Core(CoreTemplatePropertyKind::wrap_template(template))
166    }
167
168    fn wrap_list_template(template: Box<dyn ListTemplate + 'a>) -> Self {
169        Self::Core(CoreTemplatePropertyKind::wrap_list_template(template))
170    }
171
172    fn type_name(&self) -> &'static str {
173        match self {
174            Self::Core(property) => property.type_name(),
175            Self::Self_(_) => "Self",
176        }
177    }
178
179    fn try_into_boolean(self) -> Option<BoxedTemplateProperty<'a, bool>> {
180        match self {
181            Self::Core(property) => property.try_into_boolean(),
182            Self::Self_(_) => None,
183        }
184    }
185
186    fn try_into_integer(self) -> Option<BoxedTemplateProperty<'a, i64>> {
187        match self {
188            Self::Core(property) => property.try_into_integer(),
189            Self::Self_(_) => None,
190        }
191    }
192
193    fn try_into_plain_text(self) -> Option<BoxedTemplateProperty<'a, String>> {
194        match self {
195            Self::Core(property) => property.try_into_plain_text(),
196            Self::Self_(_) => None,
197        }
198    }
199
200    fn try_into_template(self) -> Option<Box<dyn Template + 'a>> {
201        match self {
202            Self::Core(property) => property.try_into_template(),
203            Self::Self_(_) => None,
204        }
205    }
206
207    fn try_into_eq(self, other: Self) -> Option<BoxedTemplateProperty<'a, bool>> {
208        match (self, other) {
209            (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_eq(rhs),
210            (Self::Core(_), _) => None,
211            (Self::Self_(_), _) => None,
212        }
213    }
214
215    fn try_into_cmp(self, other: Self) -> Option<BoxedTemplateProperty<'a, Ordering>> {
216        match (self, other) {
217            (Self::Core(lhs), Self::Core(rhs)) => lhs.try_into_cmp(rhs),
218            (Self::Core(_), _) => None,
219            (Self::Self_(_), _) => None,
220        }
221    }
222}
223
224/// Function that translates keyword (or 0-ary method call node of the self type
225/// `C`.)
226///
227/// Because the `GenericTemplateLanguage` doesn't provide a way to pass around
228/// global resources, the keyword function is allowed to capture resources.
229pub type GenericTemplateBuildKeywordFn<'a, C> = Box<
230    dyn Fn(BoxedTemplateProperty<'a, C>) -> TemplateParseResult<GenericTemplatePropertyKind<'a, C>>
231        + 'a,
232>;
233
234/// Table of functions that translate keyword node.
235pub type GenericTemplateBuildKeywordFnMap<'a, C> =
236    HashMap<&'static str, GenericTemplateBuildKeywordFn<'a, C>>;
237
238/// Symbol table of methods available in the general-purpose template.
239struct GenericTemplateBuildFnTable<'a, C> {
240    core: CoreTemplateBuildFnTable<'a, GenericTemplateLanguage<'a, C>>,
241    keywords: GenericTemplateBuildKeywordFnMap<'a, C>,
242}