The engine uses syntax that is similar to Liquid or Mustache, but despite looking alike, they are fundamentally different. That's because this template engine uses a Lisp-like language for templates. This allows the parser to be extended with custom C and C++ functions.
By default, we ship with a standard library containing basic functionality that most projects will use close to 100% of the time.
Type system
Before we begin with the actual guide, we need to explain the type system of the templating engine. Our custom language is weakly-typed and uses strings to represent all values.
We also support 4 type hints:
- Normal - A regular string
- Function - The string contains code that can be passed to the
parser. Commonly, this is used for function bodies of functions like
forandif - Array - The string needs to be treated as an array
- Map - The string needs to be treated as a map
Functions and variables
The syntax for calling a function is the following:
{{ my-function arg1 arg2 arg3 ... argn }}
Variables are also functions, which means that to get the value of a variable, you simply need to call it like a function:
{{ my-variable }}
Arrays and maps
Accessing using the
at function
You can access an array at an index using the following call:
{{ at {{ my-array }} 0 }}
A map can be accessed by replacing the numeric index with a string:
{{ at {{ my-map }} my-key }}
If the value is neither an array nor a map, at treats it
as a plain string and returns the character at the given index, or an
empty string if the index is out of range.
Iterating using the
for function
You can iterate an array like this:
{{ for it {{ my-array }} {{ func This is {{ it }}
}} }}
Its arguments are like this:
- Name of the iterator variable
- The array data. This must be an actual array (e.g. produced by
listor forwarded by a variable function call); otherwise the parser returns an error - A function representing the body of the loop - inside the body you can reference the iterator variable
You can iterate a map like this:
{{ for key val {{ my-map }} {{ func Key: {{ key }}
Value: {{ val }}
}} }}
Its arguments are like this:
- Name for the key iterator variable
- Name for the value iterator variable
- The map data. This must be an actual map (e.g. produced by
dictor forwarded by a variable function call); otherwise the parser returns an error - A function representing the body of the loop - inside the body you can reference the iterator variables
The list function
The list function takes a variadic list of arguments and
converts it into an array variable that can be forwarded to any function
that requires an array argument. It is used like this:
{{ list A B C D E F }}
The dict function
The dict function takes a variadic list of arguments and
converts it into a map variable that can be forwarded to any function
that requires a map argument. It is used like this:
{{ dict A B C D E F }}
Each odd element (counting from 1) of the variadic arguments list is used as the key, while each even element is used as the value.
Note
If the function call has an odd number of arguments, i.e. the last argument is a new key, not a new value, the key will be created with an empty value.
The raw function
The raw function constructs a raw string, which is not
parsed. It is used like this:
{{ raw {{ for a {{ arr }}
{{ func
{{ a }}
}}
}} }}
This will return the following string:
{{ for a {{ arr }}
{{ func
{{ a }}
}}
}}
The comment function
You can use the comment function to write comments in
the file template without them being saved to the final template. It is
used like this:
{{ comment This is a comment and will only be here in the template }}
The func function
The func function returns a raw string with no
formatting from its arguments and marks it with the function hint. This
allows other functions to use it as a callback function by passing its
string data to a parser instance. It is used like this:
{{ my-func {{ func This raw string will be passed to "my-func". Call number: {{ my-func-call-num }} }} }}
As you can see, we call {{ my-func-call-num }}. As
functions can add additional variables and functions to the parser,
functions that accept functions can utilise this to enable the function
callback to have access to certain values.
Boolean operators
The following functions implement boolean operator constructs:
==- Checks if all values of the variadic arguments list of the function are equal!=- Checks if all values of the variadic arguments list of the function are not equal&&- Returns the value of a boolean AND operation done on all arguments part of the variadic arguments list of the function||- Returns the value of a boolean OR operation done on all arguments part of the variadic arguments list of the function!- Given a single argument with a boolean value, returns its boolean NOT value
Branches
The if function
The if function is different compared to most
if statements in modern programming languages. It's more
like the ternary
operator, without the possibility of assigning a variable to the
result. It looks like this:
{{ if {{ == {{ value }} test }}
{{ func {{ test_val }} }}
{{ func {{ not_test_val }} }}
}}
The arguments are like this:
- A boolean expression. Write it as a normal expression (e.g.
{{ == a b }}) so the parser evaluates it while cutting the arguments — a deferred{{ func ... }}body is not evaluated as a condition and almost always reads as true - The branch used when the expression is true
- The branch used when the expression is false. This argument is
optional — if it is omitted and the expression is false, the
ifreturns an empty string
Each branch may be either a deferred {{ func ... }} body
(only the selected branch is ever rendered) or a plain,
already-evaluated value such as a bare {{ some_var }}.
The switch function
Additionally, the switch function can be used as a
switch statement in high-level languages like JavaScript. The syntax
looks like this:
{{ switch {{ value }}
test {{ func {{ test_val}} }}
example {{ func {{ example_val }} }}
{{ func {{ fallback_val }} }}
}}
The arguments are like this:
- Value to match
- A variadic arguments list. Arguments should be ordered so that each argument with an odd index (starting from 1) corresponds to a value, and each with an even index corresponds to a function. The only exception is the last argument, which may be a function.
- The last argument is the fallback function.
Note
The fallback function is optional. It is only present when the final
argument is a lone function that does not pair with a preceding value.
If no case matches and no fallback function was supplied, the
switch returns an empty string.
The cond function
Finally, the cond function can be used as a replacement
for the if statement in modern programming languages. It
looks like this:
{{ cond
{{ == {{ value }} test }}{{ func {{ test_val }} }}
{{ == {{ value }} example }}{{ func {{ example_val }} }}
{{ func {{ fallback_val }} }}
}}
The function takes a variadic list of arguments, where each odd index (counting from 1) corresponds to a boolean expression and each even index corresponds to a function. The only exception to this is the final argument, which should always be a function. It will be called if all other conditions fail.
Note
The fallback function is optional. If no condition is true and no
fallback function was supplied, the cond returns an empty
string.
Important notes
The following special functions differ in parsing behaviour:
funcrawcomment
The differences are these:
- Trailing whitespace or separator characters are not removed from the
last argument, so for example
{{ func A B C D E }}will produce the stringA B C D E - There always needs to be a whitespace or separator character between
the function name and the beginning of the raw string. This invalidates
expressions like these:
{{ func{{ my_var }}}}. Instead, a whitespace should always be added, like this:{{ func {{my_var}}}}