From 20a75947fe08bdcb8ece1d73cd0ed8daca5837ce Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Tue, 14 Mar 2023 20:25:54 +0900 Subject: [PATCH] templater: add stub BuildContext object, pass it around build_() functions A lambda parameter will be added there just like "locals" of expand_aliases(). --- src/commit_templater.rs | 18 +++---- src/operation_templater.rs | 13 ++--- src/template_builder.rs | 98 +++++++++++++++++++++++++------------- 3 files changed, 83 insertions(+), 46 deletions(-) diff --git a/src/commit_templater.rs b/src/commit_templater.rs index 4bc362c56..368480f50 100644 --- a/src/commit_templater.rs +++ b/src/commit_templater.rs @@ -25,7 +25,7 @@ use jujutsu_lib::rewrite; use crate::formatter::Formatter; use crate::template_builder::{ - self, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateLanguage, + self, BuildContext, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateLanguage, }; use crate::template_parser::{ self, FunctionCallNode, TemplateAliasesMap, TemplateParseError, TemplateParseResult, @@ -53,21 +53,22 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo, '_> { fn build_method( &self, + build_ctx: &BuildContext, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult { match property { CommitTemplatePropertyKind::Core(property) => { - template_builder::build_core_method(self, property, function) + template_builder::build_core_method(self, build_ctx, property, function) } CommitTemplatePropertyKind::CommitOrChangeId(property) => { - build_commit_or_change_id_method(self, property, function) + build_commit_or_change_id_method(self, build_ctx, property, function) } CommitTemplatePropertyKind::CommitOrChangeIdList(property) => { - template_builder::build_list_method(self, property, function) + template_builder::build_list_method(self, build_ctx, property, function) } CommitTemplatePropertyKind::ShortestIdPrefix(property) => { - build_shortest_id_prefix_method(self, property, function) + build_shortest_id_prefix_method(self, build_ctx, property, function) } } } @@ -358,13 +359,14 @@ impl Template<()> for Vec> { fn build_commit_or_change_id_method<'repo>( language: &CommitTemplateLanguage<'repo, '_>, + build_ctx: &BuildContext>, self_property: impl TemplateProperty> + 'repo, function: &FunctionCallNode, ) -> TemplateParseResult> { let parse_optional_integer = |function| -> Result, TemplateParseError> { let ([], [len_node]) = template_parser::expect_arguments(function)?; len_node - .map(|node| template_builder::expect_integer_expression(language, node)) + .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) .transpose() }; let property = match function.name { @@ -421,6 +423,7 @@ impl ShortestIdPrefix { fn build_shortest_id_prefix_method<'repo>( language: &CommitTemplateLanguage<'repo, '_>, + _build_ctx: &BuildContext>, self_property: impl TemplateProperty + 'repo, function: &FunctionCallNode, ) -> TemplateParseResult> { @@ -461,6 +464,5 @@ pub fn parse<'repo>( ) -> TemplateParseResult + 'repo>> { let language = CommitTemplateLanguage { repo, workspace_id }; let node = template_parser::parse(template_text, aliases_map)?; - let expression = template_builder::build_expression(&language, &node)?; - Ok(expression.into_template()) + template_builder::build(&language, &node) } diff --git a/src/operation_templater.rs b/src/operation_templater.rs index 7dd73ada3..c80f36f90 100644 --- a/src/operation_templater.rs +++ b/src/operation_templater.rs @@ -21,7 +21,7 @@ use jujutsu_lib::repo::ReadonlyRepo; use crate::formatter::Formatter; use crate::template_builder::{ - self, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateLanguage, + self, BuildContext, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateLanguage, }; use crate::template_parser::{ self, FunctionCallNode, TemplateAliasesMap, TemplateParseError, TemplateParseResult, @@ -47,15 +47,16 @@ impl TemplateLanguage<'static> for OperationTemplateLanguage<'_> { fn build_method( &self, + build_ctx: &BuildContext, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult { match property { OperationTemplatePropertyKind::Core(property) => { - template_builder::build_core_method(self, property, function) + template_builder::build_core_method(self, build_ctx, property, function) } OperationTemplatePropertyKind::OperationId(property) => { - build_operation_id_method(self, property, function) + build_operation_id_method(self, build_ctx, property, function) } } } @@ -159,6 +160,7 @@ impl Template<()> for OperationId { fn build_operation_id_method( language: &OperationTemplateLanguage, + build_ctx: &BuildContext, self_property: impl TemplateProperty + 'static, function: &FunctionCallNode, ) -> TemplateParseResult { @@ -166,7 +168,7 @@ fn build_operation_id_method( "short" => { let ([], [len_node]) = template_parser::expect_arguments(function)?; let len_property = len_node - .map(|node| template_builder::expect_integer_expression(language, node)) + .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) .transpose()?; language.wrap_string(TemplateFunction::new( (self_property, len_property), @@ -190,6 +192,5 @@ pub fn parse( let head_op_id = repo.op_id(); let language = OperationTemplateLanguage { head_op_id }; let node = template_parser::parse(template_text, aliases_map)?; - let expression = template_builder::build_expression(&language, &node)?; - Ok(expression.into_template()) + template_builder::build(&language, &node) } diff --git a/src/template_builder.rs b/src/template_builder.rs index 5ddc9270c..19bf9d1e8 100644 --- a/src/template_builder.rs +++ b/src/template_builder.rs @@ -64,6 +64,7 @@ pub trait TemplateLanguage<'a> { fn build_keyword(&self, name: &str, span: pest::Span) -> TemplateParseResult; fn build_method( &self, + build_ctx: &BuildContext, property: Self::Property, function: &FunctionCallNode, ) -> TemplateParseResult; @@ -235,42 +236,49 @@ impl<'a, C: 'a, P: IntoTemplate<'a, C>> IntoTemplate<'a, C> for Expression

{ } } +pub struct BuildContext<'i, P> { + _phantom: std::marker::PhantomData<&'i P>, // TODO +} + fn build_method_call<'a, L: TemplateLanguage<'a>>( language: &L, + build_ctx: &BuildContext, method: &MethodCallNode, ) -> TemplateParseResult> { - let mut expression = build_expression(language, &method.object)?; - expression.property = language.build_method(expression.property, &method.function)?; + let mut expression = build_expression(language, build_ctx, &method.object)?; + expression.property = + language.build_method(build_ctx, expression.property, &method.function)?; expression.labels.push(method.function.name.to_owned()); Ok(expression) } pub fn build_core_method<'a, L: TemplateLanguage<'a>>( language: &L, + build_ctx: &BuildContext, property: CoreTemplatePropertyKind<'a, L::Context>, function: &FunctionCallNode, ) -> TemplateParseResult { match property { CoreTemplatePropertyKind::String(property) => { - build_string_method(language, property, function) + build_string_method(language, build_ctx, property, function) } CoreTemplatePropertyKind::StringList(property) => { - build_list_method(language, property, function) + build_list_method(language, build_ctx, property, function) } CoreTemplatePropertyKind::Boolean(property) => { - build_boolean_method(language, property, function) + build_boolean_method(language, build_ctx, property, function) } CoreTemplatePropertyKind::Integer(property) => { - build_integer_method(language, property, function) + build_integer_method(language, build_ctx, property, function) } CoreTemplatePropertyKind::Signature(property) => { - build_signature_method(language, property, function) + build_signature_method(language, build_ctx, property, function) } CoreTemplatePropertyKind::Timestamp(property) => { - build_timestamp_method(language, property, function) + build_timestamp_method(language, build_ctx, property, function) } CoreTemplatePropertyKind::TimestampRange(property) => { - build_timestamp_range_method(language, property, function) + build_timestamp_range_method(language, build_ctx, property, function) } CoreTemplatePropertyKind::Template(_) => { Err(TemplateParseError::no_such_method("Template", function)) @@ -280,6 +288,7 @@ pub fn build_core_method<'a, L: TemplateLanguage<'a>>( fn build_string_method<'a, L: TemplateLanguage<'a>>( language: &L, + build_ctx: &BuildContext, self_property: impl TemplateProperty + 'a, function: &FunctionCallNode, ) -> TemplateParseResult { @@ -287,7 +296,8 @@ fn build_string_method<'a, L: TemplateLanguage<'a>>( "contains" => { let [needle_node] = template_parser::expect_exact_arguments(function)?; // TODO: or .try_into_string() to disable implicit type cast? - let needle_property = build_expression(language, needle_node)?.into_plain_text(); + let needle_property = + build_expression(language, build_ctx, needle_node)?.into_plain_text(); language.wrap_boolean(TemplateFunction::new( (self_property, needle_property), |(haystack, needle)| haystack.contains(&needle), @@ -320,6 +330,7 @@ fn build_string_method<'a, L: TemplateLanguage<'a>>( fn build_boolean_method<'a, L: TemplateLanguage<'a>>( _language: &L, + _build_ctx: &BuildContext, _self_property: impl TemplateProperty + 'a, function: &FunctionCallNode, ) -> TemplateParseResult { @@ -328,6 +339,7 @@ fn build_boolean_method<'a, L: TemplateLanguage<'a>>( fn build_integer_method<'a, L: TemplateLanguage<'a>>( _language: &L, + _build_ctx: &BuildContext, _self_property: impl TemplateProperty + 'a, function: &FunctionCallNode, ) -> TemplateParseResult { @@ -336,6 +348,7 @@ fn build_integer_method<'a, L: TemplateLanguage<'a>>( fn build_signature_method<'a, L: TemplateLanguage<'a>>( language: &L, + _build_ctx: &BuildContext, self_property: impl TemplateProperty + 'a, function: &FunctionCallNode, ) -> TemplateParseResult { @@ -372,6 +385,7 @@ fn build_signature_method<'a, L: TemplateLanguage<'a>>( fn build_timestamp_method<'a, L: TemplateLanguage<'a>>( language: &L, + _build_ctx: &BuildContext, self_property: impl TemplateProperty + 'a, function: &FunctionCallNode, ) -> TemplateParseResult { @@ -403,6 +417,7 @@ fn build_timestamp_method<'a, L: TemplateLanguage<'a>>( fn build_timestamp_range_method<'a, L: TemplateLanguage<'a>>( language: &L, + _build_ctx: &BuildContext, self_property: impl TemplateProperty + 'a, function: &FunctionCallNode, ) -> TemplateParseResult { @@ -437,13 +452,14 @@ fn build_timestamp_range_method<'a, L: TemplateLanguage<'a>>( pub fn build_list_method<'a, L: TemplateLanguage<'a>, P: Template<()> + 'a>( language: &L, + build_ctx: &BuildContext, self_property: impl TemplateProperty> + 'a, function: &FunctionCallNode, ) -> TemplateParseResult { let property = match function.name { "join" => { let [separator_node] = template_parser::expect_exact_arguments(function)?; - let separator = build_expression(language, separator_node)?.into_template(); + let separator = build_expression(language, build_ctx, separator_node)?.into_template(); let template = ListPropertyTemplate::new(self_property, separator, |_, formatter, item| { item.format(&(), formatter) @@ -458,13 +474,14 @@ pub fn build_list_method<'a, L: TemplateLanguage<'a>, P: Template<()> + 'a>( fn build_global_function<'a, L: TemplateLanguage<'a>>( language: &L, + build_ctx: &BuildContext, function: &FunctionCallNode, ) -> TemplateParseResult> { let property = match function.name { "fill" => { let [width_node, content_node] = template_parser::expect_exact_arguments(function)?; - let width = expect_integer_expression(language, width_node)?; - let content = build_expression(language, content_node)?.into_template(); + let width = expect_integer_expression(language, build_ctx, width_node)?; + let content = build_expression(language, build_ctx, content_node)?.into_template(); let template = ReformatTemplate::new(content, move |context, formatter, recorded| { let width = width.extract(context).try_into().unwrap_or(0); text_util::write_wrapped(formatter, recorded, width) @@ -473,8 +490,8 @@ fn build_global_function<'a, L: TemplateLanguage<'a>>( } "indent" => { let [prefix_node, content_node] = template_parser::expect_exact_arguments(function)?; - let prefix = build_expression(language, prefix_node)?.into_template(); - let content = build_expression(language, content_node)?.into_template(); + let prefix = build_expression(language, build_ctx, prefix_node)?.into_template(); + let content = build_expression(language, build_ctx, content_node)?.into_template(); let template = ReformatTemplate::new(content, move |context, formatter, recorded| { text_util::write_indented(formatter, recorded, |formatter| { // If Template::format() returned a custom error type, we would need to @@ -490,8 +507,9 @@ fn build_global_function<'a, L: TemplateLanguage<'a>>( } "label" => { let [label_node, content_node] = template_parser::expect_exact_arguments(function)?; - let label_property = build_expression(language, label_node)?.into_plain_text(); - let content = build_expression(language, content_node)?.into_template(); + let label_property = + build_expression(language, build_ctx, label_node)?.into_plain_text(); + let content = build_expression(language, build_ctx, content_node)?.into_template(); let labels = TemplateFunction::new(label_property, |s| { s.split_whitespace().map(ToString::to_string).collect() }); @@ -500,10 +518,10 @@ fn build_global_function<'a, L: TemplateLanguage<'a>>( "if" => { let ([condition_node, true_node], [false_node]) = template_parser::expect_arguments(function)?; - let condition = expect_boolean_expression(language, condition_node)?; - let true_template = build_expression(language, true_node)?.into_template(); + let condition = expect_boolean_expression(language, build_ctx, condition_node)?; + let true_template = build_expression(language, build_ctx, true_node)?.into_template(); let false_template = false_node - .map(|node| build_expression(language, node)) + .map(|node| build_expression(language, build_ctx, node)) .transpose()? .map(|x| x.into_template()); let template = ConditionalTemplate::new(condition, true_template, false_template); @@ -513,17 +531,17 @@ fn build_global_function<'a, L: TemplateLanguage<'a>>( let contents = function .args .iter() - .map(|node| build_expression(language, node).map(|x| x.into_template())) + .map(|node| build_expression(language, build_ctx, node).map(|x| x.into_template())) .try_collect()?; language.wrap_template(ConcatTemplate(contents)) } "separate" => { let ([separator_node], content_nodes) = template_parser::expect_some_arguments(function)?; - let separator = build_expression(language, separator_node)?.into_template(); + let separator = build_expression(language, build_ctx, separator_node)?.into_template(); let contents = content_nodes .iter() - .map(|node| build_expression(language, node).map(|x| x.into_template())) + .map(|node| build_expression(language, build_ctx, node).map(|x| x.into_template())) .try_collect()?; language.wrap_template(SeparateTemplate::new(separator, contents)) } @@ -532,9 +550,10 @@ fn build_global_function<'a, L: TemplateLanguage<'a>>( Ok(Expression::unlabeled(property)) } -/// Builds template evaluation tree from AST nodes. +/// Builds intermediate expression tree from AST nodes. pub fn build_expression<'a, L: TemplateLanguage<'a>>( language: &L, + build_ctx: &BuildContext, node: &ExpressionNode, ) -> TemplateParseResult> { match &node.kind { @@ -553,37 +572,52 @@ pub fn build_expression<'a, L: TemplateLanguage<'a>>( ExpressionKind::Concat(nodes) => { let templates = nodes .iter() - .map(|node| build_expression(language, node).map(|x| x.into_template())) + .map(|node| build_expression(language, build_ctx, node).map(|x| x.into_template())) .try_collect()?; let property = language.wrap_template(ConcatTemplate(templates)); Ok(Expression::unlabeled(property)) } - ExpressionKind::FunctionCall(function) => build_global_function(language, function), - ExpressionKind::MethodCall(method) => build_method_call(language, method), + ExpressionKind::FunctionCall(function) => { + build_global_function(language, build_ctx, function) + } + ExpressionKind::MethodCall(method) => build_method_call(language, build_ctx, method), ExpressionKind::Lambda(_) => Err(TemplateParseError::unexpected_expression( "Lambda cannot be defined here", node.span, )), - ExpressionKind::AliasExpanded(id, subst) => { - build_expression(language, subst).map_err(|e| e.within_alias_expansion(*id, node.span)) - } + ExpressionKind::AliasExpanded(id, subst) => build_expression(language, build_ctx, subst) + .map_err(|e| e.within_alias_expansion(*id, node.span)), } } +/// Builds template evaluation tree from AST nodes, with fresh build context. +pub fn build<'a, L: TemplateLanguage<'a>>( + language: &L, + node: &ExpressionNode, +) -> TemplateParseResult + 'a>> { + let build_ctx = BuildContext { + _phantom: std::marker::PhantomData, + }; + let expression = build_expression(language, &build_ctx, node)?; + Ok(expression.into_template()) +} + pub fn expect_boolean_expression<'a, L: TemplateLanguage<'a>>( language: &L, + build_ctx: &BuildContext, node: &ExpressionNode, ) -> TemplateParseResult + 'a>> { - build_expression(language, node)? + build_expression(language, build_ctx, node)? .try_into_boolean() .ok_or_else(|| TemplateParseError::expected_type("Boolean", node.span)) } pub fn expect_integer_expression<'a, L: TemplateLanguage<'a>>( language: &L, + build_ctx: &BuildContext, node: &ExpressionNode, ) -> TemplateParseResult + 'a>> { - build_expression(language, node)? + build_expression(language, build_ctx, node)? .try_into_integer() .ok_or_else(|| TemplateParseError::expected_type("Integer", node.span)) }