barber 0.2.1 → 0.3.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.
@@ -1,3 +1,27 @@
1
+ /*
2
+
3
+ Copyright (C) 2011 by Yehuda Katz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
23
+ */
24
+
1
25
  // lib/handlebars/base.js
2
26
 
3
27
  /*jshint eqnull:true*/
@@ -5,7 +29,13 @@ this.Handlebars = {};
5
29
 
6
30
  (function(Handlebars) {
7
31
 
8
- Handlebars.VERSION = "1.0.rc.2";
32
+ Handlebars.VERSION = "1.0.0-rc.3";
33
+ Handlebars.COMPILER_REVISION = 2;
34
+
35
+ Handlebars.REVISION_CHANGES = {
36
+ 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
37
+ 2: '>= 1.0.0-rc.3'
38
+ };
9
39
 
10
40
  Handlebars.helpers = {};
11
41
  Handlebars.partials = {};
@@ -618,9 +648,13 @@ return new Parser;
618
648
  // lib/handlebars/compiler/base.js
619
649
  Handlebars.Parser = handlebars;
620
650
 
621
- Handlebars.parse = function(string) {
651
+ Handlebars.parse = function(input) {
652
+
653
+ // Just return if an already-compile AST was passed in.
654
+ if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
655
+
622
656
  Handlebars.Parser.yy = Handlebars.AST;
623
- return Handlebars.Parser.parse(string);
657
+ return Handlebars.Parser.parse(input);
624
658
  };
625
659
 
626
660
  Handlebars.print = function(ast) {
@@ -702,8 +736,11 @@ Handlebars.print = function(ast) {
702
736
  for(var i=0,l=parts.length; i<l; i++) {
703
737
  var part = parts[i];
704
738
 
705
- if(part === "..") { depth++; }
706
- else if(part === "." || part === "this") { this.isScoped = true; }
739
+ if (part === ".." || part === "." || part === "this") {
740
+ if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
741
+ else if (part === "..") { depth++; }
742
+ else { this.isScoped = true; }
743
+ }
707
744
  else { dig.push(part); }
708
745
  }
709
746
 
@@ -853,6 +890,26 @@ Handlebars.JavaScriptCompiler = function() {};
853
890
 
854
891
  return out.join("\n");
855
892
  },
893
+ equals: function(other) {
894
+ var len = this.opcodes.length;
895
+ if (other.opcodes.length !== len) {
896
+ return false;
897
+ }
898
+
899
+ for (var i = 0; i < len; i++) {
900
+ var opcode = this.opcodes[i],
901
+ otherOpcode = other.opcodes[i];
902
+ if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
903
+ return false;
904
+ }
905
+ for (var j = 0; j < opcode.args.length; j++) {
906
+ if (opcode.args[j] !== otherOpcode.args[j]) {
907
+ return false;
908
+ }
909
+ }
910
+ }
911
+ return true;
912
+ },
856
913
 
857
914
  guid: 0,
858
915
 
@@ -944,7 +1001,7 @@ Handlebars.JavaScriptCompiler = function() {};
944
1001
  // evaluate it by executing `blockHelperMissing`
945
1002
  this.opcode('pushProgram', program);
946
1003
  this.opcode('pushProgram', inverse);
947
- this.opcode('pushHash');
1004
+ this.opcode('emptyHash');
948
1005
  this.opcode('blockValue');
949
1006
  } else {
950
1007
  this.ambiguousMustache(mustache, program, inverse);
@@ -953,7 +1010,7 @@ Handlebars.JavaScriptCompiler = function() {};
953
1010
  // evaluate it by executing `blockHelperMissing`
954
1011
  this.opcode('pushProgram', program);
955
1012
  this.opcode('pushProgram', inverse);
956
- this.opcode('pushHash');
1013
+ this.opcode('emptyHash');
957
1014
  this.opcode('ambiguousBlockValue');
958
1015
  }
959
1016
 
@@ -977,6 +1034,7 @@ Handlebars.JavaScriptCompiler = function() {};
977
1034
 
978
1035
  this.opcode('assignToHash', pair[0]);
979
1036
  }
1037
+ this.opcode('popHash');
980
1038
  },
981
1039
 
