@samuelbines/nunjucks 0.0.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.
Files changed (66) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +55 -0
  3. package/dist/scripts/smoke.d.ts +1 -0
  4. package/dist/scripts/smoke.js +95 -0
  5. package/dist/src/compiler.d.ts +12 -0
  6. package/dist/src/compiler.js +1050 -0
  7. package/dist/src/environment.d.ts +103 -0
  8. package/dist/src/environment.js +621 -0
  9. package/dist/src/express-app.d.ts +2 -0
  10. package/dist/src/express-app.js +33 -0
  11. package/dist/src/filters.d.ts +44 -0
  12. package/dist/src/filters.js +424 -0
  13. package/dist/src/globals.d.ts +14 -0
  14. package/dist/src/globals.js +342 -0
  15. package/dist/src/index.d.ts +28 -0
  16. package/dist/src/index.js +116 -0
  17. package/dist/src/interpreter.d.ts +16 -0
  18. package/dist/src/interpreter.js +489 -0
  19. package/dist/src/lexer.d.ts +72 -0
  20. package/dist/src/lexer.js +480 -0
  21. package/dist/src/lib.d.ts +74 -0
  22. package/dist/src/lib.js +237 -0
  23. package/dist/src/loader.d.ts +80 -0
  24. package/dist/src/loader.js +175 -0
  25. package/dist/src/nodes.d.ts +362 -0
  26. package/dist/src/nodes.js +894 -0
  27. package/dist/src/parser.d.ts +66 -0
  28. package/dist/src/parser.js +1068 -0
  29. package/dist/src/precompile.d.ts +15 -0
  30. package/dist/src/precompile.js +108 -0
  31. package/dist/src/runtime.d.ts +33 -0
  32. package/dist/src/runtime.js +314 -0
  33. package/dist/src/transformer.d.ts +3 -0
  34. package/dist/src/transformer.js +161 -0
  35. package/dist/src/types.d.ts +27 -0
  36. package/dist/src/types.js +2 -0
  37. package/dist/tests/compiler.test.d.ts +1 -0
  38. package/dist/tests/compiler.test.js +201 -0
  39. package/dist/tests/enviornment.test.d.ts +1 -0
  40. package/dist/tests/enviornment.test.js +279 -0
  41. package/dist/tests/express.test.d.ts +1 -0
  42. package/dist/tests/express.test.js +86 -0
  43. package/dist/tests/filters.test.d.ts +13 -0
  44. package/dist/tests/filters.test.js +286 -0
  45. package/dist/tests/globals.test.d.ts +1 -0
  46. package/dist/tests/globals.test.js +579 -0
  47. package/dist/tests/interpreter.test.d.ts +1 -0
  48. package/dist/tests/interpreter.test.js +208 -0
  49. package/dist/tests/lexer.test.d.ts +1 -0
  50. package/dist/tests/lexer.test.js +249 -0
  51. package/dist/tests/lib.test.d.ts +1 -0
  52. package/dist/tests/lib.test.js +236 -0
  53. package/dist/tests/loader.test.d.ts +1 -0
  54. package/dist/tests/loader.test.js +301 -0
  55. package/dist/tests/nodes.test.d.ts +1 -0
  56. package/dist/tests/nodes.test.js +137 -0
  57. package/dist/tests/parser.test.d.ts +1 -0
  58. package/dist/tests/parser.test.js +294 -0
  59. package/dist/tests/precompile.test.d.ts +1 -0
  60. package/dist/tests/precompile.test.js +224 -0
  61. package/dist/tests/runtime.test.d.ts +1 -0
  62. package/dist/tests/runtime.test.js +237 -0
  63. package/dist/tests/transformer.test.d.ts +1 -0
  64. package/dist/tests/transformer.test.js +125 -0
  65. package/dist/tsconfig.tsbuildinfo +1 -0
  66. package/package.json +59 -0
