templebars 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,11 @@
1
1
  // lib/handlebars/base.js
2
2
 
3
3
  /*jshint eqnull:true*/
4
- var Handlebars = {};
4
+ this.Handlebars = {};
5
5
 
6
- Handlebars.VERSION = "1.0.beta.5";
6
+ (function(Handlebars) {
7
+
8
+ Handlebars.VERSION = "1.0.rc.1";
7
9
 
8
10
  Handlebars.helpers = {};
9
11
  Handlebars.partials = {};
@@ -42,25 +44,36 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) {
42
44
  return inverse(this);
43
45
  } else if(type === "[object Array]") {
44
46
  if(context.length > 0) {
45
- for(var i=0, j=context.length; i<j; i++) {
46
- ret = ret + fn(context[i]);
47
- }
47
+ return Handlebars.helpers.each(context, options);
48
48
  } else {
49
- ret = inverse(this);
49
+ return inverse(this);
50
50
  }
51
- return ret;
52
51
  } else {
53
52
  return fn(context);
54
53
  }
55
54
  });
56
55
 
56
+ Handlebars.K = function() {};
57
+
58
+ Handlebars.createFrame = Object.create || function(object) {
59
+ Handlebars.K.prototype = object;
60
+ var obj = new Handlebars.K();
61
+ Handlebars.K.prototype = null;
62
+ return obj;
63
+ };
64
+
57
65
  Handlebars.registerHelper('each', function(context, options) {
58
66
  var fn = options.fn, inverse = options.inverse;
59
- var ret = "";
67
+ var ret = "", data;
68
+
69
+ if (options.data) {
70
+ data = Handlebars.createFrame(options.data);
71
+ }
60
72
 
61
73
  if(context && context.length > 0) {
62
74
  for(var i=0, j=context.length; i<j; i++) {
63
- ret = ret + fn(context[i]);
75
+ if (data) { data.index = i; }
76
+ ret = ret + fn(context[i], { data: data });
64
77
  }
65
78
  } else {
66
79
  ret = inverse(this);
@@ -94,15 +107,17 @@ Handlebars.registerHelper('with', function(context, options) {
94
107
  Handlebars.registerHelper('log', function(context) {
95
108
  Handlebars.log(context);
96
109
  });
110
+
111
+ }(this.Handlebars));
97
112
  ;
98
113
  // lib/handlebars/compiler/parser.js
99
114
  /* Jison generated parser */
100
115
  var handlebars = (function(){
101
116
  var parser = {trace: function trace() { },
102
117
  yy: {},
103
- symbols_: {"error":2,"root":3,"program":4,"EOF":5,"statements":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"params":25,"hash":26,"param":27,"STRING":28,"INTEGER":29,"BOOLEAN":30,"hashSegments":31,"hashSegment":32,"ID":33,"EQUALS":34,"pathSegments":35,"SEP":36,"$accept":0,"$end":1},
104
- terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",28:"STRING",29:"INTEGER",30:"BOOLEAN",33:"ID",34:"EQUALS",36:"SEP"},
105
- productions_: [0,[3,2],[4,3],[4,1],[4,0],[6,1],[6,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[7,2],[17,3],[17,2],[17,2],[17,1],[25,2],[25,1],[27,1],[27,1],[27,1],[27,1],[26,1],[31,2],[31,1],[32,3],[32,3],[32,3],[32,3],[21,1],[35,3],[35,1]],
118
+ symbols_: {"error":2,"root":3,"program":4,"EOF":5,"statements":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"params":25,"hash":26,"DATA":27,"param":28,"STRING":29,"INTEGER":30,"BOOLEAN":31,"hashSegments":32,"hashSegment":33,"ID":34,"EQUALS":35,"pathSegments":36,"SEP":37,"$accept":0,"$end":1},
119
+ terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",27:"DATA",29:"STRING",30:"INTEGER",31:"BOOLEAN",34:"ID",35:"EQUALS",37:"SEP"},
120
+ productions_: [0,[3,2],[4,3],[4,1],[4,0],[6,1],[6,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[7,2],[17,3],[17,2],[17,2],[17,1],[17,1],[25,2],[25,1],[28,1],[28,1],[28,1],[28,1],[28,1],[26,1],[32,2],[32,1],[33,3],[33,3],[33,3],[33,3],[33,3],[21,1],[36,3],[36,1]],
106
121
  performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
107
122
 
108
123
  var $0 = $$.length - 1;
@@ -119,9 +134,9 @@ case 5: this.$ = [$$[$0]];
119
134
  break;
120
135
  case 6: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
121
136
  break;
122
- case 7: this.$ = new yy.InverseNode($$[$0-2], $$[$0-1], $$[$0]);
137
+ case 7: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]);
123
138
  break;
124
- case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0]);
139
+ case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]);
125
140
  break;
126
141
  case 9: this.$ = $$[$0];
127
142
  break;
@@ -155,235 +170,161 @@ case 23: this.$ = [[$$[$0-1]], $$[$0]];
155
170
  break;
156
171
  case 24: this.$ = [[$$[$0]], null];
157
172
  break;
158
- case 25: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
173
+ case 25: this.$ = [[new yy.DataNode($$[$0])], null];
159
174
  break;
160
- case 26: this.$ = [$$[$0]];
175
+ case 26: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
161
176
  break;
162
- case 27: this.$ = $$[$0];
177
+ case 27: this.$ = [$$[$0]];
163
178
  break;
164
- case 28: this.$ = new yy.StringNode($$[$0]);
179
+ case 28: this.$ = $$[$0];
165
180
  break;
166
- case 29: this.$ = new yy.IntegerNode($$[$0]);
181
+ case 29: this.$ = new yy.StringNode($$[$0]);
167
182
  break;
168
- case 30: this.$ = new yy.BooleanNode($$[$0]);
183
+ case 30: this.$ = new yy.IntegerNode($$[$0]);
169
184
  break;
170
- case 31: this.$ = new yy.HashNode($$[$0]);
185
+ case 31: this.$ = new yy.BooleanNode($$[$0]);
171
186
  break;
172
- case 32: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
187
+ case 32: this.$ = new yy.DataNode($$[$0]);
173
188
  break;
174
- case 33: this.$ = [$$[$0]];
189
+ case 33: this.$ = new yy.HashNode($$[$0]);
175
190
  break;
176
- case 34: this.$ = [$$[$0-2], $$[$0]];
191
+ case 34: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
177
192
  break;
178
- case 35: this.$ = [$$[$0-2], new yy.StringNode($$[$0])];
193
+ case 35: this.$ = [$$[$0]];
179
194
  break;
180
- case 36: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])];
195
+ case 36: this.$ = [$$[$0-2], $$[$0]];
181
196
  break;
182
- case 37: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])];
197
+ case 37: this.$ = [$$[$0-2], new yy.StringNode($$[$0])];
183
198
  break;
184
- case 38: this.$ = new yy.IdNode($$[$0]);
199
+ case 38: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])];
185
200
  break;
186
- case 39: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
201
+ case 39: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])];
187
202
  break;
188
- case 40: this.$ = [$$[$0]];
203
+ case 40: this.$ = [$$[$0-2], new yy.DataNode($$[$0])];
204
+ break;
205
+ case 41: this.$ = new yy.IdNode($$[$0]);
206
+ break;
207
+ case 42: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
208
+ break;
209
+ case 43: this.$ = [$$[$0]];
189
210
  break;
190
211
  }
191
212
  },