982
1040
  partial: function(partial) {
@@ -1017,17 +1075,19 @@ Handlebars.JavaScriptCompiler = function() {};
1017
1075
  },
1018
1076
 
1019
1077
  ambiguousMustache: function(mustache, program, inverse) {
1020
- var id = mustache.id, name = id.parts[0];
1078
+ var id = mustache.id,
1079
+ name = id.parts[0],
1080
+ isBlock = program != null || inverse != null;
1021
1081
 
1022
1082
  this.opcode('getContext', id.depth);
1023
1083
 
1024
1084
  this.opcode('pushProgram', program);
1025
1085
  this.opcode('pushProgram', inverse);
1026
1086
 
1027
- this.opcode('invokeAmbiguous', name);
1087
+ this.opcode('invokeAmbiguous', name, isBlock);
1028
1088
  },
1029
1089
 
1030
- simpleMustache: function(mustache, program, inverse) {
1090
+ simpleMustache: function(mustache) {
1031
1091
  var id = mustache.id;
1032
1092
 
1033
1093
  if (id.type === 'DATA') {
@@ -1158,7 +1218,7 @@ Handlebars.JavaScriptCompiler = function() {};
1158
1218
  if(mustache.hash) {
1159
1219
  this.hash(mustache.hash);
1160
1220
  } else {
1161
- this.opcode('pushHash');
1221
+ this.opcode('emptyHash');
1162
1222
  }
1163
1223
 
1164
1224
  return params;
@@ -1175,7 +1235,7 @@ Handlebars.JavaScriptCompiler = function() {};
1175
1235
  if(mustache.hash) {
1176
1236
  this.hash(mustache.hash);
1177
1237
  } else {
1178
- this.opcode('pushHash');
1238
+ this.opcode('emptyHash');
1179
1239
  }
1180
1240
 
1181
1241
  return params;
@@ -1189,7 +1249,7 @@ Handlebars.JavaScriptCompiler = function() {};
1189
1249
  JavaScriptCompiler.prototype = {
1190
1250
  // PUBLIC API: You can override these methods in a subclass to provide
1191
1251
  // alternative compiled forms for name lookup and buffering semantics
1192
- nameLookup: function(parent, name, type) {
1252
+ nameLookup: function(parent, name /* , type*/) {
1193
1253
  if (/^[0-9]+$/.test(name)) {
1194
1254
  return parent + "[" + name + "]";
1195
1255
  } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
@@ -1204,7 +1264,11 @@ Handlebars.JavaScriptCompiler = function() {};
1204
1264
  if (this.environment.isSimple) {
1205
1265
  return "return " + string + ";";
1206
1266
  } else {
1207
- return "buffer += " + string + ";";
1267
+ return {
1268
+ appendToBuffer: true,
1269
+ content: string,
1270
+ toString: function() { return "buffer += " + string + ";"; }
1271
+ };
1208
1272
  }
1209
1273
  },
1210
1274
 
@@ -1225,6 +1289,7 @@ Handlebars.JavaScriptCompiler = function() {};
1225
1289
  this.isChild = !!context;
1226
1290
  this.context = context || {
1227
1291
  programs: [],
1292
+ environments: [],
1228
1293
  aliases: { }
1229
1294
  };
1230
1295
 
@@ -1234,6 +1299,7 @@ Handlebars.JavaScriptCompiler = function() {};
1234
1299
  this.stackVars = [];
1235
1300
  this.registers = { list: [] };
1236
1301
  this.compileStack = [];
1302
+ this.inlineStack = [];
1237
1303
 
1238
1304
  this.compileChildren(environment, options);
1239
1305
 
@@ -1255,11 +1321,11 @@ Handlebars.JavaScriptCompiler = function() {};
1255
1321
  },
1256
1322
 
1257
1323
  nextOpcode: function() {
1258
- var opcodes = this.environment.opcodes, opcode = opcodes[this.i + 1];
1324
+ var opcodes = this.environment.opcodes;
1259
1325
  return opcodes[this.i + 1];
1260
1326
  },
1261
1327
 
1262
- eat: function(opcode) {
1328
+ eat: function() {
1263
1329
  this.i = this.i + 1;
1264
1330
  },
1265
1331
 
@@ -1297,7 +1363,6 @@ Handlebars.JavaScriptCompiler = function() {};
1297
1363
 
1298
1364
  // Generate minimizer alias mappings
1299
1365
  if (!this.isChild) {
1300
- var aliases = [];
1301
1366
  for (var alias in this.context.aliases) {
1302
1367
  this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
1303
1368
  }
@@ -1322,16 +1387,48 @@ Handlebars.JavaScriptCompiler = function() {};
1322
1387
  params.push("depth" + this.environment.depths.list[i]);
1323
1388
  }
1324
1389
 
1390
+ // Perform a second pass over the output to merge content when possible
1391
+ var source = this.mergeSource();
1392
+
1393
+ if (!this.isChild) {
1394
+ var revision = Handlebars.COMPILER_REVISION,
1395
+ versions = Handlebars.REVISION_CHANGES[revision];
1396
+ source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
1397
+ }
1398
+
1325
1399
  if (asObject) {
1326
- params.push(this.source.join("\n "));
1400
+ params.push(source);
1327
1401
 
1328
1402
  return Function.apply(this, params);
1329
1403
  } else {
1330
- var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}';
1404
+ var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
1331
1405
  Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
1332
1406
  return functionSource;
1333
1407
  }
1334
1408
  },
1409
+ mergeSource: function() {
1410
+ // WARN: We are not handling the case where buffer is still populated as the source should
1411
+ // not have buffer append operations as their final action.
1412
+ var source = '',
1413
+ buffer;
1414
+ for (var i = 0, len = this.source.length; i < len; i++) {
1415
+ var line = this.source[i];
1416
+ if (line.appendToBuffer) {
1417
+ if (buffer) {
1418
+ buffer = buffer + '\n + ' + line.content;
1419
+ } else {
1420
+ buffer = line.content;
1421
+ }
1422
+ } else {
1423
+ if (buffer) {
1424
+ source += 'buffer += ' + buffer + ';\n ';
1425
+ buffer = undefined;
1426
+ }
1427
+ source += line + '\n ';
1428
+ }
1429
+ }
1430
+ return source;
1431
+ },
1335
1432
 
1336
1433
  // [blockValue]
1337
1434
  //
@@ -1369,6 +1466,9 @@ Handlebars.JavaScriptCompiler = function() {};
1369
1466
  var current = this.topStack();
1370
1467
  params.splice(1, 0, current);
1371
1468
 
1469
+ // Use the options value generated from the invocation
1470
+ params[params.length-1] = 'options';
1471
+
1372
1472
  this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
1373
1473
  },
1374
1474
 
@@ -1392,6 +1492,9 @@ Handlebars.JavaScriptCompiler = function() {};
1392
1492
  // If `value` is truthy, or 0, it is coerced into a string and appended
1393
1493
  // Otherwise, the empty string is appended
1394
1494
  append: function() {
1495
+ // Force anything that is inlined onto the stack so we don't have duplication
1496
+ // when we examine local
1497
+ this.flushInline();
1395
1498
  var local = this.popStack();
1396
1499
  this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
1397
1500
  if (this.environment.isSimple) {
@@ -1406,15 +1509,9 @@ Handlebars.JavaScriptCompiler = function() {};
1406
1509
  //
1407
1510
  // Escape `value` and append it to the buffer
1408
1511
  appendEscaped: function() {
1409
- var opcode = this.nextOpcode(), extra = "";
1410
1512
  this.context.aliases.escapeExpression = 'this.escapeExpression';
1411
1513
 
1412
- if(opcode && opcode.opcode === 'appendContent') {
1413
- extra = " + " + this.quotedString(opcode.args[0]);
1414
- this.eat(opcode);
1415
- }
1416
-
1417
- this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
1514
+ this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
1418
1515
  },
1419
1516
 
1420
1517
  // [getContext]
@@ -1438,7 +1535,7 @@ Handlebars.JavaScriptCompiler = function() {};
1438
1535
  // Looks up the value of `name` on the current context and pushes
1439
1536
  // it onto the stack.
1440
1537
  lookupOnContext: function(name) {
1441
- this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context'));
1538
+ this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
1442
1539
  },
1443
1540
 
1444
1541
  // [pushContext]
@@ -1486,7 +1583,7 @@ Handlebars.JavaScriptCompiler = function() {};
1486
1583
  //
1487
1584
  // Push the result of looking up `id` on the current data
1488
1585
  lookupData: function(id) {
1489
- this.pushStack(this.nameLookup('data', id, 'data'));
1586
+ this.push(this.nameLookup('data', id, 'data'));
1490
1587
  },
1491
1588
 
1492
1589
  // [pushStringParam]
@@ -1509,13 +1606,25 @@ Handlebars.JavaScriptCompiler = function() {};
1509
1606
  }
1510
1607
  },
1511
1608
 
1512
- pushHash: function() {
1513
- this.push('{}');
1609
+ emptyHash: function() {
1610
+ this.pushStackLiteral('{}');
1514
1611
 
1515
1612
  if (this.options.stringParams) {
1516
1613
  this.register('hashTypes', '{}');
1517
1614
  }
1518
1615
  },
1616
+ pushHash: function() {
1617
+ this.hash = {values: [], types: []};
1618
+ },
1619
+ popHash: function() {
1620
+ var hash = this.hash;
1621
+ this.hash = undefined;
1622
+
1623
+ if (this.options.stringParams) {
1624
+ this.register('hashTypes', '{' + hash.types.join(',') + '}');
1625
+ }
1626
+ this.push('{\n ' + hash.values.join(',\n ') + '\n }');
1627
+ },
1519
1628
 
1520
1629
  // [pushString]
1521
1630
  //
@@ -1534,7 +1643,8 @@ Handlebars.JavaScriptCompiler = function() {};
1534
1643
  //
1535
1644
  // Push an expression onto the stack
1536
1645
  push: function(expr) {
1537
- this.pushStack(expr);
1646
+ this.inlineStack.push(expr);
1647
+ return expr;
1538
1648
  },
1539
1649
 
1540
1650
  // [pushLiteral]
@@ -1577,12 +1687,14 @@ Handlebars.JavaScriptCompiler = function() {};
1577
1687
  invokeHelper: function(paramSize, name) {
1578
1688
  this.context.aliases.helperMissing = 'helpers.helperMissing';
1579
1689
 
1580
- var helper = this.lastHelper = this.setupHelper(paramSize, name);
1581
- this.register('foundHelper', helper.name);
1690
+ var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
1582
1691
 
1583
- this.pushStack("foundHelper ? foundHelper.call(" +
1584
- helper.callParams + ") " + ": helperMissing.call(" +
1585
- helper.helperMissingParams + ")");
1692
+ this.push(helper.name);
1693
+ this.replaceStack(function(name) {
1694
+ return name + ' ? ' + name + '.call(' +
1695
+ helper.callParams + ") " + ": helperMissing.call(" +
1696
+ helper.helperMissingParams + ")";
1697
+ });
1586
1698
  },
1587
1699
 
1588
1700
  // [invokeKnownHelper]
@@ -1594,7 +1706,7 @@ Handlebars.JavaScriptCompiler = function() {};
1594
1706
  // so a `helperMissing` fallback is not required.
1595
1707
  invokeKnownHelper: function(paramSize, name) {
1596
1708
  var helper = this.setupHelper(paramSize, name);
1597
- this.pushStack(helper.name + ".call(" + helper.callParams + ")");
1709
+ this.push(helper.name + ".call(" + helper.callParams + ")");
1598
1710
  },
1599
1711
 
1600
1712
  // [invokeAmbiguous]
@@ -1609,19 +1721,18 @@ Handlebars.JavaScriptCompiler = function() {};
1609
1721
  // This operation emits more code than the other options,
1610
1722
  // and can be avoided by passing the `knownHelpers` and
1611
1723
  // `knownHelpersOnly` flags at compile-time.
1612
- invokeAmbiguous: function(name) {
1724
+ invokeAmbiguous: function(name, helperCall) {
1613
1725
  this.context.aliases.functionType = '"function"';
1614
1726
 
1615
- this.pushStackLiteral('{}');
1616
- var helper = this.setupHelper(0, name);
1727
+ this.pushStackLiteral('{}'); // Hash value
1728
+ var helper = this.setupHelper(0, name, helperCall);
1617
1729
 
1618
1730
  var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
1619
- this.register('foundHelper', helperName);
1620
1731
 
1621
1732
  var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
1622
1733
  var nextStack = this.nextStack();
1623
1734
 
1624
- this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
1735
+ this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
1625
1736
  this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
1626
1737
  },
1627
1738
 
@@ -1640,7 +1751,7 @@ Handlebars.JavaScriptCompiler = function() {};
1640
1751
  }
1641
1752
 
1642
1753
  this.context.aliases.self = "this";
1643
- this.pushStack("self.invokePartial(" + params.join(", ") + ")");
1754
+ this.push("self.invokePartial(" + params.join(", ") + ")");
1644
1755
  },
1645
1756
 
1646
1757
  // [assignToHash]
@@ -1651,17 +1762,19 @@ Handlebars.JavaScriptCompiler = function() {};
1651
1762
  // Pops a value and hash off the stack, assigns `hash[key] = value`
1652
1763
  // and pushes the hash back onto the stack.
1653
1764
  assignToHash: function(key) {
1654
- var value = this.popStack();
1765
+ var value = this.popStack(),
1766
+ type;
1655
1767
 
1656
1768
  if (this.options.stringParams) {
1657
- var type = this.popStack();
1769
+ type = this.popStack();
1658
1770
  this.popStack();
1659
- this.source.push("hashTypes['" + key + "'] = " + type + ";");
1660
1771
  }
1661
1772
 
1662
- var hash = this.topStack();
1663
-
1664
- this.source.push(hash + "['" + key + "'] = " + value + ";");
1773
+ var hash = this.hash;
1774
+ if (type) {
1775
+ hash.types.push("'" + key + "': " + type);
1776
+ }
1777
+ hash.values.push("'" + key + "': (" + value + ")");
1665
1778
  },
1666
1779
 
1667
1780
  // HELPERS
@@ -1675,11 +1788,27 @@ Handlebars.JavaScriptCompiler = function() {};
1675
1788
  child = children[i];
1676
1789
  compiler = new this.compiler();
1677
1790
 
1678
- this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
1679
- var index = this.context.programs.length;
1680
- child.index = index;
1681
- child.name = 'program' + index;
1682
- this.context.programs[index] = compiler.compile(child, options, this.context);
1791
+ var index = this.matchExistingProgram(child);
1792
+
1793
+ if (index == null) {
1794
+ this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
1795
+ index = this.context.programs.length;
1796
+ child.index = index;
1797
+ child.name = 'program' + index;
1798
+ this.context.programs[index] = compiler.compile(child, options, this.context);
1799
+ this.context.environments[index] = child;
1800
+ } else {
1801
+ child.index = index;
1802
+ child.name = 'program' + index;
1803
+ }
1804
+ }
1805
+ },
1806
+ matchExistingProgram: function(child) {
1807
+ for (var i = 0, len = this.context.environments.length; i < len; i++) {
1808
+ var environment = this.context.environments[i];
1809
+ if (environment && environment.equals(child)) {
1810
+ return i;
1811
+ }
1683
1812
  }
1684
1813
  },
