Skip to content

Expressions

An expression is what appears before the first | in an expression chain (see Transforms for the pipe itself). It is either a single operand, or exactly one binary comparison between two operands — kBars does not chain multiple operators in a single expression without parentheses. An operand is either a primary value or a parenthesized sub-expression.

{{ name }}                {{! operand: an identifier }}
{{ "literal" }}           {{! operand: a string literal }}
{{ a == b }}               {{! binary: exactly one operator }}
{{ a == b | upcase }}      {{! the pipe applies to the whole expression's result }}

Primaries

A primary is one of:

  • An identifier — see Interpolation for every recognized form.
  • A string literal — "…" or '…', with escapes \\, \", \', \n, \r, \t, \uXXXX.
  • A number literal — -?[0-9]+(\.[0-9]+)?.
  • true, false, or null.

Binary operators

Operator Meaning
or Logical OR (both sides coerced to truthy); case-insensitive
and Logical AND (both sides coerced to truthy); case-insensitive
== Equality
!= Inequality (!(a == b))
<, <=, >, >= Ordering comparison

Whitespace is required around a binary operator, matching the pattern for the pipe operator: {{ a==b }} is not valid, {{ a == b }} is.

Parenthesized sub-expressions

An operand may be a parenthesized expression chain instead of a bare primary, so a binary expression can combine more than one operator by grouping:

{{#if a and (b or c) }}          {{! (b or c) is evaluated first, then and'd with a }}
{{#if (a or b) and (c or d) }}   {{! both sides may be grouped }}
{{#if a and (b or (c and d)) }}  {{! groups can nest }}
{{ (items | size) > 0 }}         {{! a group may itself carry transforms }}

Whitespace immediately inside the parentheses is optional: (b or c) and ( b or c ) both parse. Parentheses used this way are a full expression chain, so transforms may appear inside them, as in the last example above. What kBars still does not support is chaining operators without parentheses — {{ a and b and c }} is a parse error regardless of grouping elsewhere in the template; each and/or/comparison needs exactly two operands, parenthesized or not.

Equality (==, !=)

Equality dispatches by operand type, checking each rule in order until one applies:

  1. If either operand is a boolean primitive, both sides are coerced to truthy and compared as booleans.
  2. Otherwise, if either operand is a string primitive, both sides render to string (the same rendering {{ }} would produce) and are compared as strings.
  3. Otherwise, if both operands are numeric, they're compared as numbers.
  4. Otherwise, structural equality: two structs are equal when they have the same keys and every value is recursively equal; two lists are equal when they have the same size and every element is recursively equal (in order); anything else compares equal only when it is the same kind of value.

!= is simply the negation of ==.

Ordering (<, <=, >, >=)

  • If either operand is a string primitive, both sides render to string and compare lexicographically.
  • Otherwise, if both operands are numeric, they compare numerically.
  • Otherwise — comparing, say, a struct to a number — the comparison silently evaluates to false.

Truthiness

{{#if}}, {{#unless}}, and the and/or operators all coerce their operand to a boolean using the same rule. A value is falsy when it is:

  • null (an absent context, or a resolved null)
  • a numeric primitive equal to 0.0 (covers 0, 0L, 0.0, -0; NaN is truthy)
  • a boolean primitive false
  • a string primitive equal to ""
  • an empty list

Everything else — including an empty struct {} — is truthy.

Where to next

  • Blocks — where truthiness drives {{#if}}/{{#unless}} arm selection.
  • Transforms — the | pipeline that follows an expression.
  • Number Rendering — how numeric results (including 2 vs. 2.0) are rendered; equality/ordering above already treat them identically.