192
- table: [{3:1,4:2,5:[2,4],6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{1:[3]},{5:[1,16]},{5:[2,3],7:17,8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,19],20:[2,3],22:[1,13],23:[1,14],24:[1,15]},{5:[2,5],14:[2,5],15:[2,5],16:[2,5],19:[2,5],20:[2,5],22:[2,5],23:[2,5],24:[2,5]},{4:20,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{4:21,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{17:22,21:23,33:[1,25],35:24},{17:26,21:23,33:[1,25],35:24},{17:27,21:23,33:[1,25],35:24},{17:28,21:23,33:[1,25],35:24},{21:29,33:[1,25],35:24},{1:[2,1]},{6:30,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{5:[2,6],14:[2,6],15:[2,6],16:[2,6],19:[2,6],20:[2,6],22:[2,6],23:[2,6],24:[2,6]},{17:22,18:[1,31],21:23,33:[1,25],35:24},{10:32,20:[1,33]},{10:34,20:[1,33]},{18:[1,35]},{18:[2,24],21:40,25:36,26:37,27:38,28:[1,41],29:[1,42],30:[1,43],31:39,32:44,33:[1,45],35:24},{18:[2,38],28:[2,38],29:[2,38],30:[2,38],33:[2,38],36:[1,46]},{18:[2,40],28:[2,40],29:[2,40],30:[2,40],33:[2,40],36:[2,40]},{18:[1,47]},{18:[1,48]},{18:[1,49]},{18:[1,50],21:51,33:[1,25],35:24},{5:[2,2],8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,2],22:[1,13],23:[1,14],24:[1,15]},{14:[2,20],15:[2,20],16:[2,20],19:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,7],14:[2,7],15:[2,7],16:[2,7],19:[2,7],20:[2,7],22:[2,7],23:[2,7],24:[2,7]},{21:52,33:[1,25],35:24},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{18:[2,22],21:40,26:53,27:54,28:[1,41],29:[1,42],30:[1,43],31:39,32:44,33:[1,45],35:24},{18:[2,23]},{18:[2,26],28:[2,26],29:[2,26],30:[2,26],33:[2,26]},{18:[2,31],32:55,33:[1,56]},{18:[2,27],28:[2,27],29:[2,27],30:[2,27],33:[2,27]},{18:[2,28],28:[2,28],29:[2,28],30:[2,28],33:[2,28]},{18:[2,29],28:[2,29],29:[2,29],30:[2,29],33:[2,29]},{18:[2,30],28:[2,30],29:[2,30],30:[2,30],33:[2,30]},{18:[2,33],33:[2,33]},{18:[2,40],28:[2,40],29:[2,40],30:[2,40],33:[2,40],34:[1,57],36:[2,40]},{33:[1,58]},{14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,17],14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]},{18:[1,59]},{18:[1,60]},{18:[2,21]},{18:[2,25],28:[2,25],29:[2,25],30:[2,25],33:[2,25]},{18:[2,32],33:[2,32]},{34:[1,57]},{21:61,28:[1,62],29:[1,63],30:[1,64],33:[1,25],35:24},{18:[2,39],28:[2,39],29:[2,39],30:[2,39],33:[2,39],36:[2,39]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{18:[2,34],33:[2,34]},{18:[2,35],33:[2,35]},{18:[2,36],33:[2,36]},{18:[2,37],33:[2,37]}],
193
- defaultActions: {16:[2,1],37:[2,23],53:[2,21]},
213
+ table: [{3:1,4:2,5:[2,4],6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{1:[3]},{5:[1,16]},{5:[2,3],7:17,8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,19],20:[2,3],22:[1,13],23:[1,14],24:[1,15]},{5:[2,5],14:[2,5],15:[2,5],16:[2,5],19:[2,5],20:[2,5],22:[2,5],23:[2,5],24:[2,5]},{4:20,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{4:21,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{17:22,21:23,27:[1,24],34:[1,26],36:25},{17:27,21:23,27:[1,24],34:[1,26],36:25},{17:28,21:23,27:[1,24],34:[1,26],36:25},{17:29,21:23,27:[1,24],34:[1,26],36:25},{21:30,34:[1,26],36:25},{1:[2,1]},{6:31,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{5:[2,6],14:[2,6],15:[2,6],16:[2,6],19:[2,6],20:[2,6],22:[2,6],23:[2,6],24:[2,6]},{17:22,18:[1,32],21:23,27:[1,24],34:[1,26],36:25},{10:33,20:[1,34]},{10:35,20:[1,34]},{18:[1,36]},{18:[2,24],21:41,25:37,26:38,27:[1,45],28:39,29:[1,42],30:[1,43],31:[1,44],32:40,33:46,34:[1,47],36:25},{18:[2,25]},{18:[2,41],27:[2,41],29:[2,41],30:[2,41],31:[2,41],34:[2,41],37:[1,48]},{18:[2,43],27:[2,43],29:[2,43],30:[2,43],31:[2,43],34:[2,43],37:[2,43]},{18:[1,49]},{18:[1,50]},{18:[1,51]},{18:[1,52],21:53,34:[1,26],36:25},{5:[2,2],8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,2],22:[1,13],23:[1,14],24:[1,15]},{14:[2,20],15:[2,20],16:[2,20],19:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,7],14:[2,7],15:[2,7],16:[2,7],19:[2,7],20:[2,7],22:[2,7],23:[2,7],24:[2,7]},{21:54,34:[1,26],36:25},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{18:[2,22],21:41,26:55,27:[1,45],28:56,29:[1,42],30:[1,43],31:[1,44],32:40,33:46,34:[1,47],36:25},{18:[2,23]},{18:[2,27],27:[2,27],29:[2,27],30:[2,27],31:[2,27],34:[2,27]},{18:[2,33],33:57,34:[1,58]},{18:[2,28],27:[2,28],29:[2,28],30:[2,28],31:[2,28],34:[2,28]},{18:[2,29],27:[2,29],29:[2,29],30:[2,29],31:[2,29],34:[2,29]},{18:[2,30],27:[2,30],29:[2,30],30:[2,30],31:[2,30],34:[2,30]},{18:[2,31],27:[2,31],29:[2,31],30:[2,31],31:[2,31],34:[2,31]},{18:[2,32],27:[2,32],29:[2,32],30:[2,32],31:[2,32],34:[2,32]},{18:[2,35],34:[2,35]},{18:[2,43],27:[2,43],29:[2,43],30:[2,43],31:[2,43],34:[2,43],35:[1,59],37:[2,43]},{34:[1,60]},{14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,17],14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]},{18:[1,61]},{18:[1,62]},{18:[2,21]},{18:[2,26],27:[2,26],29:[2,26],30:[2,26],31:[2,26],34:[2,26]},{18:[2,34],34:[2,34]},{35:[1,59]},{21:63,27:[1,67],29:[1,64],30:[1,65],31:[1,66],34:[1,26],36:25},{18:[2,42],27:[2,42],29:[2,42],30:[2,42],31:[2,42],34:[2,42],37:[2,42]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{18:[2,36],34:[2,36]},{18:[2,37],34:[2,37]},{18:[2,38],34:[2,38]},{18:[2,39],34:[2,39]},{18:[2,40],34:[2,40]}],
214
+ defaultActions: {16:[2,1],24:[2,25],38:[2,23],55:[2,21]},
194
215
  parseError: function parseError(str, hash) {
195
216
  throw new Error(str);
196
217
  },
