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.
Files changed (34) hide show
  1. data/lib/handlebars/context.rb +3 -5
  2. data/lib/handlebars/version.rb +1 -1
  3. data/spec/handlebars_spec.rb +3 -3
  4. data/vendor/handlebars/.gitignore +3 -1
  5. data/vendor/handlebars/.jshintrc +2 -0
  6. data/vendor/handlebars/.npmignore +0 -1
  7. data/vendor/handlebars/.rspec +1 -0
  8. data/vendor/handlebars/Gemfile +1 -1
  9. data/vendor/handlebars/LICENSE +0 -1
  10. data/vendor/handlebars/README.markdown +8 -6
  11. data/vendor/handlebars/Rakefile +14 -21
  12. data/vendor/handlebars/bench/benchwarmer.js +1 -1
  13. data/vendor/handlebars/bench/handlebars.js +43 -34
  14. data/vendor/handlebars/bin/handlebars +56 -2
  15. data/vendor/handlebars/dist/handlebars.js +2201 -0
  16. data/vendor/handlebars/dist/handlebars.runtime.js +321 -0
  17. data/vendor/handlebars/lib/handlebars/base.js +68 -15
  18. data/vendor/handlebars/lib/handlebars/compiler/ast.js +50 -20
  19. data/vendor/handlebars/lib/handlebars/compiler/base.js +7 -13
  20. data/vendor/handlebars/lib/handlebars/compiler/compiler.js +758 -299
  21. data/vendor/handlebars/lib/handlebars/compiler/printer.js +24 -30
  22. data/vendor/handlebars/lib/handlebars/runtime.js +23 -3
  23. data/vendor/handlebars/lib/handlebars/utils.js +9 -10
  24. data/vendor/handlebars/package.json +15 -5
  25. data/vendor/handlebars/spec/acceptance_spec.rb +5 -5
  26. data/vendor/handlebars/spec/parser_spec.rb +201 -32
  27. data/vendor/handlebars/spec/qunit_spec.js +462 -159
  28. data/vendor/handlebars/spec/spec_helper.rb +7 -7
  29. data/vendor/handlebars/spec/tokenizer_spec.rb +55 -8
  30. data/vendor/handlebars/src/handlebars.l +14 -3
  31. data/vendor/handlebars/src/handlebars.yy +15 -5
  32. data/vendor/handlebars/src/parser-prefix.js +1 -0
  33. data/vendor/handlebars/src/parser-suffix.js +4 -0
  34. metadata +8 -3
