This is basically a copy of revset::RevsetAliasesMap. We could extract a
common table struct, but that wouldn't be worth the effort since the core
alias substitution logic can't be easily abstracted.
This prepares for template aliases support #1190. Unlike revset, template
expressions can be of various types, whereas alias substitution will process
untyped nodes. That's one reason that ExpressionNode is closer to parsed tree
than evaluatable Property structs. Another reason is that it's uneasy to split
name/type checking into "parsing" and "building property function" stages.
We could do alias expansion at once while building Property functions, but
that would make testing harder because Property isn't Debug + PartialEq.
I'm going to split 'parse() -> Expression' functions into 'parse() -> AST'
and 'build(AST) -> Expression'. The duplicated functions will be baseline of
new 'parse() -> AST' functions.
This will be used as a parameter of id.shortest*() methods. For now, only
decimal literal is supported, and there's no unary negate operator.
"0"-prefix is disallowed because it looks like an octal number.
I don't think we would want multiple integer types in the template language,
so I chose i64 as the integer type of reasonable width.
This prepares for adding method arguments support. Since a method argument
should be evaluated in the surrounding scope, its type will be
'TemplateProperty<I>', not 'TemplateProperty<J>' for the receiver type 'J'.
Then, the receiver of type 'TemplateProperty<I, Output = J>', and arguments
of type 'TemplateProperty<I, Output = Pn>' will be composed to an input of
type 'TemplateProperty<I, Output = (J, Pn...)>'.
wrap_fn() is removed since it's only useful for nullary methods, and we
no longer need Box<dyn> to abstract the return value.
We'll probably need a better abstraction, but a parameterized keyword
function is a good first step. This unblocks the use of parse_term() for
context-less properties (or literals). I'm going to add a proper support
for context-aware method arguments, but literals can be parsed without it.
parse_keyword() closure is passed by reference to avoid infinite expansion.
I believe this isn't a good abstraction, but I need to add one more variant
for "at least n arguments" error. A stringified count like "2 to 3" could be
embedded in an error variant, but I don't think it's good idea to build error
message in that way.
Maybe I didn't make this change before because the closure needs to capture
WorkspaceId by value. Since the inlined version looks pretty simple, let's
go forward to do that.
Even though the template syntax is experimental, panicking parser makes
it difficult to write tests. So let's add minimal error handling. The error
types are basically copied from the revset module.
I made write_commit_summary() fall back to the default template if user
template had syntax error. It should be better than reporting parse error
after e.g. "jj abandon" finished successfully.
I have no idea whether or not any template expressions are intentionally
allowed as a label, but it makes sense to write something like
'label("phase-" phase, ...)' (if we had a phase keyword.) So I decided to
add .into_plain_text() instead of stricter .try_into_string().
Perhaps, non-trivial keywords can be extracted to free functions, and both
parsing and property functions can be eventually moved to a commit templater
module?
This allows us to merge parse_boolean_commit_property() into
parse_commit_term(). We'll probably need similar type coercion methods
for the other basic types.
I considered adding something like Property::Template(), which could be
reused for map operation (e.g. 'revset().map(commit_id).join(" ")'.)
However, a mapped commit template would be different from the top-level
commit template regarding the lifetime of the context.
"Expression::<Commit>::Template()" takes "for<'b> &'b Commit" as an argument,
whereas a mapped template property would capture Commit object somewhere.
Many template keywords and methods are one liners, and I think that's actually
good because writing tests for templater would be more involved than for pure
functions.
This patch introduces a wrapper for such one-line functions, and migrates
method parser to that. Some of the commit keyword structs can also be ported
to this wrapper.
Before, "f()" was parsed as a function call with one empty argument. In
practice, this change means "if(divergent,,false_template)" is no longer
valid.