Skip to content

Commit

Permalink
Qute - ignore parentheses when parsing a value of an expression
Browse files Browse the repository at this point in the history
- this makes it possible to use the infix notation in section parameters
and default values of type-safe paramerer declarations, e.g. "{#let foo
= (val ? 1 : 2)}"
  • Loading branch information
mkouba committed Nov 9, 2022
1 parent d3d7663 commit d7b6d8a
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 9 deletions.
21 changes: 13 additions & 8 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -576,9 +576,13 @@ It may be empty, i.e. the start tag ends with `/`: `{#myEmptySection /}`.
Sections usually contain nested expressions and other sections.
The end tag starts with `/` and contains the name of the section (optional): `{#if foo}Foo!{/if}` or `{#if foo}Foo!{/}`.

The start tag can also define parameters: `{#if item.isActive}`.
Parameters are separated by one or more spaces.
However, parameters can have optional names separated by the equals sign prefixed and suffixed with any number of spaces, e.g. `{#let id='Foo'}` and `{#let id = 'Foo'}` are equivalents where the name of the parameter is `id` and the value is `Foo`.
A start tag can define parameters with optional names, e.g. `{#if item.isActive}` and `{#let foo=1 bar=false}`.
Parameters are separated by one or more spaces.
Names are separated from the values by the equals sign.
Names and values can be prefixed and suffixed with any number of spaces, e.g. `{#let id='Foo'}` and `{#let id = 'Foo'}` are equivalents where the name of the parameter is `id` and the value is `Foo`.
Values can be grouped using parentheses, e.g. `{#let id=(item.id ?: 42)}` where the name is `id` and the value is `item.id ?: 42`.
Sections can interpret parameter values in any way, e.g. take the value as is.
However, in most cases the parameter value is registered as an <<expressions,expression>> and evaluated before use.

A section may contain several content *blocks*.
The "main" block is always present.
Expand Down Expand Up @@ -903,14 +907,15 @@ This section allows you to define named local variables:

[source,html]
----
{#let myParent=order.item.parent isActive=false age=10} <1>
{#let myParent=order.item.parent isActive=false age=10 price=(order.price + 10)} <1><2>
<h1>{myParent.name}</h1>
Is active: {isActive}
Age: {age}
{/let} <2>
{/let} <3>
----
<1> The local variable is initialized with an expression that can also represent a <<literals,literal>>.
<2> Keep in mind that the variable is not available outside the `let` section that defines it.
<1> The local variable is initialized with an expression that can also represent a <<literals,literal>>, i.e. `isActive=false` and `age=10`.
<2> The infix notation is only supported if parentheses are used for grouping, e.g. `price=(order.price + 10)` is equivalent to `price=order.price.plus(10)`.
<3> Keep in mind that the variable is not available outside the `let` section that defines it.

If a key of a section parameter (aka the name of the local variable) ends with a `?` then the local variable is only set if the key without the `?` suffix resolves to `null` or _"not found"_:

Expand Down Expand Up @@ -1547,7 +1552,7 @@ On the other hand, if the value is set (e.g. via `TemplateInstance.data("foo", "

The type of a default value must be assignable to the type of the parameter declaration, i.e. the following parameter declaration is incorrect and results in a build failure: `{@org.acme.Foo foo=1}`.

TIP: The default value is actually an <<expressions,expression>>. So the default value does not have to be a literal (such as `42` or `true`). For example, you can leverage the `@TemplateEnum` and specify an enum constant as a default value of a parameter declaration: `{@org.acme.MyEnum myEnum=MyEnum:FOO}`. However, the infix notation is not supported in default values.
TIP: The default value is actually an <<expressions,expression>>. So the default value does not have to be a literal (such as `42` or `true`). For example, you can leverage the `@TemplateEnum` and specify an enum constant as a default value of a parameter declaration: `{@org.acme.MyEnum myEnum=MyEnum:FOO}`. However, the infix notation is not supported in default values unless the parentheses are used for grouping, e.g. `{@org.acme.Foo foo=(foo1 ?: foo2)}``.

IMPORTANT: The type of a default value is not validated in <<standalone, Qute standalone>>.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,10 @@ static ExpressionImpl parseExpression(Supplier<Integer> idGenerator, String valu
if (value == null || value.isEmpty()) {
return ExpressionImpl.EMPTY;
}
// (foo ?: bar) -> foo ?: bar
if (value.charAt(0) == START_COMPOSITE_PARAM && value.charAt(value.length() - 1) == END_COMPOSITE_PARAM) {
value = value.substring(1, value.length() - 1);
}
String namespace = null;
int namespaceIdx = value.indexOf(NAMESPACE_SEPARATOR);
int spaceIdx;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ default boolean treatUnknownSectionsAsBlocks() {
}

/**
* Initialize a new helper instance for a specific section node in a template.
*
* @param context
* @return a new helper instance
Expand All @@ -103,6 +104,12 @@ default boolean treatUnknownSectionsAsBlocks() {

/**
* Initialize a section block.
* <p>
* All section blocks are initialized before {@link #initialize(SectionInitContext)} is called.
* <p>
* The factory is responsible to register all expression via {@link BlockInfo#addExpression(String, String)}. The expression
* can be then used during {@link #initialize(SectionInitContext)} via {@link SectionInitContext#getExpression(String)} and
* {@link SectionBlock#expressions}.
*
* @return a new scope if this section introduces a new scope, or the outer scope
* @see BlockInfo#addExpression(String, String)
Expand Down Expand Up @@ -200,7 +207,7 @@ default public String getParameterOrDefault(String name, String defaultValue) {
* first.
*
* @param parameterName
* @return an expression registered for the specified param name, or {@code null}
* @return the expression registered for the main block under the specified param name, or {@code null}
* @see BlockInfo#addExpression(String, String)
*/
public Expression getExpression(String parameterName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;

import java.util.List;

Expand All @@ -20,6 +21,16 @@ public void testDefaultValue() {
assertDefaultValue(engine.parse("{@java.lang.String val= 'foo and bar'}\n{val.toUpperCase}"), "FOO AND BAR");
}

@Test
public void testDefaultValueWithComposite() {
Engine engine = Engine.builder().addDefaults().addValueResolver(new ReflectionValueResolver()).build();
Template template = engine.parse("{@java.lang.String val=(foo or bar)}{val}");
assertEquals("barbar", template.data("bar", "barbar").render());
Expression fooExpr = template.getExpressions().stream().filter(e -> !e.isLiteral()).findFirst().orElse(null);
assertNotNull(fooExpr);
assertNull(fooExpr.collectTypeInfo());
}

@Test
public void testMultipleDefaultValues() {
Engine engine = Engine.builder().addDefaults().addValueResolver(new ReflectionValueResolver()).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,16 @@ public void testParameterOrigin() {
}
}

@Test
public void testCompositeParams() {
Engine engine = Engine.builder().addDefaults().addValueResolver(new ReflectionValueResolver()).build();
assertEquals("1x2x::false",
engine.parse(
"{#let foo=(baz + 1) bar=(name ? true : false)}"
+ "{#for i in foo}{i_count}x{/for}::{bar}"
+ "{/let}")
.data("baz", 1)
.render());
}

}

0 comments on commit d7b6d8a

Please sign in to comment.