binja 0.5.1 → 0.5.3

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/index.js CHANGED
@@ -153,6 +153,159 @@ function tokenizeNative(source) {
153
153
  return tokens;
154
154
  }
155
155
 
156
+ // src/errors/index.ts
157
+ var colors = {
158
+ red: "\x1B[31m",
159
+ yellow: "\x1B[33m",
160
+ cyan: "\x1B[36m",
161
+ gray: "\x1B[90m",
162
+ bold: "\x1B[1m",
163
+ dim: "\x1B[2m",
164
+ reset: "\x1B[0m"
165
+ };
166
+ var useColors = process.stdout?.isTTY !== false;
167
+ function c(color, text) {
168
+ return useColors ? `${colors[color]}${text}${colors.reset}` : text;
169
+ }
170
+
171
+ class TemplateError extends Error {
172
+ line;
173
+ column;
174
+ source;
175
+ templateName;
176
+ suggestion;
177
+ availableOptions;
178
+ constructor(message, options) {
179
+ const formatted = formatError("TemplateError", message, options);
180
+ super(formatted);
181
+ this.name = "TemplateError";
182
+ this.line = options.line;
183
+ this.column = options.column;
184
+ this.source = options.source;
185
+ this.templateName = options.templateName;
186
+ this.suggestion = options.suggestion;
187
+ this.availableOptions = options.availableOptions;
188
+ }
189
+ }
190
+
191
+ class TemplateSyntaxError extends Error {
192
+ line;
193
+ column;
194
+ source;
195
+ templateName;
196
+ suggestion;
197
+ constructor(message, options) {
198
+ const formatted = formatError("TemplateSyntaxError", message, options);
199
+ super(formatted);
200
+ this.name = "TemplateSyntaxError";
201
+ this.line = options.line;
202
+ this.column = options.column;
203
+ this.source = options.source;
204
+ this.templateName = options.templateName;
205
+ this.suggestion = options.suggestion;
206
+ }
207
+ }
208
+
209
+ class TemplateRuntimeError extends Error {
210
+ line;
211
+ column;
212
+ source;
213
+ templateName;
214
+ suggestion;
215
+ availableOptions;
216
+ constructor(message, options) {
217
+ const formatted = formatError("TemplateRuntimeError", message, options);
218
+ super(formatted);
219
+ this.name = "TemplateRuntimeError";
220
+ this.line = options.line;
221
+ this.column = options.column;
222
+ this.source = options.source;
223
+ this.templateName = options.templateName;
224
+ this.suggestion = options.suggestion;
225
+ this.availableOptions = options.availableOptions;
226
+ }
227
+ }
228
+ function formatError(type, message, options) {
229
+ const parts = [];
230
+ const location = options.templateName ? `${options.templateName}:${options.line}:${options.column}` : `line ${options.line}, column ${options.column}`;
231
+ parts.push(`${c("red", c("bold", type))}: ${message} at ${c("cyan", location)}`);
232
+ if (options.source) {
233
+ parts.push("");
234
+ parts.push(generateSnippet(options.source, options.line, options.column));
235
+ }
236
+ if (options.suggestion) {
237
+ parts.push("");
238
+ parts.push(`${c("yellow", "Did you mean")}: ${c("cyan", options.suggestion)}?`);
239
+ }
240
+ if (options.availableOptions && options.availableOptions.length > 0) {
241
+ parts.push("");
242
+ const truncated = options.availableOptions.slice(0, 8);
243
+ const more = options.availableOptions.length > 8 ? ` ${c("gray", `... and ${options.availableOptions.length - 8} more`)}` : "";
244
+ parts.push(`${c("gray", "Available")}: ${truncated.join(", ")}${more}`);
245
+ }
246
+ return parts.join(`
247
+ `);
248
+ }
249
+ function generateSnippet(source, errorLine, errorColumn) {
250
+ const lines = source.split(`
251
+ `);
252
+ const parts = [];
253
+ const startLine = Math.max(1, errorLine - 2);
254
+ const endLine = Math.min(lines.length, errorLine + 1);
255
+ const gutterWidth = String(endLine).length;
256
+ for (let i = startLine;i <= endLine; i++) {
257
+ const lineContent = lines[i - 1] || "";
258
+ const lineNum = String(i).padStart(gutterWidth, " ");
259
+ const isErrorLine = i === errorLine;
260
+ if (isErrorLine) {
261
+ parts.push(`${c("red", " \u2192")} ${c("gray", lineNum)} ${c("dim", "\u2502")} ${lineContent}`);
262
+ const caretPadding = " ".repeat(gutterWidth + 4 + Math.max(0, errorColumn - 1));
263
+ const caret = c("red", "^");
264
+ parts.push(`${caretPadding}${caret}`);
265
+ } else {
266
+ parts.push(` ${c("gray", lineNum)} ${c("dim", "\u2502")} ${c("gray", lineContent)}`);
267
+ }
268
+ }
269
+ return parts.join(`
270
+ `);
271
+ }
272
+ function findSimilar(input, candidates, maxDistance = 3) {
273
+ let bestMatch = null;
274
+ let bestDistance = maxDistance + 1;
275
+ const inputLower = input.toLowerCase();
276
+ for (const candidate of candidates) {
277
+ const distance = levenshteinDistance(inputLower, candidate.toLowerCase());
278
+ if (distance < bestDistance) {
279
+ bestDistance = distance;
280
+ bestMatch = candidate;
281
+ }
282
+ }
283
+ return bestDistance <= maxDistance ? bestMatch : null;
284
+ }
285
+ function levenshteinDistance(a, b) {
286
+ if (a.length === 0)
287
+ return b.length;
288
+ if (b.length === 0)
289
+ return a.length;
290
+ const matrix = [];
291
+ for (let i = 0;i <= b.length; i++) {
292
+ matrix[i] = [i];
293
+ }
294
+ for (let j = 0;j <= a.length; j++) {
295
+ matrix[0][j] = j;
296
+ }
297
+ for (let i = 1;i <= b.length; i++) {
298
+ for (let j = 1;j <= a.length; j++) {
299
+ if (b[i - 1] === a[j - 1]) {
300
+ matrix[i][j] = matrix[i - 1][j - 1];
301
+ } else {
302
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
303
+ }
304
+ }
305
+ }
306
+ return matrix[b.length][a.length];
307
+ }
308
+
156
309
  // src/lexer/index.ts
