@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.
- package/LICENSE +26 -0
- package/README.md +55 -0
- package/dist/scripts/smoke.d.ts +1 -0
- package/dist/scripts/smoke.js +95 -0
- package/dist/src/compiler.d.ts +12 -0
- package/dist/src/compiler.js +1050 -0
- package/dist/src/environment.d.ts +103 -0
- package/dist/src/environment.js +621 -0
- package/dist/src/express-app.d.ts +2 -0
- package/dist/src/express-app.js +33 -0
- package/dist/src/filters.d.ts +44 -0
- package/dist/src/filters.js +424 -0
- package/dist/src/globals.d.ts +14 -0
- package/dist/src/globals.js +342 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.js +116 -0
- package/dist/src/interpreter.d.ts +16 -0
- package/dist/src/interpreter.js +489 -0
- package/dist/src/lexer.d.ts +72 -0
- package/dist/src/lexer.js +480 -0
- package/dist/src/lib.d.ts +74 -0
- package/dist/src/lib.js +237 -0
- package/dist/src/loader.d.ts +80 -0
- package/dist/src/loader.js +175 -0
- package/dist/src/nodes.d.ts +362 -0
- package/dist/src/nodes.js +894 -0
- package/dist/src/parser.d.ts +66 -0
- package/dist/src/parser.js +1068 -0
- package/dist/src/precompile.d.ts +15 -0
- package/dist/src/precompile.js +108 -0
- package/dist/src/runtime.d.ts +33 -0
- package/dist/src/runtime.js +314 -0
- package/dist/src/transformer.d.ts +3 -0
- package/dist/src/transformer.js +161 -0
- package/dist/src/types.d.ts +27 -0
- package/dist/src/types.js +2 -0
- package/dist/tests/compiler.test.d.ts +1 -0
- package/dist/tests/compiler.test.js +201 -0
- package/dist/tests/enviornment.test.d.ts +1 -0
- package/dist/tests/enviornment.test.js +279 -0
- package/dist/tests/express.test.d.ts +1 -0
- package/dist/tests/express.test.js +86 -0
- package/dist/tests/filters.test.d.ts +13 -0
- package/dist/tests/filters.test.js +286 -0
- package/dist/tests/globals.test.d.ts +1 -0
- package/dist/tests/globals.test.js +579 -0
- package/dist/tests/interpreter.test.d.ts +1 -0
- package/dist/tests/interpreter.test.js +208 -0
- package/dist/tests/lexer.test.d.ts +1 -0
- package/dist/tests/lexer.test.js +249 -0
- package/dist/tests/lib.test.d.ts +1 -0
- package/dist/tests/lib.test.js +236 -0
- package/dist/tests/loader.test.d.ts +1 -0
- package/dist/tests/loader.test.js +301 -0
- package/dist/tests/nodes.test.d.ts +1 -0
- package/dist/tests/nodes.test.js +137 -0
- package/dist/tests/parser.test.d.ts +1 -0
- package/dist/tests/parser.test.js +294 -0
- package/dist/tests/precompile.test.d.ts +1 -0
- package/dist/tests/precompile.test.js +224 -0
- package/dist/tests/runtime.test.d.ts +1 -0
- package/dist/tests/runtime.test.js +237 -0
- package/dist/tests/transformer.test.d.ts +1 -0
- package/dist/tests/transformer.test.js +125 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- 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;
|