handlebars 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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);