handlebars-source 1.0.7 → 1.0.9

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.

Potentially problematic release.


This version of handlebars-source might be problematic. Click here for more details.

Files changed (3) hide show
  1. data/dist/handlebars.js +1232 -1024
  2. data/dist/handlebars.runtime.js +100 -55
  3. metadata +1 -1
@@ -1,11 +1,41 @@
1
- // lib/handlebars/base.js
1
+ /*
2
2
 
3
- /*jshint eqnull:true*/
4
- this.Handlebars = {};
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.
5
14
 
6
- (function(Handlebars) {
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.
7
22
 
8
- Handlebars.VERSION = "1.0.rc.2";
23
+ */
24
+
25
+ // lib/handlebars/browser-prefix.js
26
+ var Handlebars = {};
27
+
28
+ (function(Handlebars, undefined) {
29
+ ;
30
+ // lib/handlebars/base.js
31
+
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 = {};
@@ -32,8 +62,6 @@ var toString = Object.prototype.toString, functionType = "[object Function]";
32
62
  Handlebars.registerHelper('blockHelperMissing', function(context, options) {
33
63
  var inverse = options.inverse || function() {}, fn = options.fn;
34
64
 
35
-
36
- var ret = "";
37
65
  var type = toString.call(context);
38
66
 
39
67
  if(type === functionType) { context = context.call(this); }
@@ -124,11 +152,7 @@ Handlebars.registerHelper('if', function(context, options) {
124
152
  });
125
153
 
126
154
  Handlebars.registerHelper('unless', function(context, options) {
127
- var fn = options.fn, inverse = options.inverse;
128
- options.fn = inverse;
129
- options.inverse = fn;
130
-
131
- return Handlebars.helpers['if'].call(this, context, options);
155
+ return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn});
132
156
  });
133
157
 
134
158
  Handlebars.registerHelper('with', function(context, options) {
@@ -139,8 +163,6 @@ Handlebars.registerHelper('log', function(context, options) {
139
163
  var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
140
164
  Handlebars.log(level, context);
141
165
  });
142
-
143
- }(this.Handlebars));
144
166
  ;
145
167
  // lib/handlebars/compiler/parser.js
146
168
  /* Jison generated parser */
@@ -530,7 +552,7 @@ pushState:function begin(condition) {
530
552
  lexer.options = {};
531
553
  lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
532
554
 
533
- var YYSTATE=YY_START;
555
+ var YYSTATE=YY_START
534
556
  switch($avoiding_name_collisions) {
535
557
  case 0:
536
558
  if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
@@ -608,150 +630,152 @@ case 32: return 5;
608
630
  break;
609
631
  }
610
632
  };
611
- lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/];
633
+ lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/];
612
634
  lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}};
613
- return lexer;})();
635
+ return lexer;})()
614
636
  parser.lexer = lexer;
615
637
  function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
616
638
  return new Parser;
617
639
  })();;
618
640
  // lib/handlebars/compiler/base.js
641
+
619
642
  Handlebars.Parser = handlebars;
620
643
 
621
- Handlebars.parse = function(string) {
644
+ Handlebars.parse = function(input) {
645
+
646
+ // Just return if an already-compile AST was passed in.
647
+ if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
648
+
622
649
  Handlebars.Parser.yy = Handlebars.AST;
623
- return Handlebars.Parser.parse(string);
650
+ return Handlebars.Parser.parse(input);
624
651
  };
625
-
626
- Handlebars.print = function(ast) {
627
- return new Handlebars.PrintVisitor().accept(ast);
628
- };;
652
+ ;
629
653
  // lib/handlebars/compiler/ast.js
630
- (function() {
631
-
632
- Handlebars.AST = {};
654
+ Handlebars.AST = {};
633
655
 
634
- Handlebars.AST.ProgramNode = function(statements, inverse) {
635
- this.type = "program";
636
- this.statements = statements;
637
- if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
638
- };
656
+ Handlebars.AST.ProgramNode = function(statements, inverse) {
657
+ this.type = "program";
658
+ this.statements = statements;
659
+ if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
660
+ };
639
661
 
640
- Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
641
- this.type = "mustache";
642
- this.escaped = !unescaped;
643
- this.hash = hash;
662
+ Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
663
+ this.type = "mustache";
664
+ this.escaped = !unescaped;
665
+ this.hash = hash;
644
666
 
645
- var id = this.id = rawParams[0];
646
- var params = this.params = rawParams.slice(1);
667
+ var id = this.id = rawParams[0];
668
+ var params = this.params = rawParams.slice(1);
647
669
 
648
- // a mustache is an eligible helper if:
649
- // * its id is simple (a single part, not `this` or `..`)
650
- var eligibleHelper = this.eligibleHelper = id.isSimple;
670
+ // a mustache is an eligible helper if:
671
+ // * its id is simple (a single part, not `this` or `..`)
672
+ var eligibleHelper = this.eligibleHelper = id.isSimple;
651
673
 
652
- // a mustache is definitely a helper if:
653
- // * it is an eligible helper, and
654
- // * it has at least one parameter or hash segment
655
- this.isHelper = eligibleHelper && (params.length || hash);
674
+ // a mustache is definitely a helper if:
675
+ // * it is an eligible helper, and
676
+ // * it has at least one parameter or hash segment
677
+ this.isHelper = eligibleHelper && (params.length || hash);
656
678
 
657
- // if a mustache is an eligible helper but not a definite
658
- // helper, it is ambiguous, and will be resolved in a later
659
- // pass or at runtime.
660
- };
679
+ // if a mustache is an eligible helper but not a definite
680
+ // helper, it is ambiguous, and will be resolved in a later
681
+ // pass or at runtime.
682
+ };
661
683
 
662
- Handlebars.AST.PartialNode = function(partialName, context) {
663
- this.type = "partial";
664
- this.partialName = partialName;
665
- this.context = context;
666
- };
684
+ Handlebars.AST.PartialNode = function(partialName, context) {
685
+ this.type = "partial";
686
+ this.partialName = partialName;
687
+ this.context = context;
688
+ };
667
689
 
690
+ Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
668
691
  var verifyMatch = function(open, close) {
669
692
  if(open.original !== close.original) {
670
693
  throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
671
694
  }
672
695
  };
673
696
 
674
- Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
675
- verifyMatch(mustache.id, close);
676
- this.type = "block";
677
- this.mustache = mustache;
678
- this.program = program;
679
- this.inverse = inverse;
697
+ verifyMatch(mustache.id, close);
698
+ this.type = "block";
699
+ this.mustache = mustache;
700
+ this.program = program;
701
+ this.inverse = inverse;
680
702
 
681
- if (this.inverse && !this.program) {
682
- this.isInverse = true;
683
- }
684
- };
703
+ if (this.inverse && !this.program) {
704
+ this.isInverse = true;
705
+ }
706
+ };
685
707
 
686
- Handlebars.AST.ContentNode = function(string) {
687
- this.type = "content";
688
- this.string = string;
689
- };
708
+ Handlebars.AST.ContentNode = function(string) {
709
+ this.type = "content";
710
+ this.string = string;
711
+ };
690
712
 
691
- Handlebars.AST.HashNode = function(pairs) {
692
- this.type = "hash";
693
- this.pairs = pairs;
694
- };
713
+ Handlebars.AST.HashNode = function(pairs) {
714
+ this.type = "hash";
715
+ this.pairs = pairs;
716
+ };
695
717
 
696
- Handlebars.AST.IdNode = function(parts) {
697
- this.type = "ID";
698
- this.original = parts.join(".");
718
+ Handlebars.AST.IdNode = function(parts) {
719
+ this.type = "ID";
720
+ this.original = parts.join(".");
699
721
 
700
- var dig = [], depth = 0;
722
+ var dig = [], depth = 0;
701
723
 
702
- for(var i=0,l=parts.length; i<l; i++) {
703
- var part = parts[i];
724
+ for(var i=0,l=parts.length; i<l; i++) {
725
+ var part = parts[i];
704
726
 
705
- if(part === "..") { depth++; }
706
- else if(part === "." || part === "this") { this.isScoped = true; }
707
- else { dig.push(part); }
727
+ if (part === ".." || part === "." || part === "this") {
728
+ if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
729
+ else if (part === "..") { depth++; }
730
+ else { this.isScoped = true; }
708
731
  }
732
+ else { dig.push(part); }
733
+ }
709
734
 
710
- this.parts = dig;
711
- this.string = dig.join('.');
712
- this.depth = depth;
713
-
714
- // an ID is simple if it only has one part, and that part is not
715
- // `..` or `this`.
716
- this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
735
+ this.parts = dig;
736
+ this.string = dig.join('.');
737
+ this.depth = depth;
717
738
 
718
- this.stringModeValue = this.string;
719
- };
739
+ // an ID is simple if it only has one part, and that part is not
740
+ // `..` or `this`.
741
+ this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
720
742
 
721
- Handlebars.AST.PartialNameNode = function(name) {
722
- this.type = "PARTIAL_NAME";
723
- this.name = name;
724
- };
743
+ this.stringModeValue = this.string;
744
+ };
725
745
 
726
- Handlebars.AST.DataNode = function(id) {
727
- this.type = "DATA";
728
- this.id = id;
729
- };
746
+ Handlebars.AST.PartialNameNode = function(name) {
747
+ this.type = "PARTIAL_NAME";
748
+ this.name = name;
749
+ };
730
750
 
731
- Handlebars.AST.StringNode = function(string) {
732
- this.type = "STRING";
733
- this.string = string;
734
- this.stringModeValue = string;
735
- };
751
+ Handlebars.AST.DataNode = function(id) {
752
+ this.type = "DATA";
753
+ this.id = id;
754
+ };
736
755
 
737
- Handlebars.AST.IntegerNode = function(integer) {
738
- this.type = "INTEGER";
739
- this.integer = integer;
740
- this.stringModeValue = Number(integer);
741
- };
756
+ Handlebars.AST.StringNode = function(string) {
757
+ this.type = "STRING";
758
+ this.string = string;
759
+ this.stringModeValue = string;
760
+ };
742
761
 
743
- Handlebars.AST.BooleanNode = function(bool) {
744
- this.type = "BOOLEAN";
745
- this.bool = bool;
746
- this.stringModeValue = bool === "true";
747
- };
762
+ Handlebars.AST.IntegerNode = function(integer) {
763
+ this.type = "INTEGER";
764
+ this.integer = integer;
765
+ this.stringModeValue = Number(integer);
766
+ };
748
767
 
749
- Handlebars.AST.CommentNode = function(comment) {
750
- this.type = "comment";
751
- this.comment = comment;
752
- };
768
+ Handlebars.AST.BooleanNode = function(bool) {
769
+ this.type = "BOOLEAN";
770
+ this.bool = bool;
771
+ this.stringModeValue = bool === "true";
772
+ };
753
773
 
754
- })();;
774
+ Handlebars.AST.CommentNode = function(comment) {
775
+ this.type = "comment";
776
+ this.comment = comment;
777
+ };
778
+ ;
755
779
  // lib/handlebars/utils.js
756
780
 
757
781
  var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
@@ -774,1135 +798,1294 @@ Handlebars.SafeString.prototype.toString = function() {
774
798
  return this.string.toString();
775
799
  };
776
800
 
