ok/jj
1
0
Fork 0
forked from mirrors/jj

templater: add boolean literals

They are implemented as literal expressions so that user cannot override
them by false and true aliases.
This commit is contained in:
Yuya Nishihara 2023-09-02 17:49:21 +09:00
parent 08e66fb7d4
commit 4394bf8de8
4 changed files with 87 additions and 21 deletions

View file

@ -764,6 +764,10 @@ pub fn build_expression<'a, L: TemplateLanguage<'a>>(
Ok(Expression::with_label(property, *name))
}
}
ExpressionKind::Boolean(value) => {
let property = language.wrap_boolean(Literal(*value));
Ok(Expression::unlabeled(property))
}
ExpressionKind::Integer(value) => {
let property = language.wrap_integer(Literal(*value));
Ok(Expression::unlabeled(property))

View file

@ -195,6 +195,7 @@ impl<'i> ExpressionNode<'i> {
#[derive(Clone, Debug, PartialEq)]
pub enum ExpressionKind<'i> {
Identifier(&'i str),
Boolean(bool),
Integer(i64),
String(String),
Concat(Vec<ExpressionNode<'i>>),
@ -226,6 +227,27 @@ pub struct LambdaNode<'i> {
pub body: Box<ExpressionNode<'i>>,
}
fn parse_identifier_or_literal(pair: Pair<Rule>) -> ExpressionKind {
assert_eq!(pair.as_rule(), Rule::identifier);
match pair.as_str() {
"false" => ExpressionKind::Boolean(false),
"true" => ExpressionKind::Boolean(true),
name => ExpressionKind::Identifier(name),
}
}
fn parse_identifier_name(pair: Pair<Rule>) -> TemplateParseResult<&str> {
let span = pair.as_span();
if let ExpressionKind::Identifier(name) = parse_identifier_or_literal(pair) {
Ok(name)
} else {
Err(TemplateParseError::unexpected_expression(
"Expected identifier",
span,
))
}
}
fn parse_string_literal(pair: Pair<Rule>) -> String {
assert_eq!(pair.as_rule(), Rule::literal);
let mut result = String::new();
@ -251,13 +273,10 @@ fn parse_string_literal(pair: Pair<Rule>) -> String {
fn parse_formal_parameters(params_pair: Pair<Rule>) -> TemplateParseResult<Vec<&str>> {
assert_eq!(params_pair.as_rule(), Rule::formal_parameters);
let params_span = params_pair.as_span();
let params = params_pair
let params: Vec<_> = params_pair
.into_inner()
.map(|pair| match pair.as_rule() {
Rule::identifier => pair.as_str(),
r => panic!("unexpected formal parameter rule {r:?}"),
})
.collect_vec();
.map(parse_identifier_name)
.try_collect()?;
if params.iter().all_unique() {
Ok(params)
} else {
@ -271,18 +290,19 @@ fn parse_formal_parameters(params_pair: Pair<Rule>) -> TemplateParseResult<Vec<&
fn parse_function_call_node(pair: Pair<Rule>) -> TemplateParseResult<FunctionCallNode> {
assert_eq!(pair.as_rule(), Rule::function);
let mut inner = pair.into_inner();
let name = inner.next().unwrap();
let name_pair = inner.next().unwrap();
let name_span = name_pair.as_span();
let args_pair = inner.next().unwrap();
let args_span = args_pair.as_span();
assert_eq!(name.as_rule(), Rule::identifier);
assert_eq!(args_pair.as_rule(), Rule::function_arguments);
let name = parse_identifier_name(name_pair)?;
let args = args_pair
.into_inner()
.map(parse_template_node)
.try_collect()?;
Ok(FunctionCallNode {
name: name.as_str(),
name_span: name.as_span(),
name,
name_span,
args,
args_span,
})
@ -319,7 +339,7 @@ fn parse_term_node(pair: Pair<Rule>) -> TemplateParseResult<ExpressionNode> {
})?;
ExpressionNode::new(ExpressionKind::Integer(value), span)
}
Rule::identifier => ExpressionNode::new(ExpressionKind::Identifier(expr.as_str()), span),
Rule::identifier => ExpressionNode::new(parse_identifier_or_literal(expr), span),
Rule::function => {
let function = parse_function_call_node(expr)?;
ExpressionNode::new(ExpressionKind::FunctionCall(function), span)
@ -431,13 +451,15 @@ impl TemplateAliasDeclaration {
let mut pairs = TemplateParser::parse(Rule::alias_declaration, source)?;
let first = pairs.next().unwrap();
match first.as_rule() {
Rule::identifier => Ok(TemplateAliasDeclaration::Symbol(first.as_str().to_owned())),
Rule::identifier => {
let name = parse_identifier_name(first)?.to_owned();
Ok(TemplateAliasDeclaration::Symbol(name))
}
Rule::function_alias_declaration => {
let mut inner = first.into_inner();
let name_pair = inner.next().unwrap();
let params_pair = inner.next().unwrap();
assert_eq!(name_pair.as_rule(), Rule::identifier);
let name = name_pair.as_str().to_owned();
let name = parse_identifier_name(name_pair)?.to_owned();
let params = parse_formal_parameters(params_pair)?
.into_iter()
.map(|s| s.to_owned())
@ -544,8 +566,9 @@ pub fn expand_aliases<'i>(
Ok(node)
}
}
ExpressionKind::Integer(_) => Ok(node),
ExpressionKind::String(_) => Ok(node),
ExpressionKind::Boolean(_) | ExpressionKind::Integer(_) | ExpressionKind::String(_) => {
Ok(node)
}
ExpressionKind::Concat(nodes) => {
node.kind = ExpressionKind::Concat(expand_list(nodes, state)?);
Ok(node)
@ -676,6 +699,7 @@ pub fn expect_string_literal_with<'a, 'i, T>(
match &node.kind {
ExpressionKind::String(s) => f(s, node.span),
ExpressionKind::Identifier(_)
| ExpressionKind::Boolean(_)
| ExpressionKind::Integer(_)
| ExpressionKind::Concat(_)
| ExpressionKind::FunctionCall(_)
@ -698,6 +722,7 @@ pub fn expect_lambda_with<'a, 'i, T>(
ExpressionKind::Lambda(lambda) => f(lambda, node.span),
ExpressionKind::String(_)
| ExpressionKind::Identifier(_)
| ExpressionKind::Boolean(_)
| ExpressionKind::Integer(_)
| ExpressionKind::Concat(_)
| ExpressionKind::FunctionCall(_)
@ -774,6 +799,7 @@ mod tests {
let normalized_kind = match node.kind {
ExpressionKind::Identifier(_)
| ExpressionKind::Boolean(_)
| ExpressionKind::Integer(_)
| ExpressionKind::String(_) => node.kind,
ExpressionKind::Concat(nodes) => ExpressionKind::Concat(normalize_list(nodes)),
@ -844,6 +870,11 @@ mod tests {
assert!(parse_template(r#" label("","") "#).is_ok());
assert!(parse_template(r#" label("","",) "#).is_ok());
assert!(parse_template(r#" label("",,"") "#).is_err());
// Boolean literal cannot be used as a function name
assert!(parse_template("false()").is_err());
// Function arguments can be any expression
assert!(parse_template("f(false)").is_ok());
}
#[test]
@ -908,6 +939,24 @@ mod tests {
parse_template("|x, x| a").unwrap_err().kind,
TemplateParseErrorKind::RedefinedFunctionParameter
);
// Boolean literal cannot be used as a parameter name
assert!(parse_template("|false| a").is_err());
}
#[test]
fn test_keyword_literal() {
assert_eq!(parse_into_kind("false"), Ok(ExpressionKind::Boolean(false)));
assert_eq!(parse_into_kind("(true)"), Ok(ExpressionKind::Boolean(true)));
// Keyword literals are case sensitive
assert_eq!(
parse_into_kind("False"),
Ok(ExpressionKind::Identifier("False")),
);
assert_eq!(
parse_into_kind("tRue"),
Ok(ExpressionKind::Identifier("tRue")),
);
}
#[test]
@ -965,6 +1014,11 @@ mod tests {
TemplateParseErrorKind::RedefinedFunctionParameter
);
// Boolean literal cannot be used as a symbol, function, or parameter name
assert!(aliases_map.insert("false", r#"""#).is_err());
assert!(aliases_map.insert("true()", r#"""#).is_err());
assert!(aliases_map.insert("f(false)", r#"""#).is_err());
// Trailing comma isn't allowed for empty parameter
assert!(aliases_map.insert("f(,)", r#"""#).is_err());
// Trailing comma is allowed for the last parameter

View file

@ -138,6 +138,14 @@ fn test_templater_parse_error() {
|
= Function "foo" doesn't exist
"###);
insta::assert_snapshot!(render_err(r#"false()"#), @r###"
Error: Failed to parse template: --> 1:1
|
1 | false()
| ^---^
|
= Expected identifier
"###);
insta::assert_snapshot!(render_err(r#"description.first_line().foo()"#), @r###"
Error: Failed to parse template: --> 1:26
@ -771,13 +779,13 @@ fn test_templater_separate_function() {
// Conditional template
insta::assert_snapshot!(
render(r#"separate(" ", "a", if("t", ""))"#), @"a");
render(r#"separate(" ", "a", if(true, ""))"#), @"a");
insta::assert_snapshot!(
render(r#"separate(" ", "a", if("t", "", "f"))"#), @"a");
render(r#"separate(" ", "a", if(true, "", "f"))"#), @"a");
insta::assert_snapshot!(
render(r#"separate(" ", "a", if("", "t", ""))"#), @"a");
render(r#"separate(" ", "a", if(false, "t", ""))"#), @"a");
insta::assert_snapshot!(
render(r#"separate(" ", "a", if("t", "t", "f"))"#), @"a t");
render(r#"separate(" ", "a", if(true, "t", "f"))"#), @"a t");
// Separate keywords
insta::assert_snapshot!(

View file

@ -75,7 +75,7 @@ The following functions are defined.
### Boolean type
No methods are defined.
No methods are defined. Can be constructed with `false` or `true` literal.
### Commit type