binja 0.4.0 → 0.4.1
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/dist/debug/index.js +678 -0
- package/dist/native/index.js +252 -0
- package/package.json +2 -2
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/debug/collector.ts
|
|
3
|
+
class DebugCollector {
|
|
4
|
+
data;
|
|
5
|
+
constructor() {
|
|
6
|
+
this.data = {
|
|
7
|
+
startTime: performance.now(),
|
|
8
|
+
templateChain: [],
|
|
9
|
+
mode: "runtime",
|
|
10
|
+
isAsync: false,
|
|
11
|
+
contextKeys: [],
|
|
12
|
+
contextSnapshot: {},
|
|
13
|
+
filtersUsed: new Map,
|
|
14
|
+
testsUsed: new Map,
|
|
15
|
+
cacheHits: 0,
|
|
16
|
+
cacheMisses: 0,
|
|
17
|
+
warnings: []
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
startLexer() {
|
|
21
|
+
this.data._lexerStart = performance.now();
|
|
22
|
+
}
|
|
23
|
+
endLexer() {
|
|
24
|
+
if (this.data._lexerStart) {
|
|
25
|
+
this.data.lexerTime = performance.now() - this.data._lexerStart;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
startParser() {
|
|
29
|
+
this.data._parserStart = performance.now();
|
|
30
|
+
}
|
|
31
|
+
endParser() {
|
|
32
|
+
if (this.data._parserStart) {
|
|
33
|
+
this.data.parserTime = performance.now() - this.data._parserStart;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
startRender() {
|
|
37
|
+
this.data._renderStart = performance.now();
|
|
38
|
+
}
|
|
39
|
+
endRender() {
|
|
40
|
+
if (this.data._renderStart) {
|
|
41
|
+
this.data.renderTime = performance.now() - this.data._renderStart;
|
|
42
|
+
}
|
|
43
|
+
this.data.endTime = performance.now();
|
|
44
|
+
this.data.totalTime = this.data.endTime - this.data.startTime;
|
|
45
|
+
}
|
|
46
|
+
addTemplate(name, type, parent) {
|
|
47
|
+
this.data.templateChain.push({ name, type, parent });
|
|
48
|
+
if (type === "root") {
|
|
49
|
+
this.data.rootTemplate = name;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
setMode(mode) {
|
|
53
|
+
this.data.mode = mode;
|
|
54
|
+
}
|
|
55
|
+
setAsync(isAsync) {
|
|
56
|
+
this.data.isAsync = isAsync;
|
|
57
|
+
}
|
|
58
|
+
captureContext(context) {
|
|
59
|
+
this.data.contextKeys = Object.keys(context);
|
|
60
|
+
for (const [key, value] of Object.entries(context)) {
|
|
61
|
+
this.data.contextSnapshot[key] = this.captureValue(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
captureValue(value, depth = 0) {
|
|
65
|
+
const type = this.getType(value);
|
|
66
|
+
const preview = this.getPreview(value);
|
|
67
|
+
const expandable = this.isExpandable(value);
|
|
68
|
+
const result = { type, preview, value, expandable };
|
|
69
|
+
if (expandable && depth < 3) {
|
|
70
|
+
result.children = {};
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
value.forEach((item, i) => {
|
|
73
|
+
result.children[String(i)] = this.captureValue(item, depth + 1);
|
|
74
|
+
});
|
|
75
|
+
} else if (typeof value === "object" && value !== null) {
|
|
76
|
+
for (const [k, v] of Object.entries(value)) {
|
|
77
|
+
result.children[k] = this.captureValue(v, depth + 1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
isExpandable(value) {
|
|
84
|
+
if (value === null || value === undefined)
|
|
85
|
+
return false;
|
|
86
|
+
if (Array.isArray(value))
|
|
87
|
+
return value.length > 0;
|
|
88
|
+
if (typeof value === "object")
|
|
89
|
+
return Object.keys(value).length > 0;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
getType(value) {
|
|
93
|
+
if (value === null)
|
|
94
|
+
return "null";
|
|
95
|
+
if (value === undefined)
|
|
96
|
+
return "undefined";
|
|
97
|
+
if (Array.isArray(value))
|
|
98
|
+
return `Array(${value.length})`;
|
|
99
|
+
if (value instanceof Date)
|
|
100
|
+
return "Date";
|
|
101
|
+
if (typeof value === "object")
|
|
102
|
+
return "Object";
|
|
103
|
+
return typeof value;
|
|
104
|
+
}
|
|
105
|
+
getPreview(value, maxLen = 50) {
|
|
106
|
+
if (value === null)
|
|
107
|
+
return "null";
|
|
108
|
+
if (value === undefined)
|
|
109
|
+
return "undefined";
|
|
110
|
+
if (typeof value === "string") {
|
|
111
|
+
return value.length > maxLen ? `"${value.slice(0, maxLen)}..."` : `"${value}"`;
|
|
112
|
+
}
|
|
113
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
114
|
+
return String(value);
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(value)) {
|
|
117
|
+
if (value.length === 0)
|
|
118
|
+
return "[]";
|
|
119
|
+
if (value.length <= 3) {
|
|
120
|
+
const items = value.map((v) => this.getPreview(v, 15)).join(", ");
|
|
121
|
+
return `[${items}]`;
|
|
122
|
+
}
|
|
123
|
+
return `[${this.getPreview(value[0], 15)}, ... +${value.length - 1}]`;
|
|
124
|
+
}
|
|
125
|
+
if (value instanceof Date) {
|
|
126
|
+
return value.toISOString();
|
|
127
|
+
}
|
|
128
|
+
if (typeof value === "object") {
|
|
129
|
+
const keys = Object.keys(value);
|
|
130
|
+
if (keys.length === 0)
|
|
131
|
+
return "{}";
|
|
132
|
+
if (keys.length <= 2) {
|
|
133
|
+
return `{ ${keys.join(", ")} }`;
|
|
134
|
+
}
|
|
135
|
+
return `{ ${keys.slice(0, 2).join(", ")}, ... +${keys.length - 2} }`;
|
|
136
|
+
}
|
|
137
|
+
if (typeof value === "function") {
|
|
138
|
+
return "function()";
|
|
139
|
+
}
|
|
140
|
+
return String(value);
|
|
141
|
+
}
|
|
142
|
+
recordFilter(name) {
|
|
143
|
+
this.data.filtersUsed.set(name, (this.data.filtersUsed.get(name) || 0) + 1);
|
|
144
|
+
}
|
|
145
|
+
recordTest(name) {
|
|
146
|
+
this.data.testsUsed.set(name, (this.data.testsUsed.get(name) || 0) + 1);
|
|
147
|
+
}
|
|
148
|
+
recordCacheHit() {
|
|
149
|
+
this.data.cacheHits++;
|
|
150
|
+
}
|
|
151
|
+
recordCacheMiss() {
|
|
152
|
+
this.data.cacheMisses++;
|
|
153
|
+
}
|
|
154
|
+
addWarning(message) {
|
|
155
|
+
this.data.warnings.push(message);
|
|
156
|
+
}
|
|
157
|
+
getData() {
|
|
158
|
+
return this.data;
|
|
159
|
+
}
|
|
160
|
+
getSummary() {
|
|
161
|
+
return {
|
|
162
|
+
totalTime: this.data.totalTime || 0,
|
|
163
|
+
templateCount: this.data.templateChain.length,
|
|
164
|
+
filterCount: this.data.filtersUsed.size,
|
|
165
|
+
mode: this.data.mode
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
var currentCollector = null;
|
|
170
|
+
function startDebugCollection() {
|
|
171
|
+
currentCollector = new DebugCollector;
|
|
172
|
+
return currentCollector;
|
|
173
|
+
}
|
|
174
|
+
function getDebugCollector() {
|
|
175
|
+
return currentCollector;
|
|
176
|
+
}
|
|
177
|
+
function endDebugCollection() {
|
|
178
|
+
if (currentCollector) {
|
|
179
|
+
const data = currentCollector.getData();
|
|
180
|
+
currentCollector = null;
|
|
181
|
+
return data;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/debug/panel.ts
|
|
187
|
+
var DEFAULT_OPTIONS = {
|
|
188
|
+
position: "bottom-right",
|
|
189
|
+
collapsed: true,
|
|
190
|
+
dark: true,
|
|
191
|
+
width: 420
|
|
192
|
+
};
|
|
193
|
+
function generateDebugPanel(data, options = {}) {
|
|
194
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
195
|
+
const id = `binja-debug-${Date.now()}`;
|
|
196
|
+
const colors = opts.dark ? darkTheme : lightTheme;
|
|
197
|
+
return `
|
|
198
|
+
<!-- Binja Debug Panel -->
|
|
199
|
+
<div id="${id}" class="binja-debugger" data-theme="${opts.dark ? "dark" : "light"}">
|
|
200
|
+
<style>${generateStyles(id, colors, opts)}</style>
|
|
201
|
+
${generateToggle(id, data, colors)}
|
|
202
|
+
${generatePanel(id, data, colors, opts)}
|
|
203
|
+
<script>${generateScript(id)}</script>
|
|
204
|
+
</div>
|
|
205
|
+
<!-- /Binja Debug Panel -->
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
var darkTheme = {
|
|
209
|
+
bg: "#0f0f0f",
|
|
210
|
+
bgSecondary: "#1a1a1a",
|
|
211
|
+
bgTertiary: "#242424",
|
|
212
|
+
border: "#2a2a2a",
|
|
213
|
+
borderLight: "#333",
|
|
214
|
+
text: "#e5e5e5",
|
|
215
|
+
textSecondary: "#a0a0a0",
|
|
216
|
+
textMuted: "#666",
|
|
217
|
+
accent: "#3b82f6",
|
|
218
|
+
accentHover: "#2563eb",
|
|
219
|
+
success: "#22c55e",
|
|
220
|
+
warning: "#eab308",
|
|
221
|
+
error: "#ef4444",
|
|
222
|
+
info: "#06b6d4"
|
|
223
|
+
};
|
|
224
|
+
var lightTheme = {
|
|
225
|
+
bg: "#ffffff",
|
|
226
|
+
bgSecondary: "#f8f9fa",
|
|
227
|
+
bgTertiary: "#f1f3f4",
|
|
228
|
+
border: "#e5e7eb",
|
|
229
|
+
borderLight: "#d1d5db",
|
|
230
|
+
text: "#111827",
|
|
231
|
+
textSecondary: "#4b5563",
|
|
232
|
+
textMuted: "#9ca3af",
|
|
233
|
+
accent: "#2563eb",
|
|
234
|
+
accentHover: "#1d4ed8",
|
|
235
|
+
success: "#16a34a",
|
|
236
|
+
warning: "#ca8a04",
|
|
237
|
+
error: "#dc2626",
|
|
238
|
+
info: "#0891b2"
|
|
239
|
+
};
|
|
240
|
+
function generateStyles(id, c, opts) {
|
|
241
|
+
const pos = getPosition(opts.position);
|
|
242
|
+
return `
|
|
243
|
+
#${id} { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; font-size: 13px; line-height: 1.5; position: fixed; ${pos} z-index: 2147483647; }
|
|
244
|
+
#${id} * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
245
|
+
#${id} .dbg-toggle { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; background: ${c.bg}; border: 1px solid ${c.border}; border-radius: 8px; color: ${c.text}; cursor: pointer; font-size: 12px; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: all 0.2s ease; }
|
|
246
|
+
#${id} .dbg-toggle:hover { border-color: ${c.accent}; box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
|
|
247
|
+
#${id} .dbg-toggle svg { width: 16px; height: 16px; }
|
|
248
|
+
#${id} .dbg-toggle .dbg-time { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 11px; padding: 2px 8px; background: ${c.bgTertiary}; border-radius: 4px; color: ${c.success}; }
|
|
249
|
+
#${id} .dbg-panel { display: none; width: ${opts.width}px; max-height: 85vh; background: ${c.bg}; border: 1px solid ${c.border}; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.24); overflow: hidden; margin-top: 8px; }
|
|
250
|
+
#${id} .dbg-panel.open { display: block; }
|
|
251
|
+
#${id} .dbg-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: ${c.bgSecondary}; border-bottom: 1px solid ${c.border}; }
|
|
252
|
+
#${id} .dbg-logo { display: flex; align-items: center; gap: 10px; font-weight: 600; color: ${c.text}; }
|
|
253
|
+
#${id} .dbg-logo svg { width: 20px; height: 20px; color: ${c.accent}; }
|
|
254
|
+
#${id} .dbg-meta { display: flex; align-items: center; gap: 12px; }
|
|
255
|
+
#${id} .dbg-badge { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; padding: 3px 10px; border-radius: 4px; font-weight: 500; }
|
|
256
|
+
#${id} .dbg-badge.time { background: rgba(34,197,94,0.1); color: ${c.success}; }
|
|
257
|
+
#${id} .dbg-badge.mode { background: rgba(59,130,246,0.1); color: ${c.accent}; }
|
|
258
|
+
#${id} .dbg-close { background: none; border: none; color: ${c.textMuted}; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; }
|
|
259
|
+
#${id} .dbg-close:hover { background: ${c.bgTertiary}; color: ${c.text}; }
|
|
260
|
+
#${id} .dbg-close svg { width: 18px; height: 18px; }
|
|
261
|
+
#${id} .dbg-body { max-height: calc(85vh - 52px); overflow-y: auto; }
|
|
262
|
+
#${id} .dbg-section { border-bottom: 1px solid ${c.border}; }
|
|
263
|
+
#${id} .dbg-section:last-child { border-bottom: none; }
|
|
264
|
+
#${id} .dbg-section-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; cursor: pointer; user-select: none; transition: background 0.15s; }
|
|
265
|
+
#${id} .dbg-section-header:hover { background: ${c.bgSecondary}; }
|
|
266
|
+
#${id} .dbg-section-title { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: ${c.textSecondary}; }
|
|
267
|
+
#${id} .dbg-section-title svg { width: 14px; height: 14px; opacity: 0.7; }
|
|
268
|
+
#${id} .dbg-section-meta { font-size: 11px; color: ${c.textMuted}; font-family: 'SF Mono', Monaco, monospace; }
|
|
269
|
+
#${id} .dbg-section-content { display: none; padding: 12px 16px; background: ${c.bgSecondary}; }
|
|
270
|
+
#${id} .dbg-section.open .dbg-section-content { display: block; }
|
|
271
|
+
#${id} .dbg-section .dbg-chevron { transition: transform 0.2s; color: ${c.textMuted}; }
|
|
272
|
+
#${id} .dbg-section.open .dbg-chevron { transform: rotate(90deg); }
|
|
273
|
+
#${id} .dbg-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid ${c.border}; }
|
|
274
|
+
#${id} .dbg-row:last-child { border-bottom: none; }
|
|
275
|
+
#${id} .dbg-label { color: ${c.textSecondary}; font-size: 12px; }
|
|
276
|
+
#${id} .dbg-value { color: ${c.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; text-align: right; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
277
|
+
#${id} .dbg-bar { height: 3px; background: ${c.bgTertiary}; border-radius: 2px; margin-top: 4px; overflow: hidden; }
|
|
278
|
+
#${id} .dbg-bar-fill { height: 100%; border-radius: 2px; transition: width 0.3s ease; }
|
|
279
|
+
#${id} .dbg-bar-fill.lexer { background: ${c.info}; }
|
|
280
|
+
#${id} .dbg-bar-fill.parser { background: ${c.warning}; }
|
|
281
|
+
#${id} .dbg-bar-fill.render { background: ${c.success}; }
|
|
282
|
+
#${id} .dbg-templates { display: flex; flex-direction: column; gap: 6px; }
|
|
283
|
+
#${id} .dbg-template { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: ${c.bg}; border-radius: 6px; font-size: 12px; }
|
|
284
|
+
#${id} .dbg-template-icon { width: 16px; height: 16px; color: ${c.textMuted}; flex-shrink: 0; }
|
|
285
|
+
#${id} .dbg-template-name { color: ${c.text}; font-family: 'SF Mono', Monaco, monospace; }
|
|
286
|
+
#${id} .dbg-template-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; text-transform: uppercase; }
|
|
287
|
+
#${id} .dbg-template-tag.root { background: rgba(59,130,246,0.15); color: ${c.accent}; }
|
|
288
|
+
#${id} .dbg-template-tag.extends { background: rgba(168,85,247,0.15); color: #a855f7; }
|
|
289
|
+
#${id} .dbg-template-tag.include { background: rgba(34,197,94,0.15); color: ${c.success}; }
|
|
290
|
+
#${id} .dbg-ctx-grid { display: flex; flex-direction: column; gap: 4px; }
|
|
291
|
+
#${id} .dbg-ctx-item { background: ${c.bg}; border-radius: 6px; overflow: hidden; }
|
|
292
|
+
#${id} .dbg-ctx-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; cursor: default; }
|
|
293
|
+
#${id} .dbg-ctx-row.expandable { cursor: pointer; }
|
|
294
|
+
#${id} .dbg-ctx-row.expandable:hover { background: ${c.bgTertiary}; }
|
|
295
|
+
#${id} .dbg-ctx-key { display: flex; align-items: center; gap: 6px; }
|
|
296
|
+
#${id} .dbg-ctx-arrow { width: 12px; height: 12px; color: ${c.textMuted}; transition: transform 0.15s; flex-shrink: 0; }
|
|
297
|
+
#${id} .dbg-ctx-item.open > .dbg-ctx-row .dbg-ctx-arrow { transform: rotate(90deg); }
|
|
298
|
+
#${id} .dbg-ctx-name { color: ${c.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; }
|
|
299
|
+
#${id} .dbg-ctx-type { font-size: 10px; color: ${c.accent}; background: rgba(59,130,246,0.1); padding: 1px 5px; border-radius: 3px; }
|
|
300
|
+
#${id} .dbg-ctx-preview { color: ${c.textSecondary}; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
301
|
+
#${id} .dbg-ctx-children { display: none; padding-left: 16px; border-left: 1px solid ${c.border}; margin-left: 10px; }
|
|
302
|
+
#${id} .dbg-ctx-item.open > .dbg-ctx-children { display: block; }
|
|
303
|
+
#${id} .dbg-ctx-children .dbg-ctx-item { background: transparent; }
|
|
304
|
+
#${id} .dbg-ctx-children .dbg-ctx-row { padding: 4px 8px; }
|
|
305
|
+
#${id} .dbg-filters { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
306
|
+
#${id} .dbg-filter { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: ${c.bg}; border-radius: 5px; font-size: 12px; font-family: 'SF Mono', Monaco, monospace; color: ${c.text}; }
|
|
307
|
+
#${id} .dbg-filter-count { font-size: 10px; color: ${c.accent}; font-weight: 600; }
|
|
308
|
+
#${id} .dbg-cache { display: flex; gap: 16px; }
|
|
309
|
+
#${id} .dbg-cache-stat { flex: 1; padding: 12px; background: ${c.bg}; border-radius: 6px; text-align: center; }
|
|
310
|
+
#${id} .dbg-cache-num { font-size: 24px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
|
|
311
|
+
#${id} .dbg-cache-num.hit { color: ${c.success}; }
|
|
312
|
+
#${id} .dbg-cache-num.miss { color: ${c.error}; }
|
|
313
|
+
#${id} .dbg-cache-label { font-size: 11px; color: ${c.textMuted}; margin-top: 4px; }
|
|
314
|
+
#${id} .dbg-warnings { display: flex; flex-direction: column; gap: 6px; }
|
|
315
|
+
#${id} .dbg-warning { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; background: rgba(234,179,8,0.1); border-radius: 6px; border-left: 3px solid ${c.warning}; }
|
|
316
|
+
#${id} .dbg-warning-icon { color: ${c.warning}; flex-shrink: 0; margin-top: 1px; }
|
|
317
|
+
#${id} .dbg-warning-text { color: ${c.text}; font-size: 12px; }
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
320
|
+
function getPosition(pos) {
|
|
321
|
+
switch (pos) {
|
|
322
|
+
case "bottom-left":
|
|
323
|
+
return "bottom: 16px; left: 16px;";
|
|
324
|
+
case "top-right":
|
|
325
|
+
return "top: 16px; right: 16px;";
|
|
326
|
+
case "top-left":
|
|
327
|
+
return "top: 16px; left: 16px;";
|
|
328
|
+
default:
|
|
329
|
+
return "bottom: 16px; right: 16px;";
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
var icons = {
|
|
333
|
+
logo: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>`,
|
|
334
|
+
close: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>`,
|
|
335
|
+
chevron: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>`,
|
|
336
|
+
perf: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>`,
|
|
337
|
+
template: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>`,
|
|
338
|
+
context: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/></svg>`,
|
|
339
|
+
filter: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>`,
|
|
340
|
+
cache: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 1010 10H12V2z"/><path d="M12 2a10 10 0 00-8.66 15"/></svg>`,
|
|
341
|
+
warning: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
|
|
342
|
+
file: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/></svg>`
|
|
343
|
+
};
|
|
344
|
+
function generateToggle(id, data, c) {
|
|
345
|
+
const time = (data.totalTime || 0).toFixed(1);
|
|
346
|
+
return `
|
|
347
|
+
<button class="dbg-toggle" onclick="document.querySelector('#${id} .dbg-panel').classList.add('open');this.style.display='none'">
|
|
348
|
+
${icons.logo}
|
|
349
|
+
<span>Binja</span>
|
|
350
|
+
<span class="dbg-time">${time}ms</span>
|
|
351
|
+
</button>`;
|
|
352
|
+
}
|
|
353
|
+
function generatePanel(id, data, c, opts) {
|
|
354
|
+
const time = (data.totalTime || 0).toFixed(2);
|
|
355
|
+
const mode = data.mode === "aot" ? "AOT" : "Runtime";
|
|
356
|
+
return `
|
|
357
|
+
<div class="dbg-panel">
|
|
358
|
+
<div class="dbg-header">
|
|
359
|
+
<div class="dbg-logo">${icons.logo} Binja Debugger</div>
|
|
360
|
+
<div class="dbg-meta">
|
|
361
|
+
<span class="dbg-badge mode">${mode}</span>
|
|
362
|
+
<span class="dbg-badge time">${time}ms</span>
|
|
363
|
+
<button class="dbg-close" onclick="document.querySelector('#${id} .dbg-panel').classList.remove('open');document.querySelector('#${id} .dbg-toggle').style.display='inline-flex'">${icons.close}</button>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="dbg-body">
|
|
367
|
+
${generatePerfSection(data)}
|
|
368
|
+
${generateTemplatesSection(data)}
|
|
369
|
+
${generateContextSection(data)}
|
|
370
|
+
${generateFiltersSection(data)}
|
|
371
|
+
${generateCacheSection(data)}
|
|
372
|
+
${generateWarningsSection(data)}
|
|
373
|
+
</div>
|
|
374
|
+
</div>`;
|
|
375
|
+
}
|
|
376
|
+
function generatePerfSection(data) {
|
|
377
|
+
const total = data.totalTime || 0.01;
|
|
378
|
+
const lexer = data.lexerTime || 0;
|
|
379
|
+
const parser = data.parserTime || 0;
|
|
380
|
+
const render = data.renderTime || 0;
|
|
381
|
+
return `
|
|
382
|
+
<div class="dbg-section open">
|
|
383
|
+
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
384
|
+
<span class="dbg-section-title">${icons.perf} Performance</span>
|
|
385
|
+
<span class="dbg-chevron">${icons.chevron}</span>
|
|
386
|
+
</div>
|
|
387
|
+
<div class="dbg-section-content">
|
|
388
|
+
<div class="dbg-row">
|
|
389
|
+
<span class="dbg-label">Lexer</span>
|
|
390
|
+
<span class="dbg-value">${lexer.toFixed(2)}ms</span>
|
|
391
|
+
</div>
|
|
392
|
+
<div class="dbg-bar"><div class="dbg-bar-fill lexer" style="width:${lexer / total * 100}%"></div></div>
|
|
393
|
+
<div class="dbg-row">
|
|
394
|
+
<span class="dbg-label">Parser</span>
|
|
395
|
+
<span class="dbg-value">${parser.toFixed(2)}ms</span>
|
|
396
|
+
</div>
|
|
397
|
+
<div class="dbg-bar"><div class="dbg-bar-fill parser" style="width:${parser / total * 100}%"></div></div>
|
|
398
|
+
<div class="dbg-row">
|
|
399
|
+
<span class="dbg-label">Render</span>
|
|
400
|
+
<span class="dbg-value">${render.toFixed(2)}ms</span>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="dbg-bar"><div class="dbg-bar-fill render" style="width:${render / total * 100}%"></div></div>
|
|
403
|
+
<div class="dbg-row" style="margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.1)">
|
|
404
|
+
<span class="dbg-label" style="font-weight:600">Total</span>
|
|
405
|
+
<span class="dbg-value" style="font-weight:600">${total.toFixed(2)}ms</span>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
</div>`;
|
|
409
|
+
}
|
|
410
|
+
function generateTemplatesSection(data) {
|
|
411
|
+
if (data.templateChain.length === 0)
|
|
412
|
+
return "";
|
|
413
|
+
const templates = data.templateChain.map((t) => `
|
|
414
|
+
<div class="dbg-template">
|
|
415
|
+
${icons.file}
|
|
416
|
+
<span class="dbg-template-name">${t.name}</span>
|
|
417
|
+
<span class="dbg-template-tag ${t.type}">${t.type}</span>
|
|
418
|
+
</div>
|
|
419
|
+
`).join("");
|
|
420
|
+
return `
|
|
421
|
+
<div class="dbg-section">
|
|
422
|
+
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
423
|
+
<span class="dbg-section-title">${icons.template} Templates</span>
|
|
424
|
+
<span class="dbg-section-meta">${data.templateChain.length}</span>
|
|
425
|
+
<span class="dbg-chevron">${icons.chevron}</span>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="dbg-section-content">
|
|
428
|
+
<div class="dbg-templates">${templates}</div>
|
|
429
|
+
</div>
|
|
430
|
+
</div>`;
|
|
431
|
+
}
|
|
432
|
+
function generateContextSection(data) {
|
|
433
|
+
const keys = Object.keys(data.contextSnapshot);
|
|
434
|
+
if (keys.length === 0)
|
|
435
|
+
return "";
|
|
436
|
+
const items = keys.map((key) => renderContextValue(key, data.contextSnapshot[key])).join("");
|
|
437
|
+
return `
|
|
438
|
+
<div class="dbg-section">
|
|
439
|
+
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
440
|
+
<span class="dbg-section-title">${icons.context} Context</span>
|
|
441
|
+
<span class="dbg-section-meta">${keys.length} vars</span>
|
|
442
|
+
<span class="dbg-chevron">${icons.chevron}</span>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="dbg-section-content">
|
|
445
|
+
<div class="dbg-ctx-grid">${items}</div>
|
|
446
|
+
</div>
|
|
447
|
+
</div>`;
|
|
448
|
+
}
|
|
449
|
+
function renderContextValue(key, ctx) {
|
|
450
|
+
const arrow = ctx.expandable ? `<svg class="dbg-ctx-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>` : "";
|
|
451
|
+
const expandableClass = ctx.expandable ? "expandable" : "";
|
|
452
|
+
const onClick = ctx.expandable ? `onclick="this.parentElement.classList.toggle('open')"` : "";
|
|
453
|
+
let children = "";
|
|
454
|
+
if (ctx.expandable && ctx.children) {
|
|
455
|
+
const childItems = Object.entries(ctx.children).map(([k, v]) => renderContextValue(k, v)).join("");
|
|
456
|
+
children = `<div class="dbg-ctx-children">${childItems}</div>`;
|
|
457
|
+
}
|
|
458
|
+
return `
|
|
459
|
+
<div class="dbg-ctx-item">
|
|
460
|
+
<div class="dbg-ctx-row ${expandableClass}" ${onClick}>
|
|
461
|
+
<div class="dbg-ctx-key">
|
|
462
|
+
${arrow}
|
|
463
|
+
<span class="dbg-ctx-name">${escapeHtml(key)}</span>
|
|
464
|
+
<span class="dbg-ctx-type">${ctx.type}</span>
|
|
465
|
+
</div>
|
|
466
|
+
<span class="dbg-ctx-preview">${escapeHtml(ctx.preview)}</span>
|
|
467
|
+
</div>
|
|
468
|
+
${children}
|
|
469
|
+
</div>`;
|
|
470
|
+
}
|
|
471
|
+
function generateFiltersSection(data) {
|
|
472
|
+
const filters = Array.from(data.filtersUsed.entries());
|
|
473
|
+
if (filters.length === 0)
|
|
474
|
+
return "";
|
|
475
|
+
const items = filters.map(([name, count]) => `<span class="dbg-filter">${name}<span class="dbg-filter-count">\xD7${count}</span></span>`).join("");
|
|
476
|
+
return `
|
|
477
|
+
<div class="dbg-section">
|
|
478
|
+
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
479
|
+
<span class="dbg-section-title">${icons.filter} Filters</span>
|
|
480
|
+
<span class="dbg-section-meta">${filters.length}</span>
|
|
481
|
+
<span class="dbg-chevron">${icons.chevron}</span>
|
|
482
|
+
</div>
|
|
483
|
+
<div class="dbg-section-content">
|
|
484
|
+
<div class="dbg-filters">${items}</div>
|
|
485
|
+
</div>
|
|
486
|
+
</div>`;
|
|
487
|
+
}
|
|
488
|
+
function generateCacheSection(data) {
|
|
489
|
+
const total = data.cacheHits + data.cacheMisses;
|
|
490
|
+
if (total === 0)
|
|
491
|
+
return "";
|
|
492
|
+
return `
|
|
493
|
+
<div class="dbg-section">
|
|
494
|
+
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
495
|
+
<span class="dbg-section-title">${icons.cache} Cache</span>
|
|
496
|
+
<span class="dbg-section-meta">${(data.cacheHits / total * 100).toFixed(0)}% hit</span>
|
|
497
|
+
<span class="dbg-chevron">${icons.chevron}</span>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="dbg-section-content">
|
|
500
|
+
<div class="dbg-cache">
|
|
501
|
+
<div class="dbg-cache-stat">
|
|
502
|
+
<div class="dbg-cache-num hit">${data.cacheHits}</div>
|
|
503
|
+
<div class="dbg-cache-label">Cache Hits</div>
|
|
504
|
+
</div>
|
|
505
|
+
<div class="dbg-cache-stat">
|
|
506
|
+
<div class="dbg-cache-num miss">${data.cacheMisses}</div>
|
|
507
|
+
<div class="dbg-cache-label">Cache Misses</div>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
</div>`;
|
|
512
|
+
}
|
|
513
|
+
function generateWarningsSection(data) {
|
|
514
|
+
if (data.warnings.length === 0)
|
|
515
|
+
return "";
|
|
516
|
+
const items = data.warnings.map((w) => `
|
|
517
|
+
<div class="dbg-warning">
|
|
518
|
+
${icons.warning}
|
|
519
|
+
<span class="dbg-warning-text">${escapeHtml(w)}</span>
|
|
520
|
+
</div>
|
|
521
|
+
`).join("");
|
|
522
|
+
return `
|
|
523
|
+
<div class="dbg-section open">
|
|
524
|
+
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
525
|
+
<span class="dbg-section-title">${icons.warning} Warnings</span>
|
|
526
|
+
<span class="dbg-section-meta" style="color:#eab308">${data.warnings.length}</span>
|
|
527
|
+
<span class="dbg-chevron">${icons.chevron}</span>
|
|
528
|
+
</div>
|
|
529
|
+
<div class="dbg-section-content">
|
|
530
|
+
<div class="dbg-warnings">${items}</div>
|
|
531
|
+
</div>
|
|
532
|
+
</div>`;
|
|
533
|
+
}
|
|
534
|
+
function generateScript(id) {
|
|
535
|
+
return `
|
|
536
|
+
(function(){
|
|
537
|
+
var panel = document.getElementById('${id}');
|
|
538
|
+
if (!panel) return;
|
|
539
|
+
var header = panel.querySelector('.dbg-header');
|
|
540
|
+
if (!header) return;
|
|
541
|
+
var isDrag = false, startX, startY, startL, startT;
|
|
542
|
+
header.style.cursor = 'grab';
|
|
543
|
+
header.onmousedown = function(e) {
|
|
544
|
+
if (e.target.closest('.dbg-close')) return;
|
|
545
|
+
isDrag = true;
|
|
546
|
+
header.style.cursor = 'grabbing';
|
|
547
|
+
startX = e.clientX;
|
|
548
|
+
startY = e.clientY;
|
|
549
|
+
var r = panel.getBoundingClientRect();
|
|
550
|
+
startL = r.left;
|
|
551
|
+
startT = r.top;
|
|
552
|
+
panel.style.right = 'auto';
|
|
553
|
+
panel.style.bottom = 'auto';
|
|
554
|
+
panel.style.left = startL + 'px';
|
|
555
|
+
panel.style.top = startT + 'px';
|
|
556
|
+
};
|
|
557
|
+
document.onmousemove = function(e) {
|
|
558
|
+
if (!isDrag) return;
|
|
559
|
+
panel.style.left = (startL + e.clientX - startX) + 'px';
|
|
560
|
+
panel.style.top = (startT + e.clientY - startY) + 'px';
|
|
561
|
+
};
|
|
562
|
+
document.onmouseup = function() {
|
|
563
|
+
isDrag = false;
|
|
564
|
+
header.style.cursor = 'grab';
|
|
565
|
+
};
|
|
566
|
+
})();`;
|
|
567
|
+
}
|
|
568
|
+
function escapeHtml(str) {
|
|
569
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/debug/index.ts
|
|
573
|
+
async function renderWithDebug(env, templateName, context = {}, options = {}) {
|
|
574
|
+
const collector = startDebugCollection();
|
|
575
|
+
collector.captureContext(context);
|
|
576
|
+
collector.addTemplate(templateName, "root");
|
|
577
|
+
collector.setMode("runtime");
|
|
578
|
+
collector.startRender();
|
|
579
|
+
const html = await env.render(templateName, context);
|
|
580
|
+
collector.endRender();
|
|
581
|
+
const data = endDebugCollection();
|
|
582
|
+
if (options.htmlOnly !== false) {
|
|
583
|
+
const isHtml = html.includes("<html") || html.includes("<body") || html.includes("<!DOCTYPE");
|
|
584
|
+
if (!isHtml) {
|
|
585
|
+
return html;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
const panel = generateDebugPanel(data, options.panel);
|
|
589
|
+
if (html.includes("</body>")) {
|
|
590
|
+
return html.replace("</body>", `${panel}</body>`);
|
|
591
|
+
}
|
|
592
|
+
return html + panel;
|
|
593
|
+
}
|
|
594
|
+
async function renderStringWithDebug(env, source, context = {}, options = {}) {
|
|
595
|
+
const collector = startDebugCollection();
|
|
596
|
+
collector.captureContext(context);
|
|
597
|
+
collector.setMode("runtime");
|
|
598
|
+
collector.startRender();
|
|
599
|
+
const html = await env.renderString(source, context);
|
|
600
|
+
collector.endRender();
|
|
601
|
+
const data = endDebugCollection();
|
|
602
|
+
if (options.htmlOnly !== false) {
|
|
603
|
+
const isHtml = html.includes("<html") || html.includes("<body");
|
|
604
|
+
if (!isHtml) {
|
|
605
|
+
return html;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const panel = generateDebugPanel(data, options.panel);
|
|
609
|
+
if (html.includes("</body>")) {
|
|
610
|
+
return html.replace("</body>", `${panel}</body>`);
|
|
611
|
+
}
|
|
612
|
+
return html + panel;
|
|
613
|
+
}
|
|
614
|
+
function createDebugRenderer(env, options = {}) {
|
|
615
|
+
return {
|
|
616
|
+
async render(templateName, context = {}) {
|
|
617
|
+
return renderWithDebug(env, templateName, context, options);
|
|
618
|
+
},
|
|
619
|
+
async renderString(source, context = {}) {
|
|
620
|
+
return renderStringWithDebug(env, source, context, options);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
function debugMiddleware(env, options = {}) {
|
|
625
|
+
return {
|
|
626
|
+
hono() {
|
|
627
|
+
return async (c, next) => {
|
|
628
|
+
await next();
|
|
629
|
+
const contentType = c.res.headers.get("content-type") || "";
|
|
630
|
+
if (!contentType.includes("text/html"))
|
|
631
|
+
return;
|
|
632
|
+
const body = await c.res.text();
|
|
633
|
+
const collector = startDebugCollection();
|
|
634
|
+
collector.captureContext({});
|
|
635
|
+
collector.setMode("runtime");
|
|
636
|
+
collector.endRender();
|
|
637
|
+
const data = endDebugCollection();
|
|
638
|
+
const panel = generateDebugPanel(data, options.panel);
|
|
639
|
+
const newBody = body.includes("</body>") ? body.replace("</body>", `${panel}</body>`) : body + panel;
|
|
640
|
+
c.res = new Response(newBody, {
|
|
641
|
+
status: c.res.status,
|
|
642
|
+
headers: c.res.headers
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
},
|
|
646
|
+
express() {
|
|
647
|
+
return (req, res, next) => {
|
|
648
|
+
const originalSend = res.send.bind(res);
|
|
649
|
+
res.send = (body) => {
|
|
650
|
+
const contentType = res.get("Content-Type") || "";
|
|
651
|
+
if (!contentType.includes("text/html") || typeof body !== "string") {
|
|
652
|
+
return originalSend(body);
|
|
653
|
+
}
|
|
654
|
+
const collector = startDebugCollection();
|
|
655
|
+
collector.captureContext({});
|
|
656
|
+
collector.setMode("runtime");
|
|
657
|
+
collector.endRender();
|
|
658
|
+
const data = endDebugCollection();
|
|
659
|
+
const panel = generateDebugPanel(data, options.panel);
|
|
660
|
+
const newBody = body.includes("</body>") ? body.replace("</body>", `${panel}</body>`) : body + panel;
|
|
661
|
+
return originalSend(newBody);
|
|
662
|
+
};
|
|
663
|
+
next();
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
export {
|
|
669
|
+
startDebugCollection,
|
|
670
|
+
renderWithDebug,
|
|
671
|
+
renderStringWithDebug,
|
|
672
|
+
getDebugCollector,
|
|
673
|
+
generateDebugPanel,
|
|
674
|
+
endDebugCollection,
|
|
675
|
+
debugMiddleware,
|
|
676
|
+
createDebugRenderer,
|
|
677
|
+
DebugCollector
|
|
678
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/native/index.ts
|
|
3
|
+
import { dlopen, FFIType, ptr, CString } from "bun:ffi";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
var TokenType = {
|
|
7
|
+
TEXT: 0,
|
|
8
|
+
VAR_START: 1,
|
|
9
|
+
VAR_END: 2,
|
|
10
|
+
BLOCK_START: 3,
|
|
11
|
+
BLOCK_END: 4,
|
|
12
|
+
COMMENT_START: 5,
|
|
13
|
+
COMMENT_END: 6,
|
|
14
|
+
IDENTIFIER: 7,
|
|
15
|
+
STRING: 8,
|
|
16
|
+
NUMBER: 9,
|
|
17
|
+
OPERATOR: 10,
|
|
18
|
+
DOT: 11,
|
|
19
|
+
COMMA: 12,
|
|
20
|
+
PIPE: 13,
|
|
21
|
+
COLON: 14,
|
|
22
|
+
LPAREN: 15,
|
|
23
|
+
RPAREN: 16,
|
|
24
|
+
LBRACKET: 17,
|
|
25
|
+
RBRACKET: 18,
|
|
26
|
+
LBRACE: 19,
|
|
27
|
+
RBRACE: 20,
|
|
28
|
+
ASSIGN: 21,
|
|
29
|
+
EOF: 22
|
|
30
|
+
};
|
|
31
|
+
var symbols = {
|
|
32
|
+
binja_lexer_new: {
|
|
33
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
34
|
+
returns: FFIType.ptr
|
|
35
|
+
},
|
|
36
|
+
binja_lexer_free: {
|
|
37
|
+
args: [FFIType.ptr],
|
|
38
|
+
returns: FFIType.void
|
|
39
|
+
},
|
|
40
|
+
binja_lexer_token_count: {
|
|
41
|
+
args: [FFIType.ptr],
|
|
42
|
+
returns: FFIType.u64
|
|
43
|
+
},
|
|
44
|
+
binja_lexer_token_type: {
|
|
45
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
46
|
+
returns: FFIType.u8
|
|
47
|
+
},
|
|
48
|
+
binja_lexer_token_start: {
|
|
49
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
50
|
+
returns: FFIType.u32
|
|
51
|
+
},
|
|
52
|
+
binja_lexer_token_end: {
|
|
53
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
54
|
+
returns: FFIType.u32
|
|
55
|
+
},
|
|
56
|
+
binja_lexer_has_error: {
|
|
57
|
+
args: [FFIType.ptr],
|
|
58
|
+
returns: FFIType.bool
|
|
59
|
+
},
|
|
60
|
+
binja_lexer_error_code: {
|
|
61
|
+
args: [FFIType.ptr],
|
|
62
|
+
returns: FFIType.u8
|
|
63
|
+
},
|
|
64
|
+
binja_lexer_error_line: {
|
|
65
|
+
args: [FFIType.ptr],
|
|
66
|
+
returns: FFIType.u32
|
|
67
|
+
},
|
|
68
|
+
binja_tokenize_count: {
|
|
69
|
+
args: [FFIType.ptr, FFIType.u64],
|
|
70
|
+
returns: FFIType.u64
|
|
71
|
+
},
|
|
72
|
+
binja_version: {
|
|
73
|
+
args: [],
|
|
74
|
+
returns: FFIType.ptr
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var _lib = null;
|
|
78
|
+
var _loadAttempted = false;
|
|
79
|
+
var _nativeAvailable = false;
|
|
80
|
+
function getLibraryPath() {
|
|
81
|
+
const platform = process.platform;
|
|
82
|
+
const arch = process.arch;
|
|
83
|
+
const libExt = platform === "darwin" ? "dylib" : platform === "win32" ? "dll" : "so";
|
|
84
|
+
const libName = `libbinja.${libExt}`;
|
|
85
|
+
const projectRoot = join(import.meta.dir, "..", "..");
|
|
86
|
+
const searchPaths = [
|
|
87
|
+
join(projectRoot, "native", `${platform}-${arch}`, libName),
|
|
88
|
+
join(projectRoot, "native", libName),
|
|
89
|
+
join(projectRoot, "zig-native", "zig-out", "lib", libName),
|
|
90
|
+
join(projectRoot, "zig-native", libName),
|
|
91
|
+
join(import.meta.dir, libName)
|
|
92
|
+
];
|
|
93
|
+
for (const p of searchPaths) {
|
|
94
|
+
if (existsSync(p)) {
|
|
95
|
+
return p;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
function loadLibrary() {
|
|
101
|
+
if (_loadAttempted) {
|
|
102
|
+
return _lib;
|
|
103
|
+
}
|
|
104
|
+
_loadAttempted = true;
|
|
105
|
+
const libPath = getLibraryPath();
|
|
106
|
+
if (!libPath) {
|
|
107
|
+
console.warn("[binja] Native library not found, using pure JS fallback");
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
_lib = dlopen(libPath, symbols);
|
|
112
|
+
_nativeAvailable = true;
|
|
113
|
+
return _lib;
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.warn(`[binja] Failed to load native library: ${e}`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function isNativeAvailable() {
|
|
120
|
+
loadLibrary();
|
|
121
|
+
return _nativeAvailable;
|
|
122
|
+
}
|
|
123
|
+
function nativeVersion() {
|
|
124
|
+
const lib = loadLibrary();
|
|
125
|
+
if (!lib)
|
|
126
|
+
return null;
|
|
127
|
+
const versionPtr = lib.symbols.binja_version();
|
|
128
|
+
if (!versionPtr)
|
|
129
|
+
return null;
|
|
130
|
+
return new CString(versionPtr).toString();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class NativeLexer {
|
|
134
|
+
lexerPtr = 0;
|
|
135
|
+
source;
|
|
136
|
+
sourceBuffer;
|
|
137
|
+
lib;
|
|
138
|
+
_tokenCount = 0;
|
|
139
|
+
_isEmpty = false;
|
|
140
|
+
constructor(source) {
|
|
141
|
+
const lib = loadLibrary();
|
|
142
|
+
if (!lib) {
|
|
143
|
+
throw new Error("Native library not available. Use isNativeAvailable() to check first.");
|
|
144
|
+
}
|
|
145
|
+
this.lib = lib;
|
|
146
|
+
this.source = source;
|
|
147
|
+
if (source.length === 0) {
|
|
148
|
+
this._isEmpty = true;
|
|
149
|
+
this._tokenCount = 1;
|
|
150
|
+
this.sourceBuffer = new Uint8Array(0);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.sourceBuffer = new TextEncoder().encode(source);
|
|
154
|
+
const result = this.lib.symbols.binja_lexer_new(ptr(this.sourceBuffer), this.sourceBuffer.length);
|
|
155
|
+
if (!result) {
|
|
156
|
+
throw new Error("Failed to create native lexer");
|
|
157
|
+
}
|
|
158
|
+
this.lexerPtr = result;
|
|
159
|
+
this._tokenCount = Number(this.lib.symbols.binja_lexer_token_count(this.lexerPtr));
|
|
160
|
+
}
|
|
161
|
+
get tokenCount() {
|
|
162
|
+
return this._tokenCount;
|
|
163
|
+
}
|
|
164
|
+
getTokenType(index) {
|
|
165
|
+
if (this._isEmpty)
|
|
166
|
+
return TokenType.EOF;
|
|
167
|
+
return Number(this.lib.symbols.binja_lexer_token_type(this.lexerPtr, index));
|
|
168
|
+
}
|
|
169
|
+
getTokenStart(index) {
|
|
170
|
+
if (this._isEmpty)
|
|
171
|
+
return 0;
|
|
172
|
+
return Number(this.lib.symbols.binja_lexer_token_start(this.lexerPtr, index));
|
|
173
|
+
}
|
|
174
|
+
getTokenEnd(index) {
|
|
175
|
+
if (this._isEmpty)
|
|
176
|
+
return 0;
|
|
177
|
+
return Number(this.lib.symbols.binja_lexer_token_end(this.lexerPtr, index));
|
|
178
|
+
}
|
|
179
|
+
hasError() {
|
|
180
|
+
if (this._isEmpty)
|
|
181
|
+
return false;
|
|
182
|
+
return Boolean(this.lib.symbols.binja_lexer_has_error(this.lexerPtr));
|
|
183
|
+
}
|
|
184
|
+
getErrorCode() {
|
|
185
|
+
if (this._isEmpty)
|
|
186
|
+
return 0;
|
|
187
|
+
return Number(this.lib.symbols.binja_lexer_error_code(this.lexerPtr));
|
|
188
|
+
}
|
|
189
|
+
getErrorLine() {
|
|
190
|
+
if (this._isEmpty)
|
|
191
|
+
return 1;
|
|
192
|
+
return Number(this.lib.symbols.binja_lexer_error_line(this.lexerPtr));
|
|
193
|
+
}
|
|
194
|
+
getTokenValue(index) {
|
|
195
|
+
if (this._isEmpty)
|
|
196
|
+
return "";
|
|
197
|
+
const start = this.getTokenStart(index);
|
|
198
|
+
const end = this.getTokenEnd(index);
|
|
199
|
+
return new TextDecoder().decode(this.sourceBuffer.slice(start, end));
|
|
200
|
+
}
|
|
201
|
+
getToken(index) {
|
|
202
|
+
return {
|
|
203
|
+
type: this.getTokenType(index),
|
|
204
|
+
start: this.getTokenStart(index),
|
|
205
|
+
end: this.getTokenEnd(index),
|
|
206
|
+
value: this.getTokenValue(index)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
getAllTokens() {
|
|
210
|
+
const tokens = [];
|
|
211
|
+
for (let i = 0;i < this._tokenCount; i++) {
|
|
212
|
+
tokens.push(this.getToken(i));
|
|
213
|
+
}
|
|
214
|
+
return tokens;
|
|
215
|
+
}
|
|
216
|
+
free() {
|
|
217
|
+
if (this.lexerPtr) {
|
|
218
|
+
this.lib.symbols.binja_lexer_free(this.lexerPtr);
|
|
219
|
+
this.lexerPtr = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
[Symbol.dispose]() {
|
|
223
|
+
this.free();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function tokenizeCount(source) {
|
|
227
|
+
if (source.length === 0) {
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
const lib = loadLibrary();
|
|
231
|
+
if (!lib) {
|
|
232
|
+
throw new Error("Native library not available");
|
|
233
|
+
}
|
|
234
|
+
const bytes = new TextEncoder().encode(source);
|
|
235
|
+
return Number(lib.symbols.binja_tokenize_count(ptr(bytes), bytes.length));
|
|
236
|
+
}
|
|
237
|
+
function tokenize(source) {
|
|
238
|
+
const lexer = new NativeLexer(source);
|
|
239
|
+
try {
|
|
240
|
+
return lexer.getAllTokens();
|
|
241
|
+
} finally {
|
|
242
|
+
lexer.free();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
export {
|
|
246
|
+
tokenizeCount,
|
|
247
|
+
tokenize,
|
|
248
|
+
nativeVersion,
|
|
249
|
+
isNativeAvailable,
|
|
250
|
+
TokenType,
|
|
251
|
+
NativeLexer
|
|
252
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "binja",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "High-performance Jinja2/Django template engine for Bun",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"LICENSE"
|
|
36
36
|
],
|
|
37
37
|
"scripts": {
|
|
38
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/cli.ts --outdir ./dist --target bun && bun run build:types",
|
|
38
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/cli.ts --outdir ./dist --target bun && bun build ./src/native/index.ts --outdir ./dist/native --target bun && bun build ./src/debug/index.ts --outdir ./dist/debug --target bun && bun run build:types",
|
|
39
39
|
"build:types": "tsc --declaration --emitDeclarationOnly --outDir ./dist",
|
|
40
40
|
"build:native": "./scripts/build-native.sh",
|
|
41
41
|
"build:all": "bun run build:native && bun run build",
|