handlebars-source 1.0.7 → 1.0.9

Sign up to get free protection for your applications and to get access to all the features.

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
+ ;