Skip to content

refactor: major template engine refactor (AST-based)#27

Open
Farrael wants to merge 36 commits intoElliesaur:mainfrom
Farrael:feature/template_ast
Open

refactor: major template engine refactor (AST-based)#27
Farrael wants to merge 36 commits intoElliesaur:mainfrom
Farrael:feature/template_ast

Conversation

@Farrael
Copy link
Contributor

@Farrael Farrael commented Feb 7, 2026

Summary

This PR introduces a complete rewrite of how templates are parsed and processed, thanks to the help of @KelpyCode .
Templates are now compiled into an AST, producing:

  • a list of template nodes
  • a list of component definitions

These structures are cached to improve performance when updating values, without having to re-parse or rebuild the template (as long as the template itself or the component set doesn’t change).

What changed

AST-based processing

  • Templates are parsed once into an AST
  • Nodes and components are cached
  • Updating variables do not invalidates the AST
  • Re-parsing only happens when the template or components actually change
  • Adding for and each flow as attributes on HTML elements

Improved variable system

  • Item properties and zero-argument methods are supported and evaluated as requested
  • Variables can be cached
  • Functions are supported (see below)

Functions in variables

  • Variables can now be functions
  • They receive the current variable stack as the first argument
  • This allows accessing context dynamically and executing logic when needed
  • See processor tests and callFunctionWithArguments for examples.

Each improvements

  • each blocks can optionally declare a loop variable name / index:
{{ each $item, $index in $items }} ... {{ /each }}
  • Loop item properties can still be shortened:
{{$key}} instead of {{$item.key}}

…but only if it does not shadow an existing variable. (using the same name as a declared one)

Breaking changes

Declaration

  • All variables must now be explicitly declared using $.
  • All flow block like each and if should not use the symbol # at the beginning.

❌ Old:

{{#if variable ...}} ... {{/if}}

✅ New:

{{if $variable ...}} ... {{/if}}

Component syntax change

  • Components are no longer declared using template syntax.
  • Attributes can contains specials keywords :
    • if="..." / else-if="" / else to create conditional tags
    • for="" to create duplicated tags
    • @<name>="" to create events listeners

❌ Old:

{{@special:foo=bar}}

✅ New:

<special foo="bar" />

Supplier / Function caching

  • Supplier and Function must now specify the caching policy when used

❌ Old:

setVariable("name", () -> heavy.computeName);

✅ New:

setVariable("name", () -> heavy.computeName, CachePolicy.CACHED);

Keywords

  • each block renamed for
  • for loop allow to retrieve key and value
  • x contains Y is now y in $x

❌ Old:

{{#each variable ...}} ... {{/if}}

✅ New:

{{for $value, $key in $variable}} ... {{/for}}
{{for $value in $variable}} ... {{/for}}
{{for $variable}} ... {{/for}}

Children support

  • Components can now receive children content, accessed via:
<slot:named> Default value </slot>
<slot:default/> 
<slot/> // Same as <slot:default/>
  • Components can define slots as input for resolution :
<my-component>
     <:header> My custom header </:header>
     Here, same as slot default !
     <:footer>
          My own footer
     </:footer>
</my-component>

Tip

$slot:<name> is still accessible as variable as a string already parsed and resolved

Variable resolution rules

  • Variables are only resolved for:
    • declared variable (block, user, ...)
    • context variable (using interface like before)
    • item properties
    • methods without arguments
  • Functions / Supplier should be explicitly opt-in for cache

Tests

  • A large number of tests were added to cover:
    • AST generation
    • caching behavior
    • variable resolution
    • components and children
    • function calls and context handling

Example

processor.setVariable("number", 12.847);

processor.registerComponent("statCard", """
    <div style="background-color: #2a2a3e; padding: 10; anchor-width: 120; anchor-height: 60;">
        <p style="color: #888888; font-size: 11;">{{$label}}</p>
        <p style="color: #ffffff; font-size: 18; font-weight: bold;">{{$value}}</p>
    </div>
""");

processor.setTemplate("""
    <statCard label="Blocks Placed" value={{$number}} />
""");

Notes

  • It would be good to test this branch on real-world use cases and report any issues we find before merging.
  • Some of the work here implement the same as kelpy (discord) did on Feature/template dx #28
    • <template> block are not implemented here

@MrMineO5 MrMineO5 self-requested a review February 7, 2026 22:07
Farrael and others added 18 commits February 9, 2026 01:02
- Introduced ElseIfAttribute and ElseAttribute records to handle else-if and else conditions in templates.
- Updated ControlAttribute to include an indexName for iteration.
- Modified EachBlockNode to accommodate the new indexName parameter.
- Enhanced parser to support new syntax for each blocks with index.
- Implemented grouping of conditional chains in the template processor.
- Added tests for conditional rendering with if/else-if/else chains.
…g literals

- Add parsing for inline if blocks within attribute values
- Enhance string literal parsing to handle single quotes
- Update tests to cover inline if blocks and single-quoted strings
…ving attribute handling

- Introduced a set of void elements that are self-closing and cannot have children.
- Updated the component evaluation logic to skip rendering completely empty non-void elements with no attributes to prevent layout issues.
- Improved error handling in the parser for unclosed inline if blocks and unexpected tokens.
- Added tests to verify correct rendering of void elements and handling of empty elements.
@Farrael Farrael marked this pull request as draft February 16, 2026 01:16
@Farrael Farrael marked this pull request as ready for review February 20, 2026 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants