Control structures enable dynamic content generation in templates by adding logical flow control, such as loops (for) and conditionals (if). These structures can be nested to create complex templates.
The for loop iterates over arrays, maps, or other iterable objects.
{% for key in iterable %}
{{ key }}
{% endfor %}
Template:
{% for key in simple.strmap %}
Key: {{ key }}
{% endfor %}
Data Data:
{
"simple": {
"strmap": {
"key1": "value1",
"key2": "value2"
}
}
}Output:
Key: key1
Key: key2
Template:
{% for item in products %}
Product: {{ item }}
{% endfor %}
Data Data:
{
"products": ["Coffee Maker", "Toaster"]
}Output:
Product: Coffee Maker
Product: Toaster
The if statement conditionally renders content based on data values.
{% if condition %}
Content to render if condition is true
{% endif %}
Template:
{% if simple.float %}
Float value is: {{ simple.float }}
{% endif %}
Data Data:
{
"simple": {
"float": 3.14
}
}Output:
Float value is: 3.14
Template:
{% if !simple %}
false
{% else %}
!simple
{% endif %}
Data Data:
{
"simple": null
}Output:
!simple
Control structures can be nested for complex logical flows.
Template:
{% for key in simple.strmap %}
{% if simple.float %}
{{ key }}: {{ simple.float }}
{% endif %}
{% endfor %}
Data Data:
{
"simple": {
"strmap": {
"key1": "value1",
"key2": "value2"
},
"float": 3.14
}
}Output:
key1: 3.14
key2: 3.14
Template:
{% if simple.float %}
{% for key in simple.strmap %}
{{ key }}
{% endfor %}
{% endif %}
Data Data:
{
"simple": {
"strmap": {
"key1": "value1",
"key2": "value2"
},
"float": 3.14
}
}Output:
key1
key2
Control structures support rich expression syntax, including:
- Numbers:
123,3.14 - Strings:
"hello",'world' - Booleans:
true,false,True,False(case-insensitive) - Null:
null,Null,none,None(case-insensitive) - Variables:
user.name,product.price
- Arithmetic:
+,-,*,/,% - Comparison:
==,!=,<,>,<=,>= - Logical:
&&,||,!(C-style) orand,or,not(Django-style) - Membership:
in,not in(check if value exists in string/list/map)
{% if user.age >= 18 && user.verified %}
Adult and verified
{% endif %}
{% if price > 100 || quantity >= 5 %}
Eligible for discount
{% endif %}
{% if !(user.blocked) %}
User is not blocked
{% endif %}
{% if user.age >= 18 and user.verified %}
Adult and verified
{% endif %}
{% if price > 100 or quantity >= 5 %}
Eligible for discount
{% endif %}
{% if not user.blocked %}
User is not blocked
{% endif %}
{% if "admin" in user.roles %}
User is an admin
{% endif %}
{% if "error" in message %}
Message contains error
{% endif %}
{% if user.status not in banned_statuses %}
User is not banned
{% endif %}
{% if user != null and user.active %}
Active user
{% endif %}
{% if settings == none %}
No settings configured
{% endif %}
Filters transform values within expressions:
{% if user.name|length > 0 %}
Username is not empty
{% endif %}
The template engine's lexer (lexer.go) breaks expressions into tokens:
- Identifiers: Variable names, property paths
- Literals: Numbers, strings, booleans
- Operators: Arithmetic, comparison, logical (uses switch-based classification)
- Others: Parentheses, pipes, filters
The parser (lexer.go) uses recursive descent to process expressions, following precedence rules:
Parse -> parseExpression
parseExpression -> parseLogicalOr
parseLogicalOr -> parseLogicalAnd ('||' | 'or') parseLogicalAnd
parseLogicalAnd -> parseNot ('&&' | 'and') parseNot
parseNot -> ('!' | 'not') parseNot | parseIn
parseIn -> parseComparison (('in' | 'not in') parseComparison)?
parseComparison -> parseAdditive (CompOp parseAdditive)(CompOp = '==' | '!=' | '<' | '>' | '<=' | '>=')
parseAdditive -> parseMultiplicative ([+-] parseMultiplicative)
parseMultiplicative -> parseUnary ([*/%] parseUnary)
parseUnary -> ('-') parseUnary | parsePrimary
parsePrimary -> parseBasicPrimary ('|' FilterName)*
parseBasicPrimary -> Number | String | Boolean | Null | Variable | '(' parseExpression ')'
Operator Precedence (from lowest to highest):
or,||- Logical OR (lowest precedence)and,&&- Logical ANDnot,!- Logical NOTin,not in- Membership test==,!=,<,>,<=,>=- Comparison+,-- Addition, Subtraction*,/,%- Multiplication, Division, Modulo- Unary
-- Negation |- Filter application (highest precedence)
The template engine follows Django-style truthiness for conditional evaluation:
false,False- Boolean falsenull,Null,none,None- Null/nil values0- Integer zero0.0- Float zero""- Empty string[]- Empty list/slice{}- Empty map
true,True- Boolean true- Non-zero numbers
- Non-empty strings
- Non-empty lists/slices
- Non-empty maps
- Struct values
{% if items %}
items list is not empty
{% endif %}
{% if user.name %}
user has a name
{% endif %}
{% if count %}
count is not zero
{% endif %}
-
Expression Evaluation
- Evaluated at runtime with precedence rules.
- Short-circuit evaluation for
&&/andand||/or. - Condition expressions are extracted via
extractConditionExpressionfromif/eliftags.
-
Error Handling
- Syntax errors during parsing.
- Runtime errors (e.g., type mismatches) during execution.
MustExecutepanics on error (useExecutefor error-returning behavior).
-
Variable Scope
- Outer scope variables are accessible within control structures.
forloops create new scopes for iteration variables.
-
Type Safety
- Operators require matching operand types.
- No implicit type conversion for arithmetic/comparison.
- Truthiness conversion for logical operators.
-
Performance
- Expressions are parsed once and cached.
- Minimal memory allocation during evaluation.
-
Operator Compatibility
- Both C-style (
&&,||,!) and Django-style (and,or,not) operators are supported. - Can be mixed in the same template for authoring convenience.
- Both C-style (
-
Thread Safety
- Filter registry is protected by
sync.RWMutexfor concurrent access. - Templates can be executed concurrently with independent contexts.
- Filter registry is protected by
{% if (price * quantity > 1000) and (user.level|upper == 'VIP') %}
Apply VIP discount
{% endif %}
{% if not (user.age < 18 or user.restricted) and user.verified %}
Access granted
{% endif %}
{% if product.name|trim|length > 0 %}
Product has valid name
{% endif %}
{% if request.method in ['POST', 'PUT', 'PATCH'] %}
Modifying request
{% endif %}
{% if user.email not in blacklist %}
Valid email
{% endif %}
{% if user != null and user.role in allowed_roles and not user.banned %}
Access granted
{% endif %}
{% if (score >= 90 or extra_credit) and "math" in subjects %}
Award distinction
{% endif %}
-
Proper Closing
- Always close control structures with
{% endfor %}or{% endif %}.
- Always close control structures with
-
Indentation
- Use consistent indentation for readability in nested structures.
-
Operator Style
- Prefer Django-style operators (
and,or,not) for better readability. - C-style operators (
&&,||,!) are supported as alternate syntax. - Be consistent within the same template.
- Prefer Django-style operators (
-
Negation
- Use
not(Django-style) or!(C-style) for logical negation. - Prefer
notfor better readability:{% if not user.blocked %}
- Use
-
Null Checking
- Use
!= nullor== nullfor explicit null checks. - Use truthiness for general existence checks:
{% if user %}
- Use
-
Membership Testing
- Use
infor checking if a value exists in a collection. - Use
not infor negative membership tests.
- Use
-
Variable Access
- Outer scope variables are accessible within loops and conditions.
-
Missing Data
- If a variable or property is missing, the template renders an empty string.
-
Operator Precedence
- Use parentheses to make complex expressions more readable.
- Remember:
or<and<not<in< comparisons