Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qute - ignore parentheses when parsing a value of an expression #29154

Merged
merged 1 commit into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}

}