binja 0.3.3 → 0.4.0
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 +70 -8
- package/dist/cli.js +417 -0
- package/dist/index.d.ts +26 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +448 -3
- package/dist/lexer/hybrid.d.ts +13 -0
- package/dist/lexer/hybrid.d.ts.map +1 -0
- package/dist/lexer/index.d.ts +4 -0
- package/dist/lexer/index.d.ts.map +1 -1
- package/dist/native/index.d.ts +72 -0
- package/dist/native/index.d.ts.map +1 -0
- package/native/darwin-arm64/libbinja.dylib +0 -0
- package/native/darwin-x64/libbinja.dylib +0 -0
- package/native/linux-arm64/libbinja.so +0 -0
- package/native/linux-x64/libbinja.so +0 -0
- package/package.json +18 -6
package/README.md
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
<h1 align="center">binja</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<strong>High-performance Jinja2/Django template engine for Bun</strong>
|
|
4
|
+
<strong>High-performance Jinja2/Django template engine for Bun with native Zig acceleration</strong>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="#installation">Installation</a> •
|
|
9
9
|
<a href="#quick-start">Quick Start</a> •
|
|
10
10
|
<a href="#features">Features</a> •
|
|
11
|
-
<a href="#
|
|
11
|
+
<a href="#native-acceleration">Native Acceleration</a> •
|
|
12
12
|
<a href="#filters">Filters</a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<img src="https://img.shields.io/badge/bun-%23000000.svg?style=for-the-badge&logo=bun&logoColor=white" alt="Bun" />
|
|
17
|
+
<img src="https://img.shields.io/badge/Zig-F7A41D?style=for-the-badge&logo=zig&logoColor=white" alt="Zig Native" />
|
|
17
18
|
<img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
|
|
18
19
|
<img src="https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=white" alt="Django Compatible" />
|
|
19
20
|
<img src="https://img.shields.io/badge/license-BSD--3--Clause-blue.svg?style=for-the-badge" alt="BSD-3-Clause License" />
|
|
@@ -25,11 +26,13 @@
|
|
|
25
26
|
|
|
26
27
|
| Feature | Binja | Other JS engines |
|
|
27
28
|
|---------|-----------|------------------|
|
|
29
|
+
| **Native Zig Lexer** | ✅ 7x faster | ❌ |
|
|
28
30
|
| **AOT Compilation** | ✅ 160x faster | ❌ |
|
|
29
31
|
| Django DTL Compatible | ✅ 100% | ❌ Partial |
|
|
30
32
|
| Jinja2 Compatible | ✅ Full | ⚠️ Limited |
|
|
31
33
|
| Template Inheritance | ✅ | ⚠️ |
|
|
32
|
-
|
|
|
34
|
+
| 70+ Built-in Filters | ✅ | ❌ |
|
|
35
|
+
| 28 Built-in Tests | ✅ | ❌ |
|
|
33
36
|
| Debug Panel | ✅ | ❌ |
|
|
34
37
|
| CLI Tool | ✅ | ⚠️ |
|
|
35
38
|
| Autoescape by Default | ✅ | ❌ |
|
|
@@ -68,6 +71,45 @@ bun run full-benchmark.ts
|
|
|
68
71
|
|
|
69
72
|
---
|
|
70
73
|
|
|
74
|
+
## Native Acceleration
|
|
75
|
+
|
|
76
|
+
Binja includes a **native Zig lexer** that provides **7x faster** tokenization through Bun's FFI. The native library is automatically used when available.
|
|
77
|
+
|
|
78
|
+
### Supported Platforms
|
|
79
|
+
|
|
80
|
+
| Platform | Architecture | Status |
|
|
81
|
+
|----------|--------------|--------|
|
|
82
|
+
| macOS | Apple Silicon (arm64) | ✅ |
|
|
83
|
+
| macOS | Intel (x64) | ✅ |
|
|
84
|
+
| Linux | x64 | ✅ |
|
|
85
|
+
| Linux | arm64 | ✅ |
|
|
86
|
+
|
|
87
|
+
### Check Native Status
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { isNativeAccelerated } from 'binja/lexer'
|
|
91
|
+
|
|
92
|
+
console.log('Using native Zig:', isNativeAccelerated())
|
|
93
|
+
// Output: Using native Zig: true
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Performance Comparison
|
|
97
|
+
|
|
98
|
+
| Template Size | TypeScript Lexer | Zig Native | Speedup |
|
|
99
|
+
|--------------|------------------|------------|---------|
|
|
100
|
+
| Small (100B) | 290K ops/s | 1.2M ops/s | **4x** |
|
|
101
|
+
| Medium (1KB) | 85K ops/s | 450K ops/s | **5x** |
|
|
102
|
+
| Large (10KB) | 12K ops/s | 85K ops/s | **7x** |
|
|
103
|
+
|
|
104
|
+
The native lexer automatically handles:
|
|
105
|
+
- ✅ All Jinja2/Django delimiters (`{{`, `{%`, `{#`)
|
|
106
|
+
- ✅ Whitespace control (`{%-`, `-%}`)
|
|
107
|
+
- ✅ Raw/verbatim blocks
|
|
108
|
+
- ✅ UTF-8 characters (€, 日本語, emoji)
|
|
109
|
+
- ✅ Error handling with line numbers
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
71
113
|
## Installation
|
|
72
114
|
|
|
73
115
|
```bash
|
|
@@ -362,9 +404,12 @@ Tests check values using the `is` operator (Jinja2 syntax):
|
|
|
362
404
|
```typescript
|
|
363
405
|
import { builtinTests } from 'binja'
|
|
364
406
|
|
|
365
|
-
// All
|
|
407
|
+
// All 28 built-in tests
|
|
366
408
|
console.log(Object.keys(builtinTests))
|
|
367
|
-
// ['divisibleby', 'even', 'odd', 'number', 'integer',
|
|
409
|
+
// ['divisibleby', 'even', 'odd', 'number', 'integer', 'float',
|
|
410
|
+
// 'defined', 'undefined', 'none', 'boolean', 'string', 'mapping',
|
|
411
|
+
// 'iterable', 'sequence', 'callable', 'upper', 'lower', 'empty',
|
|
412
|
+
// 'in', 'eq', 'ne', 'sameas', 'equalto', 'truthy', 'falsy', ...]
|
|
368
413
|
```
|
|
369
414
|
|
|
370
415
|
---
|
|
@@ -400,6 +445,10 @@ const env = new Environment({
|
|
|
400
445
|
// Auto-escape HTML (default: true)
|
|
401
446
|
autoescape: true,
|
|
402
447
|
|
|
448
|
+
// Cache settings
|
|
449
|
+
cache: true, // Enable template caching (default: true)
|
|
450
|
+
cacheMaxSize: 100, // LRU cache limit (default: 100)
|
|
451
|
+
|
|
403
452
|
// Timezone for date/time operations
|
|
404
453
|
// All date filters and {% now %} tag will use this timezone
|
|
405
454
|
timezone: 'Europe/Rome', // or 'UTC', 'America/New_York', etc.
|
|
@@ -426,6 +475,11 @@ const env = new Environment({
|
|
|
426
475
|
// Static file resolver for {% static %} tag
|
|
427
476
|
staticResolver: (path: string) => `/static/${path}`
|
|
428
477
|
})
|
|
478
|
+
|
|
479
|
+
// Cache monitoring
|
|
480
|
+
env.cacheSize() // Number of cached templates
|
|
481
|
+
env.cacheStats() // { size, maxSize, hits, misses, hitRate }
|
|
482
|
+
env.clearCache() // Clear cache and reset stats
|
|
429
483
|
```
|
|
430
484
|
|
|
431
485
|
---
|
|
@@ -568,7 +622,8 @@ await render('{{ script }}', {
|
|
|
568
622
|
1. **Use AOT in Production** - `compile()` is 160x faster than Nunjucks
|
|
569
623
|
2. **Pre-compile at Startup** - Compile templates once, use many times
|
|
570
624
|
3. **Reuse Environment** - For templates with `{% extends %}`, create once
|
|
571
|
-
4. **
|
|
625
|
+
4. **LRU Cache** - Templates cached with LRU eviction (default: 100, prevents memory leaks)
|
|
626
|
+
5. **Monitor Cache** - Use `env.cacheStats()` to optimize `cacheMaxSize`
|
|
572
627
|
|
|
573
628
|
```typescript
|
|
574
629
|
import { compile } from 'binja'
|
|
@@ -650,12 +705,19 @@ Create a configured template environment.
|
|
|
650
705
|
```typescript
|
|
651
706
|
const env = new Environment(options)
|
|
652
707
|
|
|
653
|
-
//
|
|
708
|
+
// Rendering
|
|
654
709
|
env.render(name, context) // Render template file
|
|
655
710
|
env.renderString(str, context) // Render template string
|
|
711
|
+
|
|
712
|
+
// Configuration
|
|
656
713
|
env.addFilter(name, fn) // Add custom filter
|
|
657
714
|
env.addGlobal(name, value) // Add global variable
|
|
658
|
-
|
|
715
|
+
|
|
716
|
+
// Cache Management (LRU with configurable max size)
|
|
717
|
+
env.loadTemplate(name) // Pre-load template (cache warming)
|
|
718
|
+
env.cacheSize() // Get number of cached templates
|
|
719
|
+
env.cacheStats() // Get { size, maxSize, hits, misses, hitRate }
|
|
720
|
+
env.clearCache() // Clear all cached templates and reset stats
|
|
659
721
|
```
|
|
660
722
|
|
|
661
723
|
---
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,285 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = __moduleCache.get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
14
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
15
|
+
get: () => from[key],
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
}));
|
|
18
|
+
__moduleCache.set(from, entry);
|
|
19
|
+
return entry;
|
|
20
|
+
};
|
|
21
|
+
var __export = (target, all) => {
|
|
22
|
+
for (var name in all)
|
|
23
|
+
__defProp(target, name, {
|
|
24
|
+
get: all[name],
|
|
25
|
+
enumerable: true,
|
|
26
|
+
configurable: true,
|
|
27
|
+
set: (newValue) => all[name] = () => newValue
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
31
|
+
|
|
32
|
+
// src/native/index.ts
|
|
33
|
+
var exports_native = {};
|
|
34
|
+
__export(exports_native, {
|
|
35
|
+
tokenizeCount: () => tokenizeCount,
|
|
36
|
+
tokenize: () => tokenize,
|
|
37
|
+
nativeVersion: () => nativeVersion,
|
|
38
|
+
isNativeAvailable: () => isNativeAvailable,
|
|
39
|
+
TokenType: () => TokenType,
|
|
40
|
+
NativeLexer: () => NativeLexer
|
|
41
|
+
});
|
|
42
|
+
import { dlopen, FFIType, ptr, CString } from "bun:ffi";
|
|
43
|
+
import { join } from "path";
|
|
44
|
+
import { existsSync } from "fs";
|
|
45
|
+
function getLibraryPath() {
|
|
46
|
+
const platform = process.platform;
|
|
47
|
+
const arch = process.arch;
|
|
48
|
+
const libExt = platform === "darwin" ? "dylib" : platform === "win32" ? "dll" : "so";
|
|
49
|
+
const libName = `libbinja.${libExt}`;
|
|
50
|
+
const projectRoot = join(import.meta.dir, "..", "..");
|
|
51
|
+
const searchPaths = [
|
|
52
|
+
join(projectRoot, "native", `${platform}-${arch}`, libName),
|
|
53
|
+
join(projectRoot, "native", libName),
|
|
54
|
+
join(projectRoot, "zig-native", "zig-out", "lib", libName),
|
|
55
|
+
join(projectRoot, "zig-native", libName),
|
|
56
|
+
join(import.meta.dir, libName)
|
|
57
|
+
];
|
|
58
|
+
for (const p of searchPaths) {
|
|
59
|
+
if (existsSync(p)) {
|
|
60
|
+
return p;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function loadLibrary() {
|
|
66
|
+
if (_loadAttempted) {
|
|
67
|
+
return _lib;
|
|
68
|
+
}
|
|
69
|
+
_loadAttempted = true;
|
|
70
|
+
const libPath = getLibraryPath();
|
|
71
|
+
if (!libPath) {
|
|
72
|
+
console.warn("[binja] Native library not found, using pure JS fallback");
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
_lib = dlopen(libPath, symbols);
|
|
77
|
+
_nativeAvailable = true;
|
|
78
|
+
return _lib;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.warn(`[binja] Failed to load native library: ${e}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function isNativeAvailable() {
|
|
85
|
+
loadLibrary();
|
|
86
|
+
return _nativeAvailable;
|
|
87
|
+
}
|
|
88
|
+
function nativeVersion() {
|
|
89
|
+
const lib = loadLibrary();
|
|
90
|
+
if (!lib)
|
|
91
|
+
return null;
|
|
92
|
+
const versionPtr = lib.symbols.binja_version();
|
|
93
|
+
if (!versionPtr)
|
|
94
|
+
return null;
|
|
95
|
+
return new CString(versionPtr).toString();
|
|
96
|
+
}
|
|
97
|
+
function tokenizeCount(source) {
|
|
98
|
+
if (source.length === 0) {
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const lib = loadLibrary();
|
|
102
|
+
if (!lib) {
|
|
103
|
+
throw new Error("Native library not available");
|
|
104
|
+
}
|
|
105
|
+
const bytes = new TextEncoder().encode(source);
|
|
106
|
+
return Number(lib.symbols.binja_tokenize_count(ptr(bytes), bytes.length));
|
|
107
|
+
}
|
|
108
|
+
function tokenize(source) {
|
|
109
|
+
const lexer = new NativeLexer(source);
|
|
110
|
+
try {
|
|
111
|
+
return lexer.getAllTokens();
|
|
112
|
+
} finally {
|
|
113
|
+
lexer.free();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
var TokenType, symbols, _lib = null, _loadAttempted = false, _nativeAvailable = false, NativeLexer;
|
|
117
|
+
var init_native = __esm(() => {
|
|
118
|
+
TokenType = {
|
|
119
|
+
TEXT: 0,
|
|
120
|
+
VAR_START: 1,
|
|
121
|
+
VAR_END: 2,
|
|
122
|
+
BLOCK_START: 3,
|
|
123
|
+
BLOCK_END: 4,
|
|
124
|
+
COMMENT_START: 5,
|
|
125
|
+
COMMENT_END: 6,
|
|
126
|
+
IDENTIFIER: 7,
|
|
127
|
+
STRING: 8,
|
|
128
|
+
NUMBER: 9,
|
|
129
|
+
OPERATOR: 10,
|
|
130
|
+
DOT: 11,
|
|
131
|
+
COMMA: 12,
|
|
132
|
+
PIPE: 13,
|
|
133
|
+
COLON: 14,
|
|
134
|
+
LPAREN: 15,
|
|
135
|
+
RPAREN: 16,
|
|
136
|
+
LBRACKET: 17,
|
|
137
|
+
RBRACKET: 18,
|
|
138
|
+
LBRACE: 19,
|
|
139
|
+
RBRACE: 20,
|
|
140
|
+
ASSIGN: 21,
|
|
141
|
+
EOF: 22
|
|
142
|
+
};
|
|
143
|
+
symbols = {
|
|
144
|
+
binja_lexer_new: {
|
|
145
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
146
|
+
returns: FFIType.ptr
|
|
147
|
+
},
|
|
148
|
+
binja_lexer_free: {
|
|
149
|
+
args: [FFIType.ptr],
|
|
150
|
+
returns: FFIType.void
|
|
151
|
+
},
|
|
152
|
+
binja_lexer_token_count: {
|
|
153
|
+
args: [FFIType.ptr],
|
|
154
|
+
returns: FFIType.u64
|
|
155
|
+
},
|
|
156
|
+
binja_lexer_token_type: {
|
|
157
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
158
|
+
returns: FFIType.u8
|
|
159
|
+
},
|
|
160
|
+
binja_lexer_token_start: {
|
|
161
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
162
|
+
returns: FFIType.u32
|
|
163
|
+
},
|
|
164
|
+
binja_lexer_token_end: {
|
|
165
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
166
|
+
returns: FFIType.u32
|
|
167
|
+
},
|
|
168
|
+
binja_lexer_has_error: {
|
|
169
|
+
args: [FFIType.ptr],
|
|
170
|
+
returns: FFIType.bool
|
|
171
|
+
},
|
|
172
|
+
binja_lexer_error_code: {
|
|
173
|
+
args: [FFIType.ptr],
|
|
174
|
+
returns: FFIType.u8
|
|
175
|
+
},
|
|
176
|
+
binja_lexer_error_line: {
|
|
177
|
+
args: [FFIType.ptr],
|
|
178
|
+
returns: FFIType.u32
|
|
179
|
+
},
|
|
180
|
+
binja_tokenize_count: {
|
|
181
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
182
|
+
returns: FFIType.u64
|
|
183
|
+
},
|
|
184
|
+
binja_version: {
|
|
185
|
+
args: [],
|
|
186
|
+
returns: FFIType.ptr
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
NativeLexer = class NativeLexer {
|
|
190
|
+
lexerPtr = 0;
|
|
191
|
+
source;
|
|
192
|
+
sourceBuffer;
|
|
193
|
+
lib;
|
|
194
|
+
_tokenCount = 0;
|
|
195
|
+
_isEmpty = false;
|
|
196
|
+
constructor(source) {
|
|
197
|
+
const lib = loadLibrary();
|
|
198
|
+
if (!lib) {
|
|
199
|
+
throw new Error("Native library not available. Use isNativeAvailable() to check first.");
|
|
200
|
+
}
|
|
201
|
+
this.lib = lib;
|
|
202
|
+
this.source = source;
|
|
203
|
+
if (source.length === 0) {
|
|
204
|
+
this._isEmpty = true;
|
|
205
|
+
this._tokenCount = 1;
|
|
206
|
+
this.sourceBuffer = new Uint8Array(0);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this.sourceBuffer = new TextEncoder().encode(source);
|
|
210
|
+
const result = this.lib.symbols.binja_lexer_new(ptr(this.sourceBuffer), this.sourceBuffer.length);
|
|
211
|
+
if (!result) {
|
|
212
|
+
throw new Error("Failed to create native lexer");
|
|
213
|
+
}
|
|
214
|
+
this.lexerPtr = result;
|
|
215
|
+
this._tokenCount = Number(this.lib.symbols.binja_lexer_token_count(this.lexerPtr));
|
|
216
|
+
}
|
|
217
|
+
get tokenCount() {
|
|
218
|
+
return this._tokenCount;
|
|
219
|
+
}
|
|
220
|
+
getTokenType(index) {
|
|
221
|
+
if (this._isEmpty)
|
|
222
|
+
return TokenType.EOF;
|
|
223
|
+
return Number(this.lib.symbols.binja_lexer_token_type(this.lexerPtr, index));
|
|
224
|
+
}
|
|
225
|
+
getTokenStart(index) {
|
|
226
|
+
if (this._isEmpty)
|
|
227
|
+
return 0;
|
|
228
|
+
return Number(this.lib.symbols.binja_lexer_token_start(this.lexerPtr, index));
|
|
229
|
+
}
|
|
230
|
+
getTokenEnd(index) {
|
|
231
|
+
if (this._isEmpty)
|
|
232
|
+
return 0;
|
|
233
|
+
return Number(this.lib.symbols.binja_lexer_token_end(this.lexerPtr, index));
|
|
234
|
+
}
|
|
235
|
+
hasError() {
|
|
236
|
+
if (this._isEmpty)
|
|
237
|
+
return false;
|
|
238
|
+
return Boolean(this.lib.symbols.binja_lexer_has_error(this.lexerPtr));
|
|
239
|
+
}
|
|
240
|
+
getErrorCode() {
|
|
241
|
+
if (this._isEmpty)
|
|
242
|
+
return 0;
|
|
243
|
+
return Number(this.lib.symbols.binja_lexer_error_code(this.lexerPtr));
|
|
244
|
+
}
|
|
245
|
+
getErrorLine() {
|
|
246
|
+
if (this._isEmpty)
|
|
247
|
+
return 1;
|
|
248
|
+
return Number(this.lib.symbols.binja_lexer_error_line(this.lexerPtr));
|
|
249
|
+
}
|
|
250
|
+
getTokenValue(index) {
|
|
251
|
+
if (this._isEmpty)
|
|
252
|
+
return "";
|
|
253
|
+
const start = this.getTokenStart(index);
|
|
254
|
+
const end = this.getTokenEnd(index);
|
|
255
|
+
return new TextDecoder().decode(this.sourceBuffer.slice(start, end));
|
|
256
|
+
}
|
|
257
|
+
getToken(index) {
|
|
258
|
+
return {
|
|
259
|
+
type: this.getTokenType(index),
|
|
260
|
+
start: this.getTokenStart(index),
|
|
261
|
+
end: this.getTokenEnd(index),
|
|
262
|
+
value: this.getTokenValue(index)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
getAllTokens() {
|
|
266
|
+
const tokens = [];
|
|
267
|
+
for (let i = 0;i < this._tokenCount; i++) {
|
|
268
|
+
tokens.push(this.getToken(i));
|
|
269
|
+
}
|
|
270
|
+
return tokens;
|
|
271
|
+
}
|
|
272
|
+
free() {
|
|
273
|
+
if (this.lexerPtr) {
|
|
274
|
+
this.lib.symbols.binja_lexer_free(this.lexerPtr);
|
|
275
|
+
this.lexerPtr = null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
[Symbol.dispose]() {
|
|
279
|
+
this.free();
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
});
|
|
3
283
|
|
|
4
284
|
// src/cli.ts
|
|
5
285
|
import * as fs from "fs";
|
|
@@ -20,6 +300,135 @@ var KEYWORDS = {
|
|
|
20
300
|
in: "NAME" /* NAME */
|
|
21
301
|
};
|
|
22
302
|
|
|
303
|
+
// src/lexer/hybrid.ts
|
|
304
|
+
var _nativeChecked = false;
|
|
305
|
+
var _nativeAvailable2 = false;
|
|
306
|
+
var NativeLexerClass = null;
|
|
307
|
+
function checkNative() {
|
|
308
|
+
if (_nativeChecked)
|
|
309
|
+
return _nativeAvailable2;
|
|
310
|
+
_nativeChecked = true;
|
|
311
|
+
try {
|
|
312
|
+
const native = (init_native(), __toCommonJS(exports_native));
|
|
313
|
+
if (typeof native.isNativeAvailable === "function" && native.isNativeAvailable()) {
|
|
314
|
+
_nativeAvailable2 = true;
|
|
315
|
+
NativeLexerClass = native.NativeLexer;
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
} catch {}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
var NATIVE_TO_TS = {
|
|
322
|
+
0: "TEXT" /* TEXT */,
|
|
323
|
+
1: "VARIABLE_START" /* VARIABLE_START */,
|
|
324
|
+
2: "VARIABLE_END" /* VARIABLE_END */,
|
|
325
|
+
3: "BLOCK_START" /* BLOCK_START */,
|
|
326
|
+
4: "BLOCK_END" /* BLOCK_END */,
|
|
327
|
+
5: "COMMENT_START" /* COMMENT_START */,
|
|
328
|
+
6: "COMMENT_END" /* COMMENT_END */,
|
|
329
|
+
7: "NAME" /* NAME */,
|
|
330
|
+
8: "STRING" /* STRING */,
|
|
331
|
+
9: "NUMBER" /* NUMBER */,
|
|
332
|
+
10: "NAME" /* NAME */,
|
|
333
|
+
11: "DOT" /* DOT */,
|
|
334
|
+
12: "COMMA" /* COMMA */,
|
|
335
|
+
13: "PIPE" /* PIPE */,
|
|
336
|
+
14: "COLON" /* COLON */,
|
|
337
|
+
15: "LPAREN" /* LPAREN */,
|
|
338
|
+
16: "RPAREN" /* RPAREN */,
|
|
339
|
+
17: "LBRACKET" /* LBRACKET */,
|
|
340
|
+
18: "RBRACKET" /* RBRACKET */,
|
|
341
|
+
19: "LBRACE" /* LBRACE */,
|
|
342
|
+
20: "RBRACE" /* RBRACE */,
|
|
343
|
+
21: "ASSIGN" /* ASSIGN */,
|
|
344
|
+
22: "EOF" /* EOF */
|
|
345
|
+
};
|
|
346
|
+
var OPERATOR_TO_TYPE = {
|
|
347
|
+
"==": "EQ" /* EQ */,
|
|
348
|
+
"!=": "NE" /* NE */,
|
|
349
|
+
"<": "LT" /* LT */,
|
|
350
|
+
">": "GT" /* GT */,
|
|
351
|
+
"<=": "LE" /* LE */,
|
|
352
|
+
">=": "GE" /* GE */,
|
|
353
|
+
"+": "ADD" /* ADD */,
|
|
354
|
+
"-": "SUB" /* SUB */,
|
|
355
|
+
"*": "MUL" /* MUL */,
|
|
356
|
+
"/": "DIV" /* DIV */,
|
|
357
|
+
"%": "MOD" /* MOD */,
|
|
358
|
+
"~": "TILDE" /* TILDE */
|
|
359
|
+
};
|
|
360
|
+
var KEYWORD_TO_TYPE = {
|
|
361
|
+
and: "AND" /* AND */,
|
|
362
|
+
or: "OR" /* OR */,
|
|
363
|
+
not: "NOT" /* NOT */
|
|
364
|
+
};
|
|
365
|
+
var ERROR_MESSAGES = {
|
|
366
|
+
1: "Unterminated string",
|
|
367
|
+
2: "Unclosed template tag",
|
|
368
|
+
3: "Invalid operator",
|
|
369
|
+
4: "Unexpected character"
|
|
370
|
+
};
|
|
371
|
+
function isNativeAccelerated() {
|
|
372
|
+
return checkNative();
|
|
373
|
+
}
|
|
374
|
+
function tokenizeNative(source) {
|
|
375
|
+
if (!checkNative() || !NativeLexerClass)
|
|
376
|
+
return null;
|
|
377
|
+
if (source.length === 0) {
|
|
378
|
+
return [{ type: "EOF" /* EOF */, value: "", line: 1, column: 1 }];
|
|
379
|
+
}
|
|
380
|
+
const lexer = new NativeLexerClass(source);
|
|
381
|
+
try {
|
|
382
|
+
if (lexer.hasError()) {
|
|
383
|
+
const errorCode = lexer.getErrorCode();
|
|
384
|
+
const errorLine = lexer.getErrorLine();
|
|
385
|
+
const message = ERROR_MESSAGES[errorCode] ?? "Unknown error";
|
|
386
|
+
throw new Error(`${message} at line ${errorLine}`);
|
|
387
|
+
}
|
|
388
|
+
const tokens = [];
|
|
389
|
+
const count = lexer.tokenCount;
|
|
390
|
+
const lineStarts = [0];
|
|
391
|
+
for (let i = 0;i < source.length; i++) {
|
|
392
|
+
if (source[i] === `
|
|
393
|
+
`)
|
|
394
|
+
lineStarts.push(i + 1);
|
|
395
|
+
}
|
|
396
|
+
for (let i = 0;i < count; i++) {
|
|
397
|
+
const nativeType = lexer.getTokenType(i);
|
|
398
|
+
const value = lexer.getTokenValue(i);
|
|
399
|
+
const start = lexer.getTokenStart(i);
|
|
400
|
+
let lo = 0, hi = lineStarts.length - 1;
|
|
401
|
+
while (lo < hi) {
|
|
402
|
+
const mid = lo + hi + 1 >> 1;
|
|
403
|
+
if (lineStarts[mid] <= start)
|
|
404
|
+
lo = mid;
|
|
405
|
+
else
|
|
406
|
+
hi = mid - 1;
|
|
407
|
+
}
|
|
408
|
+
const line = lo + 1;
|
|
409
|
+
const column = start - lineStarts[lo] + 1;
|
|
410
|
+
let type = NATIVE_TO_TS[nativeType] ?? "NAME" /* NAME */;
|
|
411
|
+
let finalValue = value;
|
|
412
|
+
if (nativeType === 10 && OPERATOR_TO_TYPE[value]) {
|
|
413
|
+
type = OPERATOR_TO_TYPE[value];
|
|
414
|
+
} else if (type === "NAME" /* NAME */ && KEYWORD_TO_TYPE[value]) {
|
|
415
|
+
type = KEYWORD_TO_TYPE[value];
|
|
416
|
+
}
|
|
417
|
+
if (type === "STRING" /* STRING */ && finalValue.length >= 2) {
|
|
418
|
+
const first = finalValue[0];
|
|
419
|
+
const last = finalValue[finalValue.length - 1];
|
|
420
|
+
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
421
|
+
finalValue = finalValue.slice(1, -1);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
tokens.push({ type, value: finalValue, line, column });
|
|
425
|
+
}
|
|
426
|
+
return tokens;
|
|
427
|
+
} finally {
|
|
428
|
+
lexer.free();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
23
432
|
// src/lexer/index.ts
|
|
24
433
|
class Lexer {
|
|
25
434
|
state;
|
|
@@ -29,6 +438,7 @@ class Lexer {
|
|
|
29
438
|
blockEnd;
|
|
30
439
|
commentStart;
|
|
31
440
|
commentEnd;
|
|
441
|
+
useNative;
|
|
32
442
|
constructor(source, options = {}) {
|
|
33
443
|
this.state = {
|
|
34
444
|
source,
|
|
@@ -43,8 +453,15 @@ class Lexer {
|
|
|
43
453
|
this.blockEnd = options.blockEnd ?? "%}";
|
|
44
454
|
this.commentStart = options.commentStart ?? "{#";
|
|
45
455
|
this.commentEnd = options.commentEnd ?? "#}";
|
|
456
|
+
const hasCustomDelimiters = options.variableStart !== undefined || options.variableEnd !== undefined || options.blockStart !== undefined || options.blockEnd !== undefined || options.commentStart !== undefined || options.commentEnd !== undefined;
|
|
457
|
+
this.useNative = !hasCustomDelimiters && isNativeAccelerated();
|
|
46
458
|
}
|
|
47
459
|
tokenize() {
|
|
460
|
+
if (this.useNative) {
|
|
461
|
+
const nativeTokens = tokenizeNative(this.state.source);
|
|
462
|
+
if (nativeTokens)
|
|
463
|
+
return nativeTokens;
|
|
464
|
+
}
|
|
48
465
|
while (!this.isAtEnd()) {
|
|
49
466
|
this.scanToken();
|
|
50
467
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface EnvironmentOptions {
|
|
|
38
38
|
staticResolver?: (path: string) => string;
|
|
39
39
|
/** Cache compiled templates (default: true) */
|
|
40
40
|
cache?: boolean;
|
|
41
|
+
/** Maximum number of templates to cache (LRU eviction, default: 100) */
|
|
42
|
+
cacheMaxSize?: number;
|
|
41
43
|
/** Template file extensions to try (default: ['.html', '.jinja', '.jinja2']) */
|
|
42
44
|
extensions?: string[];
|
|
43
45
|
/** Enable debug panel injection (default: false) */
|
|
@@ -47,11 +49,25 @@ export interface EnvironmentOptions {
|
|
|
47
49
|
/** Timezone for date/time operations (e.g., 'Europe/Rome', 'UTC', 'America/New_York') */
|
|
48
50
|
timezone?: string;
|
|
49
51
|
}
|
|
52
|
+
export interface CacheStats {
|
|
53
|
+
/** Number of templates currently in cache */
|
|
54
|
+
size: number;
|
|
55
|
+
/** Maximum cache size */
|
|
56
|
+
maxSize: number;
|
|
57
|
+
/** Number of cache hits */
|
|
58
|
+
hits: number;
|
|
59
|
+
/** Number of cache misses */
|
|
60
|
+
misses: number;
|
|
61
|
+
/** Hit rate percentage */
|
|
62
|
+
hitRate: number;
|
|
63
|
+
}
|
|
50
64
|
export declare class Environment {
|
|
51
65
|
private options;
|
|
52
66
|
private runtime;
|
|
53
67
|
private templateCache;
|
|
54
68
|
private routes;
|
|
69
|
+
private cacheHits;
|
|
70
|
+
private cacheMisses;
|
|
55
71
|
constructor(options?: EnvironmentOptions);
|
|
56
72
|
/**
|
|
57
73
|
* Render a template file with the given context
|
|
@@ -78,13 +94,21 @@ export declare class Environment {
|
|
|
78
94
|
*/
|
|
79
95
|
compile(source: string): TemplateNode;
|
|
80
96
|
/**
|
|
81
|
-
* Load and compile a template file
|
|
97
|
+
* Load and compile a template file (with LRU cache)
|
|
82
98
|
*/
|
|
83
99
|
loadTemplate(templateName: string): Promise<TemplateNode>;
|
|
84
100
|
/**
|
|
85
|
-
* Clear the template cache
|
|
101
|
+
* Clear the template cache and reset stats
|
|
86
102
|
*/
|
|
87
103
|
clearCache(): void;
|
|
104
|
+
/**
|
|
105
|
+
* Get current cache size
|
|
106
|
+
*/
|
|
107
|
+
cacheSize(): number;
|
|
108
|
+
/**
|
|
109
|
+
* Get cache statistics
|
|
110
|
+
*/
|
|
111
|
+
cacheStats(): CacheStats;
|
|
88
112
|
/**
|
|
89
113
|
* Add a custom filter
|
|
90
114
|
*/
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAU,YAAY,EAAE,MAAM,UAAU,CAAA;AAE/C,OAAO,EAAkB,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,EAAsC,cAAc,EAAE,MAAM,YAAY,CAAA;AAG/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAM3C,MAAM,WAAW,kBAAkB;IACjC,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACxC,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7B,qCAAqC;IACrC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM,CAAA;IAChF,gDAAgD;IAChD,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACzC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,0BAA0B;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|