157
310
  class Lexer {
158
311
  state;
@@ -240,7 +393,11 @@ class Lexer {
240
393
  if (this.peek() === "-")
241
394
  this.advance();
242
395
  if (!this.match(this.blockEnd)) {
243
- throw new Error(`Expected ${this.blockEnd} after ${tagName} at line ${this.state.line}`);
396
+ throw new TemplateSyntaxError(`Expected '${this.blockEnd}' after '${tagName}'`, {
397
+ line: this.state.line,
398
+ column: this.state.column,
399
+ source: this.state.source
400
+ });
244
401
  }
245
402
  const endTag = `end${tagName}`;
246
403
  const contentStart = this.state.pos;
@@ -270,7 +427,11 @@ class Lexer {
270
427
  if (this.peek() === "-")
271
428
  this.advance();
272
429
  if (!this.match(this.blockEnd)) {
273
- throw new Error(`Expected ${this.blockEnd} after ${endTag} at line ${this.state.line}`);
430
+ throw new TemplateSyntaxError(`Expected '${this.blockEnd}' after '${endTag}'`, {
431
+ line: this.state.line,
432
+ column: this.state.column,
433
+ source: this.state.source
434
+ });
274
435
  }
275
436
  return;
276
437
  }
@@ -285,7 +446,12 @@ class Lexer {
285
446
  }
286
447
  this.advance();
287
448
  }
288
- throw new Error(`Unclosed ${tagName} block starting at line ${startLine}`);
449
+ throw new TemplateSyntaxError(`Unclosed '${tagName}' block`, {
450
+ line: startLine,
451
+ column: startColumn,
452
+ source: this.state.source,
453
+ suggestion: `Add {% end${tagName} %} to close the block`
454
+ });
289
455
  }
290
456
  scanText() {
291
457
  const start = this.state.pos;
@@ -325,22 +491,27 @@ class Lexer {
325
491
  }
326
492
  this.scanExpressionToken();
327
493
  }
328
- throw new Error(`Unclosed template tag at line ${this.state.line}`);
494
+ throw new TemplateSyntaxError(`Unclosed template tag`, {
495
+ line: this.state.line,
496
+ column: this.state.column,
497
+ source: this.state.source,
498
+ suggestion: `Add closing delimiter '${endDelimiter}'`
499
+ });
329
500
  }
