2127 lines · 85.1 KB · Rust
RawDownload
// 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 itertools::Itertools as _;use jj_lib::backend::{Signature, Timestamp}; use crate::template_parser::{    self, BinaryOp, ExpressionKind, ExpressionNode, FunctionCallNode, MethodCallNode,    TemplateAliasesMap, TemplateParseError, TemplateParseErrorKind, TemplateParseResult, UnaryOp,};use crate::templater::{    ConcatTemplate, ConditionalTemplate, IntoTemplate, LabelTemplate, ListPropertyTemplate,    ListTemplate, Literal, PlainTextFormattedProperty, PropertyPlaceholder, ReformatTemplate,    SeparateTemplate, Template, TemplateFunction, TemplateProperty, TemplatePropertyError,    TimestampRange,};use crate::{text_util, time_util}; /// Callbacks to build language-specific evaluation objects from AST nodes.pub trait TemplateLanguage<'a> {    type Context: 'a;    type Property: IntoTemplateProperty<'a, Self::Context>;     fn wrap_string(        &self,        property: impl TemplateProperty<Self::Context, Output = String> + 'a,    ) -> Self::Property;    fn wrap_string_list(        &self,        property: impl TemplateProperty<Self::Context, Output = Vec<String>> + 'a,    ) -> Self::Property;    fn wrap_boolean(        &self,        property: impl TemplateProperty<Self::Context, Output = bool> + 'a,    ) -> Self::Property;    fn wrap_integer(        &self,        property: impl TemplateProperty<Self::Context, Output = i64> + 'a,    ) -> Self::Property;    fn wrap_signature(        &self,        property: impl TemplateProperty<Self::Context, Output = Signature> + 'a,    ) -> Self::Property;    fn wrap_timestamp(        &self,        property: impl TemplateProperty<Self::Context, Output = Timestamp> + 'a,    ) -> Self::Property;    fn wrap_timestamp_range(        &self,        property: impl TemplateProperty<Self::Context, Output = TimestampRange> + 'a,    ) -> Self::Property;     fn wrap_template(&self, template: Box<dyn Template<Self::Context> + 'a>) -> Self::Property;    fn wrap_list_template(        &self,        template: Box<dyn ListTemplate<Self::Context> + 'a>,    ) -> Self::Property;     /// Creates the `self` template property, which is usually a function that    /// clones the `Context` object.    fn build_self(&self) -> Self::Property;     fn build_method(        &self,        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_signature(jj_lib::backend::Signature) => Signature,                wrap_timestamp(jj_lib::backend::Timestamp) => Timestamp,                wrap_timestamp_range($crate::templater::TimestampRange) => TimestampRange,            }        );        fn wrap_template(            &self,            template: Box<dyn $crate::templater::Template<Self::Context> + $a>,        ) -> Self::Property {            use $crate::template_builder::CoreTemplatePropertyKind as Kind;            $outer(Kind::Template(template))        }        fn wrap_list_template(            &self,            template: Box<dyn $crate::templater::ListTemplate<Self::Context> + $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(                &self,                property: impl $crate::templater::TemplateProperty<                    Self::Context, 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, impl_wrap_property_fns}; /// Provides access to basic template property types.pub trait IntoTemplateProperty<'a, C> {    fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<C, Output = bool> + 'a>>;    fn try_into_integer(self) -> Option<Box<dyn TemplateProperty<C, Output = i64> + 'a>>;     fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<C, Output = String> + 'a>>;    fn try_into_template(self) -> Option<Box<dyn Template<C> + 'a>>;} pub enum CoreTemplatePropertyKind<'a, I> {    String(Box<dyn TemplateProperty<I, Output = String> + 'a>),    StringList(Box<dyn TemplateProperty<I, Output = Vec<String>> + 'a>),    Boolean(Box<dyn TemplateProperty<I, Output = bool> + 'a>),    Integer(Box<dyn TemplateProperty<I, Output = i64> + 'a>),    Signature(Box<dyn TemplateProperty<I, Output = Signature> + 'a>),    Timestamp(Box<dyn TemplateProperty<I, Output = Timestamp> + 'a>),    TimestampRange(Box<dyn TemplateProperty<I, Output = TimestampRange> + 'a>),     // Similar to `TemplateProperty<I, Output = Box<dyn Template<()> + 'a>`, but doesn't    // capture `I` to produce `Template<()>`. The context `I` would have to be cloned    // to convert `Template<I>` to `Template<()>`.    Template(Box<dyn Template<I> + 'a>),    ListTemplate(Box<dyn ListTemplate<I> + 'a>),} impl<'a, I: 'a> IntoTemplateProperty<'a, I> for CoreTemplatePropertyKind<'a, I> {    fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<I, Output = bool> + 'a>> {        match self {            CoreTemplatePropertyKind::String(property) => {                Some(Box::new(TemplateFunction::new(property, |s| {                    Ok(!s.is_empty())                })))            }            CoreTemplatePropertyKind::StringList(property) => {                Some(Box::new(TemplateFunction::new(property, |l| {                    Ok(!l.is_empty())                })))            }            CoreTemplatePropertyKind::Boolean(property) => Some(property),            CoreTemplatePropertyKind::Integer(_) => None,            CoreTemplatePropertyKind::Signature(_) => 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<I, Output = i64> + 'a>> {        match self {            CoreTemplatePropertyKind::Integer(property) => Some(property),            _ => None,        }    }     fn try_into_plain_text(self) -> Option<Box<dyn TemplateProperty<I, Output = String> + 'a>> {        match self {            CoreTemplatePropertyKind::String(property) => Some(property),            _ => {                // TODO: Runtime property evaluation error will be materialized                // as string here. Ideally, the error should propagate because                // the caller expects a value, not a template to render. Some                // ideas to work around the problem:                // 1. stringify property without using Template type (works only for                //    non-template expressions)                // 2. add flag to propagate property error as io::Error::other() (e.g.                //    Template::format(context, formatter, propagate_err))                let template = self.try_into_template()?;                Some(Box::new(PlainTextFormattedProperty::new(template)))            }        }    }     fn try_into_template(self) -> Option<Box<dyn Template<I> + '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::Signature(property) => Some(property.into_template()),            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()),        }    }} /// Function that translates method call node of self type `T`.// 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 TemplateBuildMethodFn<'a, L, T> =    fn(        &L,        &BuildContext<<L as TemplateLanguage<'a>>::Property>,        Box<dyn TemplateProperty<<L as TemplateLanguage<'a>>::Context, Output = T> + 'a>,        &FunctionCallNode,    ) -> TemplateParseResult<<L as TemplateLanguage<'a>>::Property>; /// 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 methods available in the core template.pub struct CoreTemplateBuildFnTable<'a, L: TemplateLanguage<'a> + ?Sized> {    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 timestamp_methods: TemplateBuildMethodFnMap<'a, L, Timestamp>,    pub timestamp_range_methods: TemplateBuildMethodFnMap<'a, L, TimestampRange>,    // TODO: add global functions table?} pub fn merge_fn_map<'a, L: TemplateLanguage<'a> + ?Sized, T>(    base: &mut TemplateBuildMethodFnMap<'a, L, T>,    extension: TemplateBuildMethodFnMap<'a, L, T>,) {    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 methods.    pub fn builtin() -> Self {        CoreTemplateBuildFnTable {            string_methods: builtin_string_methods(),            boolean_methods: HashMap::new(),            integer_methods: HashMap::new(),            signature_methods: builtin_signature_methods(),            timestamp_methods: builtin_timestamp_methods(),            timestamp_range_methods: builtin_timestamp_range_methods(),        }    }     pub fn empty() -> Self {        CoreTemplateBuildFnTable {            string_methods: HashMap::new(),            boolean_methods: HashMap::new(),            integer_methods: HashMap::new(),            signature_methods: HashMap::new(),            timestamp_methods: HashMap::new(),            timestamp_range_methods: HashMap::new(),        }    }     pub fn merge(&mut self, extension: CoreTemplateBuildFnTable<'a, L>) {        let CoreTemplateBuildFnTable {            string_methods,            boolean_methods,            integer_methods,            signature_methods,            timestamp_methods,            timestamp_range_methods,        } = extension;         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.timestamp_methods, timestamp_methods);        merge_fn_map(&mut self.timestamp_range_methods, timestamp_range_methods);    }     /// Applies the method call node `function` to the given `property` by using    /// this symbol table.    pub fn build_method(        &self,        language: &L,        build_ctx: &BuildContext<L::Property>,        property: CoreTemplatePropertyKind<'a, L::Context>,        function: &FunctionCallNode,    ) -> TemplateParseResult<L::Property> {        match property {            CoreTemplatePropertyKind::String(property) => {                let table = &self.string_methods;                let build = template_parser::lookup_method("String", table, function)?;                build(language, build_ctx, property, function)            }            CoreTemplatePropertyKind::StringList(property) => {                // TODO: migrate to table?                build_formattable_list_method(language, build_ctx, property, function, |item| {                    language.wrap_string(item)                })            }            CoreTemplatePropertyKind::Boolean(property) => {                let table = &self.boolean_methods;                let build = template_parser::lookup_method("Boolean", table, function)?;                build(language, build_ctx, property, function)            }            CoreTemplatePropertyKind::Integer(property) => {                let table = &self.integer_methods;                let build = template_parser::lookup_method("Integer", table, function)?;                build(language, build_ctx, property, function)            }            CoreTemplatePropertyKind::Signature(property) => {                let table = &self.signature_methods;                let build = template_parser::lookup_method("Signature", table, function)?;                build(language, build_ctx, property, function)            }            CoreTemplatePropertyKind::Timestamp(property) => {                let table = &self.timestamp_methods;                let build = template_parser::lookup_method("Timestamp", table, function)?;                build(language, build_ctx, property, function)            }            CoreTemplatePropertyKind::TimestampRange(property) => {                let table = &self.timestamp_range_methods;                let build = template_parser::lookup_method("TimestampRange", table, function)?;                build(language, build_ctx, property, function)            }            CoreTemplatePropertyKind::Template(_) => {                // TODO: migrate to table?                Err(TemplateParseError::no_such_method("Template", function))            }            CoreTemplatePropertyKind::ListTemplate(template) => {                // TODO: migrate to table?                build_list_template_method(language, build_ctx, template, function)            }        }    }} /// Opaque struct that represents a template value.pub struct Expression<P> {    property: P,    labels: Vec<String>,} impl<P> Expression<P> {    fn unlabeled(property: P) -> Self {        let labels = vec![];        Expression { property, labels }    }     fn with_label(property: P, label: impl Into<String>) -> Self {        let labels = vec![label.into()];        Expression { property, labels }    }     pub fn try_into_boolean<'a, C: 'a>(        self,    ) -> Option<Box<dyn TemplateProperty<C, Output = bool> + 'a>>    where        P: IntoTemplateProperty<'a, C>,    {        self.property.try_into_boolean()    }     pub fn try_into_integer<'a, C: 'a>(        self,    ) -> Option<Box<dyn TemplateProperty<C, Output = i64> + 'a>>    where        P: IntoTemplateProperty<'a, C>,    {        self.property.try_into_integer()    }     pub fn try_into_plain_text<'a, C: 'a>(        self,    ) -> Option<Box<dyn TemplateProperty<C, Output = String> + 'a>>    where        P: IntoTemplateProperty<'a, C>,    {        self.property.try_into_plain_text()    }     pub fn try_into_template<'a, C: 'a>(self) -> Option<Box<dyn Template<C> + 'a>>    where        P: IntoTemplateProperty<'a, C>,    {        let template = self.property.try_into_template()?;        if self.labels.is_empty() {            Some(template)        } else {            Some(Box::new(LabelTemplate::new(template, Literal(self.labels))))        }    }} pub struct BuildContext<'i, P> {    /// Map of functions to create `L::Property`.    local_variables: HashMap<&'i str, &'i (dyn Fn() -> P)>,} fn build_keyword<'a, L: TemplateLanguage<'a> + ?Sized>(    language: &L,    build_ctx: &BuildContext<L::Property>,    name: &str,    name_span: pest::Span<'_>,) -> TemplateParseResult<Expression<L::Property>> {    // Keyword is a 0-ary method on the "self" property    let self_property = language.build_self();    let function = FunctionCallNode {        name,        name_span,        args: vec![],        args_span: name_span.end_pos().span(&name_span.end_pos()),    };    match language.build_method(build_ctx, self_property, &function) {        Ok(property) => Ok(Expression::with_label(property, name)),        Err(err) => {            let kind = if let TemplateParseErrorKind::NoSuchMethod { candidates, .. } = err.kind() {                TemplateParseErrorKind::NoSuchKeyword {                    name: name.to_owned(),                    // TODO: filter methods by arity?                    candidates: candidates.clone(),                }            } else {                // Since keyword is a 0-ary method, any argument-related errors                // mean there's no such keyword.                TemplateParseErrorKind::NoSuchKeyword {                    name: name.to_owned(),                    // TODO: might be better to phrase the error differently                    candidates: vec![format!("self.{name}(..)")],                }            };            Err(TemplateParseError::with_span(kind, name_span))        }    }} fn build_unary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(    language: &L,    build_ctx: &BuildContext<L::Property>,    op: UnaryOp,    arg_node: &ExpressionNode,) -> TemplateParseResult<Expression<L::Property>> {    let property = match op {        UnaryOp::LogicalNot => {            let arg = expect_boolean_expression(language, build_ctx, arg_node)?;            language.wrap_boolean(TemplateFunction::new(arg, |v| Ok(!v)))        }        UnaryOp::Negate => {            let arg = expect_integer_expression(language, build_ctx, arg_node)?;            language.wrap_integer(TemplateFunction::new(arg, |v| {                v.checked_neg()                    .ok_or_else(|| TemplatePropertyError("Attempt to negate with overflow".into()))            }))        }    };    Ok(Expression::unlabeled(property))} fn build_binary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(    language: &L,    build_ctx: &BuildContext<L::Property>,    op: BinaryOp,    lhs_node: &ExpressionNode,    rhs_node: &ExpressionNode,) -> TemplateParseResult<Expression<L::Property>> {    let property = match op {        BinaryOp::LogicalOr => {            // No short-circuiting supported            let lhs = expect_boolean_expression(language, build_ctx, lhs_node)?;

Lines 1–500 of 2127.