Skip to content

Commit

Permalink
templater: migrate operation template methods to symbol table
Browse files Browse the repository at this point in the history
  • Loading branch information
yuja committed Feb 27, 2024
1 parent 10acfdc commit 9ad2fee
Showing 1 changed file with 109 additions and 87 deletions.
196 changes: 109 additions & 87 deletions cli/src/operation_templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -34,6 +33,7 @@ use crate::templater::{
struct OperationTemplateLanguage {
root_op_id: OperationId,
current_op_id: Option<OperationId>,
build_fn_table: OperationTemplateBuildFnTable,
}

impl TemplateLanguage<'static> for OperationTemplateLanguage {
Expand All @@ -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)
}
}
}
Expand Down Expand Up @@ -124,67 +128,91 @@ impl IntoTemplateProperty<'static, Operation> for OperationTemplatePropertyKind
}
}

fn build_operation_method(
language: &OperationTemplateLanguage,
_build_ctx: &BuildContext<OperationTemplatePropertyKind>,
self_property: impl TemplateProperty<Operation, Output = Operation> + 'static,
function: &FunctionCallNode,
) -> TemplateParseResult<OperationTemplatePropertyKind> {
let property = match function.name {
"current_operation" => {
/// Table of functions that translate method call node of self type `T`.
type OperationTemplateBuildMethodFnMap<T> =
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>,
operation_id_methods: OperationTemplateBuildMethodFnMap<OperationId>,
}

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<Operation> {
// Not using maplit::hashmap!{} or custom declarative macro here because
// code completion inside macro is quite restricted.
let mut map = OperationTemplateBuildMethodFnMap::<Operation>::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 {
Expand All @@ -193,30 +221,23 @@ impl Template<()> for OperationId {
}
}

fn build_operation_id_method(
language: &OperationTemplateLanguage,
build_ctx: &BuildContext<OperationTemplatePropertyKind>,
self_property: impl TemplateProperty<Operation, Output = OperationId> + 'static,
function: &FunctionCallNode,
) -> TemplateParseResult<OperationTemplatePropertyKind> {
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<OperationId> {
// Not using maplit::hashmap!{} or custom declarative macro here because
// code completion inside macro is quite restricted.
let mut map = OperationTemplateBuildMethodFnMap::<OperationId>::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(
Expand All @@ -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)
Expand Down

0 comments on commit 9ad2fee

Please sign in to comment.