January 17th, 2021 at 7:32 PM
I've been doing some work to make this a more fully-featured template engine. Its goal is still to be incredibly lightweight, but it now supports:
Loops:
This is perhaps one of the most useful features that is present in established engines such as Twig or Blade. It allows you to load a list of posts, for example, by "looping" the same HTML code for each post. This can be applied for pretty much any kind of list imagineable. This engine has native support for simple for loops, such as:
This was implemented with a BBcode-like Regex parser that supports nesting via recursive callbacks. It first looks for loops and creates a recursive "tree" - and it evaluates the inner loops first to determine if it needs to execute them. It works its way back up the "tree", finishing them one-by-one as to support multiple consecutive loops or complex nested structures. The "inner-loop first" structure is a bit unorthodox as far as parsing is concerned, but but was a concession made to support more complex structures using plain regex.
Currently, only "FOR" loops are supported. No plans are in place to implement "while" loops.
Language strings
This engine will now parse and manage basic language strings. It keeps track of a "locale" that looks into a specific folder for the core language file. You may load additional language files for specific pages as needed, which saves performance by not loading every single language string for every page.
IF/ELSE statements
These features are almost universally present in other established template engines, and allow conditions to be evaluated directly in the template. This results in cleaner code and less PHP evaluation work.
Developing support for these proved to be unusually tricky. Recursive Regex callbacks were used for implementation (an unusual choice for this type of job). Pure Posix-compliant regex actually cannot parse XML-like languages in this manner (tokenizers and specialized parse trees are used instead). However, PHP uses the PCRE regex engine (one of the most powerful regex engines in existence) that has the necessary features to make this possible.
Unfortunately, it has drawbacks. Performance being one of them, among strict syntax requirements built around the requirements of the regex. It is also easy to break with mismatched tags. However, with properly formatted syntax, nested if/elseif/else blocks are fully supported. Most of the StackOverflow examples lacked support for nesting, so this implementation is a unique Regex implementation.
Standard Template Variables
What would a template engine be without template variables? Thankfully, we aren't using straight PHP, nor are we using eval. This reduces the risk of PHP code injection and improves security over using flat PHP templates.
Right now, it's sizing out around 10KB of code or so. It will likely expand a little as I debug things, but the goal is to keep it fully contained within about 15KB or so. If it proves to be stable enough to be used in real world applications, I'll post the updated version on Github and will probably try to use it in a few projects of my own. I'll post some explanations on the code as a reference on how to do syntax parsing with Regex as well.
Edit: It now compiles templates to pure PHP for native performance - After doing all of the work using regex to write a full interpreter for the basic template conditionals/loops, I wrote a compiler to do the exact same thing. It writes pure PHP template cache files and saves them for future use. The template engine will now run at native speeds, and has an interpreter as a fallback. Unfortunately, this exploded it to about 20KB in size. Going to trim this and see what can be done.
Loops:
This is perhaps one of the most useful features that is present in established engines such as Twig or Blade. It allows you to load a list of posts, for example, by "looping" the same HTML code for each post. This can be applied for pretty much any kind of list imagineable. This engine has native support for simple for loops, such as:
Code:
[@loop: people as person]
Person's name: [@person:name] <br />
Person's age: [@person: age]<br />
[/loop]
HOST CODE (PHP)
$templates->set_array("people", $people_array); // Sets the "people" variable for the looping engine
This was implemented with a BBcode-like Regex parser that supports nesting via recursive callbacks. It first looks for loops and creates a recursive "tree" - and it evaluates the inner loops first to determine if it needs to execute them. It works its way back up the "tree", finishing them one-by-one as to support multiple consecutive loops or complex nested structures. The "inner-loop first" structure is a bit unorthodox as far as parsing is concerned, but but was a concession made to support more complex structures using plain regex.
Currently, only "FOR" loops are supported. No plans are in place to implement "while" loops.
Language strings
This engine will now parse and manage basic language strings. It keeps track of a "locale" that looks into a specific folder for the core language file. You may load additional language files for specific pages as needed, which saves performance by not loading every single language string for every page.
Code:
$templates->lang_load("users"); // Loads all language strings in Languages/[locale]/users.lang.php
TEMPLATE:
<div class="user">
[@lang:username] - Insert stuff here <br />
</div>
IF/ELSE statements
These features are almost universally present in other established template engines, and allow conditions to be evaluated directly in the template. This results in cleaner code and less PHP evaluation work.
Code:
Before IF statement: <br />
[@if: $logged_in == true]
- Display logged out link
[/if]
[@else]
- Display logged in link
[/else]
Developing support for these proved to be unusually tricky. Recursive Regex callbacks were used for implementation (an unusual choice for this type of job). Pure Posix-compliant regex actually cannot parse XML-like languages in this manner (tokenizers and specialized parse trees are used instead). However, PHP uses the PCRE regex engine (one of the most powerful regex engines in existence) that has the necessary features to make this possible.
Unfortunately, it has drawbacks. Performance being one of them, among strict syntax requirements built around the requirements of the regex. It is also easy to break with mismatched tags. However, with properly formatted syntax, nested if/elseif/else blocks are fully supported. Most of the StackOverflow examples lacked support for nesting, so this implementation is a unique Regex implementation.
Standard Template Variables
What would a template engine be without template variables? Thankfully, we aren't using straight PHP, nor are we using eval. This reduces the risk of PHP code injection and improves security over using flat PHP templates.
Code:
$templates->set("Username", "Steve");
TEMPLATE:
<strong>Username: </strong> [@username]
Right now, it's sizing out around 10KB of code or so. It will likely expand a little as I debug things, but the goal is to keep it fully contained within about 15KB or so. If it proves to be stable enough to be used in real world applications, I'll post the updated version on Github and will probably try to use it in a few projects of my own. I'll post some explanations on the code as a reference on how to do syntax parsing with Regex as well.
Edit: It now compiles templates to pure PHP for native performance - After doing all of the work using regex to write a full interpreter for the basic template conditionals/loops, I wrote a compiler to do the exact same thing. It writes pure PHP template cache files and saves them for future use. The template engine will now run at native speeds, and has an interpreter as a fallback. Unfortunately, this exploded it to about 20KB in size. Going to trim this and see what can be done.