Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80738 views
1
define(
2
["../exception","../utils","./ast","exports"],
3
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
4
"use strict";
5
var Exception = __dependency1__["default"];
6
var isArray = __dependency2__.isArray;
7
var indexOf = __dependency2__.indexOf;
8
var AST = __dependency3__["default"];
9
10
var slice = [].slice;
11
12
13
function Compiler() {}
14
15
__exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a
16
// function in a context. This is necessary for mustache compatibility, which
17
// requires that context functions in blocks are evaluated by blockHelperMissing,
18
// and then proceed as if the resulting value was provided to blockHelperMissing.
19
20
Compiler.prototype = {
21
compiler: Compiler,
22
23
equals: function(other) {
24
var len = this.opcodes.length;
25
if (other.opcodes.length !== len) {
26
return false;
27
}
28
29
for (var i = 0; i < len; i++) {
30
var opcode = this.opcodes[i],
31
otherOpcode = other.opcodes[i];
32
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
33
return false;
34
}
35
}
36
37
// We know that length is the same between the two arrays because they are directly tied
38
// to the opcode behavior above.
39
len = this.children.length;
40
for (i = 0; i < len; i++) {
41
if (!this.children[i].equals(other.children[i])) {
42
return false;
43
}
44
}
45
46
return true;
47
},
48
49
guid: 0,
50
51
compile: function(program, options) {
52
this.sourceNode = [];
53
this.opcodes = [];
54
this.children = [];
55
this.options = options;
56
this.stringParams = options.stringParams;
57
this.trackIds = options.trackIds;
58
59
options.blockParams = options.blockParams || [];
60
61
// These changes will propagate to the other compiler components
62
var knownHelpers = options.knownHelpers;
63
options.knownHelpers = {
64
'helperMissing': true,
65
'blockHelperMissing': true,
66
'each': true,
67
'if': true,
68
'unless': true,
69
'with': true,
70
'log': true,
71
'lookup': true
72
};
73
if (knownHelpers) {
74
for (var name in knownHelpers) {
75
options.knownHelpers[name] = knownHelpers[name];
76
}
77
}
78
79
return this.accept(program);
80
},
81
82
compileProgram: function(program) {
83
var result = new this.compiler().compile(program, this.options);
84
var guid = this.guid++;
85
86
this.usePartial = this.usePartial || result.usePartial;
87
88
this.children[guid] = result;
89
this.useDepths = this.useDepths || result.useDepths;
90
91
return guid;
92
},
93
94
accept: function(node) {
95
this.sourceNode.unshift(node);
96
var ret = this[node.type](node);
97
this.sourceNode.shift();
98
return ret;
99
},
100
101
Program: function(program) {
102
this.options.blockParams.unshift(program.blockParams);
103
104
var body = program.body;
105
for(var i=0, l=body.length; i<l; i++) {
106
this.accept(body[i]);
107
}
108
109
this.options.blockParams.shift();
110
111
this.isSimple = l === 1;
112
this.blockParams = program.blockParams ? program.blockParams.length : 0;
113
114
return this;
115
},
116
117
BlockStatement: function(block) {
118
transformLiteralToPath(block);
119
120
var program = block.program,
121
inverse = block.inverse;
122
123
program = program && this.compileProgram(program);
124
inverse = inverse && this.compileProgram(inverse);
125
126
var type = this.classifySexpr(block);
127
128
if (type === 'helper') {
129
this.helperSexpr(block, program, inverse);
130
} else if (type === 'simple') {
131
this.simpleSexpr(block);
132
133
// now that the simple mustache is resolved, we need to
134
// evaluate it by executing `blockHelperMissing`
135
this.opcode('pushProgram', program);
136
this.opcode('pushProgram', inverse);
137
this.opcode('emptyHash');
138
this.opcode('blockValue', block.path.original);
139
} else {
140
this.ambiguousSexpr(block, program, inverse);
141
142
// now that the simple mustache is resolved, we need to
143
// evaluate it by executing `blockHelperMissing`
144
this.opcode('pushProgram', program);
145
this.opcode('pushProgram', inverse);
146
this.opcode('emptyHash');
147
this.opcode('ambiguousBlockValue');
148
}
149
150
this.opcode('append');
151
},
152
153
PartialStatement: function(partial) {
154
this.usePartial = true;
155
156
var params = partial.params;
157
if (params.length > 1) {
158
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
159
} else if (!params.length) {
160
params.push({type: 'PathExpression', parts: [], depth: 0});
161
}
162
163
var partialName = partial.name.original,
164
isDynamic = partial.name.type === 'SubExpression';
165
if (isDynamic) {
166
this.accept(partial.name);
167
}
168
169
this.setupFullMustacheParams(partial, undefined, undefined, true);
170
171
var indent = partial.indent || '';
172
if (this.options.preventIndent && indent) {
173
this.opcode('appendContent', indent);
174
indent = '';
175
}
176
177
this.opcode('invokePartial', isDynamic, partialName, indent);
178
this.opcode('append');
179
},
180
181
MustacheStatement: function(mustache) {
182
this.SubExpression(mustache);
183
184
if(mustache.escaped && !this.options.noEscape) {
185
this.opcode('appendEscaped');
186
} else {
187
this.opcode('append');
188
}
189
},
190
191
ContentStatement: function(content) {
192
if (content.value) {
193
this.opcode('appendContent', content.value);
194
}
195
},
196
197
CommentStatement: function() {},
198
199
SubExpression: function(sexpr) {
200
transformLiteralToPath(sexpr);
201
var type = this.classifySexpr(sexpr);
202
203
if (type === 'simple') {
204
this.simpleSexpr(sexpr);
205
} else if (type === 'helper') {
206
this.helperSexpr(sexpr);
207
} else {
208
this.ambiguousSexpr(sexpr);
209
}
210
},
211
ambiguousSexpr: function(sexpr, program, inverse) {
212
var path = sexpr.path,
213
name = path.parts[0],
214
isBlock = program != null || inverse != null;
215
216
this.opcode('getContext', path.depth);
217
218
this.opcode('pushProgram', program);
219
this.opcode('pushProgram', inverse);
220
221
this.accept(path);
222
223
this.opcode('invokeAmbiguous', name, isBlock);
224
},
225
226
simpleSexpr: function(sexpr) {
227
this.accept(sexpr.path);
228
this.opcode('resolvePossibleLambda');
229
},
230
231
helperSexpr: function(sexpr, program, inverse) {
232
var params = this.setupFullMustacheParams(sexpr, program, inverse),
233
path = sexpr.path,
234
name = path.parts[0];
235
236
if (this.options.knownHelpers[name]) {
237
this.opcode('invokeKnownHelper', params.length, name);
238
} else if (this.options.knownHelpersOnly) {
239
throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
240
} else {
241
path.falsy = true;
242
243
this.accept(path);
244
this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
245
}
246
},
247
248
PathExpression: function(path) {
249
this.addDepth(path.depth);
250
this.opcode('getContext', path.depth);
251
252
var name = path.parts[0],
253
scoped = AST.helpers.scopedId(path),
254
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
255
256
if (blockParamId) {
257
this.opcode('lookupBlockParam', blockParamId, path.parts);
258
} else if (!name) {
259
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
260
this.opcode('pushContext');
261
} else if (path.data) {
262
this.options.data = true;
263
this.opcode('lookupData', path.depth, path.parts);
264
} else {
265
this.opcode('lookupOnContext', path.parts, path.falsy, scoped);
266
}
267
},
268
269
StringLiteral: function(string) {
270
this.opcode('pushString', string.value);
271
},
272
273
NumberLiteral: function(number) {
274
this.opcode('pushLiteral', number.value);
275
},
276
277
BooleanLiteral: function(bool) {
278
this.opcode('pushLiteral', bool.value);
279
},
280
281
Hash: function(hash) {
282
var pairs = hash.pairs, i, l;
283
284
this.opcode('pushHash');
285
286
for (i=0, l=pairs.length; i<l; i++) {
287
this.pushParam(pairs[i].value);
288
}
289
while (i--) {
290
this.opcode('assignToHash', pairs[i].key);
291
}
292
this.opcode('popHash');
293
},
294
295
// HELPERS
296
opcode: function(name) {
297
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
298
},
299
300
addDepth: function(depth) {
301
if (!depth) {
302
return;
303
}
304
305
this.useDepths = true;
306
},
307
308
classifySexpr: function(sexpr) {
309
var isSimple = AST.helpers.simpleId(sexpr.path);
310
311
var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
312
313
// a mustache is an eligible helper if:
314
// * its id is simple (a single part, not `this` or `..`)
315
var isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);
316
317
// if a mustache is an eligible helper but not a definite
318
// helper, it is ambiguous, and will be resolved in a later
319
// pass or at runtime.
320
var isEligible = !isBlockParam && (isHelper || isSimple);
321
322
var options = this.options;
323
324
// if ambiguous, we can possibly resolve the ambiguity now
325
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
326
if (isEligible && !isHelper) {
327
var name = sexpr.path.parts[0];
328
329
if (options.knownHelpers[name]) {
330
isHelper = true;
331
} else if (options.knownHelpersOnly) {
332
isEligible = false;
333
}
334
}
335
336
if (isHelper) { return 'helper'; }
337
else if (isEligible) { return 'ambiguous'; }
338
else { return 'simple'; }
339
},
340
341
pushParams: function(params) {
342
for(var i=0, l=params.length; i<l; i++) {
343
this.pushParam(params[i]);
344
}
345
},
346
347
pushParam: function(val) {
348
var value = val.value != null ? val.value : val.original || '';
349
350
if (this.stringParams) {
351
if (value.replace) {
352
value = value
353
.replace(/^(\.?\.\/)*/g, '')
354
.replace(/\//g, '.');
355
}
356
357
if(val.depth) {
358
this.addDepth(val.depth);
359
}
360
this.opcode('getContext', val.depth || 0);
361
this.opcode('pushStringParam', value, val.type);
362
363
if (val.type === 'SubExpression') {
364
// SubExpressions get evaluated and passed in
365
// in string params mode.
366
this.accept(val);
367
}
368
} else {
369
if (this.trackIds) {
370
var blockParamIndex;
371
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
372
blockParamIndex = this.blockParamIndex(val.parts[0]);
373
}
374
if (blockParamIndex) {
375
var blockParamChild = val.parts.slice(1).join('.');
376
this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
377
} else {
378
value = val.original || value;
379
if (value.replace) {
380
value = value
381
.replace(/^\.\//g, '')
382
.replace(/^\.$/g, '');
383
}
384
385
this.opcode('pushId', val.type, value);
386
}
387
}
388
this.accept(val);
389
}
390
},
391
392
setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
393
var params = sexpr.params;
394
this.pushParams(params);
395
396
this.opcode('pushProgram', program);
397
this.opcode('pushProgram', inverse);
398
399
if (sexpr.hash) {
400
this.accept(sexpr.hash);
401
} else {
402
this.opcode('emptyHash', omitEmpty);
403
}
404
405
return params;
406
},
407
408
blockParamIndex: function(name) {
409
for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
410
var blockParams = this.options.blockParams[depth],
411
param = blockParams && indexOf(blockParams, name);
412
if (blockParams && param >= 0) {
413
return [depth, param];
414
}
415
}
416
}
417
};
418
419
function precompile(input, options, env) {
420
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
421
throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
422
}
423
424
options = options || {};
425
if (!('data' in options)) {
426
options.data = true;
427
}
428
if (options.compat) {
429
options.useDepths = true;
430
}
431
432
var ast = env.parse(input, options);
433
var environment = new env.Compiler().compile(ast, options);
434
return new env.JavaScriptCompiler().compile(environment, options);
435
}
436
437
__exports__.precompile = precompile;function compile(input, options, env) {
438
if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
439
throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
440
}
441
442
options = options || {};
443
444
if (!('data' in options)) {
445
options.data = true;
446
}
447
if (options.compat) {
448
options.useDepths = true;
449
}
450
451
var compiled;
452
453
function compileInput() {
454
var ast = env.parse(input, options);
455
var environment = new env.Compiler().compile(ast, options);
456
var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
457
return env.template(templateSpec);
458
}
459
460
// Template is only compiled on first use and cached after that point.
461
var ret = function(context, options) {
462
if (!compiled) {
463
compiled = compileInput();
464
}
465
return compiled.call(this, context, options);
466
};
467
ret._setup = function(options) {
468
if (!compiled) {
469
compiled = compileInput();
470
}
471
return compiled._setup(options);
472
};
473
ret._child = function(i, data, blockParams, depths) {
474
if (!compiled) {
475
compiled = compileInput();
476
}
477
return compiled._child(i, data, blockParams, depths);
478
};
479
return ret;
480
}
481
482
__exports__.compile = compile;function argEquals(a, b) {
483
if (a === b) {
484
return true;
485
}
486
487
if (isArray(a) && isArray(b) && a.length === b.length) {
488
for (var i = 0; i < a.length; i++) {
489
if (!argEquals(a[i], b[i])) {
490
return false;
491
}
492
}
493
return true;
494
}
495
}
496
497
function transformLiteralToPath(sexpr) {
498
if (!sexpr.path.parts) {
499
var literal = sexpr.path;
500
// Casting to string here to make false and 0 literal values play nicely with the rest
501
// of the system.
502
sexpr.path = new AST.PathExpression(false, 0, [literal.original+''], literal.original+'', literal.log);
503
}
504
}
505
});
506