Skip to content
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
54 changes: 34 additions & 20 deletions oap-formats/oap-template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,42 +204,49 @@ Supported literal types:

### Concatenation

Concatenation combines multiple fields and string literals into a single output without a separator.
`+` is the string concatenation operator. All items are rendered as strings and joined without any separator character between them. Items can be: field names, double-quoted strings, single-quoted strings, decimal integers, floats.

Root concatenation (the whole expression is a concat):
> **`+` is string concatenation** when the left operand is a non-numeric field, or when more than two items are joined. When the left is a numeric field and the right is a single numeric literal, `+` is numeric addition (see [Math](#math)).

Top-level concatenation (no braces needed):

```
${ {field1, "/", field2} }
{{ field1 + "/" + field2 }}
${ field1 + "/" + field2 }
{{ stringField + 'x' + 10 + intField }} → "strx10456"
```

Suffix concatenation after a path:
Scoped concatenation — items resolved relative to a scope path:

```
{{ child{field1, "x", field2} }}
{{ child.{field1, "x", field2} }}
{{ child{field1 + "x" + field2} }}
{{ child.{field1 + "x" + field2} }}
```

Items inside `{}` can be: field names, double-quoted strings, single-quoted strings, decimal integers, floats.

```
{{ {scheme, "://", host, "/", path} }} → "https://example.com/api"
{{ scheme + "://" + host + "/" + path }} → "https://example.com/api"
{{ child{intField + "_" + field} }} → "42_hello"
```

### Math

```
{{ numericField + 12.45 }}
{{ intField + 10 }}
{{ doubleField + 3.3 }}
{{ intField * 2 }}
{{ price - discount }}
{{ total / count }}
{{ value % 100 }}
```

Operators: `+`, `-`, `*`, `/`, `%`. The right-hand operand must be a numeric literal. The result type is widened as needed.
Operators: `+`, `-`, `*`, `/`, `%`. The right-hand operand must be a numeric literal (integer or float). The result type is widened as needed.

> **`+` is numeric addition when the left operand is a numeric field** (int, long, float, double, etc.). When the left operand is a non-numeric field or there are more than two items, `+` is string concatenation — see [Concatenation](#concatenation).

```
{{ score + 100 }} → score value + 100
{{ price * 1.1 }} → price * 1.1
{{ intField + 10 }} → intField value + 10
{{ price * 1.1 }} → price × 1.1
{{ score - 5 }} → score - 5
```

### If / then / else (inline)
Expand Down Expand Up @@ -401,39 +408,46 @@ Any field type may appear in a `{{% if … }}` condition. The field value is coe

### With scope (inline)

`{{ with (scopePath) bodyExpr end }}` — evaluates `bodyExpr` relative to the object resolved by `scopePath`. At compile time the scope path is prepended to each body expression, so this is purely syntactic sugar for chained field access.
`{{ scope{bodyExpr} }}` — evaluates `bodyExpr` relative to the object resolved by `scope`. At compile time the scope path is prepended to each body expression, so this is purely syntactic sugar for chained field access.

```
{{ with (child) field end }}
{{ child{field} }}
```
is equivalent to `{{ child.field }}`.

If `scopePath` resolves to null, the body expression renders its default value, or empty string if no default is set.
If `scope` resolves to null, the body expression renders its default value, or empty string if no default is set.

**With a default:**

```
{{ with (child) field ?? 'n/a' end }}
{{ child{field} ?? 'n/a' }}
```

Renders `n/a` when `child` is null or `child.field` is null.

**Fallback chain in body:**

```
{{ with (child) field | default field2 end }}
{{ child{field | default field2} }}
```

Both alternatives are evaluated against `child` (expanded to `child.field | default child.field2`); the first non-null result is used.

**Root scope (`$`):** prefix any body expression with `$.` to resolve it from the root object instead of the `with` scope:
**Root scope (`$`):** prefix any body expression with `$.` to resolve it from the root object instead of the scope:

```
{{ with (child) field | default $.rootField end }}
{{ child{field | default $.rootField} }}
```

When `child.field` is null, `$.rootField` is resolved from the root object.

**Concatenation inside scope:** when `+` separates two or more items inside `{}`, the result is a string concatenation of all items rendered in the context of `scope`:

```
{{ child{field1 + "-" + field2} }}
{{ parent{str1 + 'f' + str2 + 6} }}
```

### With scope (block)

`{{% with scopePath }} … {{% end %}}` — all `{{ expr }}` blocks inside the body are resolved relative to the object at `scopePath`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,38 @@ ifCode returns [IfCondition ret]
;

withCode returns [WithCondition ret]
: WITH LPAREN scopePath=exprs RPAREN bodyExprs=exprsCode END {
: scopePath=exprs LBRACE concatItems=concatBody RBRACE {
Exprs bodyExprs = new Exprs();
bodyExprs.concatenation = new Concatenation( $concatItems.ret );
ArrayList<Exprs> body = new ArrayList<>();
body.add( bodyExprs );
$ret = new WithCondition( $scopePath.ret, body );
}
| scopePath=exprs LBRACE bodyExprs=exprsCode RBRACE {
$ret = new WithCondition( $scopePath.ret, $bodyExprs.ret );
}
;

concatBody returns [ArrayList<Object> ret = new ArrayList<>()]
: citem { $ret.add( $citem.ret ); }
( PLUS citem { $ret.add( $citem.ret ); } )+
;

topLevelConcat returns [Exprs ret = new Exprs()]
: first=citem {
$ret.concatenation = new Concatenation( new ArrayList<>() );
$ret.concatenation.items.add( $first.ret );
}
( PLUS next=citem {
$ret.concatenation.items.add( $next.ret );
} )+
;

exprsCode returns [ArrayList<Exprs> ret = new ArrayList<>()]
: exprs orExprs {
: topLevelConcat {
$ret.add( $topLevelConcat.ret );
}
| exprs orExprs {
$ret.add( $exprs.ret );
$ret.addAll( $orExprs.ret );
}
Expand Down Expand Up @@ -171,7 +196,6 @@ exprs returns [Exprs ret = new Exprs()]
DOT expr { $ret.exprs.add( $expr.ret ); }
DOT? concatenation { $ret.concatenation = $concatenation.ret; }
math? { if( $math.ctx != null ) $ret.math = $math.ret; }
| concatenation { $ret.concatenation = $concatenation.ret; }
;

expr returns [Expr ret]
Expand All @@ -184,16 +208,16 @@ concatenation returns [Concatenation ret]
;

citems returns [ArrayList<Object> ret = new ArrayList<>()]
: citem { $ret.add($citem.ret); }
( COMMA citem { $ret.add($citem.ret); } )*
: citem { $ret.add( $citem.ret ); }
( PLUS citem { $ret.add( $citem.ret ); } )*
;

citem returns [Object ret]
: ID { $ret = new Expr( $ID.text, false, List.of() ); }
| DSTRING { $ret = sdStringToString( $DSTRING.text ); }
| SSTRING { $ret = sdStringToString( $SSTRING.text ); }
| DECDIGITS { $ret = String.valueOf( $DECDIGITS.text ); }
| FLOAT { $ret = String.valueOf( $FLOAT.text ); }
| DECDIGITS { $ret = new NumericLiteral( $DECDIGITS.text ); }
| FLOAT { $ret = new NumericLiteral( $FLOAT.text ); }
;

math returns [Math ret]
Expand All @@ -209,7 +233,6 @@ mathOperation
: STAR
| SLASH
| PERCENT
| PLUS
| MINUS
;

Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ IF : 'if' ;
THEN : 'then' ;
ELSE : 'else' ;
END : 'end' ;
WITH : 'with' ;
AND : 'and' ;
OR : 'or' ;
NOT : 'not' ;
Expand Down Expand Up @@ -144,7 +143,11 @@ C_HORZ_WS : Hws+ -> skip ;
C_VERT_WS : Vws+ -> skip ;

CRBRACE : RBrace -> popMode, type(RBRACE) ;
CCOMMA : Comma -> type(COMMA) ;
CPLUS : Plus -> type(PLUS) ;
CDOT : Dot -> type(DOT) ;
CDEFAULT : Pipe Hws* Default -> type(DEFAULT) ;
CVAR_ID : '$' NameChar (NameChar|DecDigit)* -> type(VAR_ID) ;
CROOT : '$' -> type(ROOT) ;

CID : NameChar (NameChar|DecDigit)* -> type(ID) ;
CDSTRING : DQuoteLiteral -> type(DSTRING) ;
Expand Down
Loading
Loading