197
218
  parse: function parse(input) {
198
- var self = this,
199
- stack = [0],
200
- vstack = [null], // semantic value stack
201
- lstack = [], // location stack
202
- table = this.table,
203
- yytext = '',
204
- yylineno = 0,
205
- yyleng = 0,
206
- recovering = 0,
207
- TERROR = 2,
208
- EOF = 1;
209
-
210
- //this.reductionCount = this.shiftCount = 0;
211
-
219
+ var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
212
220
  this.lexer.setInput(input);
213
221
  this.lexer.yy = this.yy;
214
222
  this.yy.lexer = this.lexer;
215
- if (typeof this.lexer.yylloc == 'undefined')
223
+ this.yy.parser = this;
224
+ if (typeof this.lexer.yylloc == "undefined")
216
225
  this.lexer.yylloc = {};
217
226
  var yyloc = this.lexer.yylloc;
218
227
  lstack.push(yyloc);
219
-
220
- if (typeof this.yy.parseError === 'function')
228
+ var ranges = this.lexer.options && this.lexer.options.ranges;
229
+ if (typeof this.yy.parseError === "function")
221
230
  this.parseError = this.yy.parseError;
222
-
223
- function popStack (n) {
224
- stack.length = stack.length - 2*n;
231
+ function popStack(n) {
232
+ stack.length = stack.length - 2 * n;
225
233
  vstack.length = vstack.length - n;
226
234
  lstack.length = lstack.length - n;
227
235
  }
228
-
229
236
  function lex() {
230
237
  var token;
231
- token = self.lexer.lex() || 1; // $end = 1
232
- // if token isn't its numeric value, convert
233
- if (typeof token !== 'number') {
238
+ token = self.lexer.lex() || 1;
239
+ if (typeof token !== "number") {
234
240
  token = self.symbols_[token] || token;
235
241
  }
236
242
  return token;
237
243
  }
238
-
239
- var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
244
+ var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
240
245
  while (true) {
241
- // retreive state number from top of stack
242
- state = stack[stack.length-1];
243
-
244
- // use default actions if available
246
+ state = stack[stack.length - 1];
245
247
  if (this.defaultActions[state]) {
246
248
  action = this.defaultActions[state];
247
249
  } else {
248
- if (symbol == null)
250
+ if (symbol === null || typeof symbol == "undefined") {
249
251
  symbol = lex();
250
- // read action for current state and first input
252
+ }
251
253
  action = table[state] && table[state][symbol];
252
254
  }
253
-
254
- // handle parse error
255
- _handle_error:
256
- if (typeof action === 'undefined' || !action.length || !action[0]) {
257
-
255
+ if (typeof action === "undefined" || !action.length || !action[0]) {
256
+ var errStr = "";
258
257
  if (!recovering) {
259
- // Report error
260
258
  expected = [];
261
- for (p in table[state]) if (this.terminals_[p] && p > 2) {
262
- expected.push("'"+this.terminals_[p]+"'");
263
- }
264
- var errStr = '';
259
+ for (p in table[state])
260
+ if (this.terminals_[p] && p > 2) {
261
+ expected.push("'" + this.terminals_[p] + "'");
262
+ }
265
263
  if (this.lexer.showPosition) {
266
- errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
264
+ errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
267
265
  } else {
268
- errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
269
- (symbol == 1 /*EOF*/ ? "end of input" :
270
- ("'"+(this.terminals_[symbol] || symbol)+"'"));
266
+ errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
271
267
  }
272
- this.parseError(errStr,
273
- {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
268
+ this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
274
269
  }
275
-
276
- // just recovered from another error
277
- if (recovering == 3) {
278
- if (symbol == EOF) {
279
- throw new Error(errStr || 'Parsing halted.');
280
- }
281
-
282
- // discard current lookahead and grab another
270
+ }
271
+ if (action[0] instanceof Array && action.length > 1) {
272
+ throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
273
+ }
274
+ switch (action[0]) {
275
+ case 1:
276
+ stack.push(symbol);
277
+ vstack.push(this.lexer.yytext);
278
+ lstack.push(this.lexer.yylloc);
279
+ stack.push(action[1]);
280
+ symbol = null;
281
+ if (!preErrorSymbol) {
283
282
  yyleng = this.lexer.yyleng;
284
283
  yytext = this.lexer.yytext;
285
284
  yylineno = this.lexer.yylineno;
286
285
  yyloc = this.lexer.yylloc;
287
- symbol = lex();
286
+ if (recovering > 0)
287
+ recovering--;
288
+ } else {
289
+ symbol = preErrorSymbol;
290
+ preErrorSymbol = null;
288
291
  }
289
-
290
- // try to recover from error
291
- while (1) {
292
- // check for error recovery rule in this state
293
- if ((TERROR.toString()) in table[state]) {
294
- break;
295
- }
296
- if (state == 0) {
297
- throw new Error(errStr || 'Parsing halted.');
298
- }
299
- popStack(1);
300
- state = stack[stack.length-1];
292
+ break;
293
+ case 2:
294
+ len = this.productions_[action[1]][1];
295
+ yyval.$ = vstack[vstack.length - len];
296
+ yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
297
+ if (ranges) {
298
+ yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
301
299
  }
302
-
303
- preErrorSymbol = symbol; // save the lookahead token
304
- symbol = TERROR; // insert generic error symbol as new lookahead
305
- state = stack[stack.length-1];
306
- action = table[state] && table[state][TERROR];
307
- recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
308
- }
309
-
310
- // this shouldn't happen, unless resolve defaults are off
311
- if (action[0] instanceof Array && action.length > 1) {
312
- throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
313
- }
314
-
315
- switch (action[0]) {
316
-
317
- case 1: // shift
318
- //this.shiftCount++;
319
-
320
- stack.push(symbol);
321
- vstack.push(this.lexer.yytext);
322
- lstack.push(this.lexer.yylloc);
323
- stack.push(action[1]); // push state
324
- symbol = null;
325
- if (!preErrorSymbol) { // normal execution/no error
326
- yyleng = this.lexer.yyleng;
327
- yytext = this.lexer.yytext;
328
- yylineno = this.lexer.yylineno;
329
- yyloc = this.lexer.yylloc;
330
- if (recovering > 0)
331
- recovering--;
332
- } else { // error just occurred, resume old lookahead f/ before error
333
- symbol = preErrorSymbol;
334
- preErrorSymbol = null;
335
- }
336
- break;
337
-
338
- case 2: // reduce
339
- //this.reductionCount++;
340
-
341
- len = this.productions_[action[1]][1];
342
-
343
- // perform semantic action
344
- yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
345
- // default location, uses first token for firsts, last for lasts
346
- yyval._$ = {
347
- first_line: lstack[lstack.length-(len||1)].first_line,
348
- last_line: lstack[lstack.length-1].last_line,
349
- first_column: lstack[lstack.length-(len||1)].first_column,
350
- last_column: lstack[lstack.length-1].last_column
351
- };
352
- r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
353
-
354
- if (typeof r !== 'undefined') {
355
- return r;
356
- }
357
-
358
- // pop off stack
359
- if (len) {
360
- stack = stack.slice(0,-1*len*2);
361
- vstack = vstack.slice(0, -1*len);
362
- lstack = lstack.slice(0, -1*len);
363
- }
364
-
365
- stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
366
- vstack.push(yyval.$);
367
- lstack.push(yyval._$);
368
- // goto new state = table[STATE][NONTERMINAL]
369
- newState = table[stack[stack.length-2]][stack[stack.length-1]];
370
- stack.push(newState);
371
- break;
372
-
373
- case 3: // accept
374
- return true;
300
+ r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
301
+ if (typeof r !== "undefined") {
302
+ return r;
303
+ }
304
+ if (len) {
305
+ stack = stack.slice(0, -1 * len * 2);
306
+ vstack = vstack.slice(0, -1 * len);
307
+ lstack = lstack.slice(0, -1 * len);
308
+ }
309
+ stack.push(this.productions_[action[1]][0]);
310
+ vstack.push(yyval.$);
311
+ lstack.push(yyval._$);
312
+ newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
313
+ stack.push(newState);
314
+ break;
315
+ case 3:
316
+ return true;
375
317
  }
376
-
377
318
  }
378
-
379
319
  return true;
380
- }};
320
+ }
321
+ };
381
322
  /* Jison generated lexer */
382
323
  var lexer = (function(){
383
324
  var lexer = ({EOF:1,
384
325
  parseError:function parseError(str, hash) {
385
- if (this.yy.parseError) {
386
- this.yy.parseError(str, hash);
326
+ if (this.yy.parser) {
327
+ this.yy.parser.parseError(str, hash);
387
328
  } else {
388
329
  throw new Error(str);
389
330
  }
@@ -395,27 +336,64 @@ setInput:function (input) {
395
336
  this.yytext = this.matched = this.match = '';
396
337
  this.conditionStack = ['INITIAL'];
397
338
  this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
339
+ if (this.options.ranges) this.yylloc.range = [0,0];
340
+ this.offset = 0;
398
341
  return this;
399
342
  },
400
343
  input:function () {
401
344
  var ch = this._input[0];
402
- this.yytext+=ch;
345
+ this.yytext += ch;
403
346
  this.yyleng++;
404
- this.match+=ch;
405
- this.matched+=ch;
406
- var lines = ch.match(/\n/);
407
- if (lines) this.yylineno++;
347
+ this.offset++;
348
+ this.match += ch;
349
+ this.matched += ch;
350
+ var lines = ch.match(/(?:\r\n?|\n).*/g);
351
+ if (lines) {
352
+ this.yylineno++;
353
+ this.yylloc.last_line++;
354
+ } else {
355
+ this.yylloc.last_column++;
356
+ }
357
+ if (this.options.ranges) this.yylloc.range[1]++;
358
+
408
359
  this._input = this._input.slice(1);
409
360
  return ch;
410
361
  },
411
362
  unput:function (ch) {
363
+ var len = ch.length;
364
+ var lines = ch.split(/(?:\r\n?|\n)/g);
365
+
412
366
  this._input = ch + this._input;
367
+ this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
368
+ //this.yyleng -= len;
369
+ this.offset -= len;
370
+ var oldLines = this.match.split(/(?:\r\n?|\n)/g);
371
+ this.match = this.match.substr(0, this.match.length-1);
372
+ this.matched = this.matched.substr(0, this.matched.length-1);
373
+
374
+ if (lines.length-1) this.yylineno -= lines.length-1;
375
+ var r = this.yylloc.range;
376
+
377
+ this.yylloc = {first_line: this.yylloc.first_line,
378
+ last_line: this.yylineno+1,
379
+ first_column: this.yylloc.first_column,
380
+ last_column: lines ?
381
+ (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
382
+ this.yylloc.first_column - len
383
+ };
384
+
385
+ if (this.options.ranges) {
386
+ this.yylloc.range = [r[0], r[0] + this.yyleng - len];
387
+ }
413
388
  return this;
414
389
  },
415
390
  more:function () {
416
391
  this._more = true;
417
392
  return this;
418
393
  },
394
+ less:function (n) {
395
+ this.unput(this.match.slice(n));
396
+ },
419
397
  pastInput:function () {
420
398
  var past = this.matched.substr(0, this.matched.length - this.match.length);
421
399
  return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
@@ -458,26 +436,31 @@ next:function () {
458
436
  }
459
437
  }
460
438
  if (match) {
461
- lines = match[0].match(/\n.*/g);
439
+ lines = match[0].match(/(?:\r\n?|\n).*/g);
462
440
  if (lines) this.yylineno += lines.length;
463
441
  this.yylloc = {first_line: this.yylloc.last_line,
464
442
  last_line: this.yylineno+1,
465
443
  first_column: this.yylloc.last_column,
466
- last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
444
+ last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
467
445
  this.yytext += match[0];
468
446
  this.match += match[0];
447
+ this.matches = match;
469
448
  this.yyleng = this.yytext.length;
449
+ if (this.options.ranges) {
450
+ this.yylloc.range = [this.offset, this.offset += this.yyleng];
451
+ }
470
452
  this._more = false;
471
453
  this._input = this._input.slice(match[0].length);
472
454
  this.matched += match[0];
473
455
  token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
456
+ if (this.done && this._input) this.done = false;
474
457
  if (token) return token;
475
458
  else return;
476
459
  }
477
460
  if (this._input === "") {
478
461
  return this.EOF;
479
462
  } else {
480
- this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
463
+ return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
481
464
  {text: "", token: null, line: this.yylineno});
482
465
  }
483
466
  },
@@ -517,7 +500,11 @@ case 0:
517
500
  break;
518
501
  case 1: return 14;
519
502
  break;
520
- case 2: this.popState(); return 14;
503
+ case 2:
504
+ if(yy_.yytext.slice(-1) !== "\\") this.popState();
505
+ if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1);
506
+ return 14;
507
+
521
508
  break;
522
509
  case 3: return 24;
523
510
  break;
@@ -537,13 +524,13 @@ case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return
537
524
  break;
538
525
  case 11: return 22;
539
526
  break;
540
- case 12: return 34;
527
+ case 12: return 35;
541
528
  break;
542
- case 13: return 33;
529
+ case 13: return 34;
543
530
  break;
544
- case 14: return 33;
531
+ case 14: return 34;
545
532
  break;
546
- case 15: return 36;
533
+ case 15: return 37;
547
534
  break;
548
535
  case 16: /*ignore whitespace*/
549
536
  break;
@@ -551,41 +538,47 @@ case 17: this.popState(); return 18;
551
538
  break;
552
539
  case 18: this.popState(); return 18;
553
540
  break;
554
- case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28;
541
+ case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 29;
555
542
  break;
556
- case 20: return 30;
543
+ case 20: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 29;
557
544
  break;
558
- case 21: return 30;
545
+ case 21: yy_.yytext = yy_.yytext.substr(1); return 27;
559
546
  break;
560
- case 22: return 29;
547
+ case 22: return 31;
561
548
  break;
562
- case 23: return 33;
549
+ case 23: return 31;
563
550
  break;
564
- case 24: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 33;
551
+ case 24: return 30;
565
552
  break;
566
- case 25: return 'INVALID';
553
+ case 25: return 34;
567
554
  break;
568
- case 26: return 5;
555
+ case 26: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 34;
556
+ break;
557
+ case 27: return 'INVALID';
558
+ break;
559
+ case 28: return 5;
569
560
  break;
570
561
  }
571
562
  };
572
- lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^[^\x00]{2,}?(?=(\{\{))/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[\/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s\/.])/,/^\[[^\]]*\]/,/^./,/^$/];
573
- lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,26],"inclusive":true}};
563
+ lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
564
+ lexer.conditions = {"mu":{"rules":[3,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],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,28],"inclusive":true}};
574
565
  return lexer;})()
575
566
  parser.lexer = lexer;
576
- return parser;
567
+ function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
568
+ return new Parser;
577
569
  })();
578
570
  if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
579
571
  exports.parser = handlebars;
572
+ exports.Parser = handlebars.Parser;
580
573
  exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
581
574
  exports.main = function commonjsMain(args) {
582
575
  if (!args[1])
583
576
  throw new Error('Usage: '+args[0]+' FILE');
577
+ var source, cwd;
584
578
  if (typeof process !== 'undefined') {
585
- var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
579
+ source = require('fs').readFileSync(require('path').resolve(args[1]), "utf8");
586
580
  } else {
587
- var cwd = require("file").path(require("file").cwd());
588
- var source = cwd.join(args[1]).read({charset: "utf-8"});
581
+ source = require("file").path(require("file").cwd()).join(args[1]).read({charset: "utf-8"});
589
582
  }
590
583
  return exports.parser.parse(source);
591
584
  }
@@ -626,12 +619,26 @@ Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
626
619
  if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
627
620
  };
628
621
 
629
- Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
622
+ Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
630
623
  this.type = "mustache";
631
- this.id = params[0];
632
- this.params = params.slice(1);
633
- this.hash = hash;
634
624
  this.escaped = !unescaped;
625
+ this.hash = hash;
626
+
627
+ var id = this.id = rawParams[0];
628
+ var params = this.params = rawParams.slice(1);
629
+
630
+ // a mustache is an eligible helper if:
631
+ // * its id is simple (a single part, not `this` or `..`)
632
+ var eligibleHelper = this.eligibleHelper = id.isSimple;
633
+
634
+ // a mustache is definitely a helper if:
635
+ // * it is an eligible helper, and
636
+ // * it has at least one parameter or hash segment
637
+ this.isHelper = eligibleHelper && (params.length || hash);
638
+
639
+ // if a mustache is an eligible helper but not a definite
640
+ // helper, it is ambiguous, and will be resolved in a later
641
+ // pass or at runtime.
635
642
  };
636
643
 
637
644
  Handlebars.AST.PartialNode = function(id, context) {
@@ -649,18 +656,16 @@ Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
649
656
  }
650
657
  };
651
658
 
652
- Handlebars.AST.BlockNode = function(mustache, program, close) {
659
+ Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
653
660
  verifyMatch(mustache.id, close);
654
661
  this.type = "block";
655
662
  this.mustache = mustache;
656
663
  this.program = program;
657
- };
664
+ this.inverse = inverse;
658
665
 
659
- Handlebars.AST.InverseNode = function(mustache, program, close) {
660
- verifyMatch(mustache.id, close);
661
- this.type = "inverse";
662
- this.mustache = mustache;
663
- this.program = program;
666
+ if (this.inverse && !this.program) {
667
+ this.isInverse = true;
668
+ }
664
669
  };
665
670
 
666
671
  Handlebars.AST.ContentNode = function(string) {
@@ -690,7 +695,15 @@ Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
690
695
  this.parts = dig;
691
696
  this.string = dig.join('.');
692
697
  this.depth = depth;
693
- this.isSimple = (dig.length === 1) && (depth === 0);
698
+
699
+ // an ID is simple if it only has one part, and that part is not
700
+ // `..` or `this`.
701
+ this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
702
+ };
703
+
704
+ Handlebars.AST.DataNode = function(id) {
705
+ this.type = "DATA";
706
+ this.id = id;
694
707
  };
695
708
 