330
501
  scanExpressionToken() {
331
502
  this.skipWhitespace();
332
503
  if (this.isAtEnd())
333
504
  return;
334
- const c = this.peek();
335
- if (c === '"' || c === "'") {
336
- this.scanString(c);
505
+ const c2 = this.peek();
506
+ if (c2 === '"' || c2 === "'") {
507
+ this.scanString(c2);
337
508
  return;
338
509
  }
339
- if (this.isDigit(c)) {
510
+ if (this.isDigit(c2)) {
340
511
  this.scanNumber();
341
512
  return;
342
513
  }
343
- if (this.isAlpha(c) || c === "_") {
514
+ if (this.isAlpha(c2) || c2 === "_") {
344
515
  this.scanIdentifier();
345
516
  return;
346
517
  }
@@ -361,7 +532,12 @@ class Lexer {
361
532
  this.advance();
362
533
  }
363
534
  if (this.isAtEnd()) {
364
- throw new Error(`Unterminated string at line ${this.state.line}`);
535
+ throw new TemplateSyntaxError(`Unterminated string literal`, {
536
+ line: this.state.line,
537
+ column: this.state.column,
538
+ source: this.state.source,
539
+ suggestion: `Add closing quote '${quote}'`
540
+ });
365
541
  }
366
542
  const value = this.state.source.slice(start, this.state.pos);
367
543
  this.advance();
@@ -391,55 +567,55 @@ class Lexer {
391
567
  this.addToken(type, value);
392
568
  }
393
569
  scanOperator() {
394
- const c = this.advance();
395
- switch (c) {
570
+ const c2 = this.advance();
571
+ switch (c2) {
396
572
  case ".":
397
- this.addToken("DOT" /* DOT */, c);
573
+ this.addToken("DOT" /* DOT */, c2);
398
574
  break;
399
575
  case ",":
400
- this.addToken("COMMA" /* COMMA */, c);
576
+ this.addToken("COMMA" /* COMMA */, c2);
401
577
  break;
402
578
  case ":":
403
- this.addToken("COLON" /* COLON */, c);
579
+ this.addToken("COLON" /* COLON */, c2);
404
580
  break;
405
581
  case "|":
406
- this.addToken("PIPE" /* PIPE */, c);
582
+ this.addToken("PIPE" /* PIPE */, c2);
407
583
  break;
408
584
  case "(":
409
- this.addToken("LPAREN" /* LPAREN */, c);
585
+ this.addToken("LPAREN" /* LPAREN */, c2);
410
586
  break;
411
587
  case ")":
412
- this.addToken("RPAREN" /* RPAREN */, c);
588
+ this.addToken("RPAREN" /* RPAREN */, c2);
413
589
  break;
414
590
  case "[":
415
- this.addToken("LBRACKET" /* LBRACKET */, c);
591
+ this.addToken("LBRACKET" /* LBRACKET */, c2);
416
592
  break;
417
593
  case "]":
418
- this.addToken("RBRACKET" /* RBRACKET */, c);
594
+ this.addToken("RBRACKET" /* RBRACKET */, c2);
419
595
  break;
420
596
  case "{":
421
- this.addToken("LBRACE" /* LBRACE */, c);
597
+ this.addToken("LBRACE" /* LBRACE */, c2);
422
598
  break;
423
599
  case "}":
424
- this.addToken("RBRACE" /* RBRACE */, c);
600
+ this.addToken("RBRACE" /* RBRACE */, c2);
425
601
  break;
426
602
  case "+":
427
- this.addToken("ADD" /* ADD */, c);
603
+ this.addToken("ADD" /* ADD */, c2);
428
604
  break;
429
605
  case "-":
430
- this.addToken("SUB" /* SUB */, c);
606
+ this.addToken("SUB" /* SUB */, c2);
431
607
  break;
432
608
  case "*":
433
- this.addToken("MUL" /* MUL */, c);
609
+ this.addToken("MUL" /* MUL */, c2);
434
610
  break;
435
611
  case "/":
436
- this.addToken("DIV" /* DIV */, c);
612
+ this.addToken("DIV" /* DIV */, c2);
437
613
  break;
438
614
  case "%":
439
- this.addToken("MOD" /* MOD */, c);
615
+ this.addToken("MOD" /* MOD */, c2);
440
616
  break;
441
617
  case "~":
442
- this.addToken("TILDE" /* TILDE */, c);
618
+ this.addToken("TILDE" /* TILDE */, c2);
443
619
  break;
444
620
  case "=":
445
621
  if (this.match("=")) {
@@ -452,7 +628,12 @@ class Lexer {
452
628
  if (this.match("=")) {
453
629
  this.addToken("NE" /* NE */, "!=");
454
630
  } else {
455
- throw new Error(`Unexpected character '!' at line ${this.state.line}`);
631
+ throw new TemplateSyntaxError(`Unexpected character '!'`, {
632
+ line: this.state.line,
633
+ column: this.state.column - 1,
634
+ source: this.state.source,
635
+ suggestion: `Use '!=' for not-equal comparison or 'not' for negation`
636
+ });
456
637
  }
457
638
  break;
458
639
  case "<":
@@ -470,8 +651,12 @@ class Lexer {
470
651
  }
471
652
  break;
472
653
  default:
473
- if (!this.isWhitespace(c)) {
474
- throw new Error(`Unexpected character '${c}' at line ${this.state.line}`);
654
+ if (!this.isWhitespace(c2)) {
655
+ throw new TemplateSyntaxError(`Unexpected character '${c2}'`, {
656
+ line: this.state.line,
657
+ column: this.state.column - 1,
658
+ source: this.state.source
659
+ });
475
660
  }
476
661
  }
477
662
  }
@@ -502,10 +687,10 @@ class Lexer {
502
687
  return this.state.source[this.state.pos + 1];
503
688
  }
504
689
  advance() {
505
- const c = this.state.source[this.state.pos];
690
+ const c2 = this.state.source[this.state.pos];
506
691
  this.state.pos++;
507
692
  this.state.column++;
508
- return c;
693
+ return c2;
509
694
  }
510
695
  match(expected, offset = 0) {
511
696
  const source = this.state.source;
@@ -545,20 +730,20 @@ class Lexer {
545
730
  this.advance();
546
731
  }
547
732
  }
548
- isWhitespace(c) {
549
- return c === " " || c === "\t" || c === `
550
- ` || c === "\r";
733
+ isWhitespace(c2) {
734
+ return c2 === " " || c2 === "\t" || c2 === `
735
+ ` || c2 === "\r";
551
736
  }
552
- isDigit(c) {
553
- const code = c.charCodeAt(0);
737
+ isDigit(c2) {
738
+ const code = c2.charCodeAt(0);
554
739
  return code >= 48 && code <= 57;
555
740
  }
556
- isAlpha(c) {
557
- const code = c.charCodeAt(0);
741
+ isAlpha(c2) {
742
+ const code = c2.charCodeAt(0);
558
743
  return code >= 97 && code <= 122 || code >= 65 && code <= 90;
559
744
  }
560
- isAlphaNumeric(c) {
561
- const code = c.charCodeAt(0);
745
+ isAlphaNumeric(c2) {
746
+ const code = c2.charCodeAt(0);
562
747
  return code >= 48 && code <= 57 || code >= 97 && code <= 122 || code >= 65 && code <= 90;
563
748
  }
564
749
  addToken(type, value) {
@@ -575,8 +760,10 @@ class Lexer {
575
760
  class Parser {
576
761
  tokens;
577
762
  current = 0;
578
- constructor(tokens) {
763
+ source;
764
+ constructor(tokens, source) {
579
765
  this.tokens = tokens;
766
+ this.source = source;
580
767
  }
581
768
  parse() {
582
769
  const body = [];
@@ -1668,7 +1855,11 @@ class Parser {
1668
1855
  }
1669
1856
  error(message) {
1670
1857
  const token = this.peek();
1671
- return new Error(`Parse error at line ${token.line}, column ${token.column}: ${message}`);
1858
+ return new TemplateSyntaxError(message, {
1859
+ line: token.line,
1860
+ column: token.column,
1861
+ source: this.source
1862
+ });
1672
1863
  }
1673
1864
  }
1674
1865
 
@@ -1830,7 +2021,7 @@ var capfirst = (value) => {
1830
2021
  const str = String(value);
1831
2022
  return str.charAt(0).toUpperCase() + str.slice(1);
1832
2023
  };
1833
- var title = (value) => String(value).replace(TITLE_REGEX, (c) => c.toUpperCase());
2024
+ var title = (value) => String(value).replace(TITLE_REGEX, (c2) => c2.toUpperCase());
1834
2025
  var trim = (value) => String(value).trim();
1835
2026
  var striptags = (value) => String(value).replace(STRIPTAGS_REGEX, "");
1836
2027
  var escape = (value) => {
@@ -1983,9 +2174,18 @@ var sort = (value, reverse2 = false) => {
1983
2174
  return reverse2 ? sorted.reverse() : sorted;
1984
2175
  };
1985
2176
  var unique = (value) => {
1986
- if (Array.isArray(value))
1987
- return [...new Set(value)];
1988
- return value;
2177
+ if (!Array.isArray(value))
2178
+ return value;
2179
+ const seen = new Set;
2180
+ const result = [];
2181
+ for (let i = 0;i < value.length; i++) {
2182
+ const v = value[i];
2183
+ if (!seen.has(v)) {
2184
+ seen.add(v);
2185
+ result.push(v);
2186
+ }
2187
+ }
2188
+ return result;
1989
2189
  };
1990
2190
  var make_list = (value) => {
1991
2191
  if (Array.isArray(value))
@@ -2358,7 +2558,12 @@ var max = (value, attribute, defaultValue) => {
2358
2558
  }
2359
2559
  return maxItem;
2360
2560
  }
2361
- return Math.max(...value);
2561
+ let maxVal = value[0];
2562
+ for (let i = 1;i < value.length; i++) {
2563
+ if (value[i] > maxVal)
2564
+ maxVal = value[i];
2565
+ }
2566
+ return maxVal;
2362
2567
  };
2363
2568
  var min = (value, attribute, defaultValue) => {
2364
2569
  if (!Array.isArray(value) || value.length === 0)
@@ -2372,7 +2577,12 @@ var min = (value, attribute, defaultValue) => {
2372
2577
  }
2373
2578
  return minItem;
2374
2579
  }
2375
- return Math.min(...value);
2580
+ let minVal = value[0];
2581
+ for (let i = 1;i < value.length; i++) {
2582
+ if (value[i] < minVal)
2583
+ minVal = value[i];
2584
+ }
2585
+ return minVal;
2376
2586
  };
2377
2587
  var sum = (value, attribute, start = 0) => {
2378
2588
  if (!Array.isArray(value))
@@ -2662,7 +2872,7 @@ var xmlattr = (value, autospace = true) => {
2662
2872
  if (val === true) {
2663
2873
  attrs.push(key);
2664
2874
  } else if (val !== false && val != null) {
2665
- const escaped = String(val).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2875
+ const escaped = Bun.escapeHTML(String(val));
2666
2876
  attrs.push(`${key}="${escaped}"`);
2667
2877
  }
2668
2878
  }
@@ -2924,6 +3134,7 @@ class Runtime {
2924
3134
  tests;
2925
3135
  blocks = new Map;
2926
3136
  parentTemplate = null;
3137
+ source;
2927
3138
  constructor(options = {}) {
2928
3139
  this.options = {
2929
3140
  autoescape: options.autoescape ?? true,
@@ -3242,9 +3453,12 @@ class Runtime {
3242
3453
  }
3243
3454
  cycleState = new Map;
3244
3455
  renderCycleSync(node, ctx) {
3245
- const key = node.values.map((v) => JSON.stringify(v)).join(",");
3456
+ const key = `cycle_${node.line}_${node.column}`;
3246
3457
  const currentIndex = this.cycleState.get(key) ?? 0;
3247
- const values = node.values.map((v) => this.eval(v, ctx));
3458
+ const values = [];
3459
+ for (let i = 0;i < node.values.length; i++) {
3460
+ values.push(this.eval(node.values[i], ctx));
3461
+ }
3248
3462
  const value = values[currentIndex % values.length];
3249
3463
  this.cycleState.set(key, currentIndex + 1);
3250
3464
  if (node.asVar) {
@@ -3276,12 +3490,16 @@ class Runtime {
3276
3490
  const key = `ifchanged_${node.line}_${node.column}`;
3277
3491
  let currentValue;
3278
3492
  if (node.values.length > 0) {
3279
- currentValue = node.values.map((v) => this.eval(v, ctx));
3493
+ const values = [];
3494
+ for (let i = 0;i < node.values.length; i++) {
3495
+ values.push(this.eval(node.values[i], ctx));
3496
+ }
3497
+ currentValue = values;
3280
3498
  } else {
3281
3499
  currentValue = this.renderNodesSync(node.body, ctx);
3282
3500
  }
3283
3501
  const lastValue = this.ifchangedState.get(key);
3284
- const changed = JSON.stringify(currentValue) !== JSON.stringify(lastValue);
3502
+ const changed = !this.deepEqual(currentValue, lastValue);
3285
3503
  this.ifchangedState.set(key, currentValue);
3286
3504
  if (changed) {
3287
3505
  if (node.values.length > 0) {
@@ -3653,8 +3871,17 @@ class Runtime {
3653
3871
  }
3654
3872
  const kwargs = this.evalObjectSync(node.kwargs, ctx);
3655
3873
  const filter = this.filters[node.filter];
3656
- if (!filter)
3657
- throw new Error(`Unknown filter: ${node.filter}`);
3874
+ if (!filter) {
3875
+ const available = Object.keys(this.filters);
3876
+ const suggestion = findSimilar(node.filter, available);
3877
+ throw new TemplateRuntimeError(`Unknown filter '${node.filter}'`, {
3878
+ line: node.line,
3879
+ column: node.column,
3880
+ source: this.source,
3881
+ suggestion: suggestion || undefined,
3882
+ availableOptions: available.slice(0, 15)
3883
+ });
3884
+ }
3658
3885
  return filter(value, ...args, ...Object.values(kwargs));
3659
3886
  }
3660
3887
  evalBinaryOp(node, ctx) {
@@ -4028,6 +4255,28 @@ class Runtime {
4028
4255
  }
4029
4256
  return true;
4030
4257
  }
4258
+ deepEqual(a, b) {
4259
+ if (a === b)
4260
+ return true;
4261
+ if (a == null || b == null)
4262
+ return a === b;
4263
+ const typeA = typeof a;
4264
+ const typeB = typeof b;
4265
+ if (typeA !== typeB)
4266
+ return false;
4267
+ if (typeA !== "object")
4268
+ return false;
4269
+ if (Array.isArray(a)) {
4270
+ if (!Array.isArray(b) || a.length !== b.length)
4271
+ return false;
4272
+ for (let i = 0;i < a.length; i++) {
4273
+ if (!this.deepEqual(a[i], b[i]))
4274
+ return false;
4275
+ }
4276
+ return true;
4277
+ }
4278
+ return JSON.stringify(a) === JSON.stringify(b);
4279
+ }
4031
4280
  isIn(needle, haystack) {
4032
4281
  if (Array.isArray(haystack))
4033
4282
  return haystack.includes(needle);
@@ -4069,6 +4318,9 @@ class Runtime {
4069
4318
  addGlobal(name, value) {
4070
4319
  this.options.globals[name] = value;
4071
4320
  }
4321
+ setSource(source) {
4322
+ this.source = source;
4323
+ }
4072
4324
  }
4073
4325
 
4074
4326
  // src/compiler/index.ts
@@ -5005,13 +5257,13 @@ var DEFAULT_OPTIONS = {
5005
5257
  function generateDebugPanel(data, options = {}) {
5006
5258
  const opts = { ...DEFAULT_OPTIONS, ...options };
5007
5259
  const id = `binja-debug-${Date.now()}`;
5008
- const colors = opts.dark ? darkTheme : lightTheme;
5260
+ const colors2 = opts.dark ? darkTheme : lightTheme;
5009
5261
  return `
5010
5262
  <!-- Binja Debug Panel -->
5011
5263
  <div id="${id}" class="binja-debugger" data-theme="${opts.dark ? "dark" : "light"}">
5012
- <style>${generateStyles(id, colors, opts)}</style>
5013
- ${generateToggle(id, data, colors)}
5014
- ${generatePanel(id, data, colors, opts)}
5264
+ <style>${generateStyles(id, colors2, opts)}</style>
5265
+ ${generateToggle(id, data, colors2)}
5266
+ ${generatePanel(id, data, colors2, opts)}
5015
5267
  <script>${generateScript(id)}</script>
5016
5268
  </div>
5017
5269
  <!-- /Binja Debug Panel -->
@@ -5049,84 +5301,84 @@ var lightTheme = {
5049
5301
  error: "#dc2626",
5050
5302
  info: "#0891b2"
5051
5303
  };
5052
- function generateStyles(id, c, opts) {
5304
+ function generateStyles(id, c2, opts) {
5053
5305
  const pos = getPosition(opts.position);
5054
5306
  return `
5055
5307
  #${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; }
5056
5308
  #${id} * { box-sizing: border-box; margin: 0; padding: 0; }
5057
- #${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; }
5058
- #${id} .dbg-toggle:hover { border-color: ${c.accent}; box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
5309
+ #${id} .dbg-toggle { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; background: ${c2.bg}; border: 1px solid ${c2.border}; border-radius: 8px; color: ${c2.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; }
5310
+ #${id} .dbg-toggle:hover { border-color: ${c2.accent}; box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
5059
5311
  #${id} .dbg-toggle svg { width: 16px; height: 16px; }
5060
- #${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}; }
5061
- #${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; }
5312
+ #${id} .dbg-toggle .dbg-time { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 11px; padding: 2px 8px; background: ${c2.bgTertiary}; border-radius: 4px; color: ${c2.success}; }
5313
+ #${id} .dbg-panel { display: none; width: ${opts.width}px; max-height: 85vh; background: ${c2.bg}; border: 1px solid ${c2.border}; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.24); overflow: hidden; margin-top: 8px; }
5062
5314
  #${id} .dbg-panel.open { display: block; }
5063
- #${id} .dbg-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: ${c.bgSecondary}; border-bottom: 1px solid ${c.border}; }
5064
- #${id} .dbg-logo { display: flex; align-items: center; gap: 10px; font-weight: 600; color: ${c.text}; }
5065
- #${id} .dbg-logo svg { width: 20px; height: 20px; color: ${c.accent}; }
5315
+ #${id} .dbg-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: ${c2.bgSecondary}; border-bottom: 1px solid ${c2.border}; }
5316
+ #${id} .dbg-logo { display: flex; align-items: center; gap: 10px; font-weight: 600; color: ${c2.text}; }
5317
+ #${id} .dbg-logo svg { width: 20px; height: 20px; color: ${c2.accent}; }
5066
5318
  #${id} .dbg-meta { display: flex; align-items: center; gap: 12px; }
5067
5319
  #${id} .dbg-badge { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; padding: 3px 10px; border-radius: 4px; font-weight: 500; }
5068
- #${id} .dbg-badge.time { background: rgba(34,197,94,0.1); color: ${c.success}; }
5069
- #${id} .dbg-badge.mode { background: rgba(59,130,246,0.1); color: ${c.accent}; }
5070
- #${id} .dbg-close { background: none; border: none; color: ${c.textMuted}; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; }
5071
- #${id} .dbg-close:hover { background: ${c.bgTertiary}; color: ${c.text}; }
5320
+ #${id} .dbg-badge.time { background: rgba(34,197,94,0.1); color: ${c2.success}; }
5321
+ #${id} .dbg-badge.mode { background: rgba(59,130,246,0.1); color: ${c2.accent}; }
5322
+ #${id} .dbg-close { background: none; border: none; color: ${c2.textMuted}; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; }
5323
+ #${id} .dbg-close:hover { background: ${c2.bgTertiary}; color: ${c2.text}; }
5072
5324
  #${id} .dbg-close svg { width: 18px; height: 18px; }
5073
5325
  #${id} .dbg-body { max-height: calc(85vh - 52px); overflow-y: auto; }
5074
- #${id} .dbg-section { border-bottom: 1px solid ${c.border}; }
5326
+ #${id} .dbg-section { border-bottom: 1px solid ${c2.border}; }
5075
5327
  #${id} .dbg-section:last-child { border-bottom: none; }
5076
5328
  #${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; }
5077
- #${id} .dbg-section-header:hover { background: ${c.bgSecondary}; }
5078
- #${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}; }
5329
+ #${id} .dbg-section-header:hover { background: ${c2.bgSecondary}; }
5330
+ #${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: ${c2.textSecondary}; }
5079
5331
  #${id} .dbg-section-title svg { width: 14px; height: 14px; opacity: 0.7; }
5080
- #${id} .dbg-section-meta { font-size: 11px; color: ${c.textMuted}; font-family: 'SF Mono', Monaco, monospace; }
5081
- #${id} .dbg-section-content { display: none; padding: 12px 16px; background: ${c.bgSecondary}; }
5332
+ #${id} .dbg-section-meta { font-size: 11px; color: ${c2.textMuted}; font-family: 'SF Mono', Monaco, monospace; }
5333
+ #${id} .dbg-section-content { display: none; padding: 12px 16px; background: ${c2.bgSecondary}; }
5082
5334
  #${id} .dbg-section.open .dbg-section-content { display: block; }
5083
- #${id} .dbg-section .dbg-chevron { transition: transform 0.2s; color: ${c.textMuted}; }
5335
+ #${id} .dbg-section .dbg-chevron { transition: transform 0.2s; color: ${c2.textMuted}; }
5084
5336
  #${id} .dbg-section.open .dbg-chevron { transform: rotate(90deg); }
5085
- #${id} .dbg-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid ${c.border}; }
5337
+ #${id} .dbg-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid ${c2.border}; }
5086
5338
  #${id} .dbg-row:last-child { border-bottom: none; }
5087
- #${id} .dbg-label { color: ${c.textSecondary}; font-size: 12px; }
5088
- #${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; }
5089
- #${id} .dbg-bar { height: 3px; background: ${c.bgTertiary}; border-radius: 2px; margin-top: 4px; overflow: hidden; }
5339
+ #${id} .dbg-label { color: ${c2.textSecondary}; font-size: 12px; }
5340
+ #${id} .dbg-value { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; text-align: right; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5341
+ #${id} .dbg-bar { height: 3px; background: ${c2.bgTertiary}; border-radius: 2px; margin-top: 4px; overflow: hidden; }
5090
5342
  #${id} .dbg-bar-fill { height: 100%; border-radius: 2px; transition: width 0.3s ease; }
5091
- #${id} .dbg-bar-fill.lexer { background: ${c.info}; }
5092
- #${id} .dbg-bar-fill.parser { background: ${c.warning}; }
5093
- #${id} .dbg-bar-fill.render { background: ${c.success}; }
5343
+ #${id} .dbg-bar-fill.lexer { background: ${c2.info}; }
5344
+ #${id} .dbg-bar-fill.parser { background: ${c2.warning}; }
5345
+ #${id} .dbg-bar-fill.render { background: ${c2.success}; }
5094
5346
  #${id} .dbg-templates { display: flex; flex-direction: column; gap: 6px; }
5095
- #${id} .dbg-template { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: ${c.bg}; border-radius: 6px; font-size: 12px; }
5096
- #${id} .dbg-template-icon { width: 16px; height: 16px; color: ${c.textMuted}; flex-shrink: 0; }
5097
- #${id} .dbg-template-name { color: ${c.text}; font-family: 'SF Mono', Monaco, monospace; }
5347
+ #${id} .dbg-template { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: ${c2.bg}; border-radius: 6px; font-size: 12px; }
5348
+ #${id} .dbg-template-icon { width: 16px; height: 16px; color: ${c2.textMuted}; flex-shrink: 0; }
5349
+ #${id} .dbg-template-name { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; }
5098
5350
  #${id} .dbg-template-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; text-transform: uppercase; }
5099
- #${id} .dbg-template-tag.root { background: rgba(59,130,246,0.15); color: ${c.accent}; }
5351
+ #${id} .dbg-template-tag.root { background: rgba(59,130,246,0.15); color: ${c2.accent}; }
5100
5352
  #${id} .dbg-template-tag.extends { background: rgba(168,85,247,0.15); color: #a855f7; }
5101
- #${id} .dbg-template-tag.include { background: rgba(34,197,94,0.15); color: ${c.success}; }
5353
+ #${id} .dbg-template-tag.include { background: rgba(34,197,94,0.15); color: ${c2.success}; }
5102
5354
  #${id} .dbg-ctx-grid { display: flex; flex-direction: column; gap: 4px; }
5103
- #${id} .dbg-ctx-item { background: ${c.bg}; border-radius: 6px; overflow: hidden; }
5355
+ #${id} .dbg-ctx-item { background: ${c2.bg}; border-radius: 6px; overflow: hidden; }
5104
5356
  #${id} .dbg-ctx-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; cursor: default; }
5105
5357
  #${id} .dbg-ctx-row.expandable { cursor: pointer; }
5106
- #${id} .dbg-ctx-row.expandable:hover { background: ${c.bgTertiary}; }
5358
+ #${id} .dbg-ctx-row.expandable:hover { background: ${c2.bgTertiary}; }
5107
5359
  #${id} .dbg-ctx-key { display: flex; align-items: center; gap: 6px; }
5108
- #${id} .dbg-ctx-arrow { width: 12px; height: 12px; color: ${c.textMuted}; transition: transform 0.15s; flex-shrink: 0; }
5360
+ #${id} .dbg-ctx-arrow { width: 12px; height: 12px; color: ${c2.textMuted}; transition: transform 0.15s; flex-shrink: 0; }
5109
5361
  #${id} .dbg-ctx-item.open > .dbg-ctx-row .dbg-ctx-arrow { transform: rotate(90deg); }
5110
- #${id} .dbg-ctx-name { color: ${c.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; }
5111
- #${id} .dbg-ctx-type { font-size: 10px; color: ${c.accent}; background: rgba(59,130,246,0.1); padding: 1px 5px; border-radius: 3px; }
5112
- #${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; }
5113
- #${id} .dbg-ctx-children { display: none; padding-left: 16px; border-left: 1px solid ${c.border}; margin-left: 10px; }
5362
+ #${id} .dbg-ctx-name { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; }
5363
+ #${id} .dbg-ctx-type { font-size: 10px; color: ${c2.accent}; background: rgba(59,130,246,0.1); padding: 1px 5px; border-radius: 3px; }
5364
+ #${id} .dbg-ctx-preview { color: ${c2.textSecondary}; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5365
+ #${id} .dbg-ctx-children { display: none; padding-left: 16px; border-left: 1px solid ${c2.border}; margin-left: 10px; }
5114
5366
  #${id} .dbg-ctx-item.open > .dbg-ctx-children { display: block; }
5115
5367
  #${id} .dbg-ctx-children .dbg-ctx-item { background: transparent; }
5116
5368
  #${id} .dbg-ctx-children .dbg-ctx-row { padding: 4px 8px; }
5117
5369
  #${id} .dbg-filters { display: flex; flex-wrap: wrap; gap: 6px; }
5118
- #${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}; }
5119
- #${id} .dbg-filter-count { font-size: 10px; color: ${c.accent}; font-weight: 600; }
5370
+ #${id} .dbg-filter { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: ${c2.bg}; border-radius: 5px; font-size: 12px; font-family: 'SF Mono', Monaco, monospace; color: ${c2.text}; }
5371
+ #${id} .dbg-filter-count { font-size: 10px; color: ${c2.accent}; font-weight: 600; }
5120
5372
  #${id} .dbg-cache { display: flex; gap: 16px; }
5121
- #${id} .dbg-cache-stat { flex: 1; padding: 12px; background: ${c.bg}; border-radius: 6px; text-align: center; }
5373
+ #${id} .dbg-cache-stat { flex: 1; padding: 12px; background: ${c2.bg}; border-radius: 6px; text-align: center; }
5122
5374
  #${id} .dbg-cache-num { font-size: 24px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
5123
- #${id} .dbg-cache-num.hit { color: ${c.success}; }
5124
- #${id} .dbg-cache-num.miss { color: ${c.error}; }
5125
- #${id} .dbg-cache-label { font-size: 11px; color: ${c.textMuted}; margin-top: 4px; }
5375
+ #${id} .dbg-cache-num.hit { color: ${c2.success}; }
5376
+ #${id} .dbg-cache-num.miss { color: ${c2.error}; }
5377
+ #${id} .dbg-cache-label { font-size: 11px; color: ${c2.textMuted}; margin-top: 4px; }
5126
5378
  #${id} .dbg-warnings { display: flex; flex-direction: column; gap: 6px; }
5127
- #${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}; }
5128
- #${id} .dbg-warning-icon { color: ${c.warning}; flex-shrink: 0; margin-top: 1px; }
5129
- #${id} .dbg-warning-text { color: ${c.text}; font-size: 12px; }
5379
+ #${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 ${c2.warning}; }
5380
+ #${id} .dbg-warning-icon { color: ${c2.warning}; flex-shrink: 0; margin-top: 1px; }
5381
+ #${id} .dbg-warning-text { color: ${c2.text}; font-size: 12px; }
5130
5382
  `;
5131
5383
  }
5132
5384
  function getPosition(pos) {
@@ -5153,7 +5405,7 @@ var icons = {
5153
5405
  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>`,
5154
5406
  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>`
5155
5407
  };
5156
- function generateToggle(id, data, c) {
5408
+ function generateToggle(id, data, c2) {
5157
5409
  const time2 = (data.totalTime || 0).toFixed(1);
5158
5410
  return `
5159
5411
  <button class="dbg-toggle" onclick="document.querySelector('#${id} .dbg-panel').classList.add('open');this.style.display='none'">
@@ -5162,7 +5414,7 @@ function generateToggle(id, data, c) {
5162
5414
  <span class="dbg-time">${time2}ms</span>
5163
5415
  </button>`;
5164
5416
  }
5165
- function generatePanel(id, data, c, opts) {
5417
+ function generatePanel(id, data, c2, opts) {
5166
5418
  const time2 = (data.totalTime || 0).toFixed(2);
5167
5419
  const mode = data.mode === "aot" ? "AOT" : "Runtime";
5168
5420
  return `
@@ -5436,12 +5688,12 @@ function createDebugRenderer(env, options = {}) {
5436
5688
  function debugMiddleware(env, options = {}) {
5437
5689
  return {
5438
5690
  hono() {
5439
- return async (c, next) => {
5691
+ return async (c2, next) => {
5440
5692
  await next();
5441
- const contentType = c.res.headers.get("content-type") || "";
5693
+ const contentType = c2.res.headers.get("content-type") || "";
5442
5694
  if (!contentType.includes("text/html"))
5443
5695
  return;
5444
- const body = await c.res.text();
5696
+ const body = await c2.res.text();
5445
5697
  const collector = startDebugCollection();
5446
5698
  collector.captureContext({});
5447
5699
  collector.setMode("runtime");
@@ -5449,9 +5701,9 @@ function debugMiddleware(env, options = {}) {
5449
5701
  const data = endDebugCollection();
5450
5702
  const panel = generateDebugPanel(data, options.panel);
5451
5703
  const newBody = body.includes("</body>") ? body.replace("</body>", `${panel}</body>`) : body + panel;
5452
- c.res = new Response(newBody, {
5453
- status: c.res.status,
5454
- headers: c.res.headers
5704
+ c2.res = new Response(newBody, {
5705
+ status: c2.res.status,
5706
+ headers: c2.res.headers
5455
5707
  });
5456
5708
  };
5457
5709
  },
@@ -5572,7 +5824,7 @@ class Environment {
5572
5824
  compile(source) {
5573
5825
  const lexer = new Lexer(source);
5574
5826
  const tokens = lexer.tokenize();
5575
- const parser = new Parser(tokens);
5827
+ const parser = new Parser(tokens, source);
5576
5828
  return parser.parse();
5577
5829
  }
5578
5830
  async loadTemplate(templateName) {
@@ -5693,7 +5945,7 @@ function Template(source, options = {}) {
5693
5945
  function compile(source, options = {}) {
5694
5946
  const lexer = new Lexer(source);
5695
5947
  const tokens = lexer.tokenize();
5696
- const parser = new Parser(tokens);
5948
+ const parser = new Parser(tokens, source);
5697
5949
  const ast = parser.parse();
5698
5950
  return compileToFunction(ast, options);
5699
5951
  }
@@ -5716,7 +5968,7 @@ async function compileWithInheritance(templateName, options) {
5716
5968
  parse(source2) {
5717
5969
  const lexer = new Lexer(source2);
5718
5970
  const tokens = lexer.tokenize();
5719
- const parser = new Parser(tokens);
5971
+ const parser = new Parser(tokens, source2);
5720
5972
  return parser.parse();
5721
5973
  }
5722
5974
  };
@@ -5748,7 +6000,7 @@ async function compileWithInheritanceToCode(templateName, options) {
5748
6000
  parse(source2) {
5749
6001
  const lexer = new Lexer(source2);
5750
6002
  const tokens = lexer.tokenize();
5751
- const parser = new Parser(tokens);
6003
+ const parser = new Parser(tokens, source2);
5752
6004
  return parser.parse();
5753
6005
  }
5754
6006
  };
@@ -5764,7 +6016,7 @@ async function compileWithInheritanceToCode(templateName, options) {
5764
6016
  function compileToCode(source, options = {}) {
5765
6017
  const lexer = new Lexer(source);
5766
6018
  const tokens = lexer.tokenize();
5767
- const parser = new Parser(tokens);
6019
+ const parser = new Parser(tokens, source);
5768
6020
  const ast = parser.parse();
5769
6021
  return compileToString(ast, options);
5770
6022
  }
@@ -5784,6 +6036,9 @@ export {
5784
6036
  builtinTests,
5785
6037
  builtinFilters,
5786
6038
  TokenType,
6039
+ TemplateSyntaxError,
6040
+ TemplateRuntimeError,
6041
+ TemplateError,
5787
6042
  Template,
5788
6043
  Runtime,
5789
6044
  Parser,