Skip to content

Commit eebe0f3

Browse files
committed
wip
1 parent 300b6c7 commit eebe0f3

2 files changed

Lines changed: 136 additions & 2 deletions

File tree

latte/en/compiler-passes.texy

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
Creating Compiler Passes
2+
************************
3+
4+
.[perex]
5+
Compiler passes provide a powerful mechanism to analyze and modify Latte templates *after* they have been parsed into an Abstract Syntax Tree (AST) and *before* the final PHP code is generated. This allows for advanced template manipulation, optimizations, security checks (like the Sandbox), and collecting template insights. This guide will walk you through creating your own compiler passes.
6+
7+
8+
What is a Compiler Pass?
9+
========================
10+
11+
To understand the role of compiler passes, see [Latte's compilation pipeline |custom-tags#Understanding the Compilation Process]. As you can see, compiler passes operate at a crucial stage, allowing deep intervention between the initial parsing and the final code output.
12+
13+
At its core, a compiler pass is simply a **PHP callable** (like a function, a static method, or an instance method) that accepts one argument: the root node of the template's AST, which is always an instance of `Latte\Compiler\Nodes\TemplateNode`.
14+
15+
The primary goal of a compiler pass is usually one or both of the following:
16+
17+
- Analysis: To walk through the AST and gather information about the template (e.g., find all defined blocks, check for specific tag usage, ensure certain security constraints are met).
18+
- Modification: To change the AST structure or node properties (e.g., automatically add HTML attributes, optimize certain tag combinations, replace deprecated tags with new ones, implement sandboxing rules).
19+
20+
21+
Registration
22+
============
23+
24+
Compiler passes are registered via an [Extension's|extending-latte#getPasses()] `getPasses()` method. This method returns an associative array where keys are unique names for the passes (used internally and for ordering) and values are the PHP callables implementing the pass logic.
25+
26+
```php
27+
use Latte\Compiler\Nodes\TemplateNode;
28+
use Latte\Extension;
29+
30+
class MyExtension extends Extension
31+
{
32+
public function getPasses(): array
33+
{
34+
return [
35+
'modificationPass' => $this->modifyTemplateAst(...),
36+
// ... other passes ...
37+
];
38+
}
39+
40+
public function modifyTemplateAst(TemplateNode $templateNode): void
41+
{
42+
// Implementation...
43+
}
44+
}
45+
```
46+
47+
Passes registered by Latte's core extensions and your custom extensions run sequentially. The order can be important, especially if one pass relies on the results or modifications of another. Latte provides a helper mechanism to control this order if needed; see the documentation for [`Extension::getPasses()` |extending-latte#getPasses()] for details.
48+
49+
50+
Traversing the AST with `NodeTraverser`
51+
=======================================
52+
53+
Manually writing recursive functions to walk through the complex AST structure is tedious and error-prone. Latte provides a dedicated tool for this: [api:Latte\Compiler\NodeTraverser]. This class implements the [visitor pattern |https://en.wikipedia.org/wiki/Visitor_pattern], making AST traversal systematic and manageable.
54+
55+
The basic usage involves creating an instance of `NodeTraverser` and calling its `traverse()` method, passing the root AST node and one or two "visitor" callables:
56+
57+
```php
58+
use Latte\Compiler\Node;
59+
use Latte\Compiler\NodeTraverser;
60+
use Latte\Compiler\Nodes;
61+
62+
$traverser = new NodeTraverser;
63+
64+
$traverser->traverse(
65+
$templateNode,
66+
67+
// 'enter' visitor: Called when entering a node (before its children)
68+
enter: function (Node $node) {
69+
echo "Entering node of type: " . $node::class . "\n";
70+
// You can inspect the node here
71+
if ($node instanceof Nodes\TextNode) {
72+
// echo "Found text: " . $node->content . "\n";
73+
}
74+
},
75+
76+
// 'leave' visitor: Called when leaving a node (after its children)
77+
leave: function (Node $node) {
78+
echo "Leaving node of type: " . $node::class . "\n";
79+
// You might perform actions here after children have been processed
80+
}
81+
);
82+
```
83+
84+
You can provide only the `enter` visitor, only the `leave` visitor, or both, depending on your needs.
85+
86+
**`enter(Node $node)`:** This function is executed for each node **before** the traverser visits any of that node's children. It's useful for:
87+
88+
- Collecting information as you descend the tree.
89+
- Making decisions *before* processing children (like deciding to skip them, see [#Optimizing Traversal]).
90+
- Potentially modifying the node before children are visited (less common).
91+
92+
**`leave(Node $node)`:** This function is executed for each node **after** all of its children (and their entire subtrees) have been fully visited (both entered and left). It's the most common place for:
93+
94+
Both `enter` and `leave` visitors can optionally return a value to influence the traversal process. Returning `null` (or nothing) continues traversal normally, returning a `Node` instance replaces the current node, and returning special constants like `NodeTraverser::RemoveNode` or `NodeTraverser::StopTraversal` modifies the flow, as explained in the following sections.
95+
96+
97+
How Traversal Works
98+
-------------------
99+
100+
The `NodeTraverser` internally uses the `getIterator()` method that every `Node` class must implement (as discussed in [Creating Custom Tags |custom-tags#Implementing getIterator() for Subnodes]). It iterates over the children yielded by `getIterator()`, recursively calls `traverse()` on them, ensuring that the `enter` and `leave` visitors are called in the correct depth-first order for every node in the tree accessible via iterators. This highlights again why a correctly implemented `getIterator()` in your custom tag nodes is absolutely essential for compiler passes to function correctly.
101+
102+
Let's write a simple pass that counts how many times the `{do}` tag (represented by `Latte\Essential\Nodes\DoNode`) is used in the template.
103+
104+
```php
105+
use Latte\Compiler\Node;
106+
use Latte\Compiler\NodeTraverser;
107+
use Latte\Compiler\Nodes\TemplateNode;
108+
use Latte\Essential\Nodes\DoNode;
109+
110+
function countDoTags(TemplateNode $templateNode): void
111+
{
112+
$count = 0;
113+
$traverser = new NodeTraverser;
114+
$traverser->traverse(
115+
$templateNode,
116+
enter: function (Node $node) use (&$count): void {
117+
if ($node instanceof DoNode) {
118+
$count++;
119+
}
120+
}
121+
// 'leave' visitor is not needed for this task
122+
);
123+
124+
echo "Found {do} tag $count times.\n";
125+
}
126+
127+
$latte = new Latte\Engine;
128+
$ast = $latte->parse($templateSource);
129+
countDoTags($ast);
130+
```
131+
132+
In this example, we only needed the `enter` visitor to check the type of each node encountered.
133+
134+
Next, we'll explore how to use these visitors to actually modify the AST.

latte/en/custom-tags.texy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ This page provides a comprehensive guide for creating custom tags in Latte. We'l
77
Custom tags provide the highest level of control over template syntax and rendering logic, but they are also the most complex extension point. Before deciding to create a custom tag, always consider if a [simpler solution exists|extending-latte#toc-ways-to-extend-latte] or if a suitable tag already exists in the [standard set|tags]. Use custom tags only when the simpler alternatives are insufficient for your needs.
88

99

10-
How Does Latte Work?
11-
====================
10+
Understanding the Compilation Process
11+
=====================================
1212

1313
To effectively create custom tags, it's helpful to explain how Latte processes templates. Understanding this process clarifies why tags are structured the way they are and how they fit into the bigger picture.
1414

0 commit comments

Comments
 (0)