777
- (function() {
778
- var escape = {
779
- "&": "&amp;",
780
- "<": "&lt;",
781
- ">": "&gt;",
782
- '"': "&quot;",
783
- "'": "&#x27;",
784
- "`": "&#x60;"
785
- };
801
+ var escape = {
802
+ "&": "&amp;",
803
+ "<": "&lt;",
804
+ ">": "&gt;",
805
+ '"': "&quot;",
806
+ "'": "&#x27;",
807
+ "`": "&#x60;"
808
+ };
786
809
 
787
- var badChars = /[&<>"'`]/g;
788
- var possible = /[&<>"'`]/;
810
+ var badChars = /[&<>"'`]/g;
811
+ var possible = /[&<>"'`]/;
789
812
 
790
- var escapeChar = function(chr) {
791
- return escape[chr] || "&amp;";
792
- };
813
+ var escapeChar = function(chr) {
814
+ return escape[chr] || "&amp;";
815
+ };
793
816
 
794
- Handlebars.Utils = {
795
- escapeExpression: function(string) {
796
- // don't escape SafeStrings, since they're already safe
797
- if (string instanceof Handlebars.SafeString) {
798
- return string.toString();
799
- } else if (string == null || string === false) {
800
- return "";
801
- }
817
+ Handlebars.Utils = {
818
+ escapeExpression: function(string) {
819
+ // don't escape SafeStrings, since they're already safe
820
+ if (string instanceof Handlebars.SafeString) {
821
+ return string.toString();
822
+ } else if (string == null || string === false) {
823
+ return "";
824
+ }
802
825
 
803
- if(!possible.test(string)) { return string; }
804
- return string.replace(badChars, escapeChar);
805
- },
826
+ if(!possible.test(string)) { return string; }
827
+ return string.replace(badChars, escapeChar);
828
+ },
806
829
 
807
- isEmpty: function(value) {
808
- if (!value && value !== 0) {
809
- return true;
810
- } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
811
- return true;
812
- } else {
813
- return false;
814
- }
830
+ isEmpty: function(value) {
831
+ if (!value && value !== 0) {
832
+ return true;
833
+ } else if(toString.call(value) === "[object Array]" && value.length === 0) {
834
+ return true;
835
+ } else {
836
+ return false;
815
837
  }
816
- };
817
- })();;
838
+ }
839
+ };
840
+ ;
818
841
  // lib/handlebars/compiler/compiler.js
819
842
 
820
843
  /*jshint eqnull:true*/
821
- Handlebars.Compiler = function() {};
822
- Handlebars.JavaScriptCompiler = function() {};
844
+ var Compiler = Handlebars.Compiler = function() {};
845
+ var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {};
823
846
 
824
- (function(Compiler, JavaScriptCompiler) {
825
- // the foundHelper register will disambiguate helper lookup from finding a
826
- // function in a context. This is necessary for mustache compatibility, which
827
- // requires that context functions in blocks are evaluated by blockHelperMissing,
828
- // and then proceed as if the resulting value was provided to blockHelperMissing.
847
+ // the foundHelper register will disambiguate helper lookup from finding a
848
+ // function in a context. This is necessary for mustache compatibility, which
849
+ // requires that context functions in blocks are evaluated by blockHelperMissing,
850
+ // and then proceed as if the resulting value was provided to blockHelperMissing.
829
851
 
830
- Compiler.prototype = {
831
- compiler: Compiler,
852
+ Compiler.prototype = {
853
+ compiler: Compiler,
832
854
 
833
- disassemble: function() {
834
- var opcodes = this.opcodes, opcode, out = [], params, param;
855
+ disassemble: function() {
856
+ var opcodes = this.opcodes, opcode, out = [], params, param;
835
857
 
836
- for (var i=0, l=opcodes.length; i<l; i++) {
837
- opcode = opcodes[i];
858
+ for (var i=0, l=opcodes.length; i<l; i++) {
859
+ opcode = opcodes[i];
838
860
 
839
- if (opcode.opcode === 'DECLARE') {
840
- out.push("DECLARE " + opcode.name + "=" + opcode.value);
841
- } else {
842
- params = [];
843
- for (var j=0; j<opcode.args.length; j++) {
844
- param = opcode.args[j];
845
- if (typeof param === "string") {
846
- param = "\"" + param.replace("\n", "\\n") + "\"";
847
- }
848
- params.push(param);
861
+ if (opcode.opcode === 'DECLARE') {
862
+ out.push("DECLARE " + opcode.name + "=" + opcode.value);
863
+ } else {
864
+ params = [];
865
+ for (var j=0; j<opcode.args.length; j++) {
866
+ param = opcode.args[j];
867
+ if (typeof param === "string") {
868
+ param = "\"" + param.replace("\n", "\\n") + "\"";
849
869
  }
850
- out.push(opcode.opcode + " " + params.join(" "));
870
+ params.push(param);
851
871
  }
872
+ out.push(opcode.opcode + " " + params.join(" "));
852
873
  }
874
+ }
853
875
 
854
- return out.join("\n");
855
- },
876
+ return out.join("\n");
877
+ },
878
+ equals: function(other) {
879
+ var len = this.opcodes.length;
880
+ if (other.opcodes.length !== len) {
881
+ return false;
882
+ }
856
883
 
857
- guid: 0,
858
-
859
- compile: function(program, options) {
860
- this.children = [];
861
- this.depths = {list: []};
862
- this.options = options;
863
-
864
- // These changes will propagate to the other compiler components
865
- var knownHelpers = this.options.knownHelpers;
866
- this.options.knownHelpers = {
867
- 'helperMissing': true,
868
- 'blockHelperMissing': true,
869
- 'each': true,
870
- 'if': true,
871
- 'unless': true,
872
- 'with': true,
873
- 'log': true
874
- };
875
- if (knownHelpers) {
876
- for (var name in knownHelpers) {
877
- this.options.knownHelpers[name] = knownHelpers[name];
884
+ for (var i = 0; i < len; i++) {
885
+ var opcode = this.opcodes[i],
886
+ otherOpcode = other.opcodes[i];
887
+ if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
888
+ return false;
889
+ }
890
+ for (var j = 0; j < opcode.args.length; j++) {
891
+ if (opcode.args[j] !== otherOpcode.args[j]) {
892
+ return false;
878
893
  }
879
894
  }
895
+ }
880
896
 
881
- return this.program(program);
882
- },
883
-
884
- accept: function(node) {
885
- return this[node.type](node);
886
- },
897
+ len = this.children.length;
898
+ if (other.children.length !== len) {
899
+ return false;
900
+ }
901
+ for (i = 0; i < len; i++) {
902
+ if (!this.children[i].equals(other.children[i])) {
903
+ return false;
904
+ }
905
+ }
887
906
 
888
- program: function(program) {
889
- var statements = program.statements, statement;
890
- this.opcodes = [];
907
+ return true;
908
+ },
891
909
 
892
- for(var i=0, l=statements.length; i<l; i++) {
893
- statement = statements[i];
894
- this[statement.type](statement);
910
+ guid: 0,
911
+
912
+ compile: function(program, options) {
913
+ this.children = [];
914
+ this.depths = {list: []};
915
+ this.options = options;
916
+
917
+ // These changes will propagate to the other compiler components
918
+ var knownHelpers = this.options.knownHelpers;
919
+ this.options.knownHelpers = {
920
+ 'helperMissing': true,
921
+ 'blockHelperMissing': true,
922
+ 'each': true,
923
+ 'if': true,
924
+ 'unless': true,
925
+ 'with': true,
926
+ 'log': true
927
+ };
928
+ if (knownHelpers) {
929
+ for (var name in knownHelpers) {
930
+ this.options.knownHelpers[name] = knownHelpers[name];
895
931
  }
896
- this.isSimple = l === 1;
932
+ }
897
933
 
898
- this.depths.list = this.depths.list.sort(function(a, b) {
899
- return a - b;
900
- });
934
+ return this.program(program);
935
+ },
901
936
 
902
- return this;
903
- },
937
+ accept: function(node) {
938
+ return this[node.type](node);
939
+ },
904
940
 
905
- compileProgram: function(program) {
906
- var result = new this.compiler().compile(program, this.options);
907
- var guid = this.guid++, depth;
941
+ program: function(program) {
942
+ var statements = program.statements, statement;
943
+ this.opcodes = [];
908
944
 
909
- this.usePartial = this.usePartial || result.usePartial;
945
+ for(var i=0, l=statements.length; i<l; i++) {
946
+ statement = statements[i];
947
+ this[statement.type](statement);
948
+ }
949
+ this.isSimple = l === 1;
910
950
 
911
- this.children[guid] = result;
951
+ this.depths.list = this.depths.list.sort(function(a, b) {
952
+ return a - b;
953
+ });
912
954
 
913
- for(var i=0, l=result.depths.list.length; i<l; i++) {
914
- depth = result.depths.list[i];
955
+ return this;
956
+ },
915
957
 
916
- if(depth < 2) { continue; }
917
- else { this.addDepth(depth - 1); }
918
- }
958
+ compileProgram: function(program) {
959
+ var result = new this.compiler().compile(program, this.options);
960
+ var guid = this.guid++, depth;
919
961
 
920
- return guid;
921
- },
962
+ this.usePartial = this.usePartial || result.usePartial;
922
963
 
923
- block: function(block) {
924
- var mustache = block.mustache,
925
- program = block.program,
926
- inverse = block.inverse;
964
+ this.children[guid] = result;
927
965
 
928
- if (program) {
929
- program = this.compileProgram(program);
930
- }
966
+ for(var i=0, l=result.depths.list.length; i<l; i++) {
967
+ depth = result.depths.list[i];
931
968
 
932
- if (inverse) {
933
- inverse = this.compileProgram(inverse);
934
- }
969
+ if(depth < 2) { continue; }
970
+ else { this.addDepth(depth - 1); }
971
+ }
935
972
 
936
- var type = this.classifyMustache(mustache);
973
+ return guid;
974
+ },
937
975
 
938
- if (type === "helper") {
939
- this.helperMustache(mustache, program, inverse);
940
- } else if (type === "simple") {
941
- this.simpleMustache(mustache);
976
+ block: function(block) {
977
+ var mustache = block.mustache,
978
+ program = block.program,
979
+ inverse = block.inverse;
942
980
 
943
- // now that the simple mustache is resolved, we need to
944
- // evaluate it by executing `blockHelperMissing`
945
- this.opcode('pushProgram', program);
946
- this.opcode('pushProgram', inverse);
947
- this.opcode('pushHash');
948
- this.opcode('blockValue');
949
- } else {
950
- this.ambiguousMustache(mustache, program, inverse);
951
-
952
- // now that the simple mustache is resolved, we need to
953
- // evaluate it by executing `blockHelperMissing`
954
- this.opcode('pushProgram', program);
955
- this.opcode('pushProgram', inverse);
956
- this.opcode('pushHash');
957
- this.opcode('ambiguousBlockValue');
958
- }
981
+ if (program) {
982
+ program = this.compileProgram(program);
983
+ }
959
984
 
960
- this.opcode('append');
961
- },
985
+ if (inverse) {
986
+ inverse = this.compileProgram(inverse);
987
+ }
962
988
 
963
- hash: function(hash) {
964
- var pairs = hash.pairs, pair, val;
989
+ var type = this.classifyMustache(mustache);
965
990
 
966
- this.opcode('pushHash');
991
+ if (type === "helper") {
992
+ this.helperMustache(mustache, program, inverse);
993
+ } else if (type === "simple") {
994
+ this.simpleMustache(mustache);
967
995
 
968
- for(var i=0, l=pairs.length; i<l; i++) {
969
- pair = pairs[i];
970
- val = pair[1];
996
+ // now that the simple mustache is resolved, we need to
997
+ // evaluate it by executing `blockHelperMissing`
998
+ this.opcode('pushProgram', program);
999
+ this.opcode('pushProgram', inverse);
1000
+ this.opcode('emptyHash');
1001
+ this.opcode('blockValue');
1002
+ } else {
1003
+ this.ambiguousMustache(mustache, program, inverse);
971
1004
 
972
- if (this.options.stringParams) {
973
- this.opcode('pushStringParam', val.stringModeValue, val.type);
974
- } else {
975
- this.accept(val);
976
- }
1005
+ // now that the simple mustache is resolved, we need to
1006
+ // evaluate it by executing `blockHelperMissing`
1007
+ this.opcode('pushProgram', program);
1008
+ this.opcode('pushProgram', inverse);
1009
+ this.opcode('emptyHash');
1010
+ this.opcode('ambiguousBlockValue');
1011
+ }
977
1012
 
978
- this.opcode('assignToHash', pair[0]);
979
- }
980
- },
1013
+ this.opcode('append');
1014
+ },
981
1015
 
982
- partial: function(partial) {
983
- var partialName = partial.partialName;
984
- this.usePartial = true;
1016
+ hash: function(hash) {
1017
+ var pairs = hash.pairs, pair, val;
985
1018
 
986
- if(partial.context) {
987
- this.ID(partial.context);
1019
+ this.opcode('pushHash');
1020
+
1021
+ for(var i=0, l=pairs.length; i<l; i++) {
1022
+ pair = pairs[i];
1023
+ val = pair[1];
1024
+
1025
+ if (this.options.stringParams) {
1026
+ this.opcode('pushStringParam', val.stringModeValue, val.type);
988
1027
  } else {
989
- this.opcode('push', 'depth0');
1028
+ this.accept(val);
990
1029
  }
991
1030
 
992
- this.opcode('invokePartial', partialName.name);
993
- this.opcode('append');
994
- },
995
-
996
- content: function(content) {
997
- this.opcode('appendContent', content.string);
998
- },
1031
+ this.opcode('assignToHash', pair[0]);
1032
+ }
1033
+ this.opcode('popHash');
1034
+ },
999
1035
 
1000
- mustache: function(mustache) {
1001
- var options = this.options;
1002
- var type = this.classifyMustache(mustache);
1036
+ partial: function(partial) {
1037
+ var partialName = partial.partialName;
1038
+ this.usePartial = true;
1003
1039
 
1004
- if (type === "simple") {
1005
- this.simpleMustache(mustache);
1006
- } else if (type === "helper") {
1007
- this.helperMustache(mustache);
1008
- } else {
1009
- this.ambiguousMustache(mustache);
1010
- }
1040
+ if(partial.context) {
1041
+ this.ID(partial.context);
1042
+ } else {
1043
+ this.opcode('push', 'depth0');
1044
+ }
1011
1045
 
1012
- if(mustache.escaped && !options.noEscape) {
1013
- this.opcode('appendEscaped');
1014
- } else {
1015
- this.opcode('append');
1016
- }
1017
- },
1046
+ this.opcode('invokePartial', partialName.name);
1047
+ this.opcode('append');
1048
+ },
1018
1049
 
1019
- ambiguousMustache: function(mustache, program, inverse) {
1020
- var id = mustache.id, name = id.parts[0];
1050
+ content: function(content) {
1051
+ this.opcode('appendContent', content.string);
1052
+ },
1021
1053
 
1022
- this.opcode('getContext', id.depth);
1054
+ mustache: function(mustache) {
1055
+ var options = this.options;
1056
+ var type = this.classifyMustache(mustache);
1023
1057
 
1024
- this.opcode('pushProgram', program);
1025
- this.opcode('pushProgram', inverse);
1058
+ if (type === "simple") {
1059
+ this.simpleMustache(mustache);
1060
+ } else if (type === "helper") {
1061
+ this.helperMustache(mustache);
1062
+ } else {
1063
+ this.ambiguousMustache(mustache);
1064
+ }
1026
1065
 
1027
- this.opcode('invokeAmbiguous', name);
1028
- },
1066
+ if(mustache.escaped && !options.noEscape) {
1067
+ this.opcode('appendEscaped');
1068
+ } else {
1069
+ this.opcode('append');
1070
+ }
1071
+ },
1029
1072
 
1030
- simpleMustache: function(mustache, program, inverse) {
1031
- var id = mustache.id;
1073
+ ambiguousMustache: function(mustache, program, inverse) {
1074
+ var id = mustache.id,
1075
+ name = id.parts[0],
1076
+ isBlock = program != null || inverse != null;
1032
1077
 
1033
- if (id.type === 'DATA') {
1034
- this.DATA(id);
1035
- } else if (id.parts.length) {
1036
- this.ID(id);
1037
- } else {
1038
- // Simplified ID for `this`
1039
- this.addDepth(id.depth);
1040
- this.opcode('getContext', id.depth);
1041
- this.opcode('pushContext');
1042
- }
1078
+ this.opcode('getContext', id.depth);
1043
1079
 
1044
- this.opcode('resolvePossibleLambda');
1045
- },
1080
+ this.opcode('pushProgram', program);
1081
+ this.opcode('pushProgram', inverse);
1046
1082
 
1047
- helperMustache: function(mustache, program, inverse) {
1048
- var params = this.setupFullMustacheParams(mustache, program, inverse),
1049
- name = mustache.id.parts[0];
1083
+ this.opcode('invokeAmbiguous', name, isBlock);
1084
+ },
1050
1085
 
1051
- if (this.options.knownHelpers[name]) {
1052
- this.opcode('invokeKnownHelper', params.length, name);
1053
- } else if (this.knownHelpersOnly) {
1054
- throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
1055
- } else {
1056
- this.opcode('invokeHelper', params.length, name);
1057
- }
1058
- },
1086
+ simpleMustache: function(mustache) {
1087
+ var id = mustache.id;
1059
1088
 
1060
- ID: function(id) {
1089
+ if (id.type === 'DATA') {
1090
+ this.DATA(id);
1091
+ } else if (id.parts.length) {
1092
+ this.ID(id);
1093
+ } else {
1094
+ // Simplified ID for `this`
1061
1095
  this.addDepth(id.depth);
1062
1096
  this.opcode('getContext', id.depth);
1097
+ this.opcode('pushContext');
1098
+ }
1063
1099
 
1064
- var name = id.parts[0];
1065
- if (!name) {
1066
- this.opcode('pushContext');
1067
- } else {
1068
- this.opcode('lookupOnContext', id.parts[0]);
1069
- }
1070
-
1071
- for(var i=1, l=id.parts.length; i<l; i++) {
1072
- this.opcode('lookup', id.parts[i]);
1073
- }
1074
- },
1100
+ this.opcode('resolvePossibleLambda');
1101
+ },
1075
1102
 
1076
- DATA: function(data) {
1077
- this.options.data = true;
1078
- this.opcode('lookupData', data.id);
1079
- },
1103
+ helperMustache: function(mustache, program, inverse) {
1104
+ var params = this.setupFullMustacheParams(mustache, program, inverse),
1105
+ name = mustache.id.parts[0];
1080
1106
 
1081
- STRING: function(string) {
1082
- this.opcode('pushString', string.string);
1083
- },
1107
+ if (this.options.knownHelpers[name]) {
1108
+ this.opcode('invokeKnownHelper', params.length, name);
1109
+ } else if (this.knownHelpersOnly) {
1110
+ throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
1111
+ } else {
1112
+ this.opcode('invokeHelper', params.length, name);
1113
+ }
1114
+ },
1084
1115
 
1085
- INTEGER: function(integer) {
1086
- this.opcode('pushLiteral', integer.integer);
1087
- },
1116
+ ID: function(id) {
1117
+ this.addDepth(id.depth);
1118
+ this.opcode('getContext', id.depth);
1088
1119
 
1089
- BOOLEAN: function(bool) {
1090
- this.opcode('pushLiteral', bool.bool);
1091
- },
1120
+ var name = id.parts[0];
1121
+ if (!name) {
1122
+ this.opcode('pushContext');
1123
+ } else {
1124
+ this.opcode('lookupOnContext', id.parts[0]);
1125
+ }
1092
1126
 
1093
- comment: function() {},
1127
+ for(var i=1, l=id.parts.length; i<l; i++) {
1128
+ this.opcode('lookup', id.parts[i]);
1129
+ }
1130
+ },
1094
1131
 
1095
- // HELPERS
1096
- opcode: function(name) {
1097
- this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
1098
- },
1132
+ DATA: function(data) {
1133
+ this.options.data = true;
1134
+ this.opcode('lookupData', data.id);
1135
+ },
1099
1136
 
1100
- declare: function(name, value) {
1101
- this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
1102
- },
1137
+ STRING: function(string) {
1138
+ this.opcode('pushString', string.string);
1139
+ },
1103
1140
 
1104
- addDepth: function(depth) {
1105
- if(isNaN(depth)) { throw new Error("EWOT"); }
1106
- if(depth === 0) { return; }
1141
+ INTEGER: function(integer) {
1142
+ this.opcode('pushLiteral', integer.integer);
1143
+ },
1107
1144
 
1108
- if(!this.depths[depth]) {
1109
- this.depths[depth] = true;
1110
- this.depths.list.push(depth);
1111
- }
1112
- },
1145
+ BOOLEAN: function(bool) {
1146
+ this.opcode('pushLiteral', bool.bool);
1147
+ },
1113
1148
 
1114
- classifyMustache: function(mustache) {
1115
- var isHelper = mustache.isHelper;
1116
- var isEligible = mustache.eligibleHelper;
1117
- var options = this.options;
1149
+ comment: function() {},
1118
1150
 
1119
- // if ambiguous, we can possibly resolve the ambiguity now
1120
- if (isEligible && !isHelper) {
1121
- var name = mustache.id.parts[0];
1151
+ // HELPERS
1152
+ opcode: function(name) {
1153
+ this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
1154
+ },
1122
1155
 
1123
- if (options.knownHelpers[name]) {
1124
- isHelper = true;
1125
- } else if (options.knownHelpersOnly) {
1126
- isEligible = false;
1127
- }
1128
- }
1156
+ declare: function(name, value) {
1157
+ this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
1158
+ },
1129
1159
 
1130
- if (isHelper) { return "helper"; }
1131
- else if (isEligible) { return "ambiguous"; }
1132
- else { return "simple"; }
1133
- },
1160
+ addDepth: function(depth) {
1161
+ if(isNaN(depth)) { throw new Error("EWOT"); }
1162
+ if(depth === 0) { return; }
1134
1163
 
1135
- pushParams: function(params) {
1136
- var i = params.length, param;
1164
+ if(!this.depths[depth]) {
1165
+ this.depths[depth] = true;
1166
+ this.depths.list.push(depth);
1167
+ }
1168
+ },
1137
1169
 
1138
- while(i--) {
1139
- param = params[i];
1170
+ classifyMustache: function(mustache) {
1171
+ var isHelper = mustache.isHelper;
1172
+ var isEligible = mustache.eligibleHelper;
1173
+ var options = this.options;
1140
1174
 
1141
- if(this.options.stringParams) {
1142
- if(param.depth) {
1143
- this.addDepth(param.depth);
1144
- }
1175
+ // if ambiguous, we can possibly resolve the ambiguity now
1176
+ if (isEligible && !isHelper) {
1177
+ var name = mustache.id.parts[0];
1145
1178
 
1146
- this.opcode('getContext', param.depth || 0);
1147
- this.opcode('pushStringParam', param.stringModeValue, param.type);
1148
- } else {
1149
- this[param.type](param);
1150
- }
1179
+ if (options.knownHelpers[name]) {
1180
+ isHelper = true;
1181
+ } else if (options.knownHelpersOnly) {
1182
+ isEligible = false;
1151
1183
  }
1152
- },
1153
-
1154
- setupMustacheParams: function(mustache) {
1155
- var params = mustache.params;
1156
- this.pushParams(params);
1184
+ }
1157
1185
 
1158
- if(mustache.hash) {
1159
- this.hash(mustache.hash);
1160
- } else {
1161
- this.opcode('pushHash');
1162
- }
1186
+ if (isHelper) { return "helper"; }
1187
+ else if (isEligible) { return "ambiguous"; }
1188
+ else { return "simple"; }
1189
+ },
1163
1190
 
1164
- return params;
1165
- },
1191
+ pushParams: function(params) {
1192
+ var i = params.length, param;
1166
1193
 
1167
- // this will replace setupMustacheParams when we're done
1168
- setupFullMustacheParams: function(mustache, program, inverse) {
1169
- var params = mustache.params;
1170
- this.pushParams(params);
1194
+ while(i--) {
1195
+ param = params[i];
1171
1196
 
1172
- this.opcode('pushProgram', program);
1173
- this.opcode('pushProgram', inverse);
1197
+ if(this.options.stringParams) {
1198
+ if(param.depth) {
1199
+ this.addDepth(param.depth);
1200
+ }
1174
1201
 
1175
- if(mustache.hash) {
1176
- this.hash(mustache.hash);
1202
+ this.opcode('getContext', param.depth || 0);
1203
+ this.opcode('pushStringParam', param.stringModeValue, param.type);
1177
1204
  } else {
1178
- this.opcode('pushHash');
1205
+ this[param.type](param);
1179
1206
  }
1207
+ }
1208
+ },
1209
+
1210
+ setupMustacheParams: function(mustache) {
1211
+ var params = mustache.params;
1212
+ this.pushParams(params);
1180
1213
 
1181
- return params;
1214
+ if(mustache.hash) {
1215
+ this.hash(mustache.hash);
1216
+ } else {
1217
+ this.opcode('emptyHash');
1182
1218
  }
1183
- };
1184
1219
 
1185
- var Literal = function(value) {
1186
- this.value = value;
1187
- };
1220
+ return params;
1221
+ },
1188
1222
 
1189
- JavaScriptCompiler.prototype = {
1190
- // PUBLIC API: You can override these methods in a subclass to provide
1191
- // alternative compiled forms for name lookup and buffering semantics
1192
- nameLookup: function(parent, name, type) {
1193
- if (/^[0-9]+$/.test(name)) {
1194
- return parent + "[" + name + "]";
1195
- } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
1196
- return parent + "." + name;
1197
- }
1198
- else {
1199
- return parent + "['" + name + "']";
1200
- }
1201
- },
1223
+ // this will replace setupMustacheParams when we're done
1224
+ setupFullMustacheParams: function(mustache, program, inverse) {
1225
+ var params = mustache.params;
1226
+ this.pushParams(params);
1202
1227
 
1203
- appendToBuffer: function(string) {
1204
- if (this.environment.isSimple) {
1205
- return "return " + string + ";";
1206
- } else {
1207
- return "buffer += " + string + ";";
1208
- }
1209
- },
1228
+ this.opcode('pushProgram', program);
1229
+ this.opcode('pushProgram', inverse);
1210
1230
 
1211
- initializeBuffer: function() {
1212
- return this.quotedString("");
1213
- },
1231
+ if(mustache.hash) {
1232
+ this.hash(mustache.hash);
1233
+ } else {
1234
+ this.opcode('emptyHash');
1235
+ }
1214
1236
 
1215
- namespace: "Handlebars",
1216
- // END PUBLIC API
1237
+ return params;
1238
+ }
1239
+ };
1217
1240
 
1218
- compile: function(environment, options, context, asObject) {
1219
- this.environment = environment;
1220
- this.options = options || {};
1241
+ var Literal = function(value) {
1242
+ this.value = value;
1243
+ };
1221
1244
 
1222
- Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n");
1245
+ JavaScriptCompiler.prototype = {
1246
+ // PUBLIC API: You can override these methods in a subclass to provide
1247
+ // alternative compiled forms for name lookup and buffering semantics
1248
+ nameLookup: function(parent, name /* , type*/) {
1249
+ if (/^[0-9]+$/.test(name)) {
1250
+ return parent + "[" + name + "]";
1251
+ } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
1252
+ return parent + "." + name;
1253
+ }
1254
+ else {
1255
+ return parent + "['" + name + "']";
1256
+ }
1257
+ },
1223
1258
 
1224
- this.name = this.environment.name;
1225
- this.isChild = !!context;
1226
- this.context = context || {
1227
- programs: [],
1228
- aliases: { }
1259
+ appendToBuffer: function(string) {
1260
+ if (this.environment.isSimple) {
1261
+ return "return " + string + ";";
1262
+ } else {
1263
+ return {
1264
+ appendToBuffer: true,
1265
+ content: string,
1266
+ toString: function() { return "buffer += " + string + ";"; }
1229
1267
  };
1268
+ }
1269
+ },
1230
1270
 
1231
- this.preamble();
1271
+ initializeBuffer: function() {
1272
+ return this.quotedString("");
1273
+ },
1232
1274
 
1233
- this.stackSlot = 0;
1234
- this.stackVars = [];
1235
- this.registers = { list: [] };
1236
- this.compileStack = [];
1275
+ namespace: "Handlebars",
1276
+ // END PUBLIC API
1237
1277
 
1238
- this.compileChildren(environment, options);
1278
+ compile: function(environment, options, context, asObject) {
1279
+ this.environment = environment;
1280
+ this.options = options || {};
1239
1281
 
1240
- var opcodes = environment.opcodes, opcode;
1282
+ Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n");
1241
1283
 
1242
- this.i = 0;
1284
+ this.name = this.environment.name;
1285
+ this.isChild = !!context;
1286
+ this.context = context || {
1287
+ programs: [],
1288
+ environments: [],
1289
+ aliases: { }
1290
+ };
1243
1291
 
1244
- for(l=opcodes.length; this.i<l; this.i++) {
1245
- opcode = opcodes[this.i];
1292
+ this.preamble();
1246
1293
 
1247
- if(opcode.opcode === 'DECLARE') {
1248
- this[opcode.name] = opcode.value;
1249
- } else {
1250
- this[opcode.opcode].apply(this, opcode.args);
1251
- }
1252
- }
1294
+ this.stackSlot = 0;
1295
+ this.stackVars = [];
1296
+ this.registers = { list: [] };
1297
+ this.compileStack = [];
1298
+ this.inlineStack = [];
1253
1299
 
1254
- return this.createFunctionContext(asObject);
1255
- },
1300
+ this.compileChildren(environment, options);
1256
1301
 
1257
- nextOpcode: function() {
1258
- var opcodes = this.environment.opcodes, opcode = opcodes[this.i + 1];
1259
- return opcodes[this.i + 1];
1260
- },
1302
+ var opcodes = environment.opcodes, opcode;
1261
1303
 
1262
- eat: function(opcode) {
1263
- this.i = this.i + 1;
1264
- },
1304
+ this.i = 0;
1265
1305
 
1266
- preamble: function() {
1267
- var out = [];
1306
+ for(l=opcodes.length; this.i<l; this.i++) {
1307
+ opcode = opcodes[this.i];
1268
1308
 
1269
- if (!this.isChild) {
1270
- var namespace = this.namespace;
1271
- var copies = "helpers = helpers || " + namespace + ".helpers;";
1272
- if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
1273
- if (this.options.data) { copies = copies + " data = data || {};"; }
1274
- out.push(copies);
1309
+ if(opcode.opcode === 'DECLARE') {
1310
+ this[opcode.name] = opcode.value;
1275
1311
  } else {
1276
- out.push('');
1312
+ this[opcode.opcode].apply(this, opcode.args);
1277
1313
  }
1314
+ }
1278
1315
 
1279
- if (!this.environment.isSimple) {
1280
- out.push(", buffer = " + this.initializeBuffer());
1281
- } else {
1282
- out.push("");
1283
- }
1316
+ return this.createFunctionContext(asObject);
1317
+ },
1284
1318
 
1285
- // track the last context pushed into place to allow skipping the
1286
- // getContext opcode when it would be a noop
1287
- this.lastContext = 0;
1288
- this.source = out;
1289
- },
1319
+ nextOpcode: function() {
1320
+ var opcodes = this.environment.opcodes;
1321
+ return opcodes[this.i + 1];
1322
+ },
1290
1323
 
1291
- createFunctionContext: function(asObject) {
1292
- var locals = this.stackVars.concat(this.registers.list);
1324
+ eat: function() {
1325
+ this.i = this.i + 1;
1326
+ },
1293
1327
 
1294
- if(locals.length > 0) {
1295
- this.source[1] = this.source[1] + ", " + locals.join(", ");
1296
- }
1328
+ preamble: function() {
1329
+ var out = [];
1297
1330
 
1298
- // Generate minimizer alias mappings
1299
- if (!this.isChild) {
1300
- var aliases = [];
1301
- for (var alias in this.context.aliases) {
1302
- this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
1303
- }
1304
- }
1331
+ if (!this.isChild) {
1332
+ var namespace = this.namespace;
1333
+ var copies = "helpers = helpers || " + namespace + ".helpers;";
1334
+ if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
1335
+ if (this.options.data) { copies = copies + " data = data || {};"; }
1336
+ out.push(copies);
1337
+ } else {
1338
+ out.push('');
1339
+ }
1305
1340
 
1306
- if (this.source[1]) {
1307
- this.source[1] = "var " + this.source[1].substring(2) + ";";
1308
- }
1341
+ if (!this.environment.isSimple) {
1342
+ out.push(", buffer = " + this.initializeBuffer());
1343
+ } else {
1344
+ out.push("");
1345
+ }
1309
1346
 
1310
- // Merge children
1311
- if (!this.isChild) {
1312
- this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
1313
- }
1347
+ // track the last context pushed into place to allow skipping the
1348
+ // getContext opcode when it would be a noop
1349
+ this.lastContext = 0;
1350
+ this.source = out;
1351
+ },
1314
1352
 
1315
- if (!this.environment.isSimple) {
1316
- this.source.push("return buffer;");
1317
- }
1353
+ createFunctionContext: function(asObject) {
1354
+ var locals = this.stackVars.concat(this.registers.list);
1318
1355
 
1319
- var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
1356
+ if(locals.length > 0) {
1357
+ this.source[1] = this.source[1] + ", " + locals.join(", ");
1358
+ }
1320
1359
 
1321
- for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
1322
- params.push("depth" + this.environment.depths.list[i]);
1360
+ // Generate minimizer alias mappings
1361
+ if (!this.isChild) {
1362
+ for (var alias in this.context.aliases) {
1363
+ this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
1323
1364
  }
1365
+ }
1366
+
1367
+ if (this.source[1]) {
1368
+ this.source[1] = "var " + this.source[1].substring(2) + ";";
1369
+ }
1370
+
1371
+ // Merge children
1372
+ if (!this.isChild) {
1373
+ this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
1374
+ }
1375
+
1376
+ if (!this.environment.isSimple) {
1377
+ this.source.push("return buffer;");
1378
+ }
1324
1379
 
1325
- if (asObject) {
1326
- params.push(this.source.join("\n "));
1380
+ var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
1327
1381
 
1328
- return Function.apply(this, params);
1382
+ for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
1383
+ params.push("depth" + this.environment.depths.list[i]);
1384
+ }
1385
+
1386
+ // Perform a second pass over the output to merge content when possible
1387
+ var source = this.mergeSource();
1388
+
1389
+ if (!this.isChild) {
1390
+ var revision = Handlebars.COMPILER_REVISION,
1391
+ versions = Handlebars.REVISION_CHANGES[revision];
1392
+ source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
1393
+ }
1394
+
1395
+ if (asObject) {
1396
+ params.push(source);
1397
+
1398
+ return Function.apply(this, params);
1399
+ } else {
1400
+ var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
1401
+ Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
1402
+ return functionSource;
1403
+ }
1404
+ },
1405
+ mergeSource: function() {
1406
+ // WARN: We are not handling the case where buffer is still populated as the source should
1407
+ // not have buffer append operations as their final action.
1408
+ var source = '',
1409
+ buffer;
1410
+ for (var i = 0, len = this.source.length; i < len; i++) {
1411
+ var line = this.source[i];
1412
+ if (line.appendToBuffer) {
1413
+ if (buffer) {
1414
+ buffer = buffer + '\n + ' + line.content;
1415
+ } else {
1416
+ buffer = line.content;
1417
+ }
1329
1418
  } else {
1330
- var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}';
1331
- Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
1332
- return functionSource;
1419
+ if (buffer) {
1420
+ source += 'buffer += ' + buffer + ';\n ';
1421
+ buffer = undefined;
1422
+ }
1423
+ source += line + '\n ';
1333
1424
  }
1334
- },
1425
+ }
1426
+ return source;
1427
+ },
1335
1428
 
1336
- // [blockValue]
1337
- //
1338
- // On stack, before: hash, inverse, program, value
1339
- // On stack, after: return value of blockHelperMissing
1340
- //
1341
- // The purpose of this opcode is to take a block of the form
1342
- // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
1343
- // replace it on the stack with the result of properly
1344
- // invoking blockHelperMissing.
1345
- blockValue: function() {
1346
- this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
1347
-
1348
- var params = ["depth0"];
1349
- this.setupParams(0, params);
1350
-
1351
- this.replaceStack(function(current) {
1352
- params.splice(1, 0, current);
1353
- return "blockHelperMissing.call(" + params.join(", ") + ")";
1354
- });
1355
- },
1429
+ // [blockValue]
1430
+ //
1431
+ // On stack, before: hash, inverse, program, value
1432
+ // On stack, after: return value of blockHelperMissing
1433
+ //
1434
+ // The purpose of this opcode is to take a block of the form
1435
+ // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
1436
+ // replace it on the stack with the result of properly
1437
+ // invoking blockHelperMissing.
1438
+ blockValue: function() {
1439
+ this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
1440
+
1441
+ var params = ["depth0"];
1442
+ this.setupParams(0, params);
1443
+
1444
+ this.replaceStack(function(current) {
1445
+ params.splice(1, 0, current);
1446
+ return "blockHelperMissing.call(" + params.join(", ") + ")";
1447
+ });
1448
+ },
1356
1449
 
1357
- // [ambiguousBlockValue]
1358
- //
1359
- // On stack, before: hash, inverse, program, value
1360
- // Compiler value, before: lastHelper=value of last found helper, if any
1361
- // On stack, after, if no lastHelper: same as [blockValue]
1362
- // On stack, after, if lastHelper: value
1363
- ambiguousBlockValue: function() {
1364
- this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
1450
+ // [ambiguousBlockValue]
1451
+ //
1452
+ // On stack, before: hash, inverse, program, value
1453
+ // Compiler value, before: lastHelper=value of last found helper, if any
1454
+ // On stack, after, if no lastHelper: same as [blockValue]
1455
+ // On stack, after, if lastHelper: value
1456
+ ambiguousBlockValue: function() {
1457
+ this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
1365
1458
 
1366
- var params = ["depth0"];
1367
- this.setupParams(0, params);
1459
+ var params = ["depth0"];
1460
+ this.setupParams(0, params);
1368
1461
 
1369
- var current = this.topStack();
1370
- params.splice(1, 0, current);
1462
+ var current = this.topStack();
1463
+ params.splice(1, 0, current);
1371
1464
 
1372
- this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
1373
- },
1465
+ // Use the options value generated from the invocation
1466
+ params[params.length-1] = 'options';
1374
1467
 
1375
- // [appendContent]
1376
- //
1377
- // On stack, before: ...
1378
- // On stack, after: ...
1379
- //
1380
- // Appends the string value of `content` to the current buffer
1381
- appendContent: function(content) {
1382
- this.source.push(this.appendToBuffer(this.quotedString(content)));
1383
- },
1468
+ this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
1469
+ },
1384
1470
 
1385
- // [append]
1386
- //
1387
- // On stack, before: value, ...
1388
- // On stack, after: ...
1389
- //
1390
- // Coerces `value` to a String and appends it to the current buffer.
1391
- //
1392
- // If `value` is truthy, or 0, it is coerced into a string and appended
1393
- // Otherwise, the empty string is appended
1394
- append: function() {
1395
- var local = this.popStack();
1396
- this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
1397
- if (this.environment.isSimple) {
1398
- this.source.push("else { " + this.appendToBuffer("''") + " }");
1399
- }
1400
- },
1471
+ // [appendContent]
1472
+ //
1473
+ // On stack, before: ...
1474
+ // On stack, after: ...
1475
+ //
1476
+ // Appends the string value of `content` to the current buffer
1477
+ appendContent: function(content) {
1478
+ this.source.push(this.appendToBuffer(this.quotedString(content)));
1479
+ },
1401
1480
 
1402
- // [appendEscaped]
1403
- //
1404
- // On stack, before: value, ...
1405
- // On stack, after: ...
1406
- //
1407
- // Escape `value` and append it to the buffer
1408
- appendEscaped: function() {
1409
- var opcode = this.nextOpcode(), extra = "";
1410
- this.context.aliases.escapeExpression = 'this.escapeExpression';
1411
-
1412
- if(opcode && opcode.opcode === 'appendContent') {
1413
- extra = " + " + this.quotedString(opcode.args[0]);
1414
- this.eat(opcode);
1415
- }
1481
+ // [append]
1482
+ //
1483
+ // On stack, before: value, ...
1484
+ // On stack, after: ...
1485
+ //
1486
+ // Coerces `value` to a String and appends it to the current buffer.
1487
+ //
1488
+ // If `value` is truthy, or 0, it is coerced into a string and appended
1489
+ // Otherwise, the empty string is appended
1490
+ append: function() {
1491
+ // Force anything that is inlined onto the stack so we don't have duplication
1492
+ // when we examine local
1493
+ this.flushInline();
1494
+ var local = this.popStack();
1495
+ this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
1496
+ if (this.environment.isSimple) {
1497
+ this.source.push("else { " + this.appendToBuffer("''") + " }");
1498
+ }
1499
+ },
1416
1500
 
1417
- this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
1418
- },
1501
+ // [appendEscaped]
1502
+ //
1503
+ // On stack, before: value, ...
1504
+ // On stack, after: ...
1505
+ //
1506
+ // Escape `value` and append it to the buffer
1507
+ appendEscaped: function() {
1508
+ this.context.aliases.escapeExpression = 'this.escapeExpression';
1419
1509
 
1420
- // [getContext]
1421
- //
1422
- // On stack, before: ...
1423
- // On stack, after: ...
1424
- // Compiler value, after: lastContext=depth
1425
- //
1426
- // Set the value of the `lastContext` compiler value to the depth
1427
- getContext: function(depth) {
1428
- if(this.lastContext !== depth) {
1429
- this.lastContext = depth;
1430
- }
1431
- },
1510
+ this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
1511
+ },
1432
1512
 
1433
- // [lookupOnContext]
1434
- //
1435
- // On stack, before: ...
1436
- // On stack, after: currentContext[name], ...
1437
- //
1438
- // Looks up the value of `name` on the current context and pushes
1439
- // it onto the stack.
1440
- lookupOnContext: function(name) {
1441
- this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context'));
1442
- },
1513
+ // [getContext]
1514
+ //
1515
+ // On stack, before: ...
1516
+ // On stack, after: ...
1517
+ // Compiler value, after: lastContext=depth
1518
+ //
1519
+ // Set the value of the `lastContext` compiler value to the depth
1520
+ getContext: function(depth) {
1521
+ if(this.lastContext !== depth) {
1522
+ this.lastContext = depth;
1523
+ }
1524
+ },
1443
1525
 
1444
- // [pushContext]
1445
- //
1446
- // On stack, before: ...
1447
- // On stack, after: currentContext, ...
1448
- //
1449
- // Pushes the value of the current context onto the stack.
1450
- pushContext: function() {
1451
- this.pushStackLiteral('depth' + this.lastContext);
1452
- },
1526
+ // [lookupOnContext]
1527
+ //
1528
+ // On stack, before: ...
1529
+ // On stack, after: currentContext[name], ...
1530
+ //
1531
+ // Looks up the value of `name` on the current context and pushes
1532
+ // it onto the stack.
1533
+ lookupOnContext: function(name) {
1534
+ this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
1535
+ },
1453
1536
 
1454
- // [resolvePossibleLambda]
1455
- //
1456
- // On stack, before: value, ...
1457
- // On stack, after: resolved value, ...
1458
- //
1459
- // If the `value` is a lambda, replace it on the stack by
1460
- // the return value of the lambda
1461
- resolvePossibleLambda: function() {
1462
- this.context.aliases.functionType = '"function"';
1463
-
1464
- this.replaceStack(function(current) {
1465
- return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
1466
- });
1467
- },
1537
+ // [pushContext]
1538
+ //
1539
+ // On stack, before: ...
1540
+ // On stack, after: currentContext, ...
1541
+ //
1542
+ // Pushes the value of the current context onto the stack.
1543
+ pushContext: function() {
1544
+ this.pushStackLiteral('depth' + this.lastContext);
1545
+ },
1468
1546
 
1469
- // [lookup]
1470
- //
1471
- // On stack, before: value, ...
1472
- // On stack, after: value[name], ...
1473
- //
1474
- // Replace the value on the stack with the result of looking
1475
- // up `name` on `value`
1476
- lookup: function(name) {
1477
- this.replaceStack(function(current) {
1478
- return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
1479
- });
1480
- },
1547
+ // [resolvePossibleLambda]
1548
+ //
1549
+ // On stack, before: value, ...
1550
+ // On stack, after: resolved value, ...
1551
+ //
1552
+ // If the `value` is a lambda, replace it on the stack by
1553
+ // the return value of the lambda
1554
+ resolvePossibleLambda: function() {
1555
+ this.context.aliases.functionType = '"function"';
1556
+
1557
+ this.replaceStack(function(current) {
1558
+ return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
1559
+ });
1560
+ },
1481
1561
 
1482
- // [lookupData]
1483
- //
1484
- // On stack, before: ...
1485
- // On stack, after: data[id], ...
1486
- //
1487
- // Push the result of looking up `id` on the current data
1488
- lookupData: function(id) {
1489
- this.pushStack(this.nameLookup('data', id, 'data'));
1490
- },
1562
+ // [lookup]
1563
+ //
1564
+ // On stack, before: value, ...
1565
+ // On stack, after: value[name], ...
1566
+ //
1567
+ // Replace the value on the stack with the result of looking
1568
+ // up `name` on `value`
1569
+ lookup: function(name) {
1570
+ this.replaceStack(function(current) {
1571
+ return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
1572
+ });
1573
+ },
1491
1574
 
1492
- // [pushStringParam]
1493
- //
1494
- // On stack, before: ...
1495
- // On stack, after: string, currentContext, ...
1496
- //
1497
- // This opcode is designed for use in string mode, which
1498
- // provides the string value of a parameter along with its
1499
- // depth rather than resolving it immediately.
1500
- pushStringParam: function(string, type) {
1501
- this.pushStackLiteral('depth' + this.lastContext);
1502
-
1503
- this.pushString(type);
1504
-
1505
- if (typeof string === 'string') {
1506
- this.pushString(string);
1507
- } else {
1508
- this.pushStackLiteral(string);
1509
- }
1510
- },
1575
+ // [lookupData]
1576
+ //
1577
+ // On stack, before: ...
1578
+ // On stack, after: data[id], ...
1579
+ //
1580
+ // Push the result of looking up `id` on the current data
1581
+ lookupData: function(id) {
1582
+ this.push(this.nameLookup('data', id, 'data'));
1583
+ },
1511
1584
 
1512
- pushHash: function() {
1513
- this.push('{}');
1585
+ // [pushStringParam]
1586
+ //
1587
+ // On stack, before: ...
1588
+ // On stack, after: string, currentContext, ...
1589
+ //
1590
+ // This opcode is designed for use in string mode, which
1591
+ // provides the string value of a parameter along with its
1592
+ // depth rather than resolving it immediately.
1593
+ pushStringParam: function(string, type) {
1594
+ this.pushStackLiteral('depth' + this.lastContext);
1595
+
1596
+ this.pushString(type);
1597
+
1598
+ if (typeof string === 'string') {
1599
+ this.pushString(string);
1600
+ } else {
1601
+ this.pushStackLiteral(string);
1602
+ }
1603
+ },
1514
1604
 
1515
- if (this.options.stringParams) {
1516
- this.register('hashTypes', '{}');
1517
- }
1518
- },
1605
+ emptyHash: function() {
1606
+ this.pushStackLiteral('{}');
1519
1607
 
1520
- // [pushString]
1521
- //
1522
- // On stack, before: ...
1523
- // On stack, after: quotedString(string), ...
1524
- //
1525
- // Push a quoted version of `string` onto the stack
1526
- pushString: function(string) {
1527
- this.pushStackLiteral(this.quotedString(string));
1528
- },
1608
+ if (this.options.stringParams) {
1609
+ this.register('hashTypes', '{}');
1610
+ }
1611
+ },
1612
+ pushHash: function() {
1613
+ this.hash = {values: [], types: []};
1614
+ },
1615
+ popHash: function() {
1616
+ var hash = this.hash;
1617
+ this.hash = undefined;
1529
1618
 
1530
- // [push]
1531
- //
1532
- // On stack, before: ...
1533
- // On stack, after: expr, ...
1534
- //
1535
- // Push an expression onto the stack
1536
- push: function(expr) {
1537
- this.pushStack(expr);
1538
- },
1619
+ if (this.options.stringParams) {
1620
+ this.register('hashTypes', '{' + hash.types.join(',') + '}');
1621
+ }
1622
+ this.push('{\n ' + hash.values.join(',\n ') + '\n }');
1623
+ },
1539
1624
 
1540
- // [pushLiteral]
1541
- //
1542
- // On stack, before: ...
1543
- // On stack, after: value, ...
1544
- //
1545
- // Pushes a value onto the stack. This operation prevents
1546
- // the compiler from creating a temporary variable to hold
1547
- // it.
1548
- pushLiteral: function(value) {
1549
- this.pushStackLiteral(value);
1550
- },
1625
+ // [pushString]
1626
+ //
1627
+ // On stack, before: ...
1628
+ // On stack, after: quotedString(string), ...
1629
+ //
1630
+ // Push a quoted version of `string` onto the stack
1631
+ pushString: function(string) {
1632
+ this.pushStackLiteral(this.quotedString(string));
1633
+ },
1551
1634
 
1552
- // [pushProgram]
1553
- //
1554
- // On stack, before: ...
1555
- // On stack, after: program(guid), ...
1556
- //
1557
- // Push a program expression onto the stack. This takes
1558
- // a compile-time guid and converts it into a runtime-accessible
1559
- // expression.
1560
- pushProgram: function(guid) {
1561
- if (guid != null) {
1562
- this.pushStackLiteral(this.programExpression(guid));
1563
- } else {
1564
- this.pushStackLiteral(null);
1565
- }
1566
- },
1635
+ // [push]
1636
+ //
1637
+ // On stack, before: ...
1638
+ // On stack, after: expr, ...
1639
+ //
1640
+ // Push an expression onto the stack
1641
+ push: function(expr) {
1642
+ this.inlineStack.push(expr);
1643
+ return expr;
1644
+ },
1567
1645
 
1568
- // [invokeHelper]
1569
- //
1570
- // On stack, before: hash, inverse, program, params..., ...
1571
- // On stack, after: result of helper invocation
1572
- //
1573
- // Pops off the helper's parameters, invokes the helper,
1574
- // and pushes the helper's return value onto the stack.
1575
- //
1576
- // If the helper is not found, `helperMissing` is called.
1577
- invokeHelper: function(paramSize, name) {
1578
- this.context.aliases.helperMissing = 'helpers.helperMissing';
1579
-
1580
- var helper = this.lastHelper = this.setupHelper(paramSize, name);
1581
- this.register('foundHelper', helper.name);
1582
-
1583
- this.pushStack("foundHelper ? foundHelper.call(" +
1584
- helper.callParams + ") " + ": helperMissing.call(" +
1585
- helper.helperMissingParams + ")");
1586
- },
1646
+ // [pushLiteral]
1647
+ //
1648
+ // On stack, before: ...
1649
+ // On stack, after: value, ...
1650
+ //
1651
+ // Pushes a value onto the stack. This operation prevents
1652
+ // the compiler from creating a temporary variable to hold
1653
+ // it.
1654
+ pushLiteral: function(value) {
1655
+ this.pushStackLiteral(value);
1656
+ },
1587
1657
 
1588
- // [invokeKnownHelper]
1589
- //
1590
- // On stack, before: hash, inverse, program, params..., ...
1591
- // On stack, after: result of helper invocation
1592
- //
1593
- // This operation is used when the helper is known to exist,
1594
- // so a `helperMissing` fallback is not required.
1595
- invokeKnownHelper: function(paramSize, name) {
1596
- var helper = this.setupHelper(paramSize, name);
1597
- this.pushStack(helper.name + ".call(" + helper.callParams + ")");
1598
- },
1658
+ // [pushProgram]
1659
+ //
1660
+ // On stack, before: ...
1661
+ // On stack, after: program(guid), ...
1662
+ //
1663
+ // Push a program expression onto the stack. This takes
1664
+ // a compile-time guid and converts it into a runtime-accessible
1665
+ // expression.
1666
+ pushProgram: function(guid) {
1667
+ if (guid != null) {
1668
+ this.pushStackLiteral(this.programExpression(guid));
1669
+ } else {
1670
+ this.pushStackLiteral(null);
1671
+ }
1672
+ },
1599
1673
 
1600
- // [invokeAmbiguous]
1601
- //
1602
- // On stack, before: hash, inverse, program, params..., ...
1603
- // On stack, after: result of disambiguation
1604
- //
1605
- // This operation is used when an expression like `{{foo}}`
1606
- // is provided, but we don't know at compile-time whether it
1607
- // is a helper or a path.
1608
- //
1609
- // This operation emits more code than the other options,
1610
- // and can be avoided by passing the `knownHelpers` and
1611
- // `knownHelpersOnly` flags at compile-time.
1612
- invokeAmbiguous: function(name) {
1613
- this.context.aliases.functionType = '"function"';
1614
-
1615
- this.pushStackLiteral('{}');
1616
- var helper = this.setupHelper(0, name);
1617
-
1618
- var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
1619
- this.register('foundHelper', helperName);
1620
-
1621
- var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
1622
- var nextStack = this.nextStack();
1623
-
1624
- this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
1625
- this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
1626
- },
1674
+ // [invokeHelper]
1675
+ //
1676
+ // On stack, before: hash, inverse, program, params..., ...
1677
+ // On stack, after: result of helper invocation
1678
+ //
1679
+ // Pops off the helper's parameters, invokes the helper,
1680
+ // and pushes the helper's return value onto the stack.
1681
+ //
1682
+ // If the helper is not found, `helperMissing` is called.
1683
+ invokeHelper: function(paramSize, name) {
1684
+ this.context.aliases.helperMissing = 'helpers.helperMissing';
1685
+
1686
+ var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
1687
+
1688
+ this.push(helper.name);
1689
+ this.replaceStack(function(name) {
1690
+ return name + ' ? ' + name + '.call(' +
1691
+ helper.callParams + ") " + ": helperMissing.call(" +
1692
+ helper.helperMissingParams + ")";
1693
+ });
1694
+ },
1627
1695
 
1628
- // [invokePartial]
1629
- //
1630
- // On stack, before: context, ...
1631
- // On stack after: result of partial invocation
1632
- //
1633
- // This operation pops off a context, invokes a partial with that context,
1634
- // and pushes the result of the invocation back.
1635
- invokePartial: function(name) {
1636
- var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
1637
-
1638
- if (this.options.data) {
1639
- params.push("data");
1640
- }
1696
+ // [invokeKnownHelper]
1697
+ //
1698
+ // On stack, before: hash, inverse, program, params..., ...
1699
+ // On stack, after: result of helper invocation
1700
+ //
1701
+ // This operation is used when the helper is known to exist,
1702
+ // so a `helperMissing` fallback is not required.
1703
+ invokeKnownHelper: function(paramSize, name) {
1704
+ var helper = this.setupHelper(paramSize, name);
1705
+ this.push(helper.name + ".call(" + helper.callParams + ")");
1706
+ },
1641
1707
 
1642
- this.context.aliases.self = "this";
1643
- this.pushStack("self.invokePartial(" + params.join(", ") + ")");
1644
- },
1708
+ // [invokeAmbiguous]
1709
+ //
1710
+ // On stack, before: hash, inverse, program, params..., ...
1711
+ // On stack, after: result of disambiguation
1712
+ //
1713
+ // This operation is used when an expression like `{{foo}}`
1714
+ // is provided, but we don't know at compile-time whether it
1715
+ // is a helper or a path.
1716
+ //
1717
+ // This operation emits more code than the other options,
1718
+ // and can be avoided by passing the `knownHelpers` and
1719
+ // `knownHelpersOnly` flags at compile-time.
1720
+ invokeAmbiguous: function(name, helperCall) {
1721
+ this.context.aliases.functionType = '"function"';
1722
+
1723
+ this.pushStackLiteral('{}'); // Hash value
1724
+ var helper = this.setupHelper(0, name, helperCall);
1725
+
1726
+ var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
1727
+
1728
+ var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
1729
+ var nextStack = this.nextStack();
1730
+
1731
+ this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
1732
+ this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
1733
+ },
1645
1734
 
1646
- // [assignToHash]
1647
- //
1648
- // On stack, before: value, hash, ...
1649
- // On stack, after: hash, ...
1650
- //
1651
- // Pops a value and hash off the stack, assigns `hash[key] = value`
1652
- // and pushes the hash back onto the stack.
1653
- assignToHash: function(key) {
1654
- var value = this.popStack();
1735
+ // [invokePartial]
1736
+ //
1737
+ // On stack, before: context, ...
1738
+ // On stack after: result of partial invocation
1739
+ //
1740
+ // This operation pops off a context, invokes a partial with that context,
1741
+ // and pushes the result of the invocation back.
1742
+ invokePartial: function(name) {
1743
+ var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
1744
+
1745
+ if (this.options.data) {
1746
+ params.push("data");
1747
+ }
1655
1748
 
1656
- if (this.options.stringParams) {
1657
- var type = this.popStack();
1658
- this.popStack();
1659
- this.source.push("hashTypes['" + key + "'] = " + type + ";");
1660
- }
1749
+ this.context.aliases.self = "this";
1750
+ this.push("self.invokePartial(" + params.join(", ") + ")");
1751
+ },
1752
+
1753
+ // [assignToHash]
1754
+ //
1755
+ // On stack, before: value, hash, ...
1756
+ // On stack, after: hash, ...
1757
+ //
1758
+ // Pops a value and hash off the stack, assigns `hash[key] = value`
1759
+ // and pushes the hash back onto the stack.
1760
+ assignToHash: function(key) {
1761
+ var value = this.popStack(),
1762
+ type;
1763
+
1764
+ if (this.options.stringParams) {
1765
+ type = this.popStack();
1766
+ this.popStack();
1767
+ }
1661
1768
 
1662
- var hash = this.topStack();
1769
+ var hash = this.hash;
1770
+ if (type) {
1771
+ hash.types.push("'" + key + "': " + type);
1772
+ }
1773
+ hash.values.push("'" + key + "': (" + value + ")");
1774
+ },
1663
1775
 
1664
- this.source.push(hash + "['" + key + "'] = " + value + ";");
1665
- },
1776
+ // HELPERS
1666
1777
 
1667
- // HELPERS
1778
+ compiler: JavaScriptCompiler,
1668
1779
 
1669
- compiler: JavaScriptCompiler,
1780
+ compileChildren: function(environment, options) {
1781
+ var children = environment.children, child, compiler;
1670
1782
 
1671
- compileChildren: function(environment, options) {
1672
- var children = environment.children, child, compiler;
1783
+ for(var i=0, l=children.length; i<l; i++) {
1784
+ child = children[i];
1785
+ compiler = new this.compiler();
1673
1786
 
1674
- for(var i=0, l=children.length; i<l; i++) {
1675
- child = children[i];
1676
- compiler = new this.compiler();
1787
+ var index = this.matchExistingProgram(child);
1677
1788
 
1789
+ if (index == null) {
1678
1790
  this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
1679
- var index = this.context.programs.length;
1791
+ index = this.context.programs.length;
1680
1792
  child.index = index;
1681
1793
  child.name = 'program' + index;
1682
1794
  this.context.programs[index] = compiler.compile(child, options, this.context);
1795
+ this.context.environments[index] = child;
1796
+ } else {
1797
+ child.index = index;
1798
+ child.name = 'program' + index;
1683
1799
  }
1684
- },
1800
+ }
1801
+ },
1802
+ matchExistingProgram: function(child) {
1803
+ for (var i = 0, len = this.context.environments.length; i < len; i++) {
1804
+ var environment = this.context.environments[i];
1805
+ if (environment && environment.equals(child)) {
1806
+ return i;
1807
+ }
1808
+ }
1809
+ },
1685
1810
 
1686
- programExpression: function(guid) {
1687
- this.context.aliases.self = "this";
1811
+ programExpression: function(guid) {
1812
+ this.context.aliases.self = "this";
1688
1813
 
1689
- if(guid == null) {
1690
- return "self.noop";
1691
- }
1814
+ if(guid == null) {
1815
+ return "self.noop";
1816
+ }
1692
1817
 
1693
- var child = this.environment.children[guid],
1694
- depths = child.depths.list, depth;
1818
+ var child = this.environment.children[guid],
1819
+ depths = child.depths.list, depth;
1695
1820
 
1696
- var programParams = [child.index, child.name, "data"];
1821
+ var programParams = [child.index, child.name, "data"];
1697
1822
 
1698
- for(var i=0, l = depths.length; i<l; i++) {
1699
- depth = depths[i];
1823
+ for(var i=0, l = depths.length; i<l; i++) {
1824
+ depth = depths[i];
1700
1825
 
1701
- if(depth === 1) { programParams.push("depth0"); }
1702
- else { programParams.push("depth" + (depth - 1)); }
1703
- }
1826
+ if(depth === 1) { programParams.push("depth0"); }
1827
+ else { programParams.push("depth" + (depth - 1)); }
1828
+ }
1704
1829
 
1705
- if(depths.length === 0) {
1706
- return "self.program(" + programParams.join(", ") + ")";
1707
- } else {
1708
- programParams.shift();
1709
- return "self.programWithDepth(" + programParams.join(", ") + ")";
1710
- }
1711
- },
1830
+ if(depths.length === 0) {
1831
+ return "self.program(" + programParams.join(", ") + ")";
1832
+ } else {
1833
+ programParams.shift();
1834
+ return "self.programWithDepth(" + programParams.join(", ") + ")";
1835
+ }
1836
+ },
1712
1837
 
1713
- register: function(name, val) {
1714
- this.useRegister(name);
1715
- this.source.push(name + " = " + val + ";");
1716
- },
1838
+ register: function(name, val) {
1839
+ this.useRegister(name);
1840
+ this.source.push(name + " = " + val + ";");
1841
+ },
1717
1842
 
1718
- useRegister: function(name) {
1719
- if(!this.registers[name]) {
1720
- this.registers[name] = true;
1721
- this.registers.list.push(name);
1722
- }
1723
- },
1843
+ useRegister: function(name) {
1844
+ if(!this.registers[name]) {
1845
+ this.registers[name] = true;
1846
+ this.registers.list.push(name);
1847
+ }
1848
+ },
1724
1849
 
1725
- pushStackLiteral: function(item) {
1726
- this.compileStack.push(new Literal(item));
1727
- return item;
1728
- },
1850
+ pushStackLiteral: function(item) {
1851
+ return this.push(new Literal(item));
1852
+ },
1853
+
1854
+ pushStack: function(item) {
1855
+ this.flushInline();
1729
1856
 
1730
- pushStack: function(item) {
1731
- var stack = this.incrStack();
1857
+ var stack = this.incrStack();
1858
+ if (item) {
1732
1859
  this.source.push(stack + " = " + item + ";");
1733
- this.compileStack.push(stack);
1734
- return stack;
1735
- },
1860
+ }
1861
+ this.compileStack.push(stack);
1862
+ return stack;
1863
+ },
1864
+
1865
+ replaceStack: function(callback) {
1866
+ var prefix = '',
1867
+ inline = this.isInline(),
1868
+ stack;
1736
1869
 
1737
- replaceStack: function(callback) {
1738
- var stack = this.topStack(),
1739
- item = callback.call(this, stack);
1870
+ // If we are currently inline then we want to merge the inline statement into the
1871
+ // replacement statement via ','
1872
+ if (inline) {
1873
+ var top = this.popStack(true);
1740
1874
 
1875
+ if (top instanceof Literal) {
1876
+ // Literals do not need to be inlined
1877
+ stack = top.value;
1878
+ } else {
1879
+ // Get or create the current stack name for use by the inline
1880
+ var name = this.stackSlot ? this.topStackName() : this.incrStack();
1881
+
1882
+ prefix = '(' + this.push(name) + ' = ' + top + '),';
1883
+ stack = this.topStack();
1884
+ }
1885
+ } else {
1886
+ stack = this.topStack();
1887
+ }
1888
+
1889
+ var item = callback.call(this, stack);
1890
+
1891
+ if (inline) {
1892
+ if (this.inlineStack.length || this.compileStack.length) {
1893
+ this.popStack();
1894
+ }
1895
+ this.push('(' + prefix + item + ')');
1896
+ } else {
1741
1897
  // Prevent modification of the context depth variable. Through replaceStack
1742
- if (/^depth/.test(stack)) {
1898
+ if (!/^stack/.test(stack)) {
1743
1899
  stack = this.nextStack();
1744
1900
  }
1745
1901
 
1746
- this.source.push(stack + " = " + item + ";");
1747
- return stack;
1748
- },
1902
+ this.source.push(stack + " = (" + prefix + item + ");");
1903
+ }
1904
+ return stack;
1905
+ },
1749
1906
 
1750
- nextStack: function(skipCompileStack) {
1751
- var name = this.incrStack();
1752
- this.compileStack.push(name);
1753
- return name;
1754
- },
1907
+ nextStack: function() {
1908
+ return this.pushStack();
1909
+ },
1755
1910
 
1756
- incrStack: function() {
1757
- this.stackSlot++;
1758
- if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1759
- return "stack" + this.stackSlot;
1760
- },
1911
+ incrStack: function() {
1912
+ this.stackSlot++;
1913
+ if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1914
+ return this.topStackName();
1915
+ },
1916
+ topStackName: function() {
1917
+ return "stack" + this.stackSlot;
1918
+ },
1919
+ flushInline: function() {
1920
+ var inlineStack = this.inlineStack;
1921
+ if (inlineStack.length) {
1922
+ this.inlineStack = [];
1923
+ for (var i = 0, len = inlineStack.length; i < len; i++) {
1924
+ var entry = inlineStack[i];
1925
+ if (entry instanceof Literal) {
1926
+ this.compileStack.push(entry);
1927
+ } else {
1928
+ this.pushStack(entry);
1929
+ }
1930
+ }
1931
+ }
1932
+ },
1933
+ isInline: function() {
1934
+ return this.inlineStack.length;
1935
+ },
1761
1936
 
1762
- popStack: function() {
1763
- var item = this.compileStack.pop();
1937
+ popStack: function(wrapped) {
1938
+ var inline = this.isInline(),
1939
+ item = (inline ? this.inlineStack : this.compileStack).pop();
1764
1940
 
1765
- if (item instanceof Literal) {
1766
- return item.value;
1767
- } else {
1941
+ if (!wrapped && (item instanceof Literal)) {
1942
+ return item.value;
1943
+ } else {
1944
+ if (!inline) {
1768
1945
  this.stackSlot--;
1769
- return item;
1770
1946
  }
1771
- },
1947
+ return item;
1948
+ }
1949
+ },
1772
1950
 
1773
- topStack: function() {
1774
- var item = this.compileStack[this.compileStack.length - 1];
1951
+ topStack: function(wrapped) {
1952
+ var stack = (this.isInline() ? this.inlineStack : this.compileStack),
1953
+ item = stack[stack.length - 1];
1775
1954
 
1776
- if (item instanceof Literal) {
1777
- return item.value;
1778
- } else {
1779
- return item;
1780
- }
1781
- },
1955
+ if (!wrapped && (item instanceof Literal)) {
1956
+ return item.value;
1957
+ } else {
1958
+ return item;
1959
+ }
1960
+ },
1782
1961
 
1783
- quotedString: function(str) {
1784
- return '"' + str
1785
- .replace(/\\/g, '\\\\')
1786
- .replace(/"/g, '\\"')
1787
- .replace(/\n/g, '\\n')
1788
- .replace(/\r/g, '\\r') + '"';
1789
- },
1962
+ quotedString: function(str) {
1963
+ return '"' + str
1964
+ .replace(/\\/g, '\\\\')
1965
+ .replace(/"/g, '\\"')
1966
+ .replace(/\n/g, '\\n')
1967
+ .replace(/\r/g, '\\r') + '"';
1968
+ },
1790
1969
 
1791
- setupHelper: function(paramSize, name) {
1792
- var params = [];
1793
- this.setupParams(paramSize, params);
1794
- var foundHelper = this.nameLookup('helpers', name, 'helper');
1970
+ setupHelper: function(paramSize, name, missingParams) {
1971
+ var params = [];
1972
+ this.setupParams(paramSize, params, missingParams);
1973
+ var foundHelper = this.nameLookup('helpers', name, 'helper');
1795
1974
 
1796
- return {
1797
- params: params,
1798
- name: foundHelper,
1799
- callParams: ["depth0"].concat(params).join(", "),
1800
- helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ")
1801
- };
1802
- },
1975
+ return {
1976
+ params: params,
1977
+ name: foundHelper,
1978
+ callParams: ["depth0"].concat(params).join(", "),
1979
+ helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
1980
+ };
1981
+ },
1803
1982
 
1804
- // the params and contexts arguments are passed in arrays
1805
- // to fill in
1806
- setupParams: function(paramSize, params) {
1807
- var options = [], contexts = [], types = [], param, inverse, program;
1983
+ // the params and contexts arguments are passed in arrays
1984
+ // to fill in
1985
+ setupParams: function(paramSize, params, useRegister) {
1986
+ var options = [], contexts = [], types = [], param, inverse, program;
1808
1987
 
1809
- options.push("hash:" + this.popStack());
1988
+ options.push("hash:" + this.popStack());
1810
1989
 
1811
- inverse = this.popStack();
1812
- program = this.popStack();
1990
+ inverse = this.popStack();
1991
+ program = this.popStack();
1813
1992
 
1814
- // Avoid setting fn and inverse if neither are set. This allows
1815
- // helpers to do a check for `if (options.fn)`
1816
- if (program || inverse) {
1817
- if (!program) {
1818
- this.context.aliases.self = "this";
1819
- program = "self.noop";
1820
- }
1821
-
1822
- if (!inverse) {
1823
- this.context.aliases.self = "this";
1824
- inverse = "self.noop";
1825
- }
1993
+ // Avoid setting fn and inverse if neither are set. This allows
1994
+ // helpers to do a check for `if (options.fn)`
1995
+ if (program || inverse) {
1996
+ if (!program) {
1997
+ this.context.aliases.self = "this";
1998
+ program = "self.noop";
1999
+ }
1826
2000
 
1827
- options.push("inverse:" + inverse);
1828
- options.push("fn:" + program);
2001
+ if (!inverse) {
2002
+ this.context.aliases.self = "this";
2003
+ inverse = "self.noop";
1829
2004
  }
1830
2005
 
1831
- for(var i=0; i<paramSize; i++) {
1832
- param = this.popStack();
1833
- params.push(param);
2006
+ options.push("inverse:" + inverse);
2007
+ options.push("fn:" + program);
2008
+ }
1834
2009
 
1835
- if(this.options.stringParams) {
1836
- types.push(this.popStack());
1837
- contexts.push(this.popStack());
1838
- }
1839
- }
2010
+ for(var i=0; i<paramSize; i++) {
2011
+ param = this.popStack();
2012
+ params.push(param);
1840
2013
 
1841
- if (this.options.stringParams) {
1842
- options.push("contexts:[" + contexts.join(",") + "]");
1843
- options.push("types:[" + types.join(",") + "]");
1844
- options.push("hashTypes:hashTypes");
2014
+ if(this.options.stringParams) {
2015
+ types.push(this.popStack());
2016
+ contexts.push(this.popStack());
1845
2017
  }
2018
+ }
1846
2019
 
1847
- if(this.options.data) {
1848
- options.push("data:data");
1849
- }
2020
+ if (this.options.stringParams) {
2021
+ options.push("contexts:[" + contexts.join(",") + "]");
2022
+ options.push("types:[" + types.join(",") + "]");
2023
+ options.push("hashTypes:hashTypes");
2024
+ }
1850
2025
 
1851
- params.push("{" + options.join(",") + "}");
1852
- return params.join(", ");
2026
+ if(this.options.data) {
2027
+ options.push("data:data");
1853
2028
  }
1854
- };
1855
2029
 
1856
- var reservedWords = (
1857
- "break else new var" +
1858
- " case finally return void" +
1859
- " catch for switch while" +
1860
- " continue function this with" +
1861
- " default if throw" +
1862
- " delete in try" +
1863
- " do instanceof typeof" +
1864
- " abstract enum int short" +
1865
- " boolean export interface static" +
1866
- " byte extends long super" +
1867
- " char final native synchronized" +
1868
- " class float package throws" +
1869
- " const goto private transient" +
1870
- " debugger implements protected volatile" +
1871
- " double import public let yield"
1872
- ).split(" ");
1873
-
1874
- var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
1875
-
1876
- for(var i=0, l=reservedWords.length; i<l; i++) {
1877
- compilerWords[reservedWords[i]] = true;
2030
+ options = "{" + options.join(",") + "}";
2031
+ if (useRegister) {
2032
+ this.register('options', options);
2033
+ params.push('options');
2034
+ } else {
2035
+ params.push(options);
2036
+ }
2037
+ return params.join(", ");
1878
2038
  }
2039
+ };
1879
2040
 
1880
- JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
1881
- if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
1882
- return true;
1883
- }
1884
- return false;
1885
- };
2041
+ var reservedWords = (
2042
+ "break else new var" +
2043
+ " case finally return void" +
2044
+ " catch for switch while" +
2045
+ " continue function this with" +
2046
+ " default if throw" +
2047
+ " delete in try" +
2048
+ " do instanceof typeof" +
2049
+ " abstract enum int short" +
2050
+ " boolean export interface static" +
2051
+ " byte extends long super" +
2052
+ " char final native synchronized" +
2053
+ " class float package throws" +
2054
+ " const goto private transient" +
2055
+ " debugger implements protected volatile" +
2056
+ " double import public let yield"
2057
+ ).split(" ");
2058
+
2059
+ var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
2060
+
2061
+ for(var i=0, l=reservedWords.length; i<l; i++) {
2062
+ compilerWords[reservedWords[i]] = true;
2063
+ }
1886
2064
 
1887
- })(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
2065
+ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
2066
+ if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
2067
+ return true;
2068
+ }
2069
+ return false;
2070
+ };
1888
2071
 
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);
2072
+ Handlebars.precompile = function(input, options) {
2073
+ if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
2074
+ throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
1892
2075
  }
1893
2076
 
1894
2077
  options = options || {};
1895
2078
  if (!('data' in options)) {
1896
2079
  options.data = true;
1897
2080
  }
1898
- var ast = Handlebars.parse(string);
1899
- var environment = new Handlebars.Compiler().compile(ast, options);
1900
- return new Handlebars.JavaScriptCompiler().compile(environment, options);
2081
+ var ast = Handlebars.parse(input);
2082
+ var environment = new Compiler().compile(ast, options);
2083
+ return new JavaScriptCompiler().compile(environment, options);
1901
2084
  };
1902
2085
 
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);
2086
+ Handlebars.compile = function(input, options) {
2087
+ if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
2088
+ throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
1906
2089
  }
1907
2090
 
1908
2091
  options = options || {};
@@ -1911,9 +2094,9 @@ Handlebars.compile = function(string, options) {
1911
2094
  }
1912
2095
  var compiled;
1913
2096
  function compile() {
1914
- var ast = Handlebars.parse(string);
1915
- var environment = new Handlebars.Compiler().compile(ast, options);
1916
- var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
2097
+ var ast = Handlebars.parse(input);
2098
+ var environment = new Compiler().compile(ast, options);
2099
+ var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true);
1917
2100
  return Handlebars.template(templateSpec);
1918
2101
  }
1919
2102
 
@@ -1925,8 +2108,10 @@ Handlebars.compile = function(string, options) {
1925
2108
  return compiled.call(this, context, options);
1926
2109
  };
1927
2110
  };
2111
+
1928
2112
  ;
1929
2113
  // lib/handlebars/runtime.js
2114
+
1930
2115
  Handlebars.VM = {
1931
2116
  template: function(templateSpec) {
1932
2117
  // Just add water
@@ -1946,12 +2131,32 @@ Handlebars.VM = {
1946
2131
  }
1947
2132
  },
1948
2133
  programWithDepth: Handlebars.VM.programWithDepth,
1949
- noop: Handlebars.VM.noop
2134
+ noop: Handlebars.VM.noop,
2135
+ compilerInfo: null
1950
2136
  };
1951
2137
 
1952
2138
  return function(context, options) {
1953
2139
  options = options || {};
1954
- return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
2140
+ var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
2141
+
2142
+ var compilerInfo = container.compilerInfo || [],
2143
+ compilerRevision = compilerInfo[0] || 1,
2144
+ currentRevision = Handlebars.COMPILER_REVISION;
2145
+
2146
+ if (compilerRevision !== currentRevision) {
2147
+ if (compilerRevision < currentRevision) {
2148
+ var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
2149
+ compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
2150
+ throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
2151
+ "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
2152
+ } else {
2153
+ // Use the embedded version info since the runtime doesn't know about this revision yet
2154
+ throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
2155
+ "Please update your runtime to a newer version ("+compilerInfo[1]+").";
2156
+ }
2157
+ }
2158
+
2159
+ return result;
1955
2160
  };
1956
2161
  },
1957
2162
 
@@ -1990,3 +2195,6 @@ Handlebars.VM = {
1990
2195
 
1991
2196
  Handlebars.template = Handlebars.VM.template;
1992
2197
  ;
2198
+ // lib/handlebars/browser-suffix.js
2199
+ })(Handlebars);
2200
+ ;