handlebars 0.3.2 → 0.4.0
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.
- data/lib/handlebars/context.rb +3 -5
- data/lib/handlebars/version.rb +1 -1
- data/spec/handlebars_spec.rb +3 -3
- data/vendor/handlebars/.gitignore +3 -1
- data/vendor/handlebars/.jshintrc +2 -0
- data/vendor/handlebars/.npmignore +0 -1
- data/vendor/handlebars/.rspec +1 -0
- data/vendor/handlebars/Gemfile +1 -1
- data/vendor/handlebars/LICENSE +0 -1
- data/vendor/handlebars/README.markdown +8 -6
- data/vendor/handlebars/Rakefile +14 -21
- data/vendor/handlebars/bench/benchwarmer.js +1 -1
- data/vendor/handlebars/bench/handlebars.js +43 -34
- data/vendor/handlebars/bin/handlebars +56 -2
- data/vendor/handlebars/dist/handlebars.js +2201 -0
- data/vendor/handlebars/dist/handlebars.runtime.js +321 -0
- data/vendor/handlebars/lib/handlebars/base.js +68 -15
- data/vendor/handlebars/lib/handlebars/compiler/ast.js +50 -20
- data/vendor/handlebars/lib/handlebars/compiler/base.js +7 -13
- data/vendor/handlebars/lib/handlebars/compiler/compiler.js +758 -299
- data/vendor/handlebars/lib/handlebars/compiler/printer.js +24 -30
- data/vendor/handlebars/lib/handlebars/runtime.js +23 -3
- data/vendor/handlebars/lib/handlebars/utils.js +9 -10
- data/vendor/handlebars/package.json +15 -5
- data/vendor/handlebars/spec/acceptance_spec.rb +5 -5
- data/vendor/handlebars/spec/parser_spec.rb +201 -32
- data/vendor/handlebars/spec/qunit_spec.js +462 -159
- data/vendor/handlebars/spec/spec_helper.rb +7 -7
- data/vendor/handlebars/spec/tokenizer_spec.rb +55 -8
- data/vendor/handlebars/src/handlebars.l +14 -3
- data/vendor/handlebars/src/handlebars.yy +15 -5
- data/vendor/handlebars/src/parser-prefix.js +1 -0
- data/vendor/handlebars/src/parser-suffix.js +4 -0
- metadata +8 -3
@@ -1,27 +1,21 @@
|
|
1
|
-
var handlebars = require("./parser")
|
1
|
+
var handlebars = require("./parser");
|
2
2
|
var Handlebars = require("../base");
|
3
3
|
|
4
4
|
// BEGIN(BROWSER)
|
5
5
|
Handlebars.Parser = handlebars;
|
6
6
|
|
7
|
-
Handlebars.parse = function(
|
7
|
+
Handlebars.parse = function(input) {
|
8
|
+
|
9
|
+
// Just return if an already-compile AST was passed in.
|
10
|
+
if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
|
11
|
+
|
8
12
|
Handlebars.Parser.yy = Handlebars.AST;
|
9
|
-
return Handlebars.Parser.parse(
|
13
|
+
return Handlebars.Parser.parse(input);
|
10
14
|
};
|
11
15
|
|
12
16
|
Handlebars.print = function(ast) {
|
13
17
|
return new Handlebars.PrintVisitor().accept(ast);
|
14
18
|
};
|
15
|
-
|
16
|
-
Handlebars.logger = {
|
17
|
-
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
18
|
-
|
19
|
-
// override in the host environment
|
20
|
-
log: function(level, str) {}
|
21
|
-
};
|
22
|
-
|
23
|
-
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
|
24
|
-
|
25
19
|
// END(BROWSER)
|
26
20
|
|
27
21
|
module.exports = Handlebars;
|
@@ -7,89 +7,57 @@ Handlebars.Compiler = function() {};
|
|
7
7
|
Handlebars.JavaScriptCompiler = function() {};
|
8
8
|
|
9
9
|
(function(Compiler, JavaScriptCompiler) {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
lookup: 4,
|
15
|
-
append: 5,
|
16
|
-
invokeMustache: 6,
|
17
|
-
appendEscaped: 7,
|
18
|
-
pushString: 8,
|
19
|
-
truthyOrFallback: 9,
|
20
|
-
functionOrFallback: 10,
|
21
|
-
invokeProgram: 11,
|
22
|
-
invokePartial: 12,
|
23
|
-
push: 13,
|
24
|
-
assignToHash: 15,
|
25
|
-
pushStringParam: 16
|
26
|
-
};
|
27
|
-
|
28
|
-
Compiler.MULTI_PARAM_OPCODES = {
|
29
|
-
appendContent: 1,
|
30
|
-
getContext: 1,
|
31
|
-
lookupWithHelpers: 2,
|
32
|
-
lookup: 1,
|
33
|
-
invokeMustache: 3,
|
34
|
-
pushString: 1,
|
35
|
-
truthyOrFallback: 1,
|
36
|
-
functionOrFallback: 1,
|
37
|
-
invokeProgram: 3,
|
38
|
-
invokePartial: 1,
|
39
|
-
push: 1,
|
40
|
-
assignToHash: 1,
|
41
|
-
pushStringParam: 1
|
42
|
-
};
|
43
|
-
|
44
|
-
Compiler.DISASSEMBLE_MAP = {};
|
45
|
-
|
46
|
-
for(var prop in Compiler.OPCODE_MAP) {
|
47
|
-
var value = Compiler.OPCODE_MAP[prop];
|
48
|
-
Compiler.DISASSEMBLE_MAP[value] = prop;
|
49
|
-
}
|
50
|
-
|
51
|
-
Compiler.multiParamSize = function(code) {
|
52
|
-
return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
|
53
|
-
};
|
10
|
+
// the foundHelper register will disambiguate helper lookup from finding a
|
11
|
+
// function in a context. This is necessary for mustache compatibility, which
|
12
|
+
// requires that context functions in blocks are evaluated by blockHelperMissing,
|
13
|
+
// and then proceed as if the resulting value was provided to blockHelperMissing.
|
54
14
|
|
55
15
|
Compiler.prototype = {
|
56
16
|
compiler: Compiler,
|
57
17
|
|
58
18
|
disassemble: function() {
|
59
|
-
var opcodes = this.opcodes, opcode,
|
60
|
-
var out = [], str, name, value;
|
19
|
+
var opcodes = this.opcodes, opcode, out = [], params, param;
|
61
20
|
|
62
|
-
for(var i=0, l=opcodes.length; i<l; i++) {
|
21
|
+
for (var i=0, l=opcodes.length; i<l; i++) {
|
63
22
|
opcode = opcodes[i];
|
64
23
|
|
65
|
-
if(opcode === 'DECLARE') {
|
66
|
-
name =
|
67
|
-
value = opcodes[++i];
|
68
|
-
out.push("DECLARE " + name + " = " + value);
|
24
|
+
if (opcode.opcode === 'DECLARE') {
|
25
|
+
out.push("DECLARE " + opcode.name + "=" + opcode.value);
|
69
26
|
} else {
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
for(var j=0; j<extraParams; j++) {
|
76
|
-
nextCode = opcodes[++i];
|
77
|
-
|
78
|
-
if(typeof nextCode === "string") {
|
79
|
-
nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
|
27
|
+
params = [];
|
28
|
+
for (var j=0; j<opcode.args.length; j++) {
|
29
|
+
param = opcode.args[j];
|
30
|
+
if (typeof param === "string") {
|
31
|
+
param = "\"" + param.replace("\n", "\\n") + "\"";
|
80
32
|
}
|
81
|
-
|
82
|
-
codes.push(nextCode);
|
33
|
+
params.push(param);
|
83
34
|
}
|
84
|
-
|
85
|
-
str = str + " " + codes.join(" ");
|
86
|
-
|
87
|
-
out.push(str);
|
35
|
+
out.push(opcode.opcode + " " + params.join(" "));
|
88
36
|
}
|
89
37
|
}
|
90
38
|
|
91
39
|
return out.join("\n");
|
92
40
|
},
|
41
|
+
equals: function(other) {
|
42
|
+
var len = this.opcodes.length;
|
43
|
+
if (other.opcodes.length !== len) {
|
44
|
+
return false;
|
45
|
+
}
|
46
|
+
|
47
|
+
for (var i = 0; i < len; i++) {
|
48
|
+
var opcode = this.opcodes[i],
|
49
|
+
otherOpcode = other.opcodes[i];
|
50
|
+
if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
|
51
|
+
return false;
|
52
|
+
}
|
53
|
+
for (var j = 0; j < opcode.args.length; j++) {
|
54
|
+
if (opcode.args[j] !== otherOpcode.args[j]) {
|
55
|
+
return false;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
return true;
|
60
|
+
},
|
93
61
|
|
94
62
|
guid: 0,
|
95
63
|
|
@@ -158,51 +126,67 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
158
126
|
},
|
159
127
|
|
160
128
|
block: function(block) {
|
161
|
-
var mustache = block.mustache
|
162
|
-
|
163
|
-
|
164
|
-
var params = this.setupStackForMustache(mustache);
|
129
|
+
var mustache = block.mustache,
|
130
|
+
program = block.program,
|
131
|
+
inverse = block.inverse;
|
165
132
|
|
166
|
-
|
167
|
-
|
168
|
-
if(block.program.inverse) {
|
169
|
-
inverseGuid = this.compileProgram(block.program.inverse);
|
170
|
-
this.declare('inverse', inverseGuid);
|
133
|
+
if (program) {
|
134
|
+
program = this.compileProgram(program);
|
171
135
|
}
|
172
136
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
},
|
137
|
+
if (inverse) {
|
138
|
+
inverse = this.compileProgram(inverse);
|
139
|
+
}
|
177
140
|
|
178
|
-
|
179
|
-
var params = this.setupStackForMustache(block.mustache);
|
141
|
+
var type = this.classifyMustache(mustache);
|
180
142
|
|
181
|
-
|
143
|
+
if (type === "helper") {
|
144
|
+
this.helperMustache(mustache, program, inverse);
|
145
|
+
} else if (type === "simple") {
|
146
|
+
this.simpleMustache(mustache);
|
182
147
|
|
183
|
-
|
148
|
+
// now that the simple mustache is resolved, we need to
|
149
|
+
// evaluate it by executing `blockHelperMissing`
|
150
|
+
this.opcode('pushProgram', program);
|
151
|
+
this.opcode('pushProgram', inverse);
|
152
|
+
this.opcode('emptyHash');
|
153
|
+
this.opcode('blockValue');
|
154
|
+
} else {
|
155
|
+
this.ambiguousMustache(mustache, program, inverse);
|
156
|
+
|
157
|
+
// now that the simple mustache is resolved, we need to
|
158
|
+
// evaluate it by executing `blockHelperMissing`
|
159
|
+
this.opcode('pushProgram', program);
|
160
|
+
this.opcode('pushProgram', inverse);
|
161
|
+
this.opcode('emptyHash');
|
162
|
+
this.opcode('ambiguousBlockValue');
|
163
|
+
}
|
184
164
|
|
185
|
-
this.opcode('invokeProgram', null, params.length, !!block.mustache.hash);
|
186
|
-
this.declare('inverse', null);
|
187
165
|
this.opcode('append');
|
188
166
|
},
|
189
167
|
|
190
168
|
hash: function(hash) {
|
191
169
|
var pairs = hash.pairs, pair, val;
|
192
170
|
|
193
|
-
this.opcode('
|
171
|
+
this.opcode('pushHash');
|
194
172
|
|
195
173
|
for(var i=0, l=pairs.length; i<l; i++) {
|
196
174
|
pair = pairs[i];
|
197
175
|
val = pair[1];
|
198
176
|
|
199
|
-
this.
|
177
|
+
if (this.options.stringParams) {
|
178
|
+
this.opcode('pushStringParam', val.stringModeValue, val.type);
|
179
|
+
} else {
|
180
|
+
this.accept(val);
|
181
|
+
}
|
182
|
+
|
200
183
|
this.opcode('assignToHash', pair[0]);
|
201
184
|
}
|
185
|
+
this.opcode('popHash');
|
202
186
|
},
|
203
187
|
|
204
188
|
partial: function(partial) {
|
205
|
-
var
|
189
|
+
var partialName = partial.partialName;
|
206
190
|
this.usePartial = true;
|
207
191
|
|
208
192
|
if(partial.context) {
|
@@ -211,7 +195,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
211
195
|
this.opcode('push', 'depth0');
|
212
196
|
}
|
213
197
|
|
214
|
-
this.opcode('invokePartial',
|
198
|
+
this.opcode('invokePartial', partialName.name);
|
215
199
|
this.opcode('append');
|
216
200
|
},
|
217
201
|
|
@@ -220,44 +204,142 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
220
204
|
},
|
221
205
|
|
222
206
|
mustache: function(mustache) {
|
223
|
-
var
|
207
|
+
var options = this.options;
|
208
|
+
var type = this.classifyMustache(mustache);
|
224
209
|
|
225
|
-
|
210
|
+
if (type === "simple") {
|
211
|
+
this.simpleMustache(mustache);
|
212
|
+
} else if (type === "helper") {
|
213
|
+
this.helperMustache(mustache);
|
214
|
+
} else {
|
215
|
+
this.ambiguousMustache(mustache);
|
216
|
+
}
|
226
217
|
|
227
|
-
if(mustache.escaped && !
|
218
|
+
if(mustache.escaped && !options.noEscape) {
|
228
219
|
this.opcode('appendEscaped');
|
229
220
|
} else {
|
230
221
|
this.opcode('append');
|
231
222
|
}
|
232
223
|
},
|
233
224
|
|
225
|
+
ambiguousMustache: function(mustache, program, inverse) {
|
226
|
+
var id = mustache.id,
|
227
|
+
name = id.parts[0],
|
228
|
+
isBlock = program != null || inverse != null;
|
229
|
+
|
230
|
+
this.opcode('getContext', id.depth);
|
231
|
+
|
232
|
+
this.opcode('pushProgram', program);
|
233
|
+
this.opcode('pushProgram', inverse);
|
234
|
+
|
235
|
+
this.opcode('invokeAmbiguous', name, isBlock);
|
236
|
+
},
|
237
|
+
|
238
|
+
simpleMustache: function(mustache) {
|
239
|
+
var id = mustache.id;
|
240
|
+
|
241
|
+
if (id.type === 'DATA') {
|
242
|
+
this.DATA(id);
|
243
|
+
} else if (id.parts.length) {
|
244
|
+
this.ID(id);
|
245
|
+
} else {
|
246
|
+
// Simplified ID for `this`
|
247
|
+
this.addDepth(id.depth);
|
248
|
+
this.opcode('getContext', id.depth);
|
249
|
+
this.opcode('pushContext');
|
250
|
+
}
|
251
|
+
|
252
|
+
this.opcode('resolvePossibleLambda');
|
253
|
+
},
|
254
|
+
|
255
|
+
helperMustache: function(mustache, program, inverse) {
|
256
|
+
var params = this.setupFullMustacheParams(mustache, program, inverse),
|
257
|
+
name = mustache.id.parts[0];
|
258
|
+
|
259
|
+
if (this.options.knownHelpers[name]) {
|
260
|
+
this.opcode('invokeKnownHelper', params.length, name);
|
261
|
+
} else if (this.knownHelpersOnly) {
|
262
|
+
throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
|
263
|
+
} else {
|
264
|
+
this.opcode('invokeHelper', params.length, name);
|
265
|
+
}
|
266
|
+
},
|
267
|
+
|
234
268
|
ID: function(id) {
|
235
269
|
this.addDepth(id.depth);
|
236
|
-
|
237
270
|
this.opcode('getContext', id.depth);
|
238
271
|
|
239
|
-
|
272
|
+
var name = id.parts[0];
|
273
|
+
if (!name) {
|
274
|
+
this.opcode('pushContext');
|
275
|
+
} else {
|
276
|
+
this.opcode('lookupOnContext', id.parts[0]);
|
277
|
+
}
|
240
278
|
|
241
279
|
for(var i=1, l=id.parts.length; i<l; i++) {
|
242
280
|
this.opcode('lookup', id.parts[i]);
|
243
281
|
}
|
244
282
|
},
|
245
283
|
|
284
|
+
DATA: function(data) {
|
285
|
+
this.options.data = true;
|
286
|
+
this.opcode('lookupData', data.id);
|
287
|
+
},
|
288
|
+
|
246
289
|
STRING: function(string) {
|
247
290
|
this.opcode('pushString', string.string);
|
248
291
|
},
|
249
292
|
|
250
293
|
INTEGER: function(integer) {
|
251
|
-
this.opcode('
|
294
|
+
this.opcode('pushLiteral', integer.integer);
|
252
295
|
},
|
253
296
|
|
254
297
|
BOOLEAN: function(bool) {
|
255
|
-
this.opcode('
|
298
|
+
this.opcode('pushLiteral', bool.bool);
|
256
299
|
},
|
257
300
|
|
258
301
|
comment: function() {},
|
259
302
|
|
260
303
|
// HELPERS
|
304
|
+
opcode: function(name) {
|
305
|
+
this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
|
306
|
+
},
|
307
|
+
|
308
|
+
declare: function(name, value) {
|
309
|
+
this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
|
310
|
+
},
|
311
|
+
|
312
|
+
addDepth: function(depth) {
|
313
|
+
if(isNaN(depth)) { throw new Error("EWOT"); }
|
314
|
+
if(depth === 0) { return; }
|
315
|
+
|
316
|
+
if(!this.depths[depth]) {
|
317
|
+
this.depths[depth] = true;
|
318
|
+
this.depths.list.push(depth);
|
319
|
+
}
|
320
|
+
},
|
321
|
+
|
322
|
+
classifyMustache: function(mustache) {
|
323
|
+
var isHelper = mustache.isHelper;
|
324
|
+
var isEligible = mustache.eligibleHelper;
|
325
|
+
var options = this.options;
|
326
|
+
|
327
|
+
// if ambiguous, we can possibly resolve the ambiguity now
|
328
|
+
if (isEligible && !isHelper) {
|
329
|
+
var name = mustache.id.parts[0];
|
330
|
+
|
331
|
+
if (options.knownHelpers[name]) {
|
332
|
+
isHelper = true;
|
333
|
+
} else if (options.knownHelpersOnly) {
|
334
|
+
isEligible = false;
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
338
|
+
if (isHelper) { return "helper"; }
|
339
|
+
else if (isEligible) { return "ambiguous"; }
|
340
|
+
else { return "simple"; }
|
341
|
+
},
|
342
|
+
|
261
343
|
pushParams: function(params) {
|
262
344
|
var i = params.length, param;
|
263
345
|
|
@@ -270,54 +352,52 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
270
352
|
}
|
271
353
|
|
272
354
|
this.opcode('getContext', param.depth || 0);
|
273
|
-
this.opcode('pushStringParam', param.
|
355
|
+
this.opcode('pushStringParam', param.stringModeValue, param.type);
|
274
356
|
} else {
|
275
357
|
this[param.type](param);
|
276
358
|
}
|
277
359
|
}
|
278
360
|
},
|
279
361
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
if(val2 !== undefined) { this.opcodes.push(val2); }
|
284
|
-
if(val3 !== undefined) { this.opcodes.push(val3); }
|
285
|
-
},
|
286
|
-
|
287
|
-
declare: function(name, value) {
|
288
|
-
this.opcodes.push('DECLARE');
|
289
|
-
this.opcodes.push(name);
|
290
|
-
this.opcodes.push(value);
|
291
|
-
},
|
292
|
-
|
293
|
-
addDepth: function(depth) {
|
294
|
-
if(depth === 0) { return; }
|
362
|
+
setupMustacheParams: function(mustache) {
|
363
|
+
var params = mustache.params;
|
364
|
+
this.pushParams(params);
|
295
365
|
|
296
|
-
if(
|
297
|
-
this.
|
298
|
-
|
366
|
+
if(mustache.hash) {
|
367
|
+
this.hash(mustache.hash);
|
368
|
+
} else {
|
369
|
+
this.opcode('emptyHash');
|
299
370
|
}
|
371
|
+
|
372
|
+
return params;
|
300
373
|
},
|
301
374
|
|
302
|
-
|
375
|
+
// this will replace setupMustacheParams when we're done
|
376
|
+
setupFullMustacheParams: function(mustache, program, inverse) {
|
303
377
|
var params = mustache.params;
|
304
|
-
|
305
378
|
this.pushParams(params);
|
306
379
|
|
380
|
+
this.opcode('pushProgram', program);
|
381
|
+
this.opcode('pushProgram', inverse);
|
382
|
+
|
307
383
|
if(mustache.hash) {
|
308
384
|
this.hash(mustache.hash);
|
385
|
+
} else {
|
386
|
+
this.opcode('emptyHash');
|
309
387
|
}
|
310
388
|
|
311
|
-
this.ID(mustache.id);
|
312
|
-
|
313
389
|
return params;
|
314
390
|
}
|
315
391
|
};
|
316
392
|
|
393
|
+
var Literal = function(value) {
|
394
|
+
this.value = value;
|
395
|
+
};
|
396
|
+
|
317
397
|
JavaScriptCompiler.prototype = {
|
318
398
|
// PUBLIC API: You can override these methods in a subclass to provide
|
319
399
|
// alternative compiled forms for name lookup and buffering semantics
|
320
|
-
nameLookup: function(parent, name, type) {
|
400
|
+
nameLookup: function(parent, name /* , type*/) {
|
321
401
|
if (/^[0-9]+$/.test(name)) {
|
322
402
|
return parent + "[" + name + "]";
|
323
403
|
} else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
@@ -332,7 +412,11 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
332
412
|
if (this.environment.isSimple) {
|
333
413
|
return "return " + string + ";";
|
334
414
|
} else {
|
335
|
-
return
|
415
|
+
return {
|
416
|
+
appendToBuffer: true,
|
417
|
+
content: string,
|
418
|
+
toString: function() { return "buffer += " + string + ";"; }
|
419
|
+
};
|
336
420
|
}
|
337
421
|
},
|
338
422
|
|
@@ -347,18 +431,23 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
347
431
|
this.environment = environment;
|
348
432
|
this.options = options || {};
|
349
433
|
|
434
|
+
Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n");
|
435
|
+
|
350
436
|
this.name = this.environment.name;
|
351
437
|
this.isChild = !!context;
|
352
438
|
this.context = context || {
|
353
439
|
programs: [],
|
354
|
-
|
355
|
-
|
440
|
+
environments: [],
|
441
|
+
aliases: { }
|
356
442
|
};
|
357
443
|
|
358
444
|
this.preamble();
|
359
445
|
|
360
446
|
this.stackSlot = 0;
|
361
447
|
this.stackVars = [];
|
448
|
+
this.registers = { list: [] };
|
449
|
+
this.compileStack = [];
|
450
|
+
this.inlineStack = [];
|
362
451
|
|
363
452
|
this.compileChildren(environment, options);
|
364
453
|
|
@@ -367,59 +456,35 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
367
456
|
this.i = 0;
|
368
457
|
|
369
458
|
for(l=opcodes.length; this.i<l; this.i++) {
|
370
|
-
opcode = this.
|
459
|
+
opcode = opcodes[this.i];
|
371
460
|
|
372
|
-
if(opcode
|
373
|
-
this.
|
374
|
-
this[opcode[1]] = opcode[2];
|
461
|
+
if(opcode.opcode === 'DECLARE') {
|
462
|
+
this[opcode.name] = opcode.value;
|
375
463
|
} else {
|
376
|
-
this.
|
377
|
-
this[opcode[0]].apply(this, opcode[1]);
|
464
|
+
this[opcode.opcode].apply(this, opcode.args);
|
378
465
|
}
|
379
466
|
}
|
380
467
|
|
381
468
|
return this.createFunctionContext(asObject);
|
382
469
|
},
|
383
470
|
|
384
|
-
nextOpcode: function(
|
385
|
-
var opcodes = this.environment.opcodes
|
386
|
-
|
387
|
-
|
388
|
-
if(opcode === 'DECLARE') {
|
389
|
-
name = opcodes[this.i + 1];
|
390
|
-
val = opcodes[this.i + 2];
|
391
|
-
return ['DECLARE', name, val];
|
392
|
-
} else {
|
393
|
-
name = Compiler.DISASSEMBLE_MAP[opcode];
|
394
|
-
|
395
|
-
extraParams = Compiler.multiParamSize(opcode);
|
396
|
-
codes = [];
|
397
|
-
|
398
|
-
for(var j=0; j<extraParams; j++) {
|
399
|
-
codes.push(opcodes[this.i + j + 1 + n]);
|
400
|
-
}
|
401
|
-
|
402
|
-
return [name, codes];
|
403
|
-
}
|
471
|
+
nextOpcode: function() {
|
472
|
+
var opcodes = this.environment.opcodes;
|
473
|
+
return opcodes[this.i + 1];
|
404
474
|
},
|
405
475
|
|
406
|
-
eat: function(
|
407
|
-
this.i = this.i +
|
476
|
+
eat: function() {
|
477
|
+
this.i = this.i + 1;
|
408
478
|
},
|
409
479
|
|
410
480
|
preamble: function() {
|
411
481
|
var out = [];
|
412
482
|
|
413
|
-
// this register will disambiguate helper lookup from finding a function in
|
414
|
-
// a context. This is necessary for mustache compatibility, which requires
|
415
|
-
// that context functions in blocks are evaluated by blockHelperMissing, and
|
416
|
-
// then proceed as if the resulting value was provided to blockHelperMissing.
|
417
|
-
this.useRegister('foundHelper');
|
418
|
-
|
419
483
|
if (!this.isChild) {
|
420
484
|
var namespace = this.namespace;
|
421
485
|
var copies = "helpers = helpers || " + namespace + ".helpers;";
|
422
|
-
if(this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
|
486
|
+
if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
|
487
|
+
if (this.options.data) { copies = copies + " data = data || {};"; }
|
423
488
|
out.push(copies);
|
424
489
|
} else {
|
425
490
|
out.push('');
|
@@ -438,10 +503,7 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
438
503
|
},
|
439
504
|
|
440
505
|
createFunctionContext: function(asObject) {
|
441
|
-
var locals = this.stackVars;
|
442
|
-
if (!this.isChild) {
|
443
|
-
locals = locals.concat(this.context.registers.list);
|
444
|
-
}
|
506
|
+
var locals = this.stackVars.concat(this.registers.list);
|
445
507
|
|
446
508
|
if(locals.length > 0) {
|
447
509
|
this.source[1] = this.source[1] + ", " + locals.join(", ");
|
@@ -449,7 +511,6 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
449
511
|
|
450
512
|
// Generate minimizer alias mappings
|
451
513
|
if (!this.isChild) {
|
452
|
-
var aliases = [];
|
453
514
|
for (var alias in this.context.aliases) {
|
454
515
|
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
|
455
516
|
}
|
@@ -474,22 +535,114 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
474
535
|
params.push("depth" + this.environment.depths.list[i]);
|
475
536
|
}
|
476
537
|
|
538
|
+
// Perform a second pass over the output to merge content when possible
|
539
|
+
var source = this.mergeSource();
|
540
|
+
|
541
|
+
if (!this.isChild) {
|
542
|
+
var revision = Handlebars.COMPILER_REVISION,
|
543
|
+
versions = Handlebars.REVISION_CHANGES[revision];
|
544
|
+
source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
|
545
|
+
}
|
546
|
+
|
477
547
|
if (asObject) {
|
478
|
-
params.push(
|
548
|
+
params.push(source);
|
479
549
|
|
480
550
|
return Function.apply(this, params);
|
481
551
|
} else {
|
482
|
-
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' +
|
552
|
+
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
|
483
553
|
Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
|
484
554
|
return functionSource;
|
485
555
|
}
|
486
556
|
},
|
557
|
+
mergeSource: function() {
|
558
|
+
// WARN: We are not handling the case where buffer is still populated as the source should
|
559
|
+
// not have buffer append operations as their final action.
|
560
|
+
var source = '',
|
561
|
+
buffer;
|
562
|
+
for (var i = 0, len = this.source.length; i < len; i++) {
|
563
|
+
var line = this.source[i];
|
564
|
+
if (line.appendToBuffer) {
|
565
|
+
if (buffer) {
|
566
|
+
buffer = buffer + '\n + ' + line.content;
|
567
|
+
} else {
|
568
|
+
buffer = line.content;
|
569
|
+
}
|
570
|
+
} else {
|
571
|
+
if (buffer) {
|
572
|
+
source += 'buffer += ' + buffer + ';\n ';
|
573
|
+
buffer = undefined;
|
574
|
+
}
|
575
|
+
source += line + '\n ';
|
576
|
+
}
|
577
|
+
}
|
578
|
+
return source;
|
579
|
+
},
|
580
|
+
|
581
|
+
// [blockValue]
|
582
|
+
//
|
583
|
+
// On stack, before: hash, inverse, program, value
|
584
|
+
// On stack, after: return value of blockHelperMissing
|
585
|
+
//
|
586
|
+
// The purpose of this opcode is to take a block of the form
|
587
|
+
// `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
|
588
|
+
// replace it on the stack with the result of properly
|
589
|
+
// invoking blockHelperMissing.
|
590
|
+
blockValue: function() {
|
591
|
+
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
592
|
+
|
593
|
+
var params = ["depth0"];
|
594
|
+
this.setupParams(0, params);
|
595
|
+
|
596
|
+
this.replaceStack(function(current) {
|
597
|
+
params.splice(1, 0, current);
|
598
|
+
return "blockHelperMissing.call(" + params.join(", ") + ")";
|
599
|
+
});
|
600
|
+
},
|
487
601
|
|
602
|
+
// [ambiguousBlockValue]
|
603
|
+
//
|
604
|
+
// On stack, before: hash, inverse, program, value
|
605
|
+
// Compiler value, before: lastHelper=value of last found helper, if any
|
606
|
+
// On stack, after, if no lastHelper: same as [blockValue]
|
607
|
+
// On stack, after, if lastHelper: value
|
608
|
+
ambiguousBlockValue: function() {
|
609
|
+
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
610
|
+
|
611
|
+
var params = ["depth0"];
|
612
|
+
this.setupParams(0, params);
|
613
|
+
|
614
|
+
var current = this.topStack();
|
615
|
+
params.splice(1, 0, current);
|
616
|
+
|
617
|
+
// Use the options value generated from the invocation
|
618
|
+
params[params.length-1] = 'options';
|
619
|
+
|
620
|
+
this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
|
621
|
+
},
|
622
|
+
|
623
|
+
// [appendContent]
|
624
|
+
//
|
625
|
+
// On stack, before: ...
|
626
|
+
// On stack, after: ...
|
627
|
+
//
|
628
|
+
// Appends the string value of `content` to the current buffer
|
488
629
|
appendContent: function(content) {
|
489
630
|
this.source.push(this.appendToBuffer(this.quotedString(content)));
|
490
631
|
},
|
491
632
|
|
633
|
+
// [append]
|
634
|
+
//
|
635
|
+
// On stack, before: value, ...
|
636
|
+
// On stack, after: ...
|
637
|
+
//
|
638
|
+
// Coerces `value` to a String and appends it to the current buffer.
|
639
|
+
//
|
640
|
+
// If `value` is truthy, or 0, it is coerced into a string and appended
|
641
|
+
// Otherwise, the empty string is appended
|
492
642
|
append: function() {
|
643
|
+
// Force anything that is inlined onto the stack so we don't have duplication
|
644
|
+
// when we examine local
|
645
|
+
this.flushInline();
|
493
646
|
var local = this.popStack();
|
494
647
|
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
|
495
648
|
if (this.environment.isSimple) {
|
@@ -497,168 +650,279 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
497
650
|
}
|
498
651
|
},
|
499
652
|
|
653
|
+
// [appendEscaped]
|
654
|
+
//
|
655
|
+
// On stack, before: value, ...
|
656
|
+
// On stack, after: ...
|
657
|
+
//
|
658
|
+
// Escape `value` and append it to the buffer
|
500
659
|
appendEscaped: function() {
|
501
|
-
var opcode = this.nextOpcode(1), extra = "";
|
502
660
|
this.context.aliases.escapeExpression = 'this.escapeExpression';
|
503
661
|
|
504
|
-
|
505
|
-
extra = " + " + this.quotedString(opcode[1][0]);
|
506
|
-
this.eat(opcode);
|
507
|
-
}
|
508
|
-
|
509
|
-
this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
|
662
|
+
this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
|
510
663
|
},
|
511
664
|
|
665
|
+
// [getContext]
|
666
|
+
//
|
667
|
+
// On stack, before: ...
|
668
|
+
// On stack, after: ...
|
669
|
+
// Compiler value, after: lastContext=depth
|
670
|
+
//
|
671
|
+
// Set the value of the `lastContext` compiler value to the depth
|
512
672
|
getContext: function(depth) {
|
513
673
|
if(this.lastContext !== depth) {
|
514
674
|
this.lastContext = depth;
|
515
675
|
}
|
516
676
|
},
|
517
677
|
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
this.usingKnownHelper = true;
|
528
|
-
} else if (isScoped || this.options.knownHelpersOnly) {
|
529
|
-
toPush = topStack + " = " + this.nameLookup('depth' + this.lastContext, name, 'context');
|
530
|
-
} else {
|
531
|
-
this.register('foundHelper', this.nameLookup('helpers', name, 'helper'));
|
532
|
-
toPush = topStack + " = foundHelper || " + this.nameLookup('depth' + this.lastContext, name, 'context');
|
533
|
-
}
|
534
|
-
|
535
|
-
toPush += ';';
|
536
|
-
this.source.push(toPush);
|
537
|
-
} else {
|
538
|
-
this.pushStack('depth' + this.lastContext);
|
539
|
-
}
|
678
|
+
// [lookupOnContext]
|
679
|
+
//
|
680
|
+
// On stack, before: ...
|
681
|
+
// On stack, after: currentContext[name], ...
|
682
|
+
//
|
683
|
+
// Looks up the value of `name` on the current context and pushes
|
684
|
+
// it onto the stack.
|
685
|
+
lookupOnContext: function(name) {
|
686
|
+
this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
|
540
687
|
},
|
541
688
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
689
|
+
// [pushContext]
|
690
|
+
//
|
691
|
+
// On stack, before: ...
|
692
|
+
// On stack, after: currentContext, ...
|
693
|
+
//
|
694
|
+
// Pushes the value of the current context onto the stack.
|
695
|
+
pushContext: function() {
|
696
|
+
this.pushStackLiteral('depth' + this.lastContext);
|
546
697
|
},
|
547
698
|
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
this.
|
559
|
-
|
560
|
-
|
561
|
-
invokeMustache: function(paramSize, original, hasHash) {
|
562
|
-
this.populateParams(paramSize, this.quotedString(original), "{}", null, hasHash, function(nextStack, helperMissingString, id) {
|
563
|
-
if (!this.usingKnownHelper) {
|
564
|
-
this.context.aliases.helperMissing = 'helpers.helperMissing';
|
565
|
-
this.context.aliases.undef = 'void 0';
|
566
|
-
this.source.push("else if(" + id + "=== undef) { " + nextStack + " = helperMissing.call(" + helperMissingString + "); }");
|
567
|
-
if (nextStack !== id) {
|
568
|
-
this.source.push("else { " + nextStack + " = " + id + "; }");
|
569
|
-
}
|
570
|
-
}
|
699
|
+
// [resolvePossibleLambda]
|
700
|
+
//
|
701
|
+
// On stack, before: value, ...
|
702
|
+
// On stack, after: resolved value, ...
|
703
|
+
//
|
704
|
+
// If the `value` is a lambda, replace it on the stack by
|
705
|
+
// the return value of the lambda
|
706
|
+
resolvePossibleLambda: function() {
|
707
|
+
this.context.aliases.functionType = '"function"';
|
708
|
+
|
709
|
+
this.replaceStack(function(current) {
|
710
|
+
return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
|
571
711
|
});
|
572
712
|
},
|
573
713
|
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
714
|
+
// [lookup]
|
715
|
+
//
|
716
|
+
// On stack, before: value, ...
|
717
|
+
// On stack, after: value[name], ...
|
718
|
+
//
|
719
|
+
// Replace the value on the stack with the result of looking
|
720
|
+
// up `name` on `value`
|
721
|
+
lookup: function(name) {
|
722
|
+
this.replaceStack(function(current) {
|
723
|
+
return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
|
583
724
|
});
|
584
725
|
},
|
585
726
|
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
727
|
+
// [lookupData]
|
728
|
+
//
|
729
|
+
// On stack, before: ...
|
730
|
+
// On stack, after: data[id], ...
|
731
|
+
//
|
732
|
+
// Push the result of looking up `id` on the current data
|
733
|
+
lookupData: function(id) {
|
734
|
+
this.push(this.nameLookup('data', id, 'data'));
|
735
|
+
},
|
590
736
|
|
591
|
-
|
592
|
-
|
593
|
-
|
737
|
+
// [pushStringParam]
|
738
|
+
//
|
739
|
+
// On stack, before: ...
|
740
|
+
// On stack, after: string, currentContext, ...
|
741
|
+
//
|
742
|
+
// This opcode is designed for use in string mode, which
|
743
|
+
// provides the string value of a parameter along with its
|
744
|
+
// depth rather than resolving it immediately.
|
745
|
+
pushStringParam: function(string, type) {
|
746
|
+
this.pushStackLiteral('depth' + this.lastContext);
|
747
|
+
|
748
|
+
this.pushString(type);
|
749
|
+
|
750
|
+
if (typeof string === 'string') {
|
751
|
+
this.pushString(string);
|
594
752
|
} else {
|
595
|
-
|
753
|
+
this.pushStackLiteral(string);
|
596
754
|
}
|
755
|
+
},
|
756
|
+
|
757
|
+
emptyHash: function() {
|
758
|
+
this.pushStackLiteral('{}');
|
597
759
|
|
598
|
-
if (
|
599
|
-
|
600
|
-
this.source.push('tmp1.hash = ' + hash + ';');
|
760
|
+
if (this.options.stringParams) {
|
761
|
+
this.register('hashTypes', '{}');
|
601
762
|
}
|
763
|
+
},
|
764
|
+
pushHash: function() {
|
765
|
+
this.hash = {values: [], types: []};
|
766
|
+
},
|
767
|
+
popHash: function() {
|
768
|
+
var hash = this.hash;
|
769
|
+
this.hash = undefined;
|
602
770
|
|
603
|
-
if(this.options.stringParams) {
|
604
|
-
this.
|
771
|
+
if (this.options.stringParams) {
|
772
|
+
this.register('hashTypes', '{' + hash.types.join(',') + '}');
|
605
773
|
}
|
774
|
+
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
|
775
|
+
},
|
606
776
|
|
607
|
-
|
608
|
-
|
609
|
-
|
777
|
+
// [pushString]
|
778
|
+
//
|
779
|
+
// On stack, before: ...
|
780
|
+
// On stack, after: quotedString(string), ...
|
781
|
+
//
|
782
|
+
// Push a quoted version of `string` onto the stack
|
783
|
+
pushString: function(string) {
|
784
|
+
this.pushStackLiteral(this.quotedString(string));
|
785
|
+
},
|
610
786
|
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
787
|
+
// [push]
|
788
|
+
//
|
789
|
+
// On stack, before: ...
|
790
|
+
// On stack, after: expr, ...
|
791
|
+
//
|
792
|
+
// Push an expression onto the stack
|
793
|
+
push: function(expr) {
|
794
|
+
this.inlineStack.push(expr);
|
795
|
+
return expr;
|
796
|
+
},
|
615
797
|
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
798
|
+
// [pushLiteral]
|
799
|
+
//
|
800
|
+
// On stack, before: ...
|
801
|
+
// On stack, after: value, ...
|
802
|
+
//
|
803
|
+
// Pushes a value onto the stack. This operation prevents
|
804
|
+
// the compiler from creating a temporary variable to hold
|
805
|
+
// it.
|
806
|
+
pushLiteral: function(value) {
|
807
|
+
this.pushStackLiteral(value);
|
808
|
+
},
|
620
809
|
|
621
|
-
|
622
|
-
|
810
|
+
// [pushProgram]
|
811
|
+
//
|
812
|
+
// On stack, before: ...
|
813
|
+
// On stack, after: program(guid), ...
|
814
|
+
//
|
815
|
+
// Push a program expression onto the stack. This takes
|
816
|
+
// a compile-time guid and converts it into a runtime-accessible
|
817
|
+
// expression.
|
818
|
+
pushProgram: function(guid) {
|
819
|
+
if (guid != null) {
|
820
|
+
this.pushStackLiteral(this.programExpression(guid));
|
821
|
+
} else {
|
822
|
+
this.pushStackLiteral(null);
|
623
823
|
}
|
824
|
+
},
|
624
825
|
|
625
|
-
|
626
|
-
|
627
|
-
|
826
|
+
// [invokeHelper]
|
827
|
+
//
|
828
|
+
// On stack, before: hash, inverse, program, params..., ...
|
829
|
+
// On stack, after: result of helper invocation
|
830
|
+
//
|
831
|
+
// Pops off the helper's parameters, invokes the helper,
|
832
|
+
// and pushes the helper's return value onto the stack.
|
833
|
+
//
|
834
|
+
// If the helper is not found, `helperMissing` is called.
|
835
|
+
invokeHelper: function(paramSize, name) {
|
836
|
+
this.context.aliases.helperMissing = 'helpers.helperMissing';
|
837
|
+
|
838
|
+
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
|
839
|
+
|
840
|
+
this.push(helper.name);
|
841
|
+
this.replaceStack(function(name) {
|
842
|
+
return name + ' ? ' + name + '.call(' +
|
843
|
+
helper.callParams + ") " + ": helperMissing.call(" +
|
844
|
+
helper.helperMissingParams + ")";
|
845
|
+
});
|
628
846
|
},
|
629
847
|
|
630
|
-
|
631
|
-
|
632
|
-
|
848
|
+
// [invokeKnownHelper]
|
849
|
+
//
|
850
|
+
// On stack, before: hash, inverse, program, params..., ...
|
851
|
+
// On stack, after: result of helper invocation
|
852
|
+
//
|
853
|
+
// This operation is used when the helper is known to exist,
|
854
|
+
// so a `helperMissing` fallback is not required.
|
855
|
+
invokeKnownHelper: function(paramSize, name) {
|
856
|
+
var helper = this.setupHelper(paramSize, name);
|
857
|
+
this.push(helper.name + ".call(" + helper.callParams + ")");
|
858
|
+
},
|
633
859
|
|
860
|
+
// [invokeAmbiguous]
|
861
|
+
//
|
862
|
+
// On stack, before: hash, inverse, program, params..., ...
|
863
|
+
// On stack, after: result of disambiguation
|
864
|
+
//
|
865
|
+
// This operation is used when an expression like `{{foo}}`
|
866
|
+
// is provided, but we don't know at compile-time whether it
|
867
|
+
// is a helper or a path.
|
868
|
+
//
|
869
|
+
// This operation emits more code than the other options,
|
870
|
+
// and can be avoided by passing the `knownHelpers` and
|
871
|
+
// `knownHelpersOnly` flags at compile-time.
|
872
|
+
invokeAmbiguous: function(name, helperCall) {
|
873
|
+
this.context.aliases.functionType = '"function"';
|
874
|
+
|
875
|
+
this.pushStackLiteral('{}'); // Hash value
|
876
|
+
var helper = this.setupHelper(0, name, helperCall);
|
877
|
+
|
878
|
+
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
|
879
|
+
|
880
|
+
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
|
634
881
|
var nextStack = this.nextStack();
|
635
882
|
|
636
|
-
if (
|
637
|
-
|
638
|
-
} else {
|
639
|
-
this.context.aliases.functionType = '"function"';
|
640
|
-
var condition = program ? "foundHelper && " : "";
|
641
|
-
this.source.push("if(" + condition + "typeof " + id + " === functionType) { " + nextStack + " = " + id + ".call(" + paramString + "); }");
|
642
|
-
}
|
643
|
-
fn.call(this, nextStack, helperMissingString, id);
|
644
|
-
this.usingKnownHelper = false;
|
883
|
+
this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
|
884
|
+
this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
|
645
885
|
},
|
646
886
|
|
647
|
-
invokePartial
|
648
|
-
|
887
|
+
// [invokePartial]
|
888
|
+
//
|
889
|
+
// On stack, before: context, ...
|
890
|
+
// On stack after: result of partial invocation
|
891
|
+
//
|
892
|
+
// This operation pops off a context, invokes a partial with that context,
|
893
|
+
// and pushes the result of the invocation back.
|
894
|
+
invokePartial: function(name) {
|
895
|
+
var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
|
649
896
|
|
650
897
|
if (this.options.data) {
|
651
898
|
params.push("data");
|
652
899
|
}
|
653
900
|
|
654
|
-
this.
|
901
|
+
this.context.aliases.self = "this";
|
902
|
+
this.push("self.invokePartial(" + params.join(", ") + ")");
|
655
903
|
},
|
656
904
|
|
905
|
+
// [assignToHash]
|
906
|
+
//
|
907
|
+
// On stack, before: value, hash, ...
|
908
|
+
// On stack, after: hash, ...
|
909
|
+
//
|
910
|
+
// Pops a value and hash off the stack, assigns `hash[key] = value`
|
911
|
+
// and pushes the hash back onto the stack.
|
657
912
|
assignToHash: function(key) {
|
658
|
-
var value = this.popStack()
|
659
|
-
|
913
|
+
var value = this.popStack(),
|
914
|
+
type;
|
915
|
+
|
916
|
+
if (this.options.stringParams) {
|
917
|
+
type = this.popStack();
|
918
|
+
this.popStack();
|
919
|
+
}
|
660
920
|
|
661
|
-
|
921
|
+
var hash = this.hash;
|
922
|
+
if (type) {
|
923
|
+
hash.types.push("'" + key + "': " + type);
|
924
|
+
}
|
925
|
+
hash.values.push("'" + key + "': (" + value + ")");
|
662
926
|
},
|
663
927
|
|
664
928
|
// HELPERS
|
@@ -672,16 +936,36 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
672
936
|
child = children[i];
|
673
937
|
compiler = new this.compiler();
|
674
938
|
|
675
|
-
this.
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
939
|
+
var index = this.matchExistingProgram(child);
|
940
|
+
|
941
|
+
if (index == null) {
|
942
|
+
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
943
|
+
index = this.context.programs.length;
|
944
|
+
child.index = index;
|
945
|
+
child.name = 'program' + index;
|
946
|
+
this.context.programs[index] = compiler.compile(child, options, this.context);
|
947
|
+
this.context.environments[index] = child;
|
948
|
+
} else {
|
949
|
+
child.index = index;
|
950
|
+
child.name = 'program' + index;
|
951
|
+
}
|
952
|
+
}
|
953
|
+
},
|
954
|
+
matchExistingProgram: function(child) {
|
955
|
+
for (var i = 0, len = this.context.environments.length; i < len; i++) {
|
956
|
+
var environment = this.context.environments[i];
|
957
|
+
if (environment && environment.equals(child)) {
|
958
|
+
return i;
|
959
|
+
}
|
680
960
|
}
|
681
961
|
},
|
682
962
|
|
683
963
|
programExpression: function(guid) {
|
684
|
-
|
964
|
+
this.context.aliases.self = "this";
|
965
|
+
|
966
|
+
if(guid == null) {
|
967
|
+
return "self.noop";
|
968
|
+
}
|
685
969
|
|
686
970
|
var child = this.environment.children[guid],
|
687
971
|
depths = child.depths.list, depth;
|
@@ -709,29 +993,122 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
709
993
|
},
|
710
994
|
|
711
995
|
useRegister: function(name) {
|
712
|
-
if(!this.
|
713
|
-
this.
|
714
|
-
this.
|
996
|
+
if(!this.registers[name]) {
|
997
|
+
this.registers[name] = true;
|
998
|
+
this.registers.list.push(name);
|
715
999
|
}
|
716
1000
|
},
|
717
1001
|
|
1002
|
+
pushStackLiteral: function(item) {
|
1003
|
+
return this.push(new Literal(item));
|
1004
|
+
},
|
1005
|
+
|
718
1006
|
pushStack: function(item) {
|
719
|
-
this.
|
720
|
-
|
1007
|
+
this.flushInline();
|
1008
|
+
|
1009
|
+
var stack = this.incrStack();
|
1010
|
+
if (item) {
|
1011
|
+
this.source.push(stack + " = " + item + ";");
|
1012
|
+
}
|
1013
|
+
this.compileStack.push(stack);
|
1014
|
+
return stack;
|
1015
|
+
},
|
1016
|
+
|
1017
|
+
replaceStack: function(callback) {
|
1018
|
+
var prefix = '',
|
1019
|
+
inline = this.isInline(),
|
1020
|
+
stack;
|
1021
|
+
|
1022
|
+
// If we are currently inline then we want to merge the inline statement into the
|
1023
|
+
// replacement statement via ','
|
1024
|
+
if (inline) {
|
1025
|
+
var top = this.popStack(true);
|
1026
|
+
|
1027
|
+
if (top instanceof Literal) {
|
1028
|
+
// Literals do not need to be inlined
|
1029
|
+
stack = top.value;
|
1030
|
+
} else {
|
1031
|
+
// Get or create the current stack name for use by the inline
|
1032
|
+
var name = this.stackSlot ? this.topStackName() : this.incrStack();
|
1033
|
+
|
1034
|
+
prefix = '(' + this.push(name) + ' = ' + top + '),';
|
1035
|
+
stack = this.topStack();
|
1036
|
+
}
|
1037
|
+
} else {
|
1038
|
+
stack = this.topStack();
|
1039
|
+
}
|
1040
|
+
|
1041
|
+
var item = callback.call(this, stack);
|
1042
|
+
|
1043
|
+
if (inline) {
|
1044
|
+
if (this.inlineStack.length || this.compileStack.length) {
|
1045
|
+
this.popStack();
|
1046
|
+
}
|
1047
|
+
this.push('(' + prefix + item + ')');
|
1048
|
+
} else {
|
1049
|
+
// Prevent modification of the context depth variable. Through replaceStack
|
1050
|
+
if (!/^stack/.test(stack)) {
|
1051
|
+
stack = this.nextStack();
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
this.source.push(stack + " = (" + prefix + item + ");");
|
1055
|
+
}
|
1056
|
+
return stack;
|
721
1057
|
},
|
722
1058
|
|
723
1059
|
nextStack: function() {
|
1060
|
+
return this.pushStack();
|
1061
|
+
},
|
1062
|
+
|
1063
|
+
incrStack: function() {
|
724
1064
|
this.stackSlot++;
|
725
1065
|
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
|
1066
|
+
return this.topStackName();
|
1067
|
+
},
|
1068
|
+
topStackName: function() {
|
726
1069
|
return "stack" + this.stackSlot;
|
727
1070
|
},
|
1071
|
+
flushInline: function() {
|
1072
|
+
var inlineStack = this.inlineStack;
|
1073
|
+
if (inlineStack.length) {
|
1074
|
+
this.inlineStack = [];
|
1075
|
+
for (var i = 0, len = inlineStack.length; i < len; i++) {
|
1076
|
+
var entry = inlineStack[i];
|
1077
|
+
if (entry instanceof Literal) {
|
1078
|
+
this.compileStack.push(entry);
|
1079
|
+
} else {
|
1080
|
+
this.pushStack(entry);
|
1081
|
+
}
|
1082
|
+
}
|
1083
|
+
}
|
1084
|
+
},
|
1085
|
+
isInline: function() {
|
1086
|
+
return this.inlineStack.length;
|
1087
|
+
},
|
728
1088
|
|
729
|
-
popStack: function() {
|
730
|
-
|
1089
|
+
popStack: function(wrapped) {
|
1090
|
+
var inline = this.isInline(),
|
1091
|
+
item = (inline ? this.inlineStack : this.compileStack).pop();
|
1092
|
+
|
1093
|
+
if (!wrapped && (item instanceof Literal)) {
|
1094
|
+
return item.value;
|
1095
|
+
} else {
|
1096
|
+
if (!inline) {
|
1097
|
+
this.stackSlot--;
|
1098
|
+
}
|
1099
|
+
return item;
|
1100
|
+
}
|
731
1101
|
},
|
732
1102
|
|
733
|
-
topStack: function() {
|
734
|
-
|
1103
|
+
topStack: function(wrapped) {
|
1104
|
+
var stack = (this.isInline() ? this.inlineStack : this.compileStack),
|
1105
|
+
item = stack[stack.length - 1];
|
1106
|
+
|
1107
|
+
if (!wrapped && (item instanceof Literal)) {
|
1108
|
+
return item.value;
|
1109
|
+
} else {
|
1110
|
+
return item;
|
1111
|
+
}
|
735
1112
|
},
|
736
1113
|
|
737
1114
|
quotedString: function(str) {
|
@@ -740,6 +1117,76 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
740
1117
|
.replace(/"/g, '\\"')
|
741
1118
|
.replace(/\n/g, '\\n')
|
742
1119
|
.replace(/\r/g, '\\r') + '"';
|
1120
|
+
},
|
1121
|
+
|
1122
|
+
setupHelper: function(paramSize, name, missingParams) {
|
1123
|
+
var params = [];
|
1124
|
+
this.setupParams(paramSize, params, missingParams);
|
1125
|
+
var foundHelper = this.nameLookup('helpers', name, 'helper');
|
1126
|
+
|
1127
|
+
return {
|
1128
|
+
params: params,
|
1129
|
+
name: foundHelper,
|
1130
|
+
callParams: ["depth0"].concat(params).join(", "),
|
1131
|
+
helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
|
1132
|
+
};
|
1133
|
+
},
|
1134
|
+
|
1135
|
+
// the params and contexts arguments are passed in arrays
|
1136
|
+
// to fill in
|
1137
|
+
setupParams: function(paramSize, params, useRegister) {
|
1138
|
+
var options = [], contexts = [], types = [], param, inverse, program;
|
1139
|
+
|
1140
|
+
options.push("hash:" + this.popStack());
|
1141
|
+
|
1142
|
+
inverse = this.popStack();
|
1143
|
+
program = this.popStack();
|
1144
|
+
|
1145
|
+
// Avoid setting fn and inverse if neither are set. This allows
|
1146
|
+
// helpers to do a check for `if (options.fn)`
|
1147
|
+
if (program || inverse) {
|
1148
|
+
if (!program) {
|
1149
|
+
this.context.aliases.self = "this";
|
1150
|
+
program = "self.noop";
|
1151
|
+
}
|
1152
|
+
|
1153
|
+
if (!inverse) {
|
1154
|
+
this.context.aliases.self = "this";
|
1155
|
+
inverse = "self.noop";
|
1156
|
+
}
|
1157
|
+
|
1158
|
+
options.push("inverse:" + inverse);
|
1159
|
+
options.push("fn:" + program);
|
1160
|
+
}
|
1161
|
+
|
1162
|
+
for(var i=0; i<paramSize; i++) {
|
1163
|
+
param = this.popStack();
|
1164
|
+
params.push(param);
|
1165
|
+
|
1166
|
+
if(this.options.stringParams) {
|
1167
|
+
types.push(this.popStack());
|
1168
|
+
contexts.push(this.popStack());
|
1169
|
+
}
|
1170
|
+
}
|
1171
|
+
|
1172
|
+
if (this.options.stringParams) {
|
1173
|
+
options.push("contexts:[" + contexts.join(",") + "]");
|
1174
|
+
options.push("types:[" + types.join(",") + "]");
|
1175
|
+
options.push("hashTypes:hashTypes");
|
1176
|
+
}
|
1177
|
+
|
1178
|
+
if(this.options.data) {
|
1179
|
+
options.push("data:data");
|
1180
|
+
}
|
1181
|
+
|
1182
|
+
options = "{" + options.join(",") + "}";
|
1183
|
+
if (useRegister) {
|
1184
|
+
this.register('options', options);
|
1185
|
+
params.push('options');
|
1186
|
+
} else {
|
1187
|
+
params.push(options);
|
1188
|
+
}
|
1189
|
+
return params.join(", ");
|
743
1190
|
}
|
744
1191
|
};
|
745
1192
|
|
@@ -776,20 +1223,32 @@ Handlebars.JavaScriptCompiler = function() {};
|
|
776
1223
|
|
777
1224
|
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
|
778
1225
|
|
779
|
-
Handlebars.precompile = function(
|
780
|
-
|
1226
|
+
Handlebars.precompile = function(input, options) {
|
1227
|
+
if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
|
1228
|
+
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
|
1229
|
+
}
|
781
1230
|
|
782
|
-
|
1231
|
+
options = options || {};
|
1232
|
+
if (!('data' in options)) {
|
1233
|
+
options.data = true;
|
1234
|
+
}
|
1235
|
+
var ast = Handlebars.parse(input);
|
783
1236
|
var environment = new Handlebars.Compiler().compile(ast, options);
|
784
1237
|
return new Handlebars.JavaScriptCompiler().compile(environment, options);
|
785
1238
|
};
|
786
1239
|
|
787
|
-
Handlebars.compile = function(
|
788
|
-
|
1240
|
+
Handlebars.compile = function(input, options) {
|
1241
|
+
if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
|
1242
|
+
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
|
1243
|
+
}
|
789
1244
|
|
1245
|
+
options = options || {};
|
1246
|
+
if (!('data' in options)) {
|
1247
|
+
options.data = true;
|
1248
|
+
}
|
790
1249
|
var compiled;
|
791
1250
|
function compile() {
|
792
|
-
var ast = Handlebars.parse(
|
1251
|
+
var ast = Handlebars.parse(input);
|
793
1252
|
var environment = new Handlebars.Compiler().compile(ast, options);
|
794
1253
|
var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
|
795
1254
|
return Handlebars.template(templateSpec);
|