tempile-core 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +913 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
# Tempile Core
|
|
2
|
+
|
|
3
|
+
A powerful multi-language template engine core that parses templates with custom syntax and generates an Abstract Syntax Tree (AST). This library is designed for compiler developers who want to build language-specific compilers for the Tempile template system.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Tempile Core is the foundation of the Tempile template engine ecosystem. It handles the parsing of template files and produces a structured AST that can be transformed into native code for any target language.
|
|
8
|
+
|
|
9
|
+
### Architecture
|
|
10
|
+
|
|
11
|
+
The Tempile ecosystem consists of three main components:
|
|
12
|
+
|
|
13
|
+
1. **Core** (this package) - Parses templates and generates AST
|
|
14
|
+
2. **Compiler** - Transforms AST into target language native code
|
|
15
|
+
3. **CLI** - Orchestrates core and compilers, handles file and config management
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install tempile-core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
import { parse } from 'tempile-core';
|
|
27
|
+
import fs from 'fs';
|
|
28
|
+
|
|
29
|
+
// Read template file
|
|
30
|
+
const template = fs.readFileSync('template.html', 'utf-8');
|
|
31
|
+
|
|
32
|
+
// Parse template and get AST root
|
|
33
|
+
const root = parse(template, 'template.html');
|
|
34
|
+
|
|
35
|
+
// Resolve includes (if template uses <include> tags)
|
|
36
|
+
root.resolveIncludes('./src');
|
|
37
|
+
|
|
38
|
+
// Match slots with contents (for component composition)
|
|
39
|
+
root.matchSlotsAndContents();
|
|
40
|
+
|
|
41
|
+
// Now you can traverse the AST and generate target code
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API Reference
|
|
45
|
+
|
|
46
|
+
### Core Functions
|
|
47
|
+
|
|
48
|
+
#### `parse(source: string, fileName: string): Root`
|
|
49
|
+
|
|
50
|
+
Parses a template string and returns the root AST node.
|
|
51
|
+
|
|
52
|
+
**Parameters:**
|
|
53
|
+
- `source` - Template content as string
|
|
54
|
+
- `fileName` - Name of the template file (used in error messages and position tracking)
|
|
55
|
+
|
|
56
|
+
**Returns:** `Root` object containing the parsed AST
|
|
57
|
+
|
|
58
|
+
**Example:**
|
|
59
|
+
```javascript
|
|
60
|
+
const root = parse('<div>Hello {{name}}</div>', 'greeting.html');
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Root Class
|
|
64
|
+
|
|
65
|
+
The `Root` class represents the top-level AST node and provides methods for post-processing.
|
|
66
|
+
|
|
67
|
+
#### Properties
|
|
68
|
+
|
|
69
|
+
- `children: Node[]` - Array of child nodes
|
|
70
|
+
- `fileName: string` - Source file name
|
|
71
|
+
|
|
72
|
+
#### Methods
|
|
73
|
+
|
|
74
|
+
##### `resolveIncludes(srcPath: string): void`
|
|
75
|
+
|
|
76
|
+
Recursively resolves all `<include>` tags by reading and parsing the referenced files. Must be called before `matchSlotsAndContents()` if your templates use includes.
|
|
77
|
+
|
|
78
|
+
**Parameters:**
|
|
79
|
+
- `srcPath` - Base directory path for resolving relative include paths
|
|
80
|
+
|
|
81
|
+
**Throws:**
|
|
82
|
+
- Error if circular includes are detected
|
|
83
|
+
- Error if included file cannot be read
|
|
84
|
+
|
|
85
|
+
**Example:**
|
|
86
|
+
```javascript
|
|
87
|
+
root.resolveIncludes('./templates');
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
##### `matchSlotsAndContents(): void`
|
|
91
|
+
|
|
92
|
+
Matches `<content>` nodes with their corresponding `<slot>` nodes in included templates. This enables component composition by filling slots with provided content.
|
|
93
|
+
|
|
94
|
+
Must be called after `resolveIncludes()` if using the slot/content system.
|
|
95
|
+
|
|
96
|
+
**Example:**
|
|
97
|
+
```javascript
|
|
98
|
+
root.resolveIncludes('./templates');
|
|
99
|
+
root.matchSlotsAndContents(); // Slots are now filled with contents
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## AST Node Types
|
|
103
|
+
|
|
104
|
+
All nodes extend the abstract `Node` class and include position information for error reporting.
|
|
105
|
+
|
|
106
|
+
### Base Node Structure
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
abstract class Node {
|
|
110
|
+
type: NodeType;
|
|
111
|
+
pos: Pos | null | undefined;
|
|
112
|
+
abstract getChildren(): Node[];
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Position Information
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
type Pos = {
|
|
120
|
+
fileName: string;
|
|
121
|
+
startLine?: number;
|
|
122
|
+
startCol?: number;
|
|
123
|
+
startOffset?: number;
|
|
124
|
+
endLine?: number;
|
|
125
|
+
endCol?: number;
|
|
126
|
+
endOffset?: number;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Node Types Enum
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
enum NodeType {
|
|
134
|
+
Import = "import",
|
|
135
|
+
Doctype = "doctype",
|
|
136
|
+
Tempile = "tempile",
|
|
137
|
+
Comment = "comment",
|
|
138
|
+
Text = "text",
|
|
139
|
+
Element = "element",
|
|
140
|
+
If = "if",
|
|
141
|
+
ElseIf = "elseif",
|
|
142
|
+
Else = "else",
|
|
143
|
+
For = "for",
|
|
144
|
+
Include = "include",
|
|
145
|
+
Slot = "slot",
|
|
146
|
+
Content = "content",
|
|
147
|
+
Out = "out",
|
|
148
|
+
Logic = "logic",
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### HTML Nodes
|
|
153
|
+
|
|
154
|
+
#### TextNode
|
|
155
|
+
|
|
156
|
+
Represents plain text content.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
class TextNode extends Node {
|
|
160
|
+
type: NodeType.Text;
|
|
161
|
+
data: string;
|
|
162
|
+
getChildren(): [] // No children
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### CommentNode
|
|
167
|
+
|
|
168
|
+
Represents HTML comments.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
class CommentNode extends Node {
|
|
172
|
+
type: NodeType.Comment;
|
|
173
|
+
data: string;
|
|
174
|
+
getChildren(): [] // No children
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### ElementNode
|
|
179
|
+
|
|
180
|
+
Represents standard HTML elements.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
class ElementNode extends Node {
|
|
184
|
+
type: NodeType.Element;
|
|
185
|
+
tag: string;
|
|
186
|
+
attrs: Attribute[];
|
|
187
|
+
children: Node[];
|
|
188
|
+
getChildren(): Node[]
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Attribute Structure:**
|
|
193
|
+
```typescript
|
|
194
|
+
type Attribute = {
|
|
195
|
+
name: string;
|
|
196
|
+
value: string;
|
|
197
|
+
valueNodes?: AttrValueNode[]; // Parsed interpolations
|
|
198
|
+
pos: Pos;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type AttrValueNode =
|
|
202
|
+
| { type: "text"; value: string }
|
|
203
|
+
| { type: "expr"; value: string }
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Attributes can contain expressions using `{{expression}}` syntax. The `valueNodes` array contains parsed segments alternating between text and expressions.
|
|
207
|
+
|
|
208
|
+
**Example:**
|
|
209
|
+
```html
|
|
210
|
+
<div class="btn {{variant}} {{size}}">
|
|
211
|
+
```
|
|
212
|
+
Results in:
|
|
213
|
+
```javascript
|
|
214
|
+
{
|
|
215
|
+
name: "class",
|
|
216
|
+
value: "btn {{variant}} {{size}}",
|
|
217
|
+
valueNodes: [
|
|
218
|
+
{ type: "text", value: "btn " },
|
|
219
|
+
{ type: "expr", value: "variant" },
|
|
220
|
+
{ type: "text", value: " " },
|
|
221
|
+
{ type: "expr", value: "size" }
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### DoctypeNode
|
|
227
|
+
|
|
228
|
+
Represents the DOCTYPE declaration.
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
class DoctypeNode extends Node {
|
|
232
|
+
type: NodeType.Doctype;
|
|
233
|
+
data: string; // "<!DOCTYPE html>"
|
|
234
|
+
getChildren(): [] // No children
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Control Flow Nodes
|
|
239
|
+
|
|
240
|
+
#### IfNode
|
|
241
|
+
|
|
242
|
+
Represents conditional rendering with optional elseif and else branches.
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
class IfNode extends Node {
|
|
246
|
+
type: NodeType.If;
|
|
247
|
+
conditions: Attribute[]; // Multi-language conditions
|
|
248
|
+
ifContent: Node[]; // Content when condition is true
|
|
249
|
+
elseIfNodes: ElseIfNode[]; // Optional elseif branches
|
|
250
|
+
elseNode?: ElseNode; // Optional else branch
|
|
251
|
+
getChildren(): Node[] // Returns ifContent
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Template Syntax:**
|
|
256
|
+
```html
|
|
257
|
+
<if @js="user.age >= 18" @go="user.Age >= 18">
|
|
258
|
+
<p>Adult content</p>
|
|
259
|
+
<elseif @js="user.age >= 13" @go="user.Age >= 13">
|
|
260
|
+
<p>Teen content</p>
|
|
261
|
+
</elseif>
|
|
262
|
+
<else>
|
|
263
|
+
<p>Child content</p>
|
|
264
|
+
</else>
|
|
265
|
+
</if>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### ElseIfNode
|
|
269
|
+
|
|
270
|
+
Represents an elseif branch within an if statement.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
class ElseIfNode extends Node {
|
|
274
|
+
type: NodeType.ElseIf;
|
|
275
|
+
conditions: Attribute[]; // Multi-language conditions
|
|
276
|
+
children: Node[];
|
|
277
|
+
getChildren(): Node[]
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Validation:**
|
|
282
|
+
- Must be a direct child of `<if>` tag
|
|
283
|
+
- Requires at least one `@[lang]` condition attribute
|
|
284
|
+
|
|
285
|
+
#### ElseNode
|
|
286
|
+
|
|
287
|
+
Represents an else branch within an if statement.
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
class ElseNode extends Node {
|
|
291
|
+
type: NodeType.Else;
|
|
292
|
+
children: Node[];
|
|
293
|
+
getChildren(): Node[]
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Validation:**
|
|
298
|
+
- Must be a direct child of `<if>` tag
|
|
299
|
+
- Only one `<else>` per `<if>` block allowed
|
|
300
|
+
|
|
301
|
+
#### ForNode
|
|
302
|
+
|
|
303
|
+
Represents loop iteration.
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
class ForNode extends Node {
|
|
307
|
+
type: NodeType.For;
|
|
308
|
+
loops: Attribute[]; // Multi-language loop expressions
|
|
309
|
+
children: Node[];
|
|
310
|
+
getChildren(): Node[]
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Template Syntax:**
|
|
315
|
+
```html
|
|
316
|
+
<for @js="const item of items" @go="_, item := range items">
|
|
317
|
+
<p>{{item.name}}</p>
|
|
318
|
+
</for>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Feature Nodes
|
|
322
|
+
|
|
323
|
+
#### ImportNode
|
|
324
|
+
|
|
325
|
+
Allows importing dependencies for specific target languages. Only text and comment nodes are allowed as children.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
class ImportNode extends Node {
|
|
329
|
+
type: NodeType.Import;
|
|
330
|
+
lang: string; // Target language (e.g., "js", "go")
|
|
331
|
+
data: Node[]; // Text/Comment nodes containing import statements
|
|
332
|
+
getChildren(): Node[]
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Template Syntax:**
|
|
337
|
+
```html
|
|
338
|
+
<import @js>
|
|
339
|
+
import axios from 'axios';
|
|
340
|
+
import { helper } from './utils';
|
|
341
|
+
</import>
|
|
342
|
+
|
|
343
|
+
<import @go>
|
|
344
|
+
"fmt"
|
|
345
|
+
"github.com/user/package"
|
|
346
|
+
</import>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Validation:**
|
|
350
|
+
- Must be at document root level (not nested in other elements)
|
|
351
|
+
- Only one `@[lang]` attribute allowed
|
|
352
|
+
- Only text and comment nodes as children
|
|
353
|
+
|
|
354
|
+
#### TempileNode
|
|
355
|
+
|
|
356
|
+
Special node for wrapping HTML structural elements (doctype, html, head, body). This provides easier parsing and allows attributes on these elements.
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
class TempileNode extends Node {
|
|
360
|
+
type: NodeType.Tempile;
|
|
361
|
+
nodeTypeData: string; // "doctype" | "html" | "head" | "body"
|
|
362
|
+
attrs: Attribute[];
|
|
363
|
+
children: Node[];
|
|
364
|
+
getChildren(): Node[]
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Template Syntax:**
|
|
369
|
+
```html
|
|
370
|
+
<tempile @doctype></tempile>
|
|
371
|
+
<tempile @html lang="en">
|
|
372
|
+
<tempile @head>
|
|
373
|
+
<meta charset="UTF-8">
|
|
374
|
+
<title>Page Title</title>
|
|
375
|
+
</tempile>
|
|
376
|
+
<tempile @body>
|
|
377
|
+
<!-- Content here -->
|
|
378
|
+
</tempile>
|
|
379
|
+
</tempile>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Note:** For `@doctype`, a `DoctypeNode` is created instead during parsing.
|
|
383
|
+
|
|
384
|
+
#### IncludeNode
|
|
385
|
+
|
|
386
|
+
Includes external template files and enables component composition.
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
class IncludeNode extends Node {
|
|
390
|
+
type: NodeType.Include;
|
|
391
|
+
ctxId: string; // Unique context ID for slot matching
|
|
392
|
+
path: Attribute; // Path to included file
|
|
393
|
+
children: Node[]; // Content nodes to fill slots
|
|
394
|
+
getChildren(): Node[]
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Template Syntax:**
|
|
399
|
+
```html
|
|
400
|
+
<!-- Simple include without slot filling -->
|
|
401
|
+
<include @path="components/header.html"></include>
|
|
402
|
+
|
|
403
|
+
<!-- Include with slot content -->
|
|
404
|
+
<include @path="layouts/main.html">
|
|
405
|
+
<content @name="sidebar">
|
|
406
|
+
<nav>Navigation items</nav>
|
|
407
|
+
</content>
|
|
408
|
+
<content @name="main">
|
|
409
|
+
<h1>Page content</h1>
|
|
410
|
+
</content>
|
|
411
|
+
</include>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**How it works:**
|
|
415
|
+
1. Parser creates `IncludeNode` with unique `ctxId`
|
|
416
|
+
2. `resolveIncludes()` reads and parses the referenced file
|
|
417
|
+
3. Parsed content is added to `IncludeNode.children`
|
|
418
|
+
4. `matchSlotsAndContents()` matches `ContentNode`s with `SlotNode`s using `ctxId`
|
|
419
|
+
|
|
420
|
+
#### SlotNode
|
|
421
|
+
|
|
422
|
+
Defines a placeholder in a template that can be filled with content from an include.
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
class SlotNode extends Node {
|
|
426
|
+
type: NodeType.Slot;
|
|
427
|
+
name: string; // Slot identifier
|
|
428
|
+
children: Node[]; // Default content (if not filled)
|
|
429
|
+
getChildren(): Node[]
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**Template Syntax:**
|
|
434
|
+
```html
|
|
435
|
+
<!-- In layout.html -->
|
|
436
|
+
<div class="layout">
|
|
437
|
+
<slot @name="header">
|
|
438
|
+
<h1>Default Header</h1>
|
|
439
|
+
</slot>
|
|
440
|
+
<slot @name="content">
|
|
441
|
+
<p>Default content</p>
|
|
442
|
+
</slot>
|
|
443
|
+
</div>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Usage in parent template:**
|
|
447
|
+
```html
|
|
448
|
+
<include @path="layout.html">
|
|
449
|
+
<content @name="header">
|
|
450
|
+
<h1>Custom Header</h1>
|
|
451
|
+
</content>
|
|
452
|
+
</include>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### ContentNode
|
|
456
|
+
|
|
457
|
+
Provides content to fill a slot in an included template. Must be a direct child of `<include>`.
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
class ContentNode extends Node {
|
|
461
|
+
type: NodeType.Content;
|
|
462
|
+
name: string; // Target slot name
|
|
463
|
+
children: Node[];
|
|
464
|
+
getChildren(): Node[]
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Validation:**
|
|
469
|
+
- Must be a direct child of `<include>` tag
|
|
470
|
+
- Only one `@[name]` attribute allowed
|
|
471
|
+
|
|
472
|
+
**After processing:**
|
|
473
|
+
- `ContentNode`s are removed from the tree
|
|
474
|
+
- Their children are moved into matching `SlotNode`s
|
|
475
|
+
- Unmatched slots keep their default content
|
|
476
|
+
|
|
477
|
+
#### OutNode
|
|
478
|
+
|
|
479
|
+
Outputs an expression value. By default, content is escaped for security. Use `@raw` to output unescaped HTML.
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
class OutNode extends Node {
|
|
483
|
+
type: NodeType.Out;
|
|
484
|
+
data: string; // Expression to output
|
|
485
|
+
isRaw: boolean; // Whether to escape output
|
|
486
|
+
getChildren(): [] // No children
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Template Syntax:**
|
|
491
|
+
```html
|
|
492
|
+
<!-- Escaped output (safe) -->
|
|
493
|
+
<out>user.name</out>
|
|
494
|
+
|
|
495
|
+
<!-- Raw output (dangerous - use with caution) -->
|
|
496
|
+
<out @raw>article.htmlContent</out>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
**Validation:**
|
|
500
|
+
- Only text nodes allowed as children
|
|
501
|
+
- Maximum one attribute (`@raw`)
|
|
502
|
+
|
|
503
|
+
#### LogicNode
|
|
504
|
+
|
|
505
|
+
Embeds native code in the target language within the template. Code is not isolated and can access variables from other parts of the template.
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
class LogicNode extends Node {
|
|
509
|
+
type: NodeType.Logic;
|
|
510
|
+
lang: string; // Target language
|
|
511
|
+
data: string; // Native code
|
|
512
|
+
getChildren(): [] // No children
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Template Syntax:**
|
|
517
|
+
```html
|
|
518
|
+
<logic @js>
|
|
519
|
+
const user = getUser();
|
|
520
|
+
const isAdmin = user.role === 'admin';
|
|
521
|
+
</logic>
|
|
522
|
+
|
|
523
|
+
<if @js="isAdmin">
|
|
524
|
+
<p>Admin panel</p>
|
|
525
|
+
</if>
|
|
526
|
+
|
|
527
|
+
<logic @go>
|
|
528
|
+
user := getUser()
|
|
529
|
+
isAdmin := user.Role == "admin"
|
|
530
|
+
</logic>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Validation:**
|
|
534
|
+
- Only one `@[lang]` attribute allowed
|
|
535
|
+
- Only text nodes as children
|
|
536
|
+
|
|
537
|
+
## Template Syntax Guide
|
|
538
|
+
|
|
539
|
+
### Multi-Language Support
|
|
540
|
+
|
|
541
|
+
Most control structures and logic nodes support multiple target languages through `@[lang]` attributes. This allows a single template to be compiled to different languages.
|
|
542
|
+
|
|
543
|
+
```html
|
|
544
|
+
<if @js="count > 0" @go="count > 0" @python="count > 0">
|
|
545
|
+
<p>Has items</p>
|
|
546
|
+
</if>
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Expression Interpolation
|
|
550
|
+
|
|
551
|
+
Use `{{expression}}` syntax within attribute values:
|
|
552
|
+
|
|
553
|
+
```html
|
|
554
|
+
<div
|
|
555
|
+
class="card {{variant}}"
|
|
556
|
+
data-id="{{item.id}}"
|
|
557
|
+
style="width: {{width}}px">
|
|
558
|
+
</div>
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Expressions are parsed into `AttrValueNode` arrays for easy processing by compilers.
|
|
562
|
+
|
|
563
|
+
### Component Composition
|
|
564
|
+
|
|
565
|
+
Tempile provides a powerful slot/content system for building reusable components:
|
|
566
|
+
|
|
567
|
+
**1. Create a layout with slots:**
|
|
568
|
+
```html
|
|
569
|
+
<!-- layouts/base.html -->
|
|
570
|
+
<tempile @html>
|
|
571
|
+
<tempile @head>
|
|
572
|
+
<slot @name="head-extra"></slot>
|
|
573
|
+
</tempile>
|
|
574
|
+
<tempile @body>
|
|
575
|
+
<header>
|
|
576
|
+
<slot @name="header">
|
|
577
|
+
<h1>Default Site Title</h1>
|
|
578
|
+
</slot>
|
|
579
|
+
</header>
|
|
580
|
+
<main>
|
|
581
|
+
<slot @name="content"></slot>
|
|
582
|
+
</main>
|
|
583
|
+
</tempile>
|
|
584
|
+
</tempile>
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**2. Use the layout and fill slots:**
|
|
588
|
+
```html
|
|
589
|
+
<!-- pages/home.html -->
|
|
590
|
+
<include @path="layouts/base.html">
|
|
591
|
+
<content @name="head-extra">
|
|
592
|
+
<link rel="stylesheet" href="home.css">
|
|
593
|
+
</content>
|
|
594
|
+
<content @name="header">
|
|
595
|
+
<h1>Welcome Home</h1>
|
|
596
|
+
<nav>...</nav>
|
|
597
|
+
</content>
|
|
598
|
+
<content @name="content">
|
|
599
|
+
<p>Homepage content</p>
|
|
600
|
+
</content>
|
|
601
|
+
</include>
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
## Building a Compiler
|
|
605
|
+
|
|
606
|
+
To build a compiler for Tempile, you need to:
|
|
607
|
+
|
|
608
|
+
1. Parse templates using `parse()`
|
|
609
|
+
2. Process includes and slots
|
|
610
|
+
3. Traverse the AST
|
|
611
|
+
4. Generate native code for your target language
|
|
612
|
+
|
|
613
|
+
### Basic Compiler Structure
|
|
614
|
+
|
|
615
|
+
```javascript
|
|
616
|
+
import { parse, NodeType } from 'tempile-core';
|
|
617
|
+
|
|
618
|
+
class MyCompiler {
|
|
619
|
+
compile(source, fileName) {
|
|
620
|
+
// 1. Parse
|
|
621
|
+
const root = parse(source, fileName);
|
|
622
|
+
|
|
623
|
+
// 2. Resolve includes
|
|
624
|
+
root.resolveIncludes('./templates');
|
|
625
|
+
|
|
626
|
+
// 3. Match slots with contents
|
|
627
|
+
root.matchSlotsAndContents();
|
|
628
|
+
|
|
629
|
+
// 4. Generate code
|
|
630
|
+
return this.generateCode(root.children);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
generateCode(nodes) {
|
|
634
|
+
let code = '';
|
|
635
|
+
|
|
636
|
+
for (const node of nodes) {
|
|
637
|
+
switch (node.type) {
|
|
638
|
+
case NodeType.Text:
|
|
639
|
+
code += this.generateText(node);
|
|
640
|
+
break;
|
|
641
|
+
case NodeType.Element:
|
|
642
|
+
code += this.generateElement(node);
|
|
643
|
+
break;
|
|
644
|
+
case NodeType.If:
|
|
645
|
+
code += this.generateIf(node);
|
|
646
|
+
break;
|
|
647
|
+
case NodeType.For:
|
|
648
|
+
code += this.generateFor(node);
|
|
649
|
+
break;
|
|
650
|
+
case NodeType.Out:
|
|
651
|
+
code += this.generateOut(node);
|
|
652
|
+
break;
|
|
653
|
+
case NodeType.Logic:
|
|
654
|
+
code += this.generateLogic(node);
|
|
655
|
+
break;
|
|
656
|
+
case NodeType.Import:
|
|
657
|
+
code += this.generateImport(node);
|
|
658
|
+
break;
|
|
659
|
+
// Handle other node types...
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return code;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
generateIf(node) {
|
|
667
|
+
// Get condition for your target language
|
|
668
|
+
const condition = node.conditions.find(c => c.name === 'js');
|
|
669
|
+
|
|
670
|
+
let code = `if (${condition.value}) {\n`;
|
|
671
|
+
code += this.generateCode(node.ifContent);
|
|
672
|
+
|
|
673
|
+
for (const elseif of node.elseIfNodes) {
|
|
674
|
+
const elseifCond = elseif.conditions.find(c => c.name === 'js');
|
|
675
|
+
code += `} else if (${elseifCond.value}) {\n`;
|
|
676
|
+
code += this.generateCode(elseif.children);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (node.elseNode) {
|
|
680
|
+
code += `} else {\n`;
|
|
681
|
+
code += this.generateCode(node.elseNode.children);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
code += `}\n`;
|
|
685
|
+
return code;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Implement other generate methods...
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Handling Attributes with Expressions
|
|
693
|
+
|
|
694
|
+
```javascript
|
|
695
|
+
generateElement(node) {
|
|
696
|
+
let code = `<${node.tag}`;
|
|
697
|
+
|
|
698
|
+
for (const attr of node.attrs) {
|
|
699
|
+
if (attr.valueNodes && attr.valueNodes.length > 0) {
|
|
700
|
+
// Attribute has expressions
|
|
701
|
+
code += ` ${attr.name}="`;
|
|
702
|
+
for (const valueNode of attr.valueNodes) {
|
|
703
|
+
if (valueNode.type === 'text') {
|
|
704
|
+
code += valueNode.value;
|
|
705
|
+
} else {
|
|
706
|
+
// Generate expression evaluation code
|
|
707
|
+
code += `\${${valueNode.value}}`;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
code += '"';
|
|
711
|
+
} else {
|
|
712
|
+
// Plain attribute
|
|
713
|
+
code += ` ${attr.name}="${attr.value}"`;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
code += '>';
|
|
718
|
+
code += this.generateCode(node.children);
|
|
719
|
+
code += `</${node.tag}>`;
|
|
720
|
+
|
|
721
|
+
return code;
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## Error Handling
|
|
726
|
+
|
|
727
|
+
All nodes include position information (`pos`) for detailed error reporting:
|
|
728
|
+
|
|
729
|
+
```javascript
|
|
730
|
+
function reportError(node, message) {
|
|
731
|
+
const pos = node.pos;
|
|
732
|
+
throw new Error(
|
|
733
|
+
`${message}\n` +
|
|
734
|
+
`File: ${pos.fileName}\n` +
|
|
735
|
+
`Line: ${pos.startLine}, Column: ${pos.startCol}`
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
## Best Practices
|
|
741
|
+
|
|
742
|
+
1. **Always process in order:**
|
|
743
|
+
```javascript
|
|
744
|
+
const root = parse(source, fileName);
|
|
745
|
+
root.resolveIncludes(srcPath); // First
|
|
746
|
+
root.matchSlotsAndContents(); // Second
|
|
747
|
+
// Then traverse and generate code
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
2. **Handle missing language attributes:**
|
|
751
|
+
```javascript
|
|
752
|
+
const condition = node.conditions.find(c => c.name === targetLang);
|
|
753
|
+
if (!condition) {
|
|
754
|
+
throw new Error(
|
|
755
|
+
`No condition for language '${targetLang}' in if statement at ` +
|
|
756
|
+
`${node.pos.fileName}:${node.pos.startLine}`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
3. **Validate node structures:**
|
|
762
|
+
- Check for required attributes
|
|
763
|
+
- Verify parent-child relationships
|
|
764
|
+
- Ensure proper nesting
|
|
765
|
+
|
|
766
|
+
4. **Use position information:**
|
|
767
|
+
- Include in error messages
|
|
768
|
+
- Generate source maps
|
|
769
|
+
- Help with debugging
|
|
770
|
+
|
|
771
|
+
## Complete Example
|
|
772
|
+
|
|
773
|
+
### Input Templates
|
|
774
|
+
|
|
775
|
+
**layout.html:**
|
|
776
|
+
```html
|
|
777
|
+
<tempile @doctype></tempile>
|
|
778
|
+
<tempile @html lang="en">
|
|
779
|
+
<tempile @head>
|
|
780
|
+
<meta charset="UTF-8">
|
|
781
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
782
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
783
|
+
<slot @name="header-extra"></slot>
|
|
784
|
+
</tempile>
|
|
785
|
+
<tempile @body>
|
|
786
|
+
<slot @name="body-content"></slot>
|
|
787
|
+
</tempile>
|
|
788
|
+
</tempile>
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
**page.html:**
|
|
792
|
+
```html
|
|
793
|
+
<include @path="layout.html">
|
|
794
|
+
<content @name="header-extra">
|
|
795
|
+
<title>Home Page</title>
|
|
796
|
+
</content>
|
|
797
|
+
<content @name="body-content">
|
|
798
|
+
<h1>Page Body</h1>
|
|
799
|
+
<if @js="1 === 1">
|
|
800
|
+
if blok
|
|
801
|
+
<elseif @js="1 > 2">
|
|
802
|
+
elseif blok
|
|
803
|
+
</elseif>
|
|
804
|
+
<else>
|
|
805
|
+
else blok
|
|
806
|
+
</else>
|
|
807
|
+
</if>
|
|
808
|
+
<for @js="const item of items">
|
|
809
|
+
<out>item.name</out>
|
|
810
|
+
</for>
|
|
811
|
+
</content>
|
|
812
|
+
</include>
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### Processing
|
|
816
|
+
|
|
817
|
+
```javascript
|
|
818
|
+
import { parse } from 'tempile-core';
|
|
819
|
+
import fs from 'fs';
|
|
820
|
+
|
|
821
|
+
// Parse page.html
|
|
822
|
+
const source = fs.readFileSync('page.html', 'utf-8');
|
|
823
|
+
const root = parse(source, 'page.html');
|
|
824
|
+
|
|
825
|
+
// Resolve includes - reads and parses layout.html
|
|
826
|
+
root.resolveIncludes('./');
|
|
827
|
+
|
|
828
|
+
// Match slots with contents - fills header-extra and body-content slots
|
|
829
|
+
root.matchSlotsAndContents();
|
|
830
|
+
|
|
831
|
+
// AST is now ready for compilation
|
|
832
|
+
console.log(JSON.stringify(root, null, 2));
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### Resulting AST Structure
|
|
836
|
+
|
|
837
|
+
After processing, the AST will have this structure:
|
|
838
|
+
|
|
839
|
+
```json
|
|
840
|
+
{
|
|
841
|
+
"children": [
|
|
842
|
+
{
|
|
843
|
+
"type": "doctype",
|
|
844
|
+
"data": "<!DOCTYPE html>"
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
"type": "element",
|
|
848
|
+
"tag": "html",
|
|
849
|
+
"attrs": [{"name": "lang", "value": "en"}],
|
|
850
|
+
"children": [
|
|
851
|
+
{
|
|
852
|
+
"type": "element",
|
|
853
|
+
"tag": "head",
|
|
854
|
+
"children": [
|
|
855
|
+
{"type": "element", "tag": "meta", "attrs": [...]},
|
|
856
|
+
{"type": "element", "tag": "title", "children": [
|
|
857
|
+
{"type": "text", "data": "Home Page"}
|
|
858
|
+
]}
|
|
859
|
+
]
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
"type": "element",
|
|
863
|
+
"tag": "body",
|
|
864
|
+
"children": [
|
|
865
|
+
{"type": "element", "tag": "h1", "children": [
|
|
866
|
+
{"type": "text", "data": "Page Body"}
|
|
867
|
+
]},
|
|
868
|
+
{
|
|
869
|
+
"type": "if",
|
|
870
|
+
"conditions": [{"name": "js", "value": "1 === 1"}],
|
|
871
|
+
"ifContent": [
|
|
872
|
+
{"type": "text", "data": "\n\t\t\tif blok\n\t\t\t"}
|
|
873
|
+
],
|
|
874
|
+
"elseIfNodes": [{
|
|
875
|
+
"type": "elseif",
|
|
876
|
+
"conditions": [{"name": "js", "value": "1 > 2"}],
|
|
877
|
+
"children": [
|
|
878
|
+
{"type": "text", "data": "\n\t\t\t\telseif blok\n\t\t\t"}
|
|
879
|
+
]
|
|
880
|
+
}],
|
|
881
|
+
"elseNode": {
|
|
882
|
+
"type": "else",
|
|
883
|
+
"children": [
|
|
884
|
+
{"type": "text", "data": "\n\t\t\t\telse blok\n\t\t\t"}
|
|
885
|
+
]
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
"type": "for",
|
|
890
|
+
"loops": [{"name": "js", "value": "const item of items"}],
|
|
891
|
+
"children": [
|
|
892
|
+
{"type": "out", "data": "item.name", "isRaw": false}
|
|
893
|
+
]
|
|
894
|
+
}
|
|
895
|
+
]
|
|
896
|
+
}
|
|
897
|
+
]
|
|
898
|
+
}
|
|
899
|
+
],
|
|
900
|
+
"fileName": "page.html"
|
|
901
|
+
}
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
**Key observations:**
|
|
905
|
+
|
|
906
|
+
1. **Include resolution**: The `<include>` and `<tempile>` tags are unwrapped, their content is merged into the main tree
|
|
907
|
+
2. **Slot matching**: `<slot>` tags are replaced with their corresponding `<content>` children
|
|
908
|
+
3. **Structure flattening**: The final AST is a clean tree of standard HTML elements and control flow nodes
|
|
909
|
+
4. **Position tracking**: Each node includes `pos` with file name and line/column information (omitted above for clarity)
|
|
910
|
+
|
|
911
|
+
## License
|
|
912
|
+
|
|
913
|
+
MIT
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED