Prana Template Language Reference

View Source

This document defines the syntax, data types, operators, and semantics of the Prana template language.

Overview

Prana templates combine literal text with dynamic expressions and control flow. The language is designed for workflow automation contexts where data flows between nodes in a graph-based execution model.

Template Syntax

Basic Structure

Literal text content
{{ expression }}
{% tag %}...{% endtag %}

Delimiters

Expression Blocks:

  • {{ expression }} - Output expression result
  • {{- expression }} - Trim whitespace before
  • {{ expression -}} - Trim whitespace after
  • {{- expression -}} - Trim whitespace both sides

Tag Blocks:

  • {% tag %} - Control flow and logic tags
  • {%- tag %} - Trim whitespace before
  • {% tag -%} - Trim whitespace after
  • {%- tag -%} - Trim whitespace both sides

Comments:

  • {# comment #} - Template comments (not rendered)

Data Types

Primitive Types

String

{{ "hello world" }}
{{ 'single quotes' }}
{{ "escape \"quotes\" with backslash" }}

Number

{{ 42 }}
{{ 3.14159 }}
{{ -17 }}
{{ 1.5e-10 }}

Boolean

{{ true }}
{{ false }}

Null

{{ nil }}

Note: Both nil and null are supported and equivalent.

Complex Types

Array/List

{{ items[0] }}          <!-- First element -->
{{ items[-1] }}         <!-- Last element (Not Yet Implemented) -->
{{ items[1:3] }}        <!-- Slice (Not Yet Implemented) -->

Object/Map

{{ user.name }}         <!-- Property access -->
{{ user["email"] }}     <!-- Bracket access -->
{{ user.address.city }} <!-- Nested access -->

Variable Access

Simple Variables

{{ name }}
{{ age }}
{{ active }}
{{ $user }}

Object Property Access

{{ user.name }}
{{ user.profile.email }}
{{ $settings.api.timeout }}

Array Access

{{ users[0] }}          <!-- Index access -->
{{ users[index] }}      <!-- Dynamic index -->
{{ $nested[0].items[1] }} <!-- Nested arrays -->

Workflow Context Access

<!-- Input data from workflow trigger -->
{{ $input.email }}
{{ $input.user.preferences.theme }}

<!-- Results from previous nodes -->
{{ $nodes.fetch_user.output.user_id }}
{{ $nodes.api_call.response.data }}

<!-- Workflow variables -->
{{ $variables.api_key }}
{{ $variables.base_url }}

<!-- Execution context -->
{{ $context.execution_id }}
{{ $context.timestamp }}

Operators

Comparison Operators

Equality

{{ user.role == "admin" }}
{{ status != "pending" }}

Relational

{{ age >= 18 }}
{{ score > 90 }}
{{ count <= 10 }}
{{ priority < 5 }}

Arithmetic Operators

Basic Math

{{ price + tax }}
{{ total - discount }}
{{ quantity * price }}
{{ total / count }}
{{ number % 2 }}

String Concatenation

{{ first_name + " " + last_name }}
{{ "Hello " + user.name }}

Logical Operators

Mau supports two styles of logical operators with different behavior:

Word-based Operators: and / or / not

Returns boolean values (true or false) using Liquid-style truthiness:

{{ user.active and user.verified }}        # Returns: true or false
{{ role == "admin" or role == "moderator" }}  # Returns: true or false
{{ not user.blocked }}                     # Returns: true or false

Truthiness (Liquid-style):

  • Falsy values: nil, false, 0, "" (empty string), [] (empty list), {} (empty map)
  • Everything else is truthy

Examples:

{{ 0 and "hello" }}          # false (0 is falsy)
{{ "" or "world" }}          # true (returns boolean, not the value)
{{ "hello" and "world" }}    # true (returns boolean)

Symbol-based Operators: && / || / !

Returns actual values (not booleans) using Elixir-style truthiness:

{{ user.active && user.verified }}        # Returns: right value or false/nil
{{ role == "admin" || role == "moderator" }}  # Returns: first truthy value
{{ !user.blocked }}                       # Returns: true or false

Truthiness (Elixir-style):

  • Falsy values: only nil and false
  • Everything else is truthy (including 0, "", [], {})

Examples:

{{ 0 && "hello" }}           # "hello" (0 is truthy, returns right value)
{{ "" || "world" }}          # "" (empty string is truthy, returns left value)
{{ "hello" && "world" }}     # "world" (returns right value)
{{ false || 42 }}            # 42 (returns first truthy value)
{{ nil && "test" }}          # nil (short-circuits, returns nil)

Choosing Between Operators

Use and / or / not when:

  • You need boolean results for conditions
  • You want Liquid-like behavior where 0 and "" are falsy
  • You're writing templates familiar to Liquid users

Use && / || / ! when:

  • You need the actual values (for default value patterns)
  • You want Elixir-like behavior where only nil and false are falsy
  • You're using || for default values: {{ user.name || "Guest" }}

Complex Conditions:

{{ (age >= 18 and age <= 65) and (role == "user" or role == "premium") }}
{{ user.active && (user.plan == "pro" || user.credits > 0) }}

Short-circuit Evaluation: Both operator styles support short-circuit evaluation:

{{ false && expensive_operation }}  # expensive_operation not evaluated
{{ true || expensive_operation }}   # expensive_operation not evaluated

Operator Precedence

Highest to Lowest:

  1. Property access: ., []
  2. Unary: not / !, -
  3. Multiplicative: *, /, %
  4. Additive: +, -
  5. Relational: <, <=, >, >=
  6. Equality: ==, !=
  7. Logical AND: and / &&
  8. Logical OR: or / ||

Parentheses can override precedence:

{{ (total + tax) * quantity }}
{{ user.active and (role == "admin" or permissions.elevated) }}

Control Flow Tags

Conditional Statements

If/Else

{% if user.active %}
  Welcome back, {{ user.name }}!
{% else %}
  Your account is inactive.
{% endif %}

If/Elsif/Else

{% if user.role == "admin" %}
  Administrator Dashboard
{% elsif user.role == "moderator" %}
  Moderator Panel
{% elsif user.role == "user" %}
  User Profile
{% else %}
  Access Denied
{% endif %}

Loops

For Loops

{% for user in users %}
  Name: {{ user.name }}
  Email: {{ user.email }}
{% endfor %}

For with Conditions (Not Yet Implemented)

{% for item in items limit: 5 %}
  {{ item.name }}
{% endfor %}

{% for user in users offset: 10 limit: 20 %}
  {{ user.name }}
{% endfor %}

For with Filtering (Not Yet Implemented)

{% for user in users where user.active %}
  {{ user.name }}
{% endfor %}

Loop Variables

{% for item in items %}
  {{ forloop.index }}: {{ item.name }}
  {% if forloop.first %}First item!{% endif %}
  {% if forloop.last %}Last item!{% endif %}
{% endfor %}

Case/Switch (Not Yet Implemented)

{% case user.role %}
  {% when "admin" %}
    Administrator
  {% when "moderator" %}
    Moderator
  {% when "user" %}
    User
  {% else %}
    Unknown role
{% endcase %}

Utility Tags

Assignment

{% assign full_name = user.first_name + " " + user.last_name %}
{% assign total_price = quantity * price + tax %}

Hello {{ full_name }}!
Total: ${{ total_price }}

Comments

{# Inline comment - not rendered #}

Filters

Filters transform expression values using the pipe (|) operator or function call syntax.

Pipe Syntax:

{{ user.name | capitalize }}
{{ text | upper_case | strip }}

Function Call Syntax:

{{ capitalize(user.name) }}
{{ upper_case(text) }}

Multiple Filters (Fully Supported)

Filters can be chained together for complex transformations:

{{ user.bio | strip | truncate(100) | capitalize }}
{{ price | multiply(quantity) | format_currency("USD") }}
{{ items | slice(0, 3) | join(", ") | upper_case }}
{{ numbers | sum | power(2) | sqrt }}

Available Filters

Mau includes 40+ built-in filters organized into categories:

  • String Filters - Text manipulation and formatting
  • Collection Filters - Array and list operations
  • Math Filters - Numeric calculations and rounding
  • Number Filters - Number formatting

For a complete list of all available filters with detailed documentation, see Filters List Reference.

Whitespace Control

Tag-Level Control

Trim Before

Text   {%- if true %}Content{% endif %}
<!-- Result: "TextContent" -->

Trim After

{% if true -%}   Content   {% endif %}
<!-- Result: "Content   " -->

Trim Both

Text   {%- if true -%}   Content   {%- endif -%}   More
<!-- Result: "TextContentMore" -->

Global Options

trim_blocks (Not Yet Implemented) - Remove newlines after tag blocks With trim_blocks: true

Start
{% if true %}
Content
{% endif %}
End
  • Result: "Start\nContent\nEnd"

lstrip_blocks (Not Yet Implemented) - Remove leading whitespace before tag blocks

  • With lstrip_blocks: true
    Start
      {% if true %}Content{% endif %}
    End
  • Result: "Start\n{% if true %}Content{% endif %}\nEnd"

Data Type Preservation

By default, all template output is converted to strings. However, Mau supports preserving original data types for single-value templates using the preserve_types option.

Basic Usage

# Default behavior (all strings)
Mau.render("{{ 42 }}", %{})           #=> {:ok, "42"}
Mau.render("{{ true }}", %{})         #=> {:ok, "true"}

# With data type preservation
Mau.render("{{ 42 }}", %{}, preserve_types: true)    #=> {:ok, 42}
Mau.render("{{ true }}", %{}, preserve_types: true)  #=> {:ok, true}

Supported Data Types

Primitive Types

{{ 42 }}        # Integer -> 42
{{ 3.14 }}      # Float -> 3.14  
{{ true }}      # Boolean -> true
{{ false }}     # Boolean -> false
{{ nil }}       # Nil -> nil
{{ "hello" }}   # String -> "hello"

Collection Types

{{ [1, 2, 3] }}           # List -> [1, 2, 3]
{{ %{"key" => "value"} }} # Map -> %{"key" => "value"} # not yet support

Expressions and Operations

{{ 5 + 3 }}         # Arithmetic -> 8
{{ 5 > 3 }}         # Comparison -> true
{{ true and false }} # Logic -> false

Filter Results

{{ items | length }}     # Numeric filter -> 3
{{ items | reverse }}    # List filter -> [3, 2, 1]
{{ items | join(",") }}  # String filter -> "1,2,3"

Mixed Content Behavior

Mixed content (text + expressions) always returns strings, even with preserve_types: true:

# These always return strings
Mau.render("Count: {{ 42 }}", %{}, preserve_types: true)  #=> {:ok, "Count: 42"}
Mau.render("{{ name }}: {{ age }}", context, preserve_types: true)  #=> {:ok, "Alice: 25"}

Single Value Detection

Data type preservation only applies to templates that contain exactly one expression and no surrounding text:

# Single value - types preserved
{{ 42 }}                 # -> 42 (Integer)
{{ user.active }}        # -> true (Boolean)
{{ items | length }}     # -> 3 (Integer)

# Multiple expressions - returns string
{{ 42 }}{{ true }}       # -> "42true" (String)

# Mixed content - returns string  
Value: {{ 42 }}          # -> "Value: 42" (String)

Error Handling

Strict Mode

strict_mode: true - Return errors for incomplete expressions and undefined variables

  • Incomplete expression: {{ variable → Error
  • Undefined variable: {{ unknown_var }} → Error

strict_mode: false (default) - Graceful degradation

  • Incomplete expression: {{ variable → Rendered as-is: "{{ variable"
  • Undefined variable: {{ unknown_var }} → Treated as nil/empty

Missing Variables

Default Behavior (strict_mode: false)

{{ undefined_variable }}           <!-- Renders as empty string -->
{{ user.missing_property }}        <!-- Renders as empty string -->

Type Coercion

Boolean Context

{% if "non_empty_string" %}       <!-- true -->
{% if "" %}                       <!-- false -->
{% if 0 %}                        <!-- false -->
{% if null %}                     <!-- false -->

Workflow Integration

Execution Context

Available Variables

  • $input - Data passed to the workflow
  • $variables - Workflow-level variables
  • $nodes - Results from executed nodes
  • $context - Execution metadata
  • $env - Environment variable for project (Not Yet Implemented)

Future Extensions

Planned Features

Enhanced Control Flow (Not Yet Implemented)

  • {% break %} - Break out of loops
  • {% case %} - Switch statements

Advanced Expressions (Not Yet Implemented)

  • Ternary operator: condition ? true_value : false_value

Implementation Status

✅ Fully Implemented Features

Core Template Syntax

  • All expression blocks with whitespace control: {{ }}, {{- }}, {{ -}}, {{- -}}
  • All tag blocks with whitespace control: {% %}, {%- %}, {% -%}, {%- -%}
  • Template comments: {# comment #}
  • Data type preservation: preserve_types: true option

Data Types & Variables

  • All primitive types: strings, numbers, booleans, null/nil
  • Complex types: arrays with indexing (positive only), objects/maps with property access
  • Workflow variables: $input, $variables, $nodes, $context

Operators & Expressions

  • All comparison operators: ==, !=, >, >=, <, <=
  • All arithmetic operators: +, -, *, /, %
  • All logical operators: and, or, not
  • Proper operator precedence and parentheses support

Control Flow

  • If/else/elsif conditionals with full nesting support
  • For loops with collection iteration
  • Loop variables: forloop.index, forloop.first, forloop.last
  • Assignment tags: {% assign var = value %}

Filters (40+ implemented)

  • String filters: upper_case, lower_case, capitalize, strip, truncate, default
  • Collection filters: length, first, last, join, sort, reverse, uniq, slice, contains, compact, flatten, sum, keys, values, group_by, map, filter, reject, dump
  • Math filters: abs, ceil, floor, round, max, min, power, sqrt, mod, clamp
  • Number filters: format_currency
  • Full filter chaining support: {{ value | filter1 | filter2 | filter3 }}

  • Function call syntax: {{ filter_name(value) }}

❌ Not Yet Implemented

Advanced Loop Features

  • Loop conditions: limit:, offset: parameters
  • Loop filtering: where conditions

Control Flow Extensions

  • Case/switch statements: {% case %}{% when %}{% endcase %}
  • Break statements: {% break %}

Filter Limitations

  • Complex filter expressions in conditionals (e.g., {% if name | upper_case == "ADMIN" %})

Global Options

  • trim_blocks and lstrip_blocks global whitespace control
  • Environment variables: $env workflow variable

Advanced Features

  • Array slicing: {{ items[1:3] }}
  • Negative array indexing: {{ items[-1] }}
  • Ternary operator: condition ? true_value : false_value

Implementation Coverage

~95% of documented features are fully implemented, with comprehensive support for core template functionality, full filter chaining, loop variables, data type preservation, and an extensive filter library.

Syntax Summary

<!-- Basic output -->
{{ variable }}
{{ $variable_with_dollar_sign }}
{{ object.property }}
{{ array[index] }}

<!-- Expressions -->
{{ a + b }}
{{ name == "John" }}
{{ active and verified }}

<!-- Control flow -->
{% if condition %}...{% endif %}
{% for item in items %}...{% endfor %}

<!-- Utility -->
{% assign var = value %}

<!-- Filters (full chaining support) -->
{{ text | upper_case | truncate(50) }}
{{ items | slice(0, 3) | join(", ") | upper_case }}
{{ price | format_currency("USD") }}
{{ format_currency(price, "USD") }}

<!-- Whitespace control -->
{{- variable -}}
{%- tag -%}...{%- endtag -%}