@@ -0,0 +1,1050 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compile = void 0;
4
+ //TODO: Sun 4th Jan 2026 [Update the compiler where I can]
5
+ const parser_1 = require("./parser");
6
+ const transformer_1 = require("./transformer");
7
+ const nodes_1 = require("./nodes");
8
+ const lib_1 = require("./lib");
9
+ const runtime_1 = require("./runtime");
10
+ // These are all the same for now, but shouldn't be passed straight
11
+ // through
12
+ const compareOps = {
13
+ '==': '==',
14
+ '===': '===',
15
+ '!=': '!=',
16
+ '!==': '!==',
17
+ '<': '<',
18
+ '>': '>',
19
+ '<=': '<=',
20
+ '>=': '>=',
21
+ };
22
+ class Compiler {
23
+ templateName = '';
24
+ lastId = 0;
25
+ _scopeClosers = '';
26
+ inBlock = false;
27
+ throwOnUndefined = false;
28
+ extname;
29
+ __typename;
30
+ // TODO: confirm these types
31
+ codebuf = [];
32
+ buffer = null;
33
+ bufferStack = [];
34
+ constructor(templateName, opts) {
35
+ this.templateName = templateName;
36
+ this.codebuf = opts?.codebuf || [];
37
+ this.lastId = opts?.lastId || 0;
38
+ this.buffer = opts?.buffer || null;
39
+ this.bufferStack = opts?.bufferStack || [];
40
+ this._scopeClosers = opts?._scopeClosers || '';
41
+ this.inBlock = opts?.inBlock || false;
42
+ this.throwOnUndefined = opts?.throwOnUndefined || false;
43
+ }
44
+ get typename() {
45
+ return 'Compiler';
46
+ }
47
+ fail(msg, lineno = 0, colno = 0) {
48
+ if (lineno !== undefined) {
49
+ lineno += 1;
50
+ }
51
+ if (colno !== undefined) {
52
+ colno += 1;
53
+ }
54
+ lib_1.p.err(msg, lineno, colno);
55
+ throw (0, lib_1.TemplateError)(msg, lineno, colno);
56
+ }
57
+ _pushBuffer() {
58
+ const id = this._tmpid();
59
+ this.bufferStack?.push(this.buffer);
60
+ this.buffer = id;
61
+ this._emit(`var ${this.buffer} = "";`);
62
+ return id;
63
+ }
64
+ _popBuffer() {
65
+ this.buffer = this.bufferStack.pop();
66
+ }
67
+ _emit(code, newLine = false) {
68
+ if (Array.isArray(code))
69
+ code = code.join('\n');
70
+ if (newLine)
71
+ code += '\n';
72
+ this.codebuf?.push(code);
73
+ }
74
+ _emitFuncBegin(node, name) {
75
+ this.buffer = 'output';
76
+ this._scopeClosers = '';
77
+ this._emit([
78
+ `function ${name}(env, context, frame, runtime, cb) {`,
79
+ `var lineno = ${node?.lineno};`,
80
+ `var colno = ${node?.colno};`,
81
+ `var ${this.buffer} = "";`,
82
+ 'try {',
83
+ ], true);
84
+ }
85
+ _emitFuncEnd(noReturn) {
86
+ if (!noReturn) {
87
+ this._emit('cb(null, ' + this.buffer + ');', true);
88
+ }
89
+ this._closeScopeLevels();
90
+ this._emit([
91
+ '} catch (e) {',
92
+ ' cb(runtime.handleError(e, lineno, colno));',
93
+ '}',
94
+ '}',
95
+ ], true);
96
+ this.buffer = null;
97
+ }
98
+ _addScopeLevel() {
99
+ this._scopeClosers += '})';
100
+ }
101
+ _closeScopeLevels() {
102
+ this._emit(this._scopeClosers + ';', true);
103
+ this._scopeClosers = '';
104
+ }
105
+ _withScopedSyntax(func) {
106
+ let _scopeClosers = this._scopeClosers;
107
+ this._scopeClosers = '';
108
+ func.call(this);
109
+ this._closeScopeLevels();
110
+ this._scopeClosers = _scopeClosers;
111
+ }
112
+ _makeCallback(res) {
113
+ let err = this._tmpid();
114
+ if (err)
115
+ lib_1.p.err(err);
116
+ return `function(${err}${res ? ',' + res : ''}) {\n
117
+ if(${err}) {
118
+ cb(${err});
119
+ return;
120
+ }`;
121
+ }
122
+ _tmpid() {
123
+ this.lastId++;
124
+ return 't_' + this.lastId;
125
+ }
126
+ _templateName() {
127
+ return this.templateName == null
128
+ ? 'undefined'
129
+ : JSON.stringify(this.templateName);
130
+ }
131
+ _compileChildren(node, frame) {
132
+ node.children.forEach((child) => {
133
+ if (!child)
134
+ lib_1.p.warn('_compileChildren: ', node);
135
+ this.compile(child, frame);
136
+ });
137
+ }
138
+ _compileAggregate(node, frame, startChar, endChar) {
139
+ if (startChar) {
140
+ this._emit(startChar);
141
+ }
142
+ node.children.forEach((child, i) => {
143
+ if (i > 0) {
144
+ this._emit(',');
145
+ }
146
+ if (!child) {
147
+ lib_1.p.warn('_compileAggregate: ', node);
148
+ }
149
+ else {
150
+ this.compile(child, frame);
151
+ }
152
+ });
153
+ if (endChar) {
154
+ this._emit(endChar);
155
+ }
156
+ }
157
+ _compileExpression(node, frame) {
158
+ // TODO: I'm not really sure if this type check is worth it or
159
+ // not.
160
+ this.assertType(node, nodes_1.Literal, nodes_1.Symbol, nodes_1.Group, nodes_1.ArrayNode, nodes_1.Dict, nodes_1.FunCall, nodes_1.Caller, nodes_1.Filter, nodes_1.LookupVal, nodes_1.Compare, nodes_1.InlineIf, nodes_1.In, nodes_1.Is, nodes_1.And, nodes_1.Or, nodes_1.Not, nodes_1.Add, nodes_1.Concat, nodes_1.Sub, nodes_1.Mul, nodes_1.Div, nodes_1.FloorDiv, nodes_1.Mod, nodes_1.Pow, nodes_1.Neg, nodes_1.Pos, nodes_1.Compare, nodes_1.NodeList);
161
+ if (!node)
162
+ lib_1.p.warn('_compileExpression');
163
+ this.compile(node, frame);
164
+ }
165
+ // Deprecated
166
+ assertType(node, ...types) {
167
+ if (!types.some((t) => node instanceof t)) {
168
+ this.fail(`assertType: invalid type: ${node?.typename}`, node?.lineno, node?.colno);
169
+ }
170
+ }
171
+ compileCallExtension(node, frame, async = true) {
172
+ let args = node.args;
173
+ let contentArgs = node.contentArgs;
174
+ let autoescape = node.autoescape;
175
+ if (!async) {
176
+ this._emit(`${this.buffer} += runtime.suppressValue(`);
177
+ }
178
+ this._emit(`env.getExtension("${node.extname}")["${node.prop}"](`);
179
+ this._emit('context');
180
+ if (args || contentArgs) {
181
+ this._emit(',');
182
+ }
183
+ if (args) {
184
+ if (!(args instanceof nodes_1.NodeList)) {
185
+ this.fail('compileCallExtension: arguments must be a NodeList, ' +
186
+ 'use `parser.parseSignature`');
187
+ }
188
+ args.children.forEach((arg, i) => {
189
+ // Tag arguments are passed normally to the call. Note
190
+ // that keyword arguments are turned into a single js
191
+ // object as the last argument, if they exist.
192
+ this._compileExpression(arg, frame);
193
+ if (i !== args.children?.length - 1 || contentArgs?.length) {
194
+ this._emit(',');
195
+ }
196
+ });
197
+ }
198
+ if (contentArgs?.length) {
199
+ contentArgs.forEach((node, i) => {
200
+ if (i > 0) {
201
+ this._emit(',');
202
+ }
203
+ if (node) {
204
+ this._emit([
205
+ 'function(cb) {',
206
+ 'if(!cb) { cb = function(err) { if(err) { throw err; }}}',
207
+ ], true);
208
+ const id = this._pushBuffer();
209
+ this._withScopedSyntax(() => {
210
+ if (!node)
211
+ lib_1.p.warn('_withScopedSyntax');
212
+ this.compile(node, frame);
213
+ this._emit(`cb(null, ${id});`, true);
214
+ });
215
+ this._popBuffer();
216
+ this._emit([`return ${id};`, '}'], true);
217
+ }
218
+ else {
219
+ this._emit('null');
220
+ }
221
+ });
222
+ }
223
+ if (async) {
224
+ const res = this._tmpid();
225
+ this._emit([
226
+ ', ' + this._makeCallback(res),
227
+ `${this.buffer} += runtime.suppressValue(${res}, ${autoescape} && env.autoescape);`,
228
+ ], true);
229
+ this._addScopeLevel();
230
+ }
231
+ else {
232
+ this._emit(')');
233
+ this._emit(`, ${autoescape} && env.autoescape);\n`);
234
+ }
235
+ }
236
+ compileCallExtensionAsync(node, frame) {
237
+ this.compileCallExtension(node, frame, true);
238
+ }
239
+ compileNodeList(node, frame) {
240
+ this._compileChildren(node, frame);
241
+ }
242
+ compileLiteral(node) {
243
+ if (typeof node.value === 'string') {
244
+ let val = node.value.replace(/\\/g, '\\\\');
245
+ val = val.replace(/"/g, '\\"');
246
+ val = val.replace(/\n/g, '\\n');
247
+ val = val.replace(/\r/g, '\\r');
248
+ val = val.replace(/\t/g, '\\t');
249
+ val = val.replace(/\u2028/g, '\\u2028');
250
+ this._emit(`"${val}"`);
251
+ }
252
+ else if (node.value === null) {
253
+ this._emit('null');
254
+ }
255
+ else {
256
+ this._emit(node.value.toString());
257
+ }
258
+ }
259
+ compileSymbol(node, frame) {
260
+ let name = node.value;
261
+ let v = frame.lookup(name);
262
+ if (v) {
263
+ this._emit(v);
264
+ }
265
+ else {
266
+ this._emit('runtime.contextOrFrameLookup(' + 'context, frame, "' + name + '")');
267
+ }
268
+ }
269
+ compileGroup(node, frame) {
270
+ this._compileAggregate(node, frame, '(', ')');
271
+ }
272
+ compileArray(node, frame) {
273
+ this._compileAggregate(node, frame, '[', ']');
274
+ }
275
+ compileDict(node, frame) {
276
+ this._compileAggregate(node, frame, '{', '}');
277
+ }
278
+ compilePair(node, frame) {
279
+ let key = node.key;
280
+ let val = node.value;
281
+ if (key instanceof nodes_1.Symbol) {
282
+ key = new nodes_1.Literal(key?.lineno, key?.colno, key.value);
283
+ }
284
+ else if (!(key instanceof nodes_1.Literal && typeof key.value === 'string')) {
285
+ this.fail('compilePair: Dict keys must be strings or names', key?.lineno, key?.colno);
286
+ }
287
+ if (!key)
288
+ lib_1.p.warn('compilePair');
289
+ this.compile(key, frame);
290
+ this._emit(': ');
291
+ this._compileExpression(val, frame);
292
+ }
293
+ compileInlineIf(node, frame) {
294
+ this._emit('(');
295
+ if (!node.cond)
296
+ lib_1.p.warn('compileInlineIf cond', node);
297
+ this.compile(node.cond, frame);
298
+ this._emit('?');
299
+ if (!node.body)
300
+ lib_1.p.warn('compileInlineIf body', node);
301
+ this.compile(node.body, frame);
302
+ this._emit(':');
303
+ if (node.else_ !== null) {
304
+ this.compile(node.else_, frame);
305
+ }
306
+ else {
307
+ this._emit('""');
308
+ }
309
+ this._emit(')');
310
+ }
311
+ compileIn(node, frame) {
312
+ this._emit('runtime.inOperator(');
313
+ this.compile(node.left, frame);
314
+ this._emit(',');
315
+ this.compile(node.right, frame);
316
+ this._emit(')');
317
+ }
318
+ compileIs(node, frame) {
319
+ // first, we need to try to get the name of the test function, if it's a
320
+ // callable (i.e., has args) and not a symbol.
321
+ let right = node.right.name
322
+ ? node.right.name.value
323
+ : // otherwise go with the symbol value
324
+ node.right.value;
325
+ this._emit('env.getTest("' + right + '").call(context, ');
326
+ this.compile(node.left, frame);
327
+ // compile the arguments for the callable if they exist
328
+ if (node.right.args) {
329
+ this._emit(',');
330
+ this.compile(node.right.args, frame);
331
+ }
332
+ this._emit(') === true');
333
+ }
334
+ _binOpEmitter(node, frame, str) {
335
+ this.compile(node.left, frame);
336
+ this._emit(str);
337
+ this.compile(node.right, frame);
338
+ }
339
+ // ensure concatenation instead of addition
340
+ // by adding empty string in between
341
+ compileOr(node, frame) {
342
+ return this._binOpEmitter(node, frame, ' || ');
343
+ }
344
+ compileAnd(node, frame) {
345
+ return this._binOpEmitter(node, frame, ' && ');
346
+ }
347
+ compileAdd(node, frame) {
348
+ return this._binOpEmitter(node, frame, ' + ');
349
+ }
350
+ compileConcat(node, frame) {
351
+ return this._binOpEmitter(node, frame, ' + "" + ');
352
+ }
353
+ compileSub(node, frame) {
354
+ return this._binOpEmitter(node, frame, ' - ');
355
+ }
356
+ compileMul(node, frame) {
357
+ return this._binOpEmitter(node, frame, ' * ');
358
+ }
359
+ compileDiv(node, frame) {
360
+ return this._binOpEmitter(node, frame, ' / ');
361
+ }
362
+ compileMod(node, frame) {
363
+ return this._binOpEmitter(node, frame, ' % ');
364
+ }
365
+ compileNot(node, frame) {
366
+ this._emit('!');
367
+ this.compile(node.target, frame);
368
+ }
369
+ compileFloorDiv(node, frame) {
370
+ this._emit('Math.floor(');
371
+ this.compile(node.left, frame);
372
+ this._emit(' / ');
373
+ this.compile(node.right, frame);
374
+ this._emit(')');
375
+ }
376
+ compilePow(node, frame) {
377
+ this._emit('Math.pow(');
378
+ this.compile(node.left, frame);
379
+ this._emit(', ');
380
+ this.compile(node.right, frame);
381
+ this._emit(')');
382
+ }
383
+ compileNeg(node, frame) {
384
+ this._emit('-');
385
+ this.compile(node.target, frame);
386
+ }
387
+ compilePos(node, frame) {
388
+ this._emit('+');
389
+ this.compile(node.target, frame);
390
+ }
391
+ compileCompare(node, frame) {
392
+ this.compile(node.expr, frame);
393
+ node.ops.forEach((op) => {
394
+ this._emit(` ${compareOps[op.type]} `);
395
+ this.compile(op.expr, frame);
396
+ });
397
+ }
398
+ compileLookupVal(node, frame) {
399
+ this._emit('runtime.memberLookup((');
400
+ this._compileExpression(node.target, frame);
401
+ this._emit('),');
402
+ this._compileExpression(node.val, frame);
403
+ this._emit(')');
404
+ }
405
+ _getNodeName(node) {
406
+ if (node instanceof nodes_1.Symbol)
407
+ return node.value;
408
+ if (node instanceof nodes_1.FunCall)
409
+ return 'the return value of (' + this._getNodeName(node.name) + ')';
410
+ if (node instanceof nodes_1.LookupVal)
411
+ return (this._getNodeName(node.target) +
412
+ '["' +
413
+ this._getNodeName(node.value) +
414
+ '"]');
415
+ if (node instanceof nodes_1.Literal)
416
+ return node.value.toString();
417
+ return '--expression--';
418
+ }
419
+ compileFunCall(node, frame) {
420
+ // Keep track of line/col info at runtime by settings
421
+ // variables within an expression. An expression in javascript
422
+ // like (x, y, z) returns the last value, and x and y can be
423
+ // anything
424
+ this._emit('(lineno = ' + node?.lineno + ', colno = ' + node?.colno + ', ');
425
+ this._emit('runtime.callWrap(');
426
+ // Compile it as normal.
427
+ this._compileExpression(node.name, frame);
428
+ // Output the name of what we're calling so we can get friendly errors
429
+ // if the lookup fails.
430
+ this._emit(', "' + this._getNodeName(node.name).replace(/"/g, '\\"') + '", context, ');
431
+ this._compileAggregate(node.args, frame, '[', '])');
432
+ this._emit(')');
433
+ }
434
+ compileFilter(node, frame) {
435
+ let name = node.name;
436
+ // this.assertType(name, Symbol);
437
+ this._emit('env.getFilter("' + name.value + '").call(context, ');
438
+ this._compileAggregate(node.args, frame);
439
+ this._emit(')');
440
+ }
441
+ compileFilterAsync(node, frame) {
442
+ let name = node.name;
443
+ let symbol = node.symbol.value;
444
+ // this.assertType(name, Symbol);
445
+ frame.set(symbol, symbol);
446
+ this._emit('env.getFilter("' + name.value + '").call(context, ');
447
+ this._compileAggregate(node.args, frame);
448
+ this._emit(', ' + this._makeCallback(symbol), true);
449
+ this._addScopeLevel();
450
+ }
451
+ compileKeywordArgs(node, frame) {
452
+ this._emit('runtime.makeKeywordArgs(');
453
+ this.compileDict(node, frame);
454
+ this._emit(')');
455
+ }
456
+ compileSet(node, frame) {
457
+ let ids = [];
458
+ // Lookup the variable names for each identifier and create
459
+ // new ones if necessary
460
+ node.targets.forEach((target) => {
461
+ let name = target.value;
462
+ let id = frame.lookup(name);
463
+ if (id === null || id === undefined) {
464
+ id = this._tmpid();
465
+ // Note: This relies on js allowing scope across
466
+ // blocks, in case this is created inside an `if`
467
+ this._emit('var ' + id + ';', true);
468
+ }
469
+ ids?.push(id);
470
+ });
471
+ if (node.value) {
472
+ this._emit(ids.join(' = ') + ' = ');
473
+ this._compileExpression(node.value, frame);
474
+ this._emit(';', true);
475
+ }
476
+ else {
477
+ this._emit(ids.join(' = ') + ' = ');
478
+ this.compile(node.body, frame);
479
+ this._emit(';', true);
480
+ }
481
+ node.targets.forEach((target, i) => {
482
+ let id = ids[i];
483
+ let name = target.value;
484
+ // We are running this for every var, but it's very
485
+ // uncommon to assign to multiple vars anyway
486
+ this._emit(`frame.set("${name}", ${id}, true);`, true);
487
+ this._emit('if(frame.topLevel) {', true);
488
+ this._emit(`context.setVariable("${name}", ${id});`, true);
489
+ this._emit('}', true);
490
+ if (name.charAt(0) !== '_') {
491
+ this._emit('if(frame.topLevel) {', true);
492
+ this._emit(`context.addExport("${name}", ${id});`, true);
493
+ this._emit('}', true);
494
+ }
495
+ });
496
+ }
497
+ compileSwitch(node, frame) {
498
+ this._emit('switch (');
499
+ this.compile(node.expr, frame);
500
+ this._emit(') {');
501
+ node.cases.forEach((c, i) => {
502
+ this._emit('case ');
503
+ this.compile(c.cond, frame);
504
+ this._emit(': ');
505
+ this.compile(c.body, frame);
506
+ // preserve fall-throughs
507
+ if (c.body.children?.length) {
508
+ this._emit('break;', true);
509
+ }
510
+ });
511
+ if (node._default) {
512
+ this._emit('default:');
513
+ this.compile(node._default, frame);
514
+ }
515
+ this._emit('}');
516
+ }
517
+ compileIf(node, frame, async = false) {
518
+ this._emit('if(');
519
+ this._compileExpression(node.cond, frame);
520
+ this._emit(') {', true);
521
+ this._withScopedSyntax(() => {
522
+ this.compile(node.body, frame);
523
+ if (async) {
524
+ this._emit('cb()');
525
+ }
526
+ });
527
+ if (node.else_) {
528
+ this._emit('}\nelse {', true);
529
+ this._withScopedSyntax(() => {
530
+ this.compile(node.else_, frame);
531
+ if (async) {
532
+ this._emit('cb()');
533
+ }
534
+ });
535
+ }
536
+ else if (async) {
537
+ this._emit('}\nelse {', true);
538
+ this._emit('cb()');
539
+ }
540
+ this._emit('}', true);
541
+ }
542
+ compileIfAsync(node, frame) {
543
+ this._emit('(function(cb) {');
544
+ this.compileIf(node, frame, true);
545
+ this._emit('})(' + this._makeCallback());
546
+ this._addScopeLevel();
547
+ }
548
+ _emitLoopBindings(node, arr, i, len) {
549
+ const bindings = [
550
+ { name: 'index', val: `${i} + 1` },
551
+ { name: 'index0', val: i },
552
+ { name: 'revindex', val: `${len} - ${i}` },
553
+ { name: 'revindex0', val: `${len} - ${i} - 1` },
554
+ { name: 'first', val: `${i} === 0` },
555
+ { name: 'last', val: `${i} === ${len} - 1` },
556
+ { name: 'length', val: len },
557
+ ];
558
+ bindings.forEach((b) => {
559
+ this._emit(`frame.set("loop.${b.name}", ${b.val});`, true);
560
+ });
561
+ }
562
+ compileFor(node, frame) {
563
+ // Some of this code is ugly, but it keeps the generated code
564
+ // as fast as possible. ForAsync also shares some of this, but
565
+ // not much.
566
+ const i = this._tmpid();
567
+ const len = this._tmpid();
568
+ const arr = this._tmpid();
569
+ frame = frame?.push();
570
+ this._emit('frame = frame?.push();', true);
571
+ this._emit(`var ${arr} = `);
572
+ this._compileExpression(node.arr, frame);
573
+ this._emit(';', true);
574
+ this._emit(`if(${arr}) {`);
575
+ this._emit(arr + ' = runtime.fromIterator(' + arr + ');', true);
576
+ // If multiple names are passed, we need to bind them
577
+ // appropriately
578
+ if (node.name instanceof nodes_1.ArrayNode) {
579
+ this._emit(`var ${i};`, true);
580
+ // The object could be an arroy or object. Note that the
581
+ // body of the loop is duplicated for each condition, but
582
+ // we are optimizing for speed over size.
583
+ this._emit(`if(Array.isArray(${arr})) {`, true);
584
+ this._emit(`var ${len} = ${arr}?.length;`, true);
585
+ this._emit(`for(${i}=0; ${i} < ${arr}?.length; ${i}++) {`, true);
586
+ // Bind each declared var
587
+ node.name.children.forEach((child, u) => {
588
+ let tid = this._tmpid();
589
+ this._emit(`var ${tid} = ${arr}[${i}][${u}];`, true);
590
+ this._emit(`frame.set("${child}", ${arr}[${i}][${u}]);`, true);
591
+ frame.set(node.name.children[u].value, tid);
592
+ });
593
+ this._emitLoopBindings(node, arr, i, len);
594
+ this._withScopedSyntax(() => {
595
+ this.compile(node.body, frame);
596
+ });
597
+ this._emit('}', true);
598
+ this._emit('} else {', true);
599
+ // Iterate over the key/values of an object
600
+ const [key, val] = node.name.children;
601
+ const k = this._tmpid();
602
+ const v = this._tmpid();
603
+ frame.set(key.value, k);
604
+ frame.set(val.value, v);
605
+ this._emit([
606
+ `${i} = -1;`,
607
+ `var ${len} = runtime.keys(${arr})?.length;`,
608
+ `for(var ${k} in ${arr}) {`,
609
+ `${i}++;`,
610
+ `var ${v} = ${arr}[${k}];`,
611
+ `frame.set("${key.value}", ${k});`,
612
+ `frame.set("${val.value}", ${v});`,
613
+ ], true);
614
+ this._emitLoopBindings(node, arr, i, len);
615
+ this._withScopedSyntax(() => {
616
+ this.compile(node.body, frame);
617
+ });
618
+ this._emit('}', true);
619
+ this._emit('}', true);
620
+ }
621
+ else {
622
+ // Generate a typical array iteration
623
+ const v = this._tmpid();
624
+ frame.set(node.name.value, v);
625
+ this._emit(`var ${len} = ${arr}?.length;`, true);
626
+ this._emit(`for(var ${i}=0; ${i} < ${arr}?.length; ${i}++) {`, true);
627
+ this._emit(`var ${v} = ${arr}[${i}];`, true);
628
+ this._emit(`frame.set("${node.name.value}", ${v});`, true);
629
+ this._emitLoopBindings(node, arr, i, len);
630
+ this._withScopedSyntax(() => {
631
+ this.compile(node.body, frame);
632
+ });
633
+ this._emit('}', true);
634
+ }
635
+ this._emit('}', true);
636
+ if (node.else_) {
637
+ this._emit('if (!' + len + ') {', true);
638
+ this.compile(node.else_, frame);
639
+ this._emit('}', true);
640
+ }
641
+ this._emit('frame = frame.pop();', true);
642
+ }
643
+ //TODO: Find out the type
644
+ _compileAsyncLoop(node, frame, parallel = true) {
645
+ // This shares some code with the For tag, but not enough to
646
+ // worry about. This iterates across an object asynchronously,
647
+ // but not in parallel.
648
+ let i = this._tmpid();
649
+ let len = this._tmpid();
650
+ let arr = this._tmpid();
651
+ let asyncMethod = parallel ? 'asyncAll' : 'asyncEach';
652
+ frame = frame?.push();
653
+ this._emit('frame = frame?.push();', true);
654
+ this._emit('var ' + arr + ' = runtime.fromIterator(');
655
+ this._compileExpression(node.arr, frame);
656
+ this._emit(');', true);
657
+ if (node.name instanceof nodes_1.ArrayNode) {
658
+ const arrayLen = node.name.children?.length;
659
+ this._emit(`runtime.${asyncMethod}(${arr}, ${arrayLen}, function(`);
660
+ node.name.children.forEach((name) => {
661
+ this._emit(`${name.value},`);
662
+ });
663
+ this._emit(i + ',' + len + ',next) {');
664
+ node.name.children.forEach((name) => {
665
+ const id = name.value;
666
+ frame.set(id, id);
667
+ this._emit(`frame.set("${id}", ${id});`, true);
668
+ });
669
+ }
670
+ else {
671
+ const id = node.name.value;
672
+ this._emit([
673
+ `runtime.${asyncMethod}(${arr}, 1, function(${id}, ${i}, ${len},next) {`,
674
+ 'frame.set("' + id + '", ' + id + ', true);',
675
+ ]);
676
+ frame.set(id, id);
677
+ }
678
+ this._emitLoopBindings(node, arr, i, len);
679
+ this._withScopedSyntax(() => {
680
+ let buf;
681
+ if (parallel) {
682
+ buf = this._pushBuffer();
683
+ }
684
+ this.compile(node.body, frame);
685
+ this._emit('next(' + i + (buf ? ',' + buf : '') + ', true);');
686
+ if (parallel) {
687
+ this._popBuffer();
688
+ }
689
+ });
690
+ const output = this._tmpid();
691
+ this._emit('}, ' + this._makeCallback(output), true);
692
+ this._addScopeLevel();
693
+ if (parallel) {
694
+ this._emit(this.buffer + ' += ' + output + ';', true);
695
+ }
696
+ if (node.else_) {
697
+ this._emit('if (!' + arr + '?.length) {', true);
698
+ this.compile(node.else_, frame);
699
+ this._emit('}', true);
700
+ }
701
+ this._emit('frame = frame.pop();', true);
702
+ }
703
+ compileAsyncEach(node, frame) {
704
+ this._compileAsyncLoop(node, frame);
705
+ }
706
+ compileAsyncAll(node, frame) {
707
+ this._compileAsyncLoop(node, frame, true);
708
+ }
709
+ _compileMacro(node, frame) {
710
+ let args = [];
711
+ let kwargs = null;
712
+ let funcId = 'macro_' + this._tmpid();
713
+ let keepFrame = frame !== undefined;
714
+ // Type check the definition of the args
715
+ node.args.children.forEach((arg, i) => {
716
+ if (i === node.args.children?.length - 1 && arg instanceof nodes_1.Dict) {
717
+ kwargs = arg;
718
+ }
719
+ else {
720
+ // this.assertType(arg, Symbol);
721
+ args?.push(arg);
722
+ }
723
+ });
724
+ const realNames = [...args.map((n) => `l_${n.value}`), 'kwargs'];
725
+ // Quoted argument names
726
+ const argNames = args.map((n) => `"${n.value}"`);
727
+ const kwargNames = ((kwargs && kwargs.children) || []).map((n) => `"${n.key.value}"`);
728
+ // We pass a function to makeMacro which destructures the
729
+ // arguments so support setting positional args with keywords
730
+ // args and passing keyword args as positional args
731
+ // (essentially default values). See runtime.js.
732
+ let currFrame;
733
+ if (keepFrame) {
734
+ currFrame = frame?.push(true);
735
+ }
736
+ else {
737
+ currFrame = new runtime_1.Frame();
738
+ }
739
+ this._emit([
740
+ `var ${funcId} = runtime.makeMacro(`,
741
+ `[${argNames.join(', ')}], `,
742
+ `[${kwargNames.join(', ')}], `,
743
+ `function (${realNames.join(', ')}) {`,
744
+ 'var callerFrame = frame;',
745
+ 'frame = ' +
746
+ (keepFrame ? 'frame?.push(true);' : 'new runtime.Frame();'),
747
+ 'kwargs = kwargs || {};',
748
+ 'if (Object.prototype.hasOwnProperty.call(kwargs, "caller")) {',
749
+ 'frame.set("caller", kwargs.caller); }',
750
+ ], true);
751
+ // Expose the arguments to the template. Don't need to use
752
+ // random names because the function
753
+ // will create a new run-time scope for us
754
+ args.forEach((arg) => {
755
+ this._emit(`frame.set("${arg.value}", l_${arg.value});`, true);
756
+ currFrame.set(arg.value, `l_${arg.value}`);
757
+ });
758
+ // Expose the keyword arguments
759
+ if (kwargs) {
760
+ kwargs.children.forEach((pair) => {
761
+ const name = pair.key.value;
762
+ this._emit(`frame.set("${name}", `);
763
+ this._emit(`Object.prototype.hasOwnProperty.call(kwargs, "${name}")`);
764
+ this._emit(` ? kwargs["${name}"] : `);
765
+ this._compileExpression(pair.value, currFrame);
766
+ this._emit(');');
767
+ });
768
+ }
769
+ const bufferId = this._pushBuffer();
770
+ this._withScopedSyntax(() => {
771
+ this.compile(node.body, currFrame);
772
+ });
773
+ this._emit([
774
+ 'frame = ' + (keepFrame ? 'frame.pop();' : 'callerFrame;'),
775
+ `return new runtime.SafeString(${bufferId});`,
776
+ '});',
777
+ ], true);
778
+ this._popBuffer();
779
+ return funcId;
780
+ }
781
+ compileMacro(node, frame) {
782
+ let funcId = this._compileMacro(node);
783
+ // Expose the macro to the templates
784
+ let name = node.name.value;
785
+ frame.set(name, funcId);
786
+ if (frame.parent) {
787
+ this._emit(`frame.set("${name}", ${funcId});`, true);
788
+ }
789
+ else {
790
+ if (node.name.value.charAt(0) !== '_') {
791
+ this._emit(`context.addExport("${name}");`, true);
792
+ }
793
+ this._emit(`context.setVariable("${name}", ${funcId});`, true);
794
+ }
795
+ }
796
+ compileCaller(node, frame) {
797
+ // basically an anonymous "macro expression"
798
+ this._emit('(function (){');
799
+ const funcId = this._compileMacro(node, frame);
800
+ this._emit(`return ${funcId};})()`);
801
+ }
802
+ _compileGetTemplate(node, frame, eagerCompile = true, ignoreMissing = true) {
803
+ const parentTemplateId = this._tmpid();
804
+ const parentName = this._templateName();
805
+ const cb = this._makeCallback(parentTemplateId);
806
+ this._emit('env.getTemplate(');
807
+ this._compileExpression(node.template, frame);
808
+ this._emit(`, ${cb}`,
809
+ // , {
810
+ // eagerCompile: ${eagerCompile ? 'true' : 'false'},
811
+ // parentName: ${parentName},
812
+ // ignoreMissing: ${ignoreMissing ? 'true' : 'false'}
813
+ // })
814
+ true);
815
+ return parentTemplateId;
816
+ }
817
+ compileImport(node, frame) {
818
+ const target = node.target.value;
819
+ const id = this._compileGetTemplate(node, frame, false, false);
820
+ this._addScopeLevel();
821
+ this._emit(id +
822
+ '.getExported(' +
823
+ (node.withContext ? 'context.getVariables(), frame, ' : '') +
824
+ this._makeCallback(id), true);
825
+ this._addScopeLevel();
826
+ frame.set(target, id);
827
+ if (frame.parent) {
828
+ this._emit(`frame.set("${target}", ${id});`, true);
829
+ }
830
+ else {
831
+ this._emit(`context.setVariable("${target}", ${id});`, true);
832
+ }
833
+ }
834
+ compileFromImport(node, frame) {
835
+ const importedId = this._compileGetTemplate(node, frame, false, false);
836
+ this._addScopeLevel();
837
+ this._emit(importedId +
838
+ '.getExported(' +
839
+ (node.withContext ? 'context.getVariables(), frame, ' : '') +
840
+ this._makeCallback(importedId), true);
841
+ this._addScopeLevel();
842
+ node.names.children.forEach((nameNode) => {
843
+ let name;
844
+ let alias;
845
+ let id = this._tmpid();
846
+ if (nameNode instanceof nodes_1.Pair) {
847
+ name = nameNode.key.value;
848
+ alias = nameNode.value.value;
849
+ }
850
+ else {
851
+ name = nameNode.value;
852
+ alias = name;
853
+ }
854
+ this._emit([
855
+ `if(Object.prototype.hasOwnProperty.call(${importedId}, "${name}")) {`,
856
+ `var ${id} = ${importedId}.${name};`,
857
+ '} else {',
858
+ `cb(new Error("cannot import '${name}'")); return;`,
859
+ '}',
860
+ ], true);
861
+ frame.set(alias, id);
862
+ if (frame.parent) {
863
+ this._emit(`frame.set("${alias}", ${id});`, true);
864
+ }
865
+ else {
866
+ this._emit(`context.setVariable("${alias}", ${id});`, true);
867
+ }
868
+ });
869
+ }
870
+ compileBlock(node) {
871
+ let id = this._tmpid();
872
+ // If we are executing outside a block (creating a top-level
873
+ // block), we really don't want to execute its code because it
874
+ // will execute twice: once when the child template runs and
875
+ // again when the parent template runs. Note that blocks
876
+ // within blocks will *always* execute immediately *and*
877
+ // wherever else they are invoked (like used in a parent
878
+ // template). This may have behavioral differences from jinja
879
+ // because blocks can have side effects, but it seems like a
880
+ // waste of performance to always execute huge top-level
881
+ // blocks twice
882
+ if (!this.inBlock) {
883
+ this._emit('(parentTemplate ? function(e, c, f, r, cb) { cb(""); } : ');
884
+ }
885
+ this._emit(`context.getBlock("${node.name.value}")`);
886
+ if (!this.inBlock) {
887
+ this._emit(')');
888
+ }
889
+ this._emit('(env, context, frame, runtime, ' + this._makeCallback(id));
890
+ this._emit(`${this.buffer} += ${id};`, true);
891
+ this._addScopeLevel();
892
+ }
893
+ compileSuper(node, frame) {
894
+ let name = node.blockName.value;
895
+ let id = node.symbol.value;
896
+ const cb = this._makeCallback(id);
897
+ this._emit([
898
+ `context.getSuper(env, "${name}", b_${name}, frame, runtime, ${cb}`,
899
+ `${id} = runtime.markSafe(${id});`,
900
+ ], true);
901
+ this._addScopeLevel();
902
+ frame.set(id, id);
903
+ }
904
+ compileExtends(node, frame) {
905
+ let k = this._tmpid();
906
+ const parentTemplateId = this._compileGetTemplate(node, frame, true, false);
907
+ // extends is a dynamic tag and can occur within a block like
908
+ // `if`, so if this happens we need to capture the parent
909
+ // template in the top-level scope
910
+ this._emit(`parentTemplate = ${parentTemplateId}`, true);
911
+ this._emit([
912
+ `for(var ${k} in parentTemplate.blocks) {`,
913
+ `context.addBlock(${k}, parentTemplate.blocks[${k}]);`,
914
+ '}',
915
+ ], true);
916
+ this._addScopeLevel();
917
+ }
918
+ compileInclude(node, frame) {
919
+ this._emit('var tasks = [];', true);
920
+ this._emit('tasks?.push(', true);
921
+ this._emit('function(callback) {', true);
922
+ const id = this._compileGetTemplate(node, frame, false, node.ignoreMissing);
923
+ this._emit(`callback(null,${id});});`, true);
924
+ this._emit('});', true);
925
+ const id2 = this._tmpid();
926
+ this._emit([
927
+ 'tasks?.push(',
928
+ 'function(template, callback){',
929
+ 'template.render(context.getVariables(), frame, ' +
930
+ this._makeCallback(id2),
931
+ 'callback(null,' + id2 + ', true);});',
932
+ '});',
933
+ 'tasks?.push(',
934
+ 'function(result, callback){',
935
+ `${this.buffer} += result;`,
936
+ 'callback(null);',
937
+ '});',
938
+ 'env.waterfall(tasks, function(){',
939
+ ], true);
940
+ this._addScopeLevel();
941
+ }
942
+ compileTemplateData(node, frame) {
943
+ this.compileLiteral(node);
944
+ }
945
+ compileCapture(node, frame) {
946
+ // we need to temporarily override the current buffer id as 'output'
947
+ // so the set block writes to the capture output instead of the buffer
948
+ let buffer = this.buffer;
949
+ this.buffer = 'output';
950
+ this._emit('(function() {', true);
951
+ this._emit('var output = "";', true);
952
+ this._withScopedSyntax(() => {
953
+ this.compile(node.body, frame);
954
+ });
955
+ this._emit('return output;', true);
956
+ this._emit('})()', true);
957
+ // and of course, revert back to the old buffer id
958
+ this.buffer = buffer;
959
+ }
960
+ compileOutput(node, frame) {
961
+ const children = node.children;
962
+ children?.forEach((child) => {
963
+ // TemplateData is a special case because it is never
964
+ // autoescaped, so simply output it for optimization
965
+ if (child instanceof nodes_1.TemplateData) {
966
+ if (child.value) {
967
+ this._emit(`${this.buffer} += `);
968
+ this.compileLiteral(child); //TODO had frame also frame
969
+ this._emit(';', true);
970
+ }
971
+ }
972
+ else {
973
+ this._emit(`${this.buffer} += runtime.suppressValue(`);
974
+ if (this.throwOnUndefined) {
975
+ this._emit('runtime.ensureDefined(');
976
+ }
977
+ this.compile(child, frame);
978
+ if (this.throwOnUndefined) {
979
+ this._emit(`,${node?.lineno},${node?.colno})`);
980
+ }
981
+ this._emit(', env.autoescape);\n');
982
+ }
983
+ });
984
+ }
985
+ compileRoot(node, frame) {
986
+ if (frame) {
987
+ this.fail("compileRoot: root node can't have frame");
988
+ }
989
+ frame = new runtime_1.Frame();
990
+ this._emitFuncBegin(node, 'root');
991
+ this._emit('var parentTemplate = null;', true);
992
+ this._compileChildren(node, frame);
993
+ this._emit([
994
+ 'if(parentTemplate) {',
995
+ 'parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);',
996
+ '} else {',
997
+ `cb(null, ${this.buffer});`,
998
+ '}',
999
+ ], true);
1000
+ this._emitFuncEnd(true);
1001
+ this.inBlock = true;
1002
+ const blockNames = [];
1003
+ const blocks = node.findAll(nodes_1.Block); //?
1004
+ blocks.forEach((block, i) => {
1005
+ const name = block.name.value;
1006
+ if (blockNames.indexOf(name) !== -1) {
1007
+ lib_1.p.err(`Block "${name}" defined more than once.`);
1008
+ throw new Error(`Block "${name}" defined more than once.`);
1009
+ }
1010
+ blockNames?.push(name);
1011
+ this._emitFuncBegin(block, `b_${name}`);
1012
+ const tmpFrame = new runtime_1.Frame();
1013
+ this._emit('var frame = frame?.push(true);', true);
1014
+ this.compile(block.body, tmpFrame);
1015
+ this._emitFuncEnd();
1016
+ });
1017
+ this._emit('return {', true);
1018
+ blocks.forEach((block, i) => {
1019
+ const blockName = `b_${block.name.value}`;
1020
+ this._emit(`${blockName}: ${blockName},`, true);
1021
+ });
1022
+ this._emit('root: root\n};', true);
1023
+ }
1024
+ compile(node, frame) {
1025
+ lib_1.p.debug('Compiling: ', node);
1026
+ let _compile = this['compile' + node?.typename];
1027
+ lib_1.p.warn('Compiling: ', _compile);
1028
+ if (_compile) {
1029
+ _compile.call(this, node, frame);
1030
+ }
1031
+ else {
1032
+ this.fail(`compile: Cannot compile node: ${node?.typename}`, node?.lineno, node?.colno);
1033
+ }
1034
+ }
1035
+ getCode() {
1036
+ return this.codebuf.join('');
1037
+ }
1038
+ }
1039
+ const compile = (src, //TODO: check this is true
1040
+ asyncFilters = [], extensions = [], name, opts) => {
1041
+ const c = new Compiler(name, opts);
1042
+ const processedSrc = extensions
1043
+ .map((ext) => ext.preprocess)
1044
+ .filter((f) => !!f)
1045
+ .reduce((s, processor) => processor(s), src);
1046
+ c.compile((0, transformer_1.transform)((0, parser_1.parse)(processedSrc, extensions, opts), asyncFilters) // TODO had 3 arguments name also
1047
+ );
1048
+ return c.getCode();
1049
+ };
1050
+ exports.compile = compile;