# Prana Template Language Reference 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 ```liquid 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** ```liquid {{ "hello world" }} {{ 'single quotes' }} {{ "escape \"quotes\" with backslash" }} ``` **Number** ```liquid {{ 42 }} {{ 3.14159 }} {{ -17 }} {{ 1.5e-10 }} ``` **Boolean** ```liquid {{ true }} {{ false }} ``` **Null** ```liquid {{ nil }} ``` Note: Both `nil` and `null` are supported and equivalent. ### Complex Types **Array/List** ```liquid {{ items[0] }} {{ items[-1] }} {{ items[1:3] }} ``` **Object/Map** ```liquid {{ user.name }} {{ user["email"] }} {{ user.address.city }} ``` ## Variable Access ### Simple Variables ```liquid {{ name }} {{ age }} {{ active }} {{ $user }} ``` ### Object Property Access ```liquid {{ user.name }} {{ user.profile.email }} {{ $settings.api.timeout }} ``` ### Array Access ```liquid {{ users[0] }} {{ users[index] }} {{ $nested[0].items[1] }} ``` ### Workflow Context Access ```liquid {{ $input.email }} {{ $input.user.preferences.theme }} {{ $nodes.fetch_user.output.user_id }} {{ $nodes.api_call.response.data }} {{ $variables.api_key }} {{ $variables.base_url }} {{ $context.execution_id }} {{ $context.timestamp }} ``` ## Operators ### Comparison Operators **Equality** ```liquid {{ user.role == "admin" }} {{ status != "pending" }} ``` **Relational** ```liquid {{ age >= 18 }} {{ score > 90 }} {{ count <= 10 }} {{ priority < 5 }} ``` ### Arithmetic Operators **Basic Math** ```liquid {{ price + tax }} {{ total - discount }} {{ quantity * price }} {{ total / count }} {{ number % 2 }} ``` **String Concatenation** ```liquid {{ 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: ```liquid {{ 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:** ```liquid {{ 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: ```liquid {{ 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:** ```liquid {{ 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:** ```liquid {{ (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: ```liquid {{ 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: ```liquid {{ (total + tax) * quantity }} {{ user.active and (role == "admin" or permissions.elevated) }} ``` ## Control Flow Tags ### Conditional Statements **If/Else** ```liquid {% if user.active %} Welcome back, {{ user.name }}! {% else %} Your account is inactive. {% endif %} ``` **If/Elsif/Else** ```liquid {% 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** ```liquid {% for user in users %} Name: {{ user.name }} Email: {{ user.email }} {% endfor %} ``` **For with Conditions** *(Not Yet Implemented)* ```liquid {% 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)* ```liquid {% for user in users where user.active %} {{ user.name }} {% endfor %} ``` **Loop Variables** ```liquid {% 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)* ```liquid {% case user.role %} {% when "admin" %} Administrator {% when "moderator" %} Moderator {% when "user" %} User {% else %} Unknown role {% endcase %} ``` ## Utility Tags ### Assignment ```liquid {% assign full_name = user.first_name + " " + user.last_name %} {% assign total_price = quantity * price + tax %} Hello {{ full_name }}! Total: ${{ total_price }} ``` ### Comments ```liquid {# Inline comment - not rendered #} ``` ## Filters Filters transform expression values using the pipe (`|`) operator or function call syntax. **Pipe Syntax:** ```liquid {{ user.name | capitalize }} {{ text | upper_case | strip }} ``` **Function Call Syntax:** ```liquid {{ capitalize(user.name) }} {{ upper_case(text) }} ``` **Multiple Filters** *(Fully Supported)* Filters can be chained together for complex transformations: ```liquid {{ 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](filters-list.md)**. ## Whitespace Control ### Tag-Level Control **Trim Before** ```liquid Text {%- if true %}Content{% endif %} ``` **Trim After** ```liquid {% if true -%} Content {% endif %} ``` **Trim Both** ```liquid Text {%- if true -%} Content {%- endif -%} More ``` ### Global Options **trim_blocks** *(Not Yet Implemented)* - Remove newlines after tag blocks With trim_blocks: true ```liquid 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 ```liquid 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 ```liquid # 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** ```liquid {{ 42 }} # Integer -> 42 {{ 3.14 }} # Float -> 3.14 {{ true }} # Boolean -> true {{ false }} # Boolean -> false {{ nil }} # Nil -> nil {{ "hello" }} # String -> "hello" ``` **Collection Types** ```liquid {{ [1, 2, 3] }} # List -> [1, 2, 3] {{ %{"key" => "value"} }} # Map -> %{"key" => "value"} # not yet support ``` **Expressions and Operations** ```liquid {{ 5 + 3 }} # Arithmetic -> 8 {{ 5 > 3 }} # Comparison -> true {{ true and false }} # Logic -> false ``` **Filter Results** ```liquid {{ 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`: ```liquid # 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: ```liquid # 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)** ```liquid {{ undefined_variable }} {{ user.missing_property }} ``` ### Type Coercion **Boolean Context** ```liquid {% if "non_empty_string" %} {% if "" %} {% if 0 %} {% if null %} ``` ## 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 ```liquid {{ variable }} {{ $variable_with_dollar_sign }} {{ object.property }} {{ array[index] }} {{ a + b }} {{ name == "John" }} {{ active and verified }} {% if condition %}...{% endif %} {% for item in items %}...{% endfor %} {% assign var = value %} {{ text | upper_case | truncate(50) }} {{ items | slice(0, 3) | join(", ") | upper_case }} {{ price | format_currency("USD") }} {{ format_currency(price, "USD") }} {{- variable -}} {%- tag -%}...{%- endtag -%} ```