From fe5a4d589a6ef0ce68f18fc0c9bf5edb88dc8939 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sun, 25 Feb 2024 10:46:23 +0900 Subject: [PATCH 1/5] templater: extract stub function that resolves method name We'll probably add "Did you mean?" hint there. --- cli/src/commit_templater.rs | 9 +++------ cli/src/template_parser.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index 9f20c4644a..26ef58b78a 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -71,12 +71,9 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { template_builder::build_core_method(self, build_ctx, property, function) } CommitTemplatePropertyKind::Commit(property) => { - // TODO: add name resolution helper that provides typo hint - if let Some(build) = self.build_fn_table.commit_methods.get(function.name) { - build(self, build_ctx, property, function) - } else { - Err(TemplateParseError::no_such_method("Commit", function)) - } + let table = &self.build_fn_table.commit_methods; + let build = template_parser::lookup_method("Commit", table, function)?; + build(self, build_ctx, property, function) } CommitTemplatePropertyKind::CommitList(property) => { template_builder::build_unformattable_list_method( diff --git a/cli/src/template_parser.rs b/cli/src/template_parser.rs index 61fbb865e5..39d04d2e77 100644 --- a/cli/src/template_parser.rs +++ b/cli/src/template_parser.rs @@ -855,6 +855,20 @@ pub fn expect_lambda_with<'a, 'i, T>( } } +/// Looks up `table` by the given method name. +pub fn lookup_method<'a, V>( + type_name: impl Into, + table: &'a HashMap<&str, V>, + function: &FunctionCallNode, +) -> TemplateParseResult<&'a V> { + if let Some(value) = table.get(function.name) { + Ok(value) + } else { + // TODO: provide typo hint + Err(TemplateParseError::no_such_method(type_name, function)) + } +} + #[cfg(test)] mod tests { use assert_matches::assert_matches; From de90e0371a490849eec1f3ee5b65156964244f11 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sun, 25 Feb 2024 11:30:20 +0900 Subject: [PATCH 2/5] templater: inline parse_optional_integer() in method body If each method body is split to closure, this parse helper will no longer be able to capture the environment. --- cli/src/commit_templater.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index 26ef58b78a..8f1e26d77c 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -706,15 +706,12 @@ fn build_commit_or_change_id_method<'repo>( 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, build_ctx, node)) - .transpose() - }; let property = match function.name { "short" => { - let len_property = parse_optional_integer(function)?; + let ([], [len_node]) = template_parser::expect_arguments(function)?; + let len_property = len_node + .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) + .transpose()?; language.wrap_string(TemplateFunction::new( (self_property, len_property), |(id, len)| id.short(len.map_or(12, |l| l.try_into().unwrap_or(0))), @@ -722,7 +719,10 @@ fn build_commit_or_change_id_method<'repo>( } "shortest" => { let id_prefix_context = &language.id_prefix_context; - let len_property = parse_optional_integer(function)?; + let ([], [len_node]) = template_parser::expect_arguments(function)?; + let len_property = len_node + .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) + .transpose()?; language.wrap_shortest_id_prefix(TemplateFunction::new( (self_property, len_property), |(id, len)| { From 9b3eebad07d2a478591c49f47e141180ad9f0014 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sun, 25 Feb 2024 10:57:34 +0900 Subject: [PATCH 3/5] templater: migrate other commit template methods to symbol table --- cli/src/commit_templater.rs | 195 ++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 100 deletions(-) diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index 8f1e26d77c..8cd50ec858 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -32,9 +32,7 @@ use crate::formatter::Formatter; use crate::template_builder::{ self, BuildContext, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateLanguage, }; -use crate::template_parser::{ - self, FunctionCallNode, TemplateAliasesMap, TemplateParseError, TemplateParseResult, -}; +use crate::template_parser::{self, FunctionCallNode, TemplateAliasesMap, TemplateParseResult}; use crate::templater::{ self, IntoTemplate, PlainTextFormattedProperty, Template, TemplateFunction, TemplateProperty, TemplatePropertyFn, @@ -76,6 +74,7 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { build(self, build_ctx, property, function) } CommitTemplatePropertyKind::CommitList(property) => { + // TODO: migrate to table? template_builder::build_unformattable_list_method( self, build_ctx, @@ -85,9 +84,12 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { ) } CommitTemplatePropertyKind::RefName(property) => { - build_ref_name_method(self, build_ctx, property, function) + let table = &self.build_fn_table.ref_name_methods; + let build = template_parser::lookup_method("RefName", table, function)?; + build(self, build_ctx, property, function) } CommitTemplatePropertyKind::RefNameList(property) => { + // TODO: migrate to table? template_builder::build_formattable_list_method( self, build_ctx, @@ -97,10 +99,14 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> { ) } CommitTemplatePropertyKind::CommitOrChangeId(property) => { - build_commit_or_change_id_method(self, build_ctx, property, function) + let table = &self.build_fn_table.commit_or_change_id_methods; + let build = template_parser::lookup_method("CommitOrChangeId", table, function)?; + build(self, build_ctx, property, function) } CommitTemplatePropertyKind::ShortestIdPrefix(property) => { - build_shortest_id_prefix_method(self, build_ctx, property, function) + let table = &self.build_fn_table.shortest_id_prefix_methods; + let build = template_parser::lookup_method("ShortestIdPrefix", table, function)?; + build(self, build_ctx, property, function) } } } @@ -233,7 +239,11 @@ type CommitTemplateBuildMethodFn<'repo, T> = struct CommitTemplateBuildFnTable<'repo> { // TODO: add core methods/functions table commit_methods: HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, Commit>>, - // TODO: migrate other build_*_method() + ref_name_methods: HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, RefName>>, + commit_or_change_id_methods: + HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, CommitOrChangeId>>, + shortest_id_prefix_methods: + HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, ShortestIdPrefix>>, } impl CommitTemplateBuildFnTable<'_> { @@ -241,6 +251,9 @@ impl CommitTemplateBuildFnTable<'_> { fn builtin() -> Self { CommitTemplateBuildFnTable { commit_methods: builtin_commit_methods(), + ref_name_methods: builtin_ref_name_methods(), + commit_or_change_id_methods: builtin_commit_or_change_id_methods(), + shortest_id_prefix_methods: builtin_shortest_id_prefix_methods(), } } } @@ -542,29 +555,24 @@ impl Template<()> for Vec { } } -fn build_ref_name_method<'repo>( - language: &CommitTemplateLanguage<'repo>, - _build_ctx: &BuildContext>, - self_property: impl TemplateProperty + 'repo, - function: &FunctionCallNode, -) -> TemplateParseResult> { - let property = match function.name { - "name" => { - template_parser::expect_no_arguments(function)?; - language.wrap_string(TemplateFunction::new(self_property, |ref_name| { - ref_name.name - })) - } - "remote" => { - template_parser::expect_no_arguments(function)?; - language.wrap_string(TemplateFunction::new(self_property, |ref_name| { - ref_name.remote.unwrap_or_default() - })) - } - // TODO: expose conflict, synced, remote.is_some() - _ => return Err(TemplateParseError::no_such_method("RefName", function)), - }; - Ok(property) +fn builtin_ref_name_methods<'repo>( +) -> HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, RefName>> { + // Not using maplit::hashmap!{} or custom declarative macro here because + // code completion inside macro is quite restricted. + let mut map: HashMap<&'static str, CommitTemplateBuildMethodFn> = HashMap::new(); + map.insert("name", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |ref_name| ref_name.name); + Ok(language.wrap_string(out_property)) + }); + map.insert("remote", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |ref_name| { + ref_name.remote.unwrap_or_default() + }); + Ok(language.wrap_string(out_property)) + }); + map } /// Cache for reverse lookup refs. @@ -700,48 +708,41 @@ impl Template<()> for CommitOrChangeId { } } -fn build_commit_or_change_id_method<'repo>( - language: &CommitTemplateLanguage<'repo>, - build_ctx: &BuildContext>, - self_property: impl TemplateProperty + 'repo, - function: &FunctionCallNode, -) -> TemplateParseResult> { - let property = match function.name { - "short" => { - let ([], [len_node]) = template_parser::expect_arguments(function)?; - let len_property = len_node - .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) - .transpose()?; - language.wrap_string(TemplateFunction::new( - (self_property, len_property), - |(id, len)| id.short(len.map_or(12, |l| l.try_into().unwrap_or(0))), - )) - } - "shortest" => { +fn builtin_commit_or_change_id_methods<'repo>( +) -> HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, CommitOrChangeId>> { + // Not using maplit::hashmap!{} or custom declarative macro here because + // code completion inside macro is quite restricted. + let mut map: HashMap<&'static str, CommitTemplateBuildMethodFn> = + HashMap::new(); + map.insert("short", |language, build_ctx, self_property, function| { + let ([], [len_node]) = template_parser::expect_arguments(function)?; + let len_property = len_node + .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) + .transpose()?; + let out_property = TemplateFunction::new((self_property, len_property), |(id, len)| { + id.short(len.map_or(12, |l| l.try_into().unwrap_or(0))) + }); + Ok(language.wrap_string(out_property)) + }); + map.insert( + "shortest", + |language, build_ctx, self_property, function| { let id_prefix_context = &language.id_prefix_context; let ([], [len_node]) = template_parser::expect_arguments(function)?; let len_property = len_node .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) .transpose()?; - language.wrap_shortest_id_prefix(TemplateFunction::new( - (self_property, len_property), - |(id, len)| { - id.shortest( - language.repo, - id_prefix_context, - len.and_then(|l| l.try_into().ok()).unwrap_or(0), - ) - }, - )) - } - _ => { - return Err(TemplateParseError::no_such_method( - "CommitOrChangeId", - function, - )) - } - }; - Ok(property) + let out_property = TemplateFunction::new((self_property, len_property), |(id, len)| { + id.shortest( + language.repo, + id_prefix_context, + len.and_then(|l| l.try_into().ok()).unwrap_or(0), + ) + }); + Ok(language.wrap_shortest_id_prefix(out_property)) + }, + ); + map } struct ShortestIdPrefix { @@ -771,39 +772,33 @@ impl ShortestIdPrefix { } } -fn build_shortest_id_prefix_method<'repo>( - language: &CommitTemplateLanguage<'repo>, - _build_ctx: &BuildContext>, - self_property: impl TemplateProperty + 'repo, - function: &FunctionCallNode, -) -> TemplateParseResult> { - let property = match function.name { - "prefix" => { - template_parser::expect_no_arguments(function)?; - language.wrap_string(TemplateFunction::new(self_property, |id| id.prefix)) - } - "rest" => { - template_parser::expect_no_arguments(function)?; - language.wrap_string(TemplateFunction::new(self_property, |id| id.rest)) - } - "upper" => { - template_parser::expect_no_arguments(function)?; - language - .wrap_shortest_id_prefix(TemplateFunction::new(self_property, |id| id.to_upper())) - } - "lower" => { - template_parser::expect_no_arguments(function)?; - language - .wrap_shortest_id_prefix(TemplateFunction::new(self_property, |id| id.to_lower())) - } - _ => { - return Err(TemplateParseError::no_such_method( - "ShortestIdPrefix", - function, - )) - } - }; - Ok(property) +fn builtin_shortest_id_prefix_methods<'repo>( +) -> HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, ShortestIdPrefix>> { + // Not using maplit::hashmap!{} or custom declarative macro here because + // code completion inside macro is quite restricted. + let mut map: HashMap<&'static str, CommitTemplateBuildMethodFn> = + HashMap::new(); + map.insert("prefix", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |id| id.prefix); + Ok(language.wrap_string(out_property)) + }); + map.insert("rest", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |id| id.rest); + Ok(language.wrap_string(out_property)) + }); + map.insert("upper", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |id| id.to_upper()); + Ok(language.wrap_shortest_id_prefix(out_property)) + }); + map.insert("lower", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |id| id.to_lower()); + Ok(language.wrap_shortest_id_prefix(out_property)) + }); + map } pub fn parse<'repo>( From 19d62e47e128d9ab91ffad5b63169ad666e3a045 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sun, 25 Feb 2024 10:09:26 +0900 Subject: [PATCH 4/5] templater: extract generic type aliases for method build fn and table I also added a type alias for HashMap<&'static str, _> because it's tedious to repeat that in function body. --- cli/src/commit_templater.rs | 49 +++++++++++++------------------------ cli/src/template_builder.rs | 18 ++++++++++++++ 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/cli/src/commit_templater.rs b/cli/src/commit_templater.rs index 8cd50ec858..3ac991bdad 100644 --- a/cli/src/commit_templater.rs +++ b/cli/src/commit_templater.rs @@ -30,7 +30,8 @@ use once_cell::unsync::OnceCell; use crate::formatter::Formatter; use crate::template_builder::{ - self, BuildContext, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateLanguage, + self, BuildContext, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateBuildMethodFnMap, + TemplateLanguage, }; use crate::template_parser::{self, FunctionCallNode, TemplateAliasesMap, TemplateParseResult}; use crate::templater::{ @@ -221,29 +222,17 @@ impl<'repo> IntoTemplateProperty<'repo, Commit> for CommitTemplatePropertyKind<' } } -/// Function that translates method call node of self type `T`. -// The lifetime parameter 'repo could be replaced with for<'repo> 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<'repo>. That's why the build fn -// table is bound to a named lifetime, and therefore can't be cached statically. -type CommitTemplateBuildMethodFn<'repo, T> = - fn( - &CommitTemplateLanguage<'repo>, - &BuildContext>, - Box + 'repo>, - &FunctionCallNode, - ) -> TemplateParseResult>; +/// Table of functions that translate method call node of self type `T`. +type CommitTemplateBuildMethodFnMap<'repo, T> = + TemplateBuildMethodFnMap<'repo, CommitTemplateLanguage<'repo>, T>; /// Symbol table of methods available in the commit template. struct CommitTemplateBuildFnTable<'repo> { // TODO: add core methods/functions table - commit_methods: HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, Commit>>, - ref_name_methods: HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, RefName>>, - commit_or_change_id_methods: - HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, CommitOrChangeId>>, - shortest_id_prefix_methods: - HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, ShortestIdPrefix>>, + commit_methods: CommitTemplateBuildMethodFnMap<'repo, Commit>, + ref_name_methods: CommitTemplateBuildMethodFnMap<'repo, RefName>, + commit_or_change_id_methods: CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId>, + shortest_id_prefix_methods: CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix>, } impl CommitTemplateBuildFnTable<'_> { @@ -283,11 +272,10 @@ impl CommitKeywordCache { } } -fn builtin_commit_methods<'repo>( -) -> HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, Commit>> { +fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Commit> { // Not using maplit::hashmap!{} or custom declarative macro here because // code completion inside macro is quite restricted. - let mut map: HashMap<&'static str, CommitTemplateBuildMethodFn> = HashMap::new(); + let mut map = CommitTemplateBuildMethodFnMap::::new(); map.insert( "description", |language, _build_ctx, self_property, function| { @@ -555,11 +543,10 @@ impl Template<()> for Vec { } } -fn builtin_ref_name_methods<'repo>( -) -> HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, RefName>> { +fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, RefName> { // Not using maplit::hashmap!{} or custom declarative macro here because // code completion inside macro is quite restricted. - let mut map: HashMap<&'static str, CommitTemplateBuildMethodFn> = HashMap::new(); + let mut map = CommitTemplateBuildMethodFnMap::::new(); map.insert("name", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |ref_name| ref_name.name); @@ -709,11 +696,10 @@ impl Template<()> for CommitOrChangeId { } fn builtin_commit_or_change_id_methods<'repo>( -) -> HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, CommitOrChangeId>> { +) -> CommitTemplateBuildMethodFnMap<'repo, CommitOrChangeId> { // Not using maplit::hashmap!{} or custom declarative macro here because // code completion inside macro is quite restricted. - let mut map: HashMap<&'static str, CommitTemplateBuildMethodFn> = - HashMap::new(); + let mut map = CommitTemplateBuildMethodFnMap::::new(); map.insert("short", |language, build_ctx, self_property, function| { let ([], [len_node]) = template_parser::expect_arguments(function)?; let len_property = len_node @@ -773,11 +759,10 @@ impl ShortestIdPrefix { } fn builtin_shortest_id_prefix_methods<'repo>( -) -> HashMap<&'static str, CommitTemplateBuildMethodFn<'repo, ShortestIdPrefix>> { +) -> CommitTemplateBuildMethodFnMap<'repo, ShortestIdPrefix> { // Not using maplit::hashmap!{} or custom declarative macro here because // code completion inside macro is quite restricted. - let mut map: HashMap<&'static str, CommitTemplateBuildMethodFn> = - HashMap::new(); + let mut map = CommitTemplateBuildMethodFnMap::::new(); map.insert("prefix", |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let out_property = TemplateFunction::new(self_property, |id| id.prefix); diff --git a/cli/src/template_builder.rs b/cli/src/template_builder.rs index 136c273ba1..f2762a4114 100644 --- a/cli/src/template_builder.rs +++ b/cli/src/template_builder.rs @@ -213,6 +213,24 @@ impl<'a, I: 'a> IntoTemplateProperty<'a, I> for CoreTemplatePropertyKind<'a, I> } } +/// 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<>::Property>, + Box>::Context, Output = T> + 'a>, + &FunctionCallNode, + ) -> TemplateParseResult<>::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>>; + /// Opaque struct that represents a template value. pub struct Expression

{ property: P, From 39d589fd1e5f776aaf83217659b90eade05af317 Mon Sep 17 00:00:00 2001 From: Yuya Nishihara Date: Sun, 25 Feb 2024 14:54:32 +0900 Subject: [PATCH 5/5] templater: migrate operation template methods to symbol table --- cli/src/operation_templater.rs | 196 ++++++++++++++++++--------------- 1 file changed, 109 insertions(+), 87 deletions(-) diff --git a/cli/src/operation_templater.rs b/cli/src/operation_templater.rs index 3d4efbba79..e8e13dc8b5 100644 --- a/cli/src/operation_templater.rs +++ b/cli/src/operation_templater.rs @@ -21,11 +21,10 @@ use jj_lib::operation::Operation; use crate::formatter::Formatter; use crate::template_builder::{ - self, BuildContext, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateLanguage, -}; -use crate::template_parser::{ - self, FunctionCallNode, TemplateAliasesMap, TemplateParseError, TemplateParseResult, + self, BuildContext, CoreTemplatePropertyKind, IntoTemplateProperty, TemplateBuildMethodFnMap, + TemplateLanguage, }; +use crate::template_parser::{self, FunctionCallNode, TemplateAliasesMap, TemplateParseResult}; use crate::templater::{ IntoTemplate, PlainTextFormattedProperty, Template, TemplateFunction, TemplateProperty, TemplatePropertyFn, TimestampRange, @@ -34,6 +33,7 @@ use crate::templater::{ struct OperationTemplateLanguage { root_op_id: OperationId, current_op_id: Option, + build_fn_table: OperationTemplateBuildFnTable, } impl TemplateLanguage<'static> for OperationTemplateLanguage { @@ -58,10 +58,14 @@ impl TemplateLanguage<'static> for OperationTemplateLanguage { template_builder::build_core_method(self, build_ctx, property, function) } OperationTemplatePropertyKind::Operation(property) => { - build_operation_method(self, build_ctx, property, function) + let table = &self.build_fn_table.operation_methods; + let build = template_parser::lookup_method("Operation", table, function)?; + build(self, build_ctx, property, function) } OperationTemplatePropertyKind::OperationId(property) => { - build_operation_id_method(self, build_ctx, property, function) + let table = &self.build_fn_table.operation_id_methods; + let build = template_parser::lookup_method("OperationId", table, function)?; + build(self, build_ctx, property, function) } } } @@ -124,67 +128,91 @@ impl IntoTemplateProperty<'static, Operation> for OperationTemplatePropertyKind } } -fn build_operation_method( - language: &OperationTemplateLanguage, - _build_ctx: &BuildContext, - self_property: impl TemplateProperty + 'static, - function: &FunctionCallNode, -) -> TemplateParseResult { - let property = match function.name { - "current_operation" => { +/// Table of functions that translate method call node of self type `T`. +type OperationTemplateBuildMethodFnMap = + TemplateBuildMethodFnMap<'static, OperationTemplateLanguage, T>; + +/// Symbol table of methods available in the operation template. +struct OperationTemplateBuildFnTable { + // TODO: add core methods/functions table + operation_methods: OperationTemplateBuildMethodFnMap, + operation_id_methods: OperationTemplateBuildMethodFnMap, +} + +impl OperationTemplateBuildFnTable { + /// Creates new symbol table containing the builtin methods. + fn builtin() -> Self { + OperationTemplateBuildFnTable { + operation_methods: builtin_operation_methods(), + operation_id_methods: builtin_operation_id_methods(), + } + } +} + +fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap { + // Not using maplit::hashmap!{} or custom declarative macro here because + // code completion inside macro is quite restricted. + let mut map = OperationTemplateBuildMethodFnMap::::new(); + map.insert( + "current_operation", + |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; let current_op_id = language.current_op_id.clone(); - language.wrap_boolean(TemplateFunction::new(self_property, move |op| { + let out_property = TemplateFunction::new(self_property, move |op| { Some(op.id()) == current_op_id.as_ref() - })) - } - "description" => { + }); + Ok(language.wrap_boolean(out_property)) + }, + ); + map.insert( + "description", + |language, _build_ctx, self_property, function| { template_parser::expect_no_arguments(function)?; - language.wrap_string(TemplateFunction::new(self_property, |op| { - op.metadata().description.clone() - })) - } - "id" => { - template_parser::expect_no_arguments(function)?; - language.wrap_operation_id(TemplateFunction::new(self_property, |op| op.id().clone())) - } - "tags" => { - template_parser::expect_no_arguments(function)?; - language.wrap_string(TemplateFunction::new(self_property, |op| { - // TODO: introduce map type - op.metadata() - .tags - .iter() - .map(|(key, value)| format!("{key}: {value}")) - .join("\n") - })) - } - "time" => { - template_parser::expect_no_arguments(function)?; - language.wrap_timestamp_range(TemplateFunction::new(self_property, |op| { - TimestampRange { - start: op.metadata().start_time.clone(), - end: op.metadata().end_time.clone(), - } - })) - } - "user" => { - template_parser::expect_no_arguments(function)?; - language.wrap_string(TemplateFunction::new(self_property, |op| { - // TODO: introduce dedicated type and provide accessors? - format!("{}@{}", op.metadata().username, op.metadata().hostname) - })) - } - "root" => { - template_parser::expect_no_arguments(function)?; - let root_op_id = language.root_op_id.clone(); - language.wrap_boolean(TemplateFunction::new(self_property, move |op| { - op.id() == &root_op_id - })) - } - _ => return Err(TemplateParseError::no_such_method("Operation", function)), - }; - Ok(property) + let out_property = + TemplateFunction::new(self_property, |op| op.metadata().description.clone()); + Ok(language.wrap_string(out_property)) + }, + ); + map.insert("id", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |op| op.id().clone()); + Ok(language.wrap_operation_id(out_property)) + }); + map.insert("tags", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |op| { + // TODO: introduce map type + op.metadata() + .tags + .iter() + .map(|(key, value)| format!("{key}: {value}")) + .join("\n") + }); + Ok(language.wrap_string(out_property)) + }); + map.insert("time", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |op| TimestampRange { + start: op.metadata().start_time.clone(), + end: op.metadata().end_time.clone(), + }); + Ok(language.wrap_timestamp_range(out_property)) + }); + map.insert("user", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let out_property = TemplateFunction::new(self_property, |op| { + // TODO: introduce dedicated type and provide accessors? + format!("{}@{}", op.metadata().username, op.metadata().hostname) + }); + Ok(language.wrap_string(out_property)) + }); + map.insert("root", |language, _build_ctx, self_property, function| { + template_parser::expect_no_arguments(function)?; + let root_op_id = language.root_op_id.clone(); + let out_property = TemplateFunction::new(self_property, move |op| op.id() == &root_op_id); + Ok(language.wrap_boolean(out_property)) + }); + map } impl Template<()> for OperationId { @@ -193,30 +221,23 @@ impl Template<()> for OperationId { } } -fn build_operation_id_method( - language: &OperationTemplateLanguage, - build_ctx: &BuildContext, - self_property: impl TemplateProperty + 'static, - function: &FunctionCallNode, -) -> TemplateParseResult { - let property = match function.name { - "short" => { - let ([], [len_node]) = template_parser::expect_arguments(function)?; - let len_property = len_node - .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) - .transpose()?; - language.wrap_string(TemplateFunction::new( - (self_property, len_property), - |(id, len)| { - let mut hex = id.hex(); - hex.truncate(len.map_or(12, |l| l.try_into().unwrap_or(0))); - hex - }, - )) - } - _ => return Err(TemplateParseError::no_such_method("OperationId", function)), - }; - Ok(property) +fn builtin_operation_id_methods() -> OperationTemplateBuildMethodFnMap { + // Not using maplit::hashmap!{} or custom declarative macro here because + // code completion inside macro is quite restricted. + let mut map = OperationTemplateBuildMethodFnMap::::new(); + map.insert("short", |language, build_ctx, self_property, function| { + let ([], [len_node]) = template_parser::expect_arguments(function)?; + let len_property = len_node + .map(|node| template_builder::expect_integer_expression(language, build_ctx, node)) + .transpose()?; + let out_property = TemplateFunction::new((self_property, len_property), |(id, len)| { + let mut hex = id.hex(); + hex.truncate(len.map_or(12, |l| l.try_into().unwrap_or(0))); + hex + }); + Ok(language.wrap_string(out_property)) + }); + map } pub fn parse( @@ -228,6 +249,7 @@ pub fn parse( let language = OperationTemplateLanguage { root_op_id: root_op_id.clone(), current_op_id: current_op_id.cloned(), + build_fn_table: OperationTemplateBuildFnTable::builtin(), }; let node = template_parser::parse(template_text, aliases_map)?; template_builder::build(&language, &node)