1685
1814
 
@@ -1723,57 +1852,111 @@ Handlebars.JavaScriptCompiler = function() {};
1723
1852
  },
1724
1853
 
1725
1854
  pushStackLiteral: function(item) {
1726
- this.compileStack.push(new Literal(item));
1727
- return item;
1855
+ return this.push(new Literal(item));
1728
1856
  },
1729
1857
 
1730
1858
  pushStack: function(item) {
1859
+ this.flushInline();
1860
+
1731
1861
  var stack = this.incrStack();
1732
- this.source.push(stack + " = " + item + ";");
1862
+ if (item) {
1863
+ this.source.push(stack + " = " + item + ";");
1864
+ }
1733
1865
  this.compileStack.push(stack);
1734
1866
  return stack;
1735
1867
  },
1736
1868
 
1737
1869
  replaceStack: function(callback) {
1738
- var stack = this.topStack(),
1739
- item = callback.call(this, stack);
1870
+ var prefix = '',
1871
+ inline = this.isInline(),
1872
+ stack;
1873
+
1874
+ // If we are currently inline then we want to merge the inline statement into the
1875
+ // replacement statement via ','
1876
+ if (inline) {
1877
+ var top = this.popStack(true);
1878
+
1879
+ if (top instanceof Literal) {
1880
+ // Literals do not need to be inlined
1881
+ stack = top.value;
1882
+ } else {
1883
+ // Get or create the current stack name for use by the inline
1884
+ var name = this.stackSlot ? this.topStackName() : this.incrStack();
1740
1885
 
1741
- // Prevent modification of the context depth variable. Through replaceStack
1742
- if (/^depth/.test(stack)) {
1743
- stack = this.nextStack();
1886
+ prefix = '(' + this.push(name) + ' = ' + top + '),';
1887
+ stack = this.topStack();
1888
+ }
1889
+ } else {
1890
+ stack = this.topStack();
1744
1891
  }
1745
1892
 
1746
- this.source.push(stack + " = " + item + ";");
1893
+ var item = callback.call(this, stack);
1894
+
1895
+ if (inline) {
1896
+ if (this.inlineStack.length || this.compileStack.length) {
1897
+ this.popStack();
1898
+ }
1899
+ this.push('(' + prefix + item + ')');
1900
+ } else {
1901
+ // Prevent modification of the context depth variable. Through replaceStack
1902
+ if (!/^stack/.test(stack)) {
1903
+ stack = this.nextStack();
1904
+ }
1905
+
1906
+ this.source.push(stack + " = (" + prefix + item + ");");
1907
+ }
1747
1908
  return stack;
1748
1909
  },
1749
1910
 
1750
- nextStack: function(skipCompileStack) {
1751
- var name = this.incrStack();
1752
- this.compileStack.push(name);
1753
- return name;
1911
+ nextStack: function() {
1912
+ return this.pushStack();
1754
1913
  },
1755
1914
 
1756
1915
  incrStack: function() {
1757
1916
  this.stackSlot++;
1758
1917
  if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1918
+ return this.topStackName();
1919
+ },
1920
+ topStackName: function() {
1759
1921
  return "stack" + this.stackSlot;
1760
1922
  },
1923
+ flushInline: function() {
1924
+ var inlineStack = this.inlineStack;
1925
+ if (inlineStack.length) {
1926
+ this.inlineStack = [];
1927
+ for (var i = 0, len = inlineStack.length; i < len; i++) {
1928
+ var entry = inlineStack[i];
1929
+ if (entry instanceof Literal) {
1930
+ this.compileStack.push(entry);
1931
+ } else {
1932
+ this.pushStack(entry);
1933
+ }
1934
+ }
1935
+ }
1936
+ },
1937
+ isInline: function() {
1938
+ return this.inlineStack.length;
1939
+ },
1761
1940
 
1762
- popStack: function() {
1763
- var item = this.compileStack.pop();
1941
+ popStack: function(wrapped) {
1942
+ var inline = this.isInline(),
1943
+ item = (inline ? this.inlineStack : this.compileStack).pop();
1764
1944
 
1765
- if (item instanceof Literal) {
1945
+ if (!wrapped && (item instanceof Literal)) {
1766
1946
  return item.value;
1767
1947
  } else {
1768
- this.stackSlot--;
1948
+ if (!inline) {
1949
+ this.stackSlot--;
1950
+ }
1769
1951
  return item;
1770
1952
  }
1771
1953
  },
1772
1954
 
1773
- topStack: function() {
1774
- var item = this.compileStack[this.compileStack.length - 1];
1955
+ topStack: function(wrapped) {
1956
+ var stack = (this.isInline() ? this.inlineStack : this.compileStack),
1957
+ item = stack[stack.length - 1];
1775
1958
 
1776
- if (item instanceof Literal) {
1959
+ if (!wrapped && (item instanceof Literal)) {
1777
1960
  return item.value;
1778
1961
  } else {
1779
1962
  return item;
@@ -1788,22 +1971,22 @@ Handlebars.JavaScriptCompiler = function() {};
1788
1971
  .replace(/\r/g, '\\r') + '"';
1789
1972
  },
1790
1973
 
1791
- setupHelper: function(paramSize, name) {
1974
+ setupHelper: function(paramSize, name, missingParams) {
1792
1975
  var params = [];
1793
- this.setupParams(paramSize, params);
1976
+ this.setupParams(paramSize, params, missingParams);
1794
1977
  var foundHelper = this.nameLookup('helpers', name, 'helper');
1795
1978
 
1796
1979
  return {
1797
1980
  params: params,
1798
1981
  name: foundHelper,
1799
1982
  callParams: ["depth0"].concat(params).join(", "),
1800
- helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ")
1983
+ helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
1801
1984
  };
1802
1985
  },
1803
1986
 
1804
1987
  // the params and contexts arguments are passed in arrays
1805
1988
  // to fill in
1806
- setupParams: function(paramSize, params) {
1989
+ setupParams: function(paramSize, params, useRegister) {
1807
1990
  var options = [], contexts = [], types = [], param, inverse, program;
1808
1991
 
1809
1992
  options.push("hash:" + this.popStack());
@@ -1848,7 +2031,13 @@ Handlebars.JavaScriptCompiler = function() {};
1848
2031
  options.push("data:data");
1849
2032
  }
1850
2033
 
1851
- params.push("{" + options.join(",") + "}");
2034
+ options = "{" + options.join(",") + "}";
2035
+ if (useRegister) {
2036
+ this.register('options', options);
2037
+ params.push('options');
2038
+ } else {
2039
+ params.push(options);
2040
+ }
1852
2041
  return params.join(", ");
1853
2042
  }
1854
2043
  };
@@ -1886,23 +2075,23 @@ Handlebars.JavaScriptCompiler = function() {};
1886
2075
 
1887
2076
  })(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
1888
2077
 
1889
- Handlebars.precompile = function(string, options) {
1890
- if (typeof string !== 'string') {
1891
- throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string);
2078
+ Handlebars.precompile = function(input, options) {
2079
+ if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
2080
+ throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
1892
2081
  }
1893
2082
 
1894
2083
  options = options || {};
1895
2084
  if (!('data' in options)) {
1896
2085
  options.data = true;
1897
2086
  }
1898
- var ast = Handlebars.parse(string);
2087
+ var ast = Handlebars.parse(input);
1899
2088
  var environment = new Handlebars.Compiler().compile(ast, options);
1900
2089
  return new Handlebars.JavaScriptCompiler().compile(environment, options);
1901
2090
  };
1902
2091
 
1903
- Handlebars.compile = function(string, options) {
1904
- if (typeof string !== 'string') {
1905
- throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string);
2092
+ Handlebars.compile = function(input, options) {
2093
+ if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
2094
+ throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
1906
2095
  }
1907
2096
 
1908
2097
  options = options || {};
@@ -1911,7 +2100,7 @@ Handlebars.compile = function(string, options) {
1911
2100
  }
1912
2101
  var compiled;
1913
2102
  function compile() {
1914
- var ast = Handlebars.parse(string);
2103
+ var ast = Handlebars.parse(input);
1915
2104
  var environment = new Handlebars.Compiler().compile(ast, options);
1916
2105
  var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
1917
2106
  return Handlebars.template(templateSpec);
@@ -1946,12 +2135,32 @@ Handlebars.VM = {
1946
2135
  }
1947
2136
  },
1948
2137
  programWithDepth: Handlebars.VM.programWithDepth,
1949
- noop: Handlebars.VM.noop
2138
+ noop: Handlebars.VM.noop,
2139
+ compilerInfo: null
1950
2140
  };
1951
2141
 
1952
2142
  return function(context, options) {
1953
2143
  options = options || {};
1954
- return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
2144
+ var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
2145
+
2146
+ var compilerInfo = container.compilerInfo || [],
2147
+ compilerRevision = compilerInfo[0] || 1,
2148
+ currentRevision = Handlebars.COMPILER_REVISION;
2149
+
2150
+ if (compilerRevision !== currentRevision) {
2151
+ if (compilerRevision < currentRevision) {
2152
+ var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
2153
+ compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
2154
+ throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
2155
+ "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
2156
+ } else {
2157
+ // Use the embedded version info since the runtime doesn't know about this revision yet
2158
+ throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
2159
+ "Please update your runtime to a newer version ("+compilerInfo[1]+").";
2160
+ }
2161
+ }
2162
+
2163
+ return result;
1955
2164
  };
1956
2165
  },
1957
2166