696
709
  Handlebars.AST.StringNode = function(string) {
@@ -736,6 +749,7 @@ Handlebars.SafeString.prototype.toString = function() {
736
749
 
737
750
  (function() {
738
751
  var escape = {
752
+ "&": "&amp;",
739
753
  "<": "&lt;",
740
754
  ">": "&gt;",
741
755
  '"': "&quot;",
@@ -743,7 +757,7 @@ Handlebars.SafeString.prototype.toString = function() {
743
757
  "`": "&#x60;"
744
758
  };
745
759
 
746
- var badChars = /&(?!\w+;)|[<>"'`]/g;
760
+ var badChars = /[&<>"'`]/g;
747
761
  var possible = /[&<>"'`]/;
748
762
 
749
763
  var escapeChar = function(chr) {
@@ -785,84 +799,32 @@ Handlebars.Compiler = function() {};
785
799
  Handlebars.JavaScriptCompiler = function() {};
786
800
 
787
801
  (function(Compiler, JavaScriptCompiler) {
788
- Compiler.OPCODE_MAP = {
789
- appendContent: 1,
790
- getContext: 2,
791
- lookupWithHelpers: 3,
792
- lookup: 4,
793
- append: 5,
794
- invokeMustache: 6,
795
- appendEscaped: 7,
796
- pushString: 8,
797
- truthyOrFallback: 9,
798
- functionOrFallback: 10,
799
- invokeProgram: 11,
800
- invokePartial: 12,
801
- push: 13,
802
- assignToHash: 15,
803
- pushStringParam: 16
804
- };
805
-
806
- Compiler.MULTI_PARAM_OPCODES = {
807
- appendContent: 1,
808
- getContext: 1,
809
- lookupWithHelpers: 2,
810
- lookup: 1,
811
- invokeMustache: 3,
812
- pushString: 1,
813
- truthyOrFallback: 1,
814
- functionOrFallback: 1,
815
- invokeProgram: 3,
816
- invokePartial: 1,
817
- push: 1,
818
- assignToHash: 1,
819
- pushStringParam: 1
820
- };
821
-
822
- Compiler.DISASSEMBLE_MAP = {};
823
-
824
- for(var prop in Compiler.OPCODE_MAP) {
825
- var value = Compiler.OPCODE_MAP[prop];
826
- Compiler.DISASSEMBLE_MAP[value] = prop;
827
- }
828
-
829
- Compiler.multiParamSize = function(code) {
830
- return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
831
- };
802
+ // the foundHelper register will disambiguate helper lookup from finding a
803
+ // function in a context. This is necessary for mustache compatibility, which
804
+ // requires that context functions in blocks are evaluated by blockHelperMissing,
805
+ // and then proceed as if the resulting value was provided to blockHelperMissing.
832
806
 
833
807
  Compiler.prototype = {
834
808
  compiler: Compiler,
835
809
 
836
810
  disassemble: function() {
837
- var opcodes = this.opcodes, opcode, nextCode;
838
- var out = [], str, name, value;
811
+ var opcodes = this.opcodes, opcode, out = [], params, param;
839
812
 
840
- for(var i=0, l=opcodes.length; i<l; i++) {
813
+ for (var i=0, l=opcodes.length; i<l; i++) {
841
814
  opcode = opcodes[i];
842
815
 
843
- if(opcode === 'DECLARE') {
844
- name = opcodes[++i];
845
- value = opcodes[++i];
846
- out.push("DECLARE " + name + " = " + value);
816
+ if (opcode.opcode === 'DECLARE') {
817
+ out.push("DECLARE " + opcode.name + "=" + opcode.value);
847
818
  } else {
848
- str = Compiler.DISASSEMBLE_MAP[opcode];
849
-
850
- var extraParams = Compiler.multiParamSize(opcode);
851
- var codes = [];
852
-
853
- for(var j=0; j<extraParams; j++) {
854
- nextCode = opcodes[++i];
855
-
856
- if(typeof nextCode === "string") {
857
- nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
819
+ params = [];
820
+ for (var j=0; j<opcode.args.length; j++) {
821
+ param = opcode.args[j];
822
+ if (typeof param === "string") {
823
+ param = "\"" + param.replace("\n", "\\n") + "\"";
858
824
  }
859
-
860
- codes.push(nextCode);
825
+ params.push(param);
861
826
  }
862
-
863
- str = str + " " + codes.join(" ");
864
-
865
- out.push(str);
827
+ out.push(opcode.opcode + " " + params.join(" "));
866
828
  }
867
829
  }
868
830
 
@@ -936,32 +898,42 @@ Handlebars.JavaScriptCompiler = function() {};
936
898
  },
937
899
 
938
900
  block: function(block) {
939
- var mustache = block.mustache;
940
- var depth, child, inverse, inverseGuid;
941
-
942
- var params = this.setupStackForMustache(mustache);
901
+ var mustache = block.mustache,
902
+ program = block.program,
903
+ inverse = block.inverse;
943
904
 
944
- var programGuid = this.compileProgram(block.program);
945
-
946
- if(block.program.inverse) {
947
- inverseGuid = this.compileProgram(block.program.inverse);
948
- this.declare('inverse', inverseGuid);
905
+ if (program) {
906
+ program = this.compileProgram(program);
949
907
  }
950
908
 
951
- this.opcode('invokeProgram', programGuid, params.length, !!mustache.hash);
952
- this.declare('inverse', null);
953
- this.opcode('append');
954
- },
909
+ if (inverse) {
910
+ inverse = this.compileProgram(inverse);
911
+ }
955
912
 
956
- inverse: function(block) {
957
- var params = this.setupStackForMustache(block.mustache);
913
+ var type = this.classifyMustache(mustache);
958
914
 
959
- var programGuid = this.compileProgram(block.program);
915
+ if (type === "helper") {
916
+ this.helperMustache(mustache, program, inverse);
917
+ } else if (type === "simple") {
918
+ this.simpleMustache(mustache);
960
919
 
961
- this.declare('inverse', programGuid);
920
+ // now that the simple mustache is resolved, we need to
921
+ // evaluate it by executing `blockHelperMissing`
922
+ this.opcode('pushProgram', program);
923
+ this.opcode('pushProgram', inverse);
924
+ this.opcode('pushLiteral', '{}');
925
+ this.opcode('blockValue');
926
+ } else {
927
+ this.ambiguousMustache(mustache, program, inverse);
928
+
929
+ // now that the simple mustache is resolved, we need to
930
+ // evaluate it by executing `blockHelperMissing`
931
+ this.opcode('pushProgram', program);
932
+ this.opcode('pushProgram', inverse);
933
+ this.opcode('pushLiteral', '{}');
934
+ this.opcode('ambiguousBlockValue');
935
+ }
962
936
 
963
- this.opcode('invokeProgram', null, params.length, !!block.mustache.hash);
964
- this.declare('inverse', null);
965
937
  this.opcode('append');
966
938
  },
967
939
 
@@ -998,44 +970,140 @@ Handlebars.JavaScriptCompiler = function() {};
998
970
  },
999
971
 
1000
972
  mustache: function(mustache) {
1001
- var params = this.setupStackForMustache(mustache);
973
+ var options = this.options;
974
+ var type = this.classifyMustache(mustache);
1002
975
 
1003
- this.opcode('invokeMustache', params.length, mustache.id.original, !!mustache.hash);
976
+ if (type === "simple") {
977
+ this.simpleMustache(mustache);
978
+ } else if (type === "helper") {
979
+ this.helperMustache(mustache);
980
+ } else {
981
+ this.ambiguousMustache(mustache);
982
+ }
1004
983
 
1005
- if(mustache.escaped && !this.options.noEscape) {
984
+ if(mustache.escaped && !options.noEscape) {
1006
985
  this.opcode('appendEscaped');
1007
986
  } else {
1008
987
  this.opcode('append');
1009
988
  }
1010
989
  },
1011
990
 
991
+ ambiguousMustache: function(mustache, program, inverse) {
992
+ var id = mustache.id, name = id.parts[0];
993
+
994
+ this.opcode('getContext', id.depth);
995
+
996
+ this.opcode('pushProgram', program);
997
+ this.opcode('pushProgram', inverse);
998
+
999
+ this.opcode('invokeAmbiguous', name);
1000
+ },
1001
+
1002
+ simpleMustache: function(mustache, program, inverse) {
1003
+ var id = mustache.id;
1004
+
1005
+ if (id.type === 'DATA') {
1006
+ this.DATA(id);
1007
+ } else if (id.parts.length) {
1008
+ this.ID(id);
1009
+ } else {
1010
+ // Simplified ID for `this`
1011
+ this.addDepth(id.depth);
1012
+ this.opcode('getContext', id.depth);
1013
+ this.opcode('pushContext');
1014
+ }
1015
+
1016
+ this.opcode('resolvePossibleLambda');
1017
+ },
1018
+
1019
+ helperMustache: function(mustache, program, inverse) {
1020
+ var params = this.setupFullMustacheParams(mustache, program, inverse),
1021
+ name = mustache.id.parts[0];
1022
+
1023
+ if (this.options.knownHelpers[name]) {
1024
+ this.opcode('invokeKnownHelper', params.length, name);
1025
+ } else if (this.knownHelpersOnly) {
1026
+ throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
1027
+ } else {
1028
+ this.opcode('invokeHelper', params.length, name);
1029
+ }
1030
+ },
1031
+
1012
1032
  ID: function(id) {
1013
1033
  this.addDepth(id.depth);
1014
-
1015
1034
  this.opcode('getContext', id.depth);
1016
1035
 
1017
- this.opcode('lookupWithHelpers', id.parts[0] || null, id.isScoped || false);
1036
+ var name = id.parts[0];
1037
+ if (!name) {
1038
+ this.opcode('pushContext');
1039
+ } else {
1040
+ this.opcode('lookupOnContext', id.parts[0]);
1041
+ }
1018
1042
 
1019
1043
  for(var i=1, l=id.parts.length; i<l; i++) {
1020
1044
  this.opcode('lookup', id.parts[i]);
1021
1045
  }
1022
1046
  },
1023
1047
 
1048
+ DATA: function(data) {
1049
+ this.options.data = true;
1050
+ this.opcode('lookupData', data.id);
1051
+ },
1052
+
1024
1053
  STRING: function(string) {
1025
1054
  this.opcode('pushString', string.string);
1026
1055
  },
1027
1056
 
1028
1057
  INTEGER: function(integer) {
1029
- this.opcode('push', integer.integer);
1058
+ this.opcode('pushLiteral', integer.integer);
1030
1059
  },
1031
1060
 
1032
1061
  BOOLEAN: function(bool) {
1033
- this.opcode('push', bool.bool);
1062
+ this.opcode('pushLiteral', bool.bool);
1034
1063
  },
1035
1064
 
1036
1065
  comment: function() {},
1037
1066
 
1038
1067
  // HELPERS
1068
+ opcode: function(name) {
1069
+ this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
1070
+ },
1071
+
1072
+ declare: function(name, value) {
1073
+ this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
1074
+ },
1075
+
1076
+ addDepth: function(depth) {
1077
+ if(isNaN(depth)) { throw new Error("EWOT"); }
1078
+ if(depth === 0) { return; }
1079
+
1080
+ if(!this.depths[depth]) {
1081
+ this.depths[depth] = true;
1082
+ this.depths.list.push(depth);
1083
+ }
1084
+ },
1085
+
1086
+ classifyMustache: function(mustache) {
1087
+ var isHelper = mustache.isHelper;
1088
+ var isEligible = mustache.eligibleHelper;
1089
+ var options = this.options;
1090
+
1091
+ // if ambiguous, we can possibly resolve the ambiguity now
1092
+ if (isEligible && !isHelper) {
1093
+ var name = mustache.id.parts[0];
1094
+
1095
+ if (options.knownHelpers[name]) {
1096
+ isHelper = true;
1097
+ } else if (options.knownHelpersOnly) {
1098
+ isEligible = false;
1099
+ }
1100
+ }
1101
+
1102
+ if (isHelper) { return "helper"; }
1103
+ else if (isEligible) { return "ambiguous"; }
1104
+ else { return "simple"; }
1105
+ },
1106
+
1039
1107
  pushParams: function(params) {
1040
1108
  var i = params.length, param;
1041
1109
 
@@ -1055,43 +1123,41 @@ Handlebars.JavaScriptCompiler = function() {};
1055
1123
  }
1056
1124
  },
1057
1125
 
1058
- opcode: function(name, val1, val2, val3) {
1059
- this.opcodes.push(Compiler.OPCODE_MAP[name]);
1060
- if(val1 !== undefined) { this.opcodes.push(val1); }
1061
- if(val2 !== undefined) { this.opcodes.push(val2); }
1062
- if(val3 !== undefined) { this.opcodes.push(val3); }
1063
- },
1064
-
1065
- declare: function(name, value) {
1066
- this.opcodes.push('DECLARE');
1067
- this.opcodes.push(name);
1068
- this.opcodes.push(value);
1069
- },
1070
-
1071
- addDepth: function(depth) {
1072
- if(depth === 0) { return; }
1126
+ setupMustacheParams: function(mustache) {
1127
+ var params = mustache.params;
1128
+ this.pushParams(params);
1073
1129
 
1074
- if(!this.depths[depth]) {
1075
- this.depths[depth] = true;
1076
- this.depths.list.push(depth);
1130
+ if(mustache.hash) {
1131
+ this.hash(mustache.hash);
1132
+ } else {
1133
+ this.opcode('pushLiteral', '{}');
1077
1134
  }
1135
+
1136
+ return params;
1078
1137
  },
1079
1138
 
1080
- setupStackForMustache: function(mustache) {
1139
+ // this will replace setupMustacheParams when we're done
1140
+ setupFullMustacheParams: function(mustache, program, inverse) {
1081
1141
  var params = mustache.params;
1082
-
1083
1142
  this.pushParams(params);
1084
1143
 
1144
+ this.opcode('pushProgram', program);
1145
+ this.opcode('pushProgram', inverse);
1146
+
1085
1147
  if(mustache.hash) {
1086
1148
  this.hash(mustache.hash);
1149
+ } else {
1150
+ this.opcode('pushLiteral', '{}');
1087
1151
  }
1088
1152
 
1089
- this.ID(mustache.id);
1090
-
1091
1153
  return params;
1092
1154
  }
1093
1155
  };
1094
1156
 
1157
+ var Literal = function(value) {
1158
+ this.value = value;
1159
+ };
1160
+
1095
1161
  JavaScriptCompiler.prototype = {
1096
1162
  // PUBLIC API: You can override these methods in a subclass to provide
1097
1163
  // alternative compiled forms for name lookup and buffering semantics
@@ -1125,18 +1191,21 @@ Handlebars.JavaScriptCompiler = function() {};
1125
1191
  this.environment = environment;
1126
1192
  this.options = options || {};
1127
1193
 
1194
+ Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n");
1195
+
1128
1196
  this.name = this.environment.name;
1129
1197
  this.isChild = !!context;
1130
1198
  this.context = context || {
1131
1199
  programs: [],
1132
- aliases: { self: 'this' },
1133
- registers: {list: []}
1200
+ aliases: { }
1134
1201
  };
1135
1202
 
1136
1203
  this.preamble();
1137
1204
 
1138
1205
  this.stackSlot = 0;
1139
1206
  this.stackVars = [];
1207
+ this.registers = { list: [] };
1208
+ this.compileStack = [];
1140
1209
 
1141
1210
  this.compileChildren(environment, options);
1142
1211
 
@@ -1145,59 +1214,35 @@ Handlebars.JavaScriptCompiler = function() {};
1145
1214
  this.i = 0;
1146
1215
 
1147
1216
  for(l=opcodes.length; this.i<l; this.i++) {
1148
- opcode = this.nextOpcode(0);
1217
+ opcode = opcodes[this.i];
1149
1218
 
1150
- if(opcode[0] === 'DECLARE') {
1151
- this.i = this.i + 2;
1152
- this[opcode[1]] = opcode[2];
1219
+ if(opcode.opcode === 'DECLARE') {
1220
+ this[opcode.name] = opcode.value;
1153
1221
  } else {
1154
- this.i = this.i + opcode[1].length;
1155
- this[opcode[0]].apply(this, opcode[1]);
1222
+ this[opcode.opcode].apply(this, opcode.args);
1156
1223
  }
1157
1224
  }
1158
1225
 
1159
1226
  return this.createFunctionContext(asObject);
1160
1227
  },
1161
1228
 
1162
- nextOpcode: function(n) {
1163
- var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
1164
- var extraParams, codes;
1165
-
1166
- if(opcode === 'DECLARE') {
1167
- name = opcodes[this.i + 1];
1168
- val = opcodes[this.i + 2];
1169
- return ['DECLARE', name, val];
1170
- } else {
1171
- name = Compiler.DISASSEMBLE_MAP[opcode];
1172
-
1173
- extraParams = Compiler.multiParamSize(opcode);
1174
- codes = [];
1175
-
1176
- for(var j=0; j<extraParams; j++) {
1177
- codes.push(opcodes[this.i + j + 1 + n]);
1178
- }
1179
-
1180
- return [name, codes];
1181
- }
1229
+ nextOpcode: function() {
1230
+ var opcodes = this.environment.opcodes, opcode = opcodes[this.i + 1];
1231
+ return opcodes[this.i + 1];
1182
1232
  },
1183
1233
 
1184
1234
  eat: function(opcode) {
1185
- this.i = this.i + opcode.length;
1235
+ this.i = this.i + 1;
1186
1236
  },
1187
1237
 
1188
1238
  preamble: function() {
1189
1239
  var out = [];
1190
1240
 
1191
- // this register will disambiguate helper lookup from finding a function in
1192
- // a context. This is necessary for mustache compatibility, which requires
1193
- // that context functions in blocks are evaluated by blockHelperMissing, and
1194
- // then proceed as if the resulting value was provided to blockHelperMissing.
1195
- this.useRegister('foundHelper');
1196
-
1197
1241
  if (!this.isChild) {
1198
1242
  var namespace = this.namespace;
1199
1243
  var copies = "helpers = helpers || " + namespace + ".helpers;";
1200
- if(this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
1244
+ if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
1245
+ if (this.options.data) { copies = copies + " data = data || {};"; }
1201
1246
  out.push(copies);
1202
1247
  } else {
1203
1248
  out.push('');
@@ -1216,10 +1261,7 @@ Handlebars.JavaScriptCompiler = function() {};
1216
1261
  },
1217
1262
 
1218
1263
  createFunctionContext: function(asObject) {
1219
- var locals = this.stackVars;
1220
- if (!this.isChild) {
1221
- locals = locals.concat(this.context.registers.list);
1222
- }
1264
+ var locals = this.stackVars.concat(this.registers.list);
1223
1265
 
1224
1266
  if(locals.length > 0) {
1225
1267
  this.source[1] = this.source[1] + ", " + locals.join(", ");
@@ -1263,10 +1305,64 @@ Handlebars.JavaScriptCompiler = function() {};
1263
1305
  }
1264
1306
  },
1265
1307
 
1308
+ // [blockValue]
1309
+ //
1310
+ // On stack, before: hash, inverse, program, value
1311
+ // On stack, after: return value of blockHelperMissing
1312
+ //
1313
+ // The purpose of this opcode is to take a block of the form
1314
+ // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
1315
+ // replace it on the stack with the result of properly
1316
+ // invoking blockHelperMissing.
1317
+ blockValue: function() {
1318
+ this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
1319
+
1320
+ var params = ["depth0"];
1321
+ this.setupParams(0, params);
1322
+
1323
+ this.replaceStack(function(current) {
1324
+ params.splice(1, 0, current);
1325
+ return current + " = blockHelperMissing.call(" + params.join(", ") + ")";
1326
+ });
1327
+ },
1328
+
1329
+ // [ambiguousBlockValue]
1330
+ //
1331
+ // On stack, before: hash, inverse, program, value
1332
+ // Compiler value, before: lastHelper=value of last found helper, if any
1333
+ // On stack, after, if no lastHelper: same as [blockValue]
1334
+ // On stack, after, if lastHelper: value
1335
+ ambiguousBlockValue: function() {
1336
+ this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
1337
+
1338
+ var params = ["depth0"];
1339
+ this.setupParams(0, params);
1340
+
1341
+ var current = this.topStack();
1342
+ params.splice(1, 0, current);
1343
+
1344
+ this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
1345
+ },
1346
+
1347
+ // [appendContent]
1348
+ //
1349
+ // On stack, before: ...
1350
+ // On stack, after: ...
1351
+ //
1352
+ // Appends the string value of `content` to the current buffer
1266
1353
  appendContent: function(content) {
1267
1354
  this.source.push(this.appendToBuffer(this.quotedString(content)));
1268
1355
  },
1269
1356
 
1357
+ // [append]
1358
+ //
1359
+ // On stack, before: value, ...
1360
+ // On stack, after: ...
1361
+ //
1362
+ // Coerces `value` to a String and appends it to the current buffer.
1363
+ //
1364
+ // If `value` is truthy, or 0, it is coerced into a string and appended
1365
+ // Otherwise, the empty string is appended
1270
1366
  append: function() {
1271
1367
  var local = this.popStack();
1272
1368
  this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
@@ -1275,163 +1371,242 @@ Handlebars.JavaScriptCompiler = function() {};
1275
1371
  }
1276
1372
  },
1277
1373
 
1374
+ // [appendEscaped]
1375
+ //
1376
+ // On stack, before: value, ...
1377
+ // On stack, after: ...
1378
+ //
1379
+ // Escape `value` and append it to the buffer
1278
1380
  appendEscaped: function() {
1279
- var opcode = this.nextOpcode(1), extra = "";
1381
+ var opcode = this.nextOpcode(), extra = "";
1280
1382
  this.context.aliases.escapeExpression = 'this.escapeExpression';
1281
1383
 
1282
- if(opcode[0] === 'appendContent') {
1283
- extra = " + " + this.quotedString(opcode[1][0]);
1384
+ if(opcode && opcode.opcode === 'appendContent') {
1385
+ extra = " + " + this.quotedString(opcode.args[0]);
1284
1386
  this.eat(opcode);
1285
1387
  }
1286
1388
 
1287
1389
  this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
1288
1390
  },
1289
1391
 
1392
+ // [getContext]
1393
+ //
1394
+ // On stack, before: ...
1395
+ // On stack, after: ...
1396
+ // Compiler value, after: lastContext=depth
1397
+ //
1398
+ // Set the value of the `lastContext` compiler value to the depth
1290
1399
  getContext: function(depth) {
1291
1400
  if(this.lastContext !== depth) {
1292
1401
  this.lastContext = depth;
1293
1402
  }
1294
1403
  },
1295
1404
 
1296
- lookupWithHelpers: function(name, isScoped) {
1297
- if(name) {
1298
- var topStack = this.nextStack();
1299
-
1300
- this.usingKnownHelper = false;
1301
-
1302
- var toPush;
1303
- if (!isScoped && this.options.knownHelpers[name]) {
1304
- toPush = topStack + " = " + this.nameLookup('helpers', name, 'helper');
1305
- this.usingKnownHelper = true;
1306
- } else if (isScoped || this.options.knownHelpersOnly) {
1307
- toPush = topStack + " = " + this.nameLookup('depth' + this.lastContext, name, 'context');
1308
- } else {
1309
- this.register('foundHelper', this.nameLookup('helpers', name, 'helper'));
1310
- toPush = topStack + " = foundHelper || " + this.nameLookup('depth' + this.lastContext, name, 'context');
1311
- }
1312
-
1313
- toPush += ';';
1314
- this.source.push(toPush);
1315
- } else {
1316
- this.pushStack('depth' + this.lastContext);
1317
- }
1405
+ // [lookupOnContext]
1406
+ //
1407
+ // On stack, before: ...
1408
+ // On stack, after: currentContext[name], ...
1409
+ //
1410
+ // Looks up the value of `name` on the current context and pushes
1411
+ // it onto the stack.
1412
+ lookupOnContext: function(name) {
1413
+ this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context'));
1414
+ },
1415
+
1416
+ // [pushContext]
1417
+ //
1418
+ // On stack, before: ...
1419
+ // On stack, after: currentContext, ...
1420
+ //
1421
+ // Pushes the value of the current context onto the stack.
1422
+ pushContext: function() {
1423
+ this.pushStackLiteral('depth' + this.lastContext);
1424
+ },
1425
+
1426
+ // [resolvePossibleLambda]
1427
+ //
1428
+ // On stack, before: value, ...
1429
+ // On stack, after: resolved value, ...
1430
+ //
1431
+ // If the `value` is a lambda, replace it on the stack by
1432
+ // the return value of the lambda
1433
+ resolvePossibleLambda: function() {
1434
+ this.context.aliases.functionType = '"function"';
1435
+
1436
+ this.replaceStack(function(current) {
1437
+ return "typeof " + current + " === functionType ? " + current + "() : " + current;
1438
+ });
1318
1439
  },
1319
1440
 
1441
+ // [lookup]
1442
+ //
1443
+ // On stack, before: value, ...
1444
+ // On stack, after: value[name], ...
1445
+ //
1446
+ // Replace the value on the stack with the result of looking
1447
+ // up `name` on `value`
1320
1448
  lookup: function(name) {
1321
- var topStack = this.topStack();
1322
- this.source.push(topStack + " = (" + topStack + " === null || " + topStack + " === undefined || " + topStack + " === false ? " +
1323
- topStack + " : " + this.nameLookup(topStack, name, 'context') + ");");
1449
+ this.replaceStack(function(current) {
1450
+ return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
1451
+ });
1324
1452
  },
1325
1453
 
1454
+ // [lookupData]
1455
+ //
1456
+ // On stack, before: ...
1457
+ // On stack, after: data[id], ...
1458
+ //
1459
+ // Push the result of looking up `id` on the current data
1460
+ lookupData: function(id) {
1461
+ this.pushStack(this.nameLookup('data', id, 'data'));
1462
+ },
1463
+
1464
+ // [pushStringParam]
1465
+ //
1466
+ // On stack, before: ...
1467
+ // On stack, after: string, currentContext, ...
1468
+ //
1469
+ // This opcode is designed for use in string mode, which
1470
+ // provides the string value of a parameter along with its
1471
+ // depth rather than resolving it immediately.
1326
1472
  pushStringParam: function(string) {
1327
- this.pushStack('depth' + this.lastContext);
1473
+ this.pushStackLiteral('depth' + this.lastContext);
1328
1474
  this.pushString(string);
1329
1475
  },
1330
1476
 
1477
+ // [pushString]
1478
+ //
1479
+ // On stack, before: ...
1480
+ // On stack, after: quotedString(string), ...
1481
+ //
1482
+ // Push a quoted version of `string` onto the stack
1331
1483
  pushString: function(string) {
1332
- this.pushStack(this.quotedString(string));
1333
- },
1334
-
1335
- push: function(name) {
1336
- this.pushStack(name);
1337
- },
1338
-
1339
- invokeMustache: function(paramSize, original, hasHash) {
1340
- this.populateParams(paramSize, this.quotedString(original), "{}", null, hasHash, function(nextStack, helperMissingString, id) {
1341
- if (!this.usingKnownHelper) {
1342
- this.context.aliases.helperMissing = 'helpers.helperMissing';
1343
- this.context.aliases.undef = 'void 0';
1344
- this.source.push("else if(" + id + "=== undef) { " + nextStack + " = helperMissing.call(" + helperMissingString + "); }");
1345
- if (nextStack !== id) {
1346
- this.source.push("else { " + nextStack + " = " + id + "; }");
1347
- }
1348
- }
1349
- });
1350
- },
1351
-
1352
- invokeProgram: function(guid, paramSize, hasHash) {
1353
- var inverse = this.programExpression(this.inverse);
1354
- var mainProgram = this.programExpression(guid);
1355
-
1356
- this.populateParams(paramSize, null, mainProgram, inverse, hasHash, function(nextStack, helperMissingString, id) {
1357
- if (!this.usingKnownHelper) {
1358
- this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
1359
- this.source.push("else { " + nextStack + " = blockHelperMissing.call(" + helperMissingString + "); }");
1360
- }
1361
- });
1362
- },
1363
-
1364
- populateParams: function(paramSize, helperId, program, inverse, hasHash, fn) {
1365
- var needsRegister = hasHash || this.options.stringParams || inverse || this.options.data;
1366
- var id = this.popStack(), nextStack;
1367
- var params = [], param, stringParam, stringOptions;
1368
-
1369
- if (needsRegister) {
1370
- this.register('tmp1', program);
1371
- stringOptions = 'tmp1';
1484
+ this.pushStackLiteral(this.quotedString(string));
1485
+ },
1486
+
1487
+ // [push]
1488
+ //
1489
+ // On stack, before: ...
1490
+ // On stack, after: expr, ...
1491
+ //
1492
+ // Push an expression onto the stack
1493
+ push: function(expr) {
1494
+ this.pushStack(expr);
1495
+ },
1496
+
1497
+ // [pushLiteral]
1498
+ //
1499
+ // On stack, before: ...
1500
+ // On stack, after: value, ...
1501
+ //
1502
+ // Pushes a value onto the stack. This operation prevents
1503
+ // the compiler from creating a temporary variable to hold
1504
+ // it.
1505
+ pushLiteral: function(value) {
1506
+ this.pushStackLiteral(value);
1507
+ },
1508
+
1509
+ // [pushProgram]
1510
+ //
1511
+ // On stack, before: ...
1512
+ // On stack, after: program(guid), ...
1513
+ //
1514
+ // Push a program expression onto the stack. This takes
1515
+ // a compile-time guid and converts it into a runtime-accessible
1516
+ // expression.
1517
+ pushProgram: function(guid) {
1518
+ if (guid != null) {
1519
+ this.pushStackLiteral(this.programExpression(guid));
1372
1520
  } else {
1373
- stringOptions = '{ hash: {} }';
1374
- }
1375
-
1376
- if (needsRegister) {
1377
- var hash = (hasHash ? this.popStack() : '{}');
1378
- this.source.push('tmp1.hash = ' + hash + ';');
1379
- }
1380
-
1381
- if(this.options.stringParams) {
1382
- this.source.push('tmp1.contexts = [];');
1383
- }
1384
-
1385
- for(var i=0; i<paramSize; i++) {
1386
- param = this.popStack();
1387
- params.push(param);
1388
-
1389
- if(this.options.stringParams) {
1390
- this.source.push('tmp1.contexts.push(' + this.popStack() + ');');
1391
- }
1521
+ this.pushStackLiteral(null);
1392
1522
  }
1393
-
1394
- if(inverse) {
1395
- this.source.push('tmp1.fn = tmp1;');
1396
- this.source.push('tmp1.inverse = ' + inverse + ';');
1397
- }
1398
-
1399
- if(this.options.data) {
1400
- this.source.push('tmp1.data = data;');
1401
- }
1402
-
1403
- params.push(stringOptions);
1404
-
1405
- this.populateCall(params, id, helperId || id, fn, program !== '{}');
1406
1523
  },
1407
1524
 
1408
- populateCall: function(params, id, helperId, fn, program) {
1409
- var paramString = ["depth0"].concat(params).join(", ");
1410
- var helperMissingString = ["depth0"].concat(helperId).concat(params).join(", ");
1411
-
1525
+ // [invokeHelper]
1526
+ //
1527
+ // On stack, before: hash, inverse, program, params..., ...
1528
+ // On stack, after: result of helper invocation
1529
+ //
1530
+ // Pops off the helper's parameters, invokes the helper,
1531
+ // and pushes the helper's return value onto the stack.
1532
+ //
1533
+ // If the helper is not found, `helperMissing` is called.
1534
+ invokeHelper: function(paramSize, name) {
1535
+ this.context.aliases.helperMissing = 'helpers.helperMissing';
1536
+
1537
+ var helper = this.lastHelper = this.setupHelper(paramSize, name);
1538
+ this.register('foundHelper', helper.name);
1539
+
1540
+ this.pushStack("foundHelper ? foundHelper.call(" +
1541
+ helper.callParams + ") " + ": helperMissing.call(" +
1542
+ helper.helperMissingParams + ")");
1543
+ },
1544
+
1545
+ // [invokeKnownHelper]
1546
+ //
1547
+ // On stack, before: hash, inverse, program, params..., ...
1548
+ // On stack, after: result of helper invocation
1549
+ //
1550
+ // This operation is used when the helper is known to exist,
1551
+ // so a `helperMissing` fallback is not required.
1552
+ invokeKnownHelper: function(paramSize, name) {
1553
+ var helper = this.setupHelper(paramSize, name);
1554
+ this.pushStack(helper.name + ".call(" + helper.callParams + ")");
1555
+ },
1556
+
1557
+ // [invokeAmbiguous]
1558
+ //
1559
+ // On stack, before: hash, inverse, program, params..., ...
1560
+ // On stack, after: result of disambiguation
1561
+ //
1562
+ // This operation is used when an expression like `{{foo}}`
1563
+ // is provided, but we don't know at compile-time whether it
1564
+ // is a helper or a path.
1565
+ //
1566
+ // This operation emits more code than the other options,
1567
+ // and can be avoided by passing the `knownHelpers` and
1568
+ // `knownHelpersOnly` flags at compile-time.
1569
+ invokeAmbiguous: function(name) {
1570
+ this.context.aliases.functionType = '"function"';
1571
+
1572
+ this.pushStackLiteral('{}');
1573
+ var helper = this.setupHelper(0, name);
1574
+
1575
+ var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
1576
+ this.register('foundHelper', helperName);
1577
+
1578
+ var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
1412
1579
  var nextStack = this.nextStack();
1413
1580
 
1414
- if (this.usingKnownHelper) {
1415
- this.source.push(nextStack + " = " + id + ".call(" + paramString + ");");
1416
- } else {
1417
- this.context.aliases.functionType = '"function"';
1418
- var condition = program ? "foundHelper && " : "";
1419
- this.source.push("if(" + condition + "typeof " + id + " === functionType) { " + nextStack + " = " + id + ".call(" + paramString + "); }");
1420
- }
1421
- fn.call(this, nextStack, helperMissingString, id);
1422
- this.usingKnownHelper = false;
1581
+ this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
1582
+ this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '() : ' + nextStack + '; }');
1423
1583
  },
1424
1584
 
1425
- invokePartial: function(context) {
1426
- var params = [this.nameLookup('partials', context, 'partial'), "'" + context + "'", this.popStack(), "helpers", "partials"];
1585
+ // [invokePartial]
1586
+ //
1587
+ // On stack, before: context, ...
1588
+ // On stack after: result of partial invocation
1589
+ //
1590
+ // This operation pops off a context, invokes a partial with that context,
1591
+ // and pushes the result of the invocation back.
1592
+ invokePartial: function(name) {
1593
+ var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
1427
1594
 
1428
1595
  if (this.options.data) {
1429
1596
  params.push("data");
1430
1597
  }
1431
1598
 
1599
+ this.context.aliases.self = "this";
1432
1600
  this.pushStack("self.invokePartial(" + params.join(", ") + ");");
1433
1601
  },
1434
1602
 
1603
+ // [assignToHash]
1604
+ //
1605
+ // On stack, before: value, hash, ...
1606
+ // On stack, after: hash, ...
1607
+ //
1608
+ // Pops a value and hash off the stack, assigns `hash[key] = value`
1609
+ // and pushes the hash back onto the stack.
1435
1610
  assignToHash: function(key) {
1436
1611
  var value = this.popStack();
1437
1612
  var hash = this.topStack();
@@ -1459,7 +1634,11 @@ Handlebars.JavaScriptCompiler = function() {};
1459
1634
  },
1460
1635
 
1461
1636
  programExpression: function(guid) {
1462
- if(guid == null) { return "self.noop"; }
1637
+ this.context.aliases.self = "this";
1638
+
1639
+ if(guid == null) {
1640
+ return "self.noop";
1641
+ }
1463
1642
 
1464
1643
  var child = this.environment.children[guid],
1465
1644
  depths = child.depths.list, depth;
@@ -1487,29 +1666,61 @@ Handlebars.JavaScriptCompiler = function() {};
1487
1666
  },
1488
1667
 
1489
1668
  useRegister: function(name) {
1490
- if(!this.context.registers[name]) {
1491
- this.context.registers[name] = true;
1492
- this.context.registers.list.push(name);
1669
+ if(!this.registers[name]) {
1670
+ this.registers[name] = true;
1671
+ this.registers.list.push(name);
1493
1672
  }
1494
1673
  },
1495
1674
 
1675
+ pushStackLiteral: function(item) {
1676
+ this.compileStack.push(new Literal(item));
1677
+ return item;
1678
+ },
1679
+
1496
1680
  pushStack: function(item) {
1497
- this.source.push(this.nextStack() + " = " + item + ";");
1681
+ this.source.push(this.incrStack() + " = " + item + ";");
1682
+ this.compileStack.push("stack" + this.stackSlot);
1683
+ return "stack" + this.stackSlot;
1684
+ },
1685
+
1686
+ replaceStack: function(callback) {
1687
+ var item = callback.call(this, this.topStack());
1688
+
1689
+ this.source.push(this.topStack() + " = " + item + ";");
1498
1690
  return "stack" + this.stackSlot;
1499
1691
  },
1500
1692
 
1501
- nextStack: function() {
1693
+ nextStack: function(skipCompileStack) {
1694
+ var name = this.incrStack();
1695
+ this.compileStack.push("stack" + this.stackSlot);
1696
+ return name;
1697
+ },
1698
+
1699
+ incrStack: function() {
1502
1700
  this.stackSlot++;
1503
1701
  if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1504
1702
  return "stack" + this.stackSlot;
1505
1703
  },
1506
1704
 
1507
1705
  popStack: function() {
1508
- return "stack" + this.stackSlot--;
1706
+ var item = this.compileStack.pop();
1707
+
1708
+ if (item instanceof Literal) {
1709
+ return item.value;
1710
+ } else {
1711
+ this.stackSlot--;
1712
+ return item;
1713
+ }
1509
1714
  },
1510
1715
 
1511
1716
  topStack: function() {
1512
- return "stack" + this.stackSlot;
1717
+ var item = this.compileStack[this.compileStack.length - 1];
1718
+
1719
+ if (item instanceof Literal) {
1720
+ return item.value;
1721
+ } else {
1722
+ return item;
1723
+ }
1513
1724
  },
1514
1725
 
1515
1726
  quotedString: function(str) {
@@ -1518,6 +1729,67 @@ Handlebars.JavaScriptCompiler = function() {};
1518
1729
  .replace(/"/g, '\\"')
1519
1730
  .replace(/\n/g, '\\n')
1520
1731
  .replace(/\r/g, '\\r') + '"';
1732
+ },
1733
+
1734
+ setupHelper: function(paramSize, name) {
1735
+ var params = [];
1736
+ this.setupParams(paramSize, params);
1737
+ var foundHelper = this.nameLookup('helpers', name, 'helper');
1738
+
1739
+ return {
1740
+ params: params,
1741
+ name: foundHelper,
1742
+ callParams: ["depth0"].concat(params).join(", "),
1743
+ helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ")
1744
+ };
1745
+ },
1746
+
1747
+ // the params and contexts arguments are passed in arrays
1748
+ // to fill in
1749
+ setupParams: function(paramSize, params) {
1750
+ var options = [], contexts = [], param, inverse, program;
1751
+
1752
+ options.push("hash:" + this.popStack());
1753
+
1754
+ inverse = this.popStack();
1755
+ program = this.popStack();
1756
+
1757
+ // Avoid setting fn and inverse if neither are set. This allows
1758
+ // helpers to do a check for `if (options.fn)`
1759
+ if (program || inverse) {
1760
+ if (!program) {
1761
+ this.context.aliases.self = "this";
1762
+ program = "self.noop";
1763
+ }
1764
+
1765
+ if (!inverse) {
1766
+ this.context.aliases.self = "this";
1767
+ inverse = "self.noop";
1768
+ }
1769
+
1770
+ options.push("inverse:" + inverse);
1771
+ options.push("fn:" + program);
1772
+ }
1773
+
1774
+ for(var i=0; i<paramSize; i++) {
1775
+ param = this.popStack();
1776
+ params.push(param);
1777
+
1778
+ if(this.options.stringParams) {
1779
+ contexts.push(this.popStack());
1780
+ }
1781
+ }
1782
+
1783
+ if (this.options.stringParams) {
1784
+ options.push("contexts:[" + contexts.join(",") + "]");
1785
+ }
1786
+
1787
+ if(this.options.data) {
1788
+ options.push("data:data");
1789
+ }
1790
+
1791
+ params.push("{" + options.join(",") + "}");
1792
+ return params.join(", ");
1521
1793
  }
1522
1794
  };
1523
1795
 
@@ -1638,7 +1910,7 @@ Handlebars.VM = {
1638
1910
  } else if (!Handlebars.compile) {
1639
1911
  throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
1640
1912
  } else {
1641
- partials[name] = Handlebars.compile(partial);
1913
+ partials[name] = Handlebars.compile(partial, {data: data !== undefined});
1642
1914
  return partials[name](context, options);
1643
1915
  }
1644
1916
  }