@@ -1,27 +1,21 @@
1
- var handlebars = require("./parser").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(string) {
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(string);
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
- Compiler.OPCODE_MAP = {
11
- appendContent: 1,
12
- getContext: 2,
13
- lookupWithHelpers: 3,
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, nextCode;
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 = opcodes[++i];
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
- str = Compiler.DISASSEMBLE_MAP[opcode];
71
-
72
- var extraParams = Compiler.multiParamSize(opcode);
73
- var codes = [];
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
- var depth, child, inverse, inverseGuid;
163
-
164
- var params = this.setupStackForMustache(mustache);
129
+ var mustache = block.mustache,
130
+ program = block.program,
131
+ inverse = block.inverse;
165
132
 
166
- var programGuid = this.compileProgram(block.program);
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
- this.opcode('invokeProgram', programGuid, params.length, !!mustache.hash);
174
- this.declare('inverse', null);
175
- this.opcode('append');
176
- },
137
+ if (inverse) {
138
+ inverse = this.compileProgram(inverse);
139
+ }
177
140
 
178
- inverse: function(block) {
179
- var params = this.setupStackForMustache(block.mustache);
141
+ var type = this.classifyMustache(mustache);
180
142
 
181
- var programGuid = this.compileProgram(block.program);
143
+ if (type === "helper") {
144
+ this.helperMustache(mustache, program, inverse);
145
+ } else if (type === "simple") {
146
+ this.simpleMustache(mustache);
182
147
 
183
- this.declare('inverse', programGuid);
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('push', '{}');
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.accept(val);
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 id = partial.id;
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', id.original);
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 params = this.setupStackForMustache(mustache);
207
+ var options = this.options;
208
+ var type = this.classifyMustache(mustache);
224
209
 
225
- this.opcode('invokeMustache', params.length, mustache.id.original, !!mustache.hash);
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 && !this.options.noEscape) {
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
- this.opcode('lookupWithHelpers', id.parts[0] || null, id.isScoped || false);
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('push', integer.integer);
294
+ this.opcode('pushLiteral', integer.integer);
252
295
  },
253
296
 
254
297
  BOOLEAN: function(bool) {
255
- this.opcode('push', bool.bool);
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.string);
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
- opcode: function(name, val1, val2, val3) {
281
- this.opcodes.push(Compiler.OPCODE_MAP[name]);
282
- if(val1 !== undefined) { this.opcodes.push(val1); }
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(!this.depths[depth]) {
297
- this.depths[depth] = true;
298
- this.depths.list.push(depth);
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
- setupStackForMustache: function(mustache) {
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 "buffer += " + string + ";";
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
- aliases: { self: 'this' },
355
- registers: {list: []}
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.nextOpcode(0);
459
+ opcode = opcodes[this.i];
371
460
 
372
- if(opcode[0] === 'DECLARE') {
373
- this.i = this.i + 2;
374
- this[opcode[1]] = opcode[2];
461
+ if(opcode.opcode === 'DECLARE') {
462
+ this[opcode.name] = opcode.value;
375
463
  } else {
376
- this.i = this.i + opcode[1].length;
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(n) {
385
- var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
386
- var extraParams, codes;
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(opcode) {
407
- this.i = this.i + opcode.length;
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(this.source.join("\n "));
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 ' + this.source.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
- if(opcode[0] === 'appendContent') {
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
- lookupWithHelpers: function(name, isScoped) {
519
- if(name) {
520
- var topStack = this.nextStack();
521
-
522
- this.usingKnownHelper = false;
523
-
524
- var toPush;
525
- if (!isScoped && this.options.knownHelpers[name]) {
526
- toPush = topStack + " = " + this.nameLookup('helpers', name, 'helper');
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
- lookup: function(name) {
543
- var topStack = this.topStack();
544
- this.source.push(topStack + " = (" + topStack + " === null || " + topStack + " === undefined || " + topStack + " === false ? " +
545
- topStack + " : " + this.nameLookup(topStack, name, 'context') + ");");
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
- pushStringParam: function(string) {
549
- this.pushStack('depth' + this.lastContext);
550
- this.pushString(string);
551
- },
552
-
553
- pushString: function(string) {
554
- this.pushStack(this.quotedString(string));
555
- },
556
-
557
- push: function(name) {
558
- this.pushStack(name);
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
- invokeProgram: function(guid, paramSize, hasHash) {
575
- var inverse = this.programExpression(this.inverse);
576
- var mainProgram = this.programExpression(guid);
577
-
578
- this.populateParams(paramSize, null, mainProgram, inverse, hasHash, function(nextStack, helperMissingString, id) {
579
- if (!this.usingKnownHelper) {
580
- this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
581
- this.source.push("else { " + nextStack + " = blockHelperMissing.call(" + helperMissingString + "); }");
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
- populateParams: function(paramSize, helperId, program, inverse, hasHash, fn) {
587
- var needsRegister = hasHash || this.options.stringParams || inverse || this.options.data;
588
- var id = this.popStack(), nextStack;
589
- var params = [], param, stringParam, stringOptions;
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
- if (needsRegister) {
592
- this.register('tmp1', program);
593
- stringOptions = 'tmp1';
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
- stringOptions = '{ hash: {} }';
753
+ this.pushStackLiteral(string);
596
754
  }
755
+ },
756
+
757
+ emptyHash: function() {
758
+ this.pushStackLiteral('{}');
597
759
 
598
- if (needsRegister) {
599
- var hash = (hasHash ? this.popStack() : '{}');
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.source.push('tmp1.contexts = [];');
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
- for(var i=0; i<paramSize; i++) {
608
- param = this.popStack();
609
- params.push(param);
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
- if(this.options.stringParams) {
612
- this.source.push('tmp1.contexts.push(' + this.popStack() + ');');
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
- if(inverse) {
617
- this.source.push('tmp1.fn = tmp1;');
618
- this.source.push('tmp1.inverse = ' + inverse + ';');
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
- if(this.options.data) {
622
- this.source.push('tmp1.data = data;');
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
- params.push(stringOptions);
626
-
627
- this.populateCall(params, id, helperId || id, fn, program !== '{}');
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
- populateCall: function(params, id, helperId, fn, program) {
631
- var paramString = ["depth0"].concat(params).join(", ");
632
- var helperMissingString = ["depth0"].concat(helperId).concat(params).join(", ");
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 (this.usingKnownHelper) {
637
- this.source.push(nextStack + " = " + id + ".call(" + paramString + ");");
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: function(context) {
648
- var params = [this.nameLookup('partials', context, 'partial'), "'" + context + "'", this.popStack(), "helpers", "partials"];
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.pushStack("self.invokePartial(" + params.join(", ") + ");");
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
- var hash = this.topStack();
913
+ var value = this.popStack(),
914
+ type;
915
+
916
+ if (this.options.stringParams) {
917
+ type = this.popStack();
918
+ this.popStack();
919
+ }
660
920
 
661
- this.source.push(hash + "['" + key + "'] = " + value + ";");
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.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
676
- var index = this.context.programs.length;
677
- child.index = index;
678
- child.name = 'program' + index;
679
- this.context.programs[index] = compiler.compile(child, options, this.context);
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
- if(guid == null) { return "self.noop"; }
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.context.registers[name]) {
713
- this.context.registers[name] = true;
714
- this.context.registers.list.push(name);
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.source.push(this.nextStack() + " = " + item + ";");
720
- return "stack" + this.stackSlot;
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
- return "stack" + this.stackSlot--;
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
- return "stack" + this.stackSlot;
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(string, options) {
780
- options = options || {};
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
- var ast = Handlebars.parse(string);
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(string, options) {
788
- options = options || {};
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(string);
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);