The UntitledTemplateEngine aims to provide text templates with superpowers. The templating engine uses syntax that is similar to Liquid or Mustache, but while looking alike, they are totally different. That’s because this template engine uses a lisp like language for templates, that allows for plugging of C and C++ functions directly into the parser.
In simple terms, this means that you can create custom logic with the power and performance of C and C++(+ other languages, since creating language bindings for the engine is extremely easy using the C API)
By default, however, the engine provides the most minimal that allows for basic logic, mainly because of bloat and security considerations. Additionally, you can use the APIs to remove even the minimal logic to create logicless templates, or you can use the UntitledTemplateEngineUtils library that contains C functions for common string, int, float, math, and other use cases.
First, let’s start with substituting strings, the following code substitutes variables into a string:
The {{ at descriptors 0 }} {{ colour }} fox {{ at action "jump" }} over the {{ at descriptors 1 }} dog
In this example, we have provided the engine with the following variables:
descriptors
- Arraycolour
- Stringaction
- MapSince the language is lisp-like, variables are also functions and if they’re called they just return their value, so the code {{ colour }}
simply returns the value of the colour
variable provided to the parser.
Additionally, for Arrays, Strings and Maps, we provide the at
function that can index into the given type. Here’s the signature of the at
function in C++ style:
// For arrays
string at(Array<string> array, string index);
// For maps
string at(Map<string, string> map, string key);
You might have noticed that all types here use string. That’s because there’s only 1 variable type supported, that being string
. While it may be convenient to use a strong typing system, it would make the parser way more complicated and rigid for a minimal library like this. Additionally, it wouldn’t be of great benefit, since most I/O operations are done to text files.
To iterate an array, simply call the for
function like this to create a foreach
loop:
{{ for it arr {{ func
This is {{ it }}
}} }}
The first argument is the iterator, the second is the array that’s going to be iterated trough and the third argument is a call to the func
function, that acts like the body of the for loop.
The func
function simply defines a templated text block, where the arguments are simply a variable arguments list. This function will be called and substituted for every element of the array.
To iterate an array using an index, simply use the double iterator for loop like this:
{{ for key val arr {{ func
Key: {{ key }}
Value: {{ val }}
}} }}
To iterate a range, simply use the ranged for loop:
{{ for i 0 100 {{ func
{{ i }}
}} }}
To iterate a map, use the double iterator for loop:
{{ for key val action {{ func
Key: {{ key }}
Value: {{ val }}
}} }}
There are 3 types of conditionals, if
, switch
and cond
.
Before we begin with conditionals, we first need ways to do boolean calculations. Fortunately, the ==
, !=
, !
, &&
and ||
functions are built-in, so you don’t have to worry about creating a custom implementation for them
All the functions, except !
accept a variable list of arguments for easily comparing between multiple values on 1 condition. This allows for the following syntax:
{{ || 0 0 0 0 0 0 0 0 0 1 }}
or
{{ == {{ a }} {{ b }} {{ c }} text }}
The only exception is !
, because that operator is only unary, since it flips the boolean value.
Additionally, we provide also provide the standard true
and false
values as functions, if you don’t want to use 1
and 0
. Simply call the functions normally like this: {{ true }} {{ false }}
If statements here don’t have else if
statements, so they should be used for binary checks. Here is an example of an if statement:
{{ if {{ == {{ value }} "test" }}
{{ func
{{ test_val}}
}}
{{ func
{{ not_test_val }}
}}
}}
Let’s translate this to C++ for better readability
if (value == "test")
test_val();
else
not_test_val();
A switch statement is more suitable if checking for multiple values of a given variable, it looks like this:
{{ switch {{ value }}
"test" {{ func
{{ test_val}}
}}
"example" {{ func
{{ example_val }}
}}
default {{ func
{{ fallback_val }}
}}
}}
The first argument should always be a primitive variable, otherwise the function will terminate with an error.
After the first argument, we have a variable arguments list, where every argument should be a pair of value and function. In an edge case where 2 values don’t have a function between them, the first variable is considered to reference an empty function and therefore nothing will be executed.
The default
key is a reserved keyword when used in a switch statement!
In C, the same code looks like this:
switch (value)
{
case "test":
test_val();
break;
case "test":
example_val();
break;
default:
fallback_val();
break;
}
Cond
statements allow representing an if chain, similarly to what’s possible in C/C++ and related languages. Here’s an example:
{{ cond
{{ == {{ value }} "test" }} {{ func
{{ test_val }}
}}
{{ == {{ value }} "example" }} {{ func
{{ example_val }}
}}
default {{ func
{{ fallback_val }}
}}
}}
This syntax is similar to the switch syntax, but here values are the result of boolean expressions. This code translates to the following C++ code:
if (value == "test")
test_val();
else if (value == "example")
example_val();
else
fallback_val();
The default
keyword is reserved to mark the fallback statement(else
in C).
In an edge case where there are 2 boolean expressions with no function between them, the first boolean expression will be marked as referencing an empty function and therefore nothing will be executed.
If you want a portion of the string to contain templates, but you don’t want them to execute(i.e. a document showcasing templating examples), you can simply put your text between the {{ raw }}
and {{ endraw }}
functions like this:
{{ raw }}
This is how you iterate an array in the UntitledTemplateEngine
{{ for a arr
{{ func
{{ a }}
}}
}}
{{ endraw }}
If you want to add comments to your logic, simply use the comment
function like this:
{{ comment This is a comment and will be automatically removed
It can be multiline as well!
}}