summaryrefslogtreecommitdiffstats
path: root/lib/handlebars/compiler/compiler.js
diff options
context:
space:
mode:
authormachty <machty@gmail.com>2013-12-24 11:01:23 -0500
committermachty <machty@gmail.com>2013-12-30 19:07:04 -0500
commitb09333db7946d20ba7dbc6d32d5496ab8295b8e1 (patch)
treeb96b31bd41c1a6c86f45e114b100c124f8217e6f /lib/handlebars/compiler/compiler.js
parentac98e7b177095be80fc5d590d15b4724dd731cab (diff)
downloadhandlebars.js-b09333db7946d20ba7dbc6d32d5496ab8295b8e1.zip
handlebars.js-b09333db7946d20ba7dbc6d32d5496ab8295b8e1.tar.gz
handlebars.js-b09333db7946d20ba7dbc6d32d5496ab8295b8e1.tar.bz2
Added support for subexpressions
Handlebars now supports subexpressions. {{foo (bar 3)}} Subexpressions are always evaluated as helpers; if `3` were omitted from the above example, `bar` would be invoked as a param-less helper, even though a top-levell `{{bar}}` would be considered ambiguous. The return value of a subexpression helper is passed in as a parameter of a parent subexpression helper, even in string params mode. Their type, as listed in `options.types` or `options.hashTypes` in string params mode, is "sexpr". The main conceptual change in the Handlebars code is that there is a new AST.SexprNode that manages the params/hash passed into a mustache, as well as the logic that governs whether that mustache is a helper invocation, property lookup, etc. MustacheNode, which used to manage this stuff, still exists, but only manages things like white-space stripping and whether the mustache is escaped or not. So, a MustacheNode _has_ a SexprNode. The introduction of subexpressions is fully backwards compatible, but a few things needed to change about the compiled output of a template in order to support subexpressions. The main one is that the options hash is no longer stashed in a local `options` var before being passed to either the helper being invoked or the `helperMissing` fallback. Rather, the options object is inlined in these cases. This does mean compiled template sizes will be a little bit larger, even those that don't make use of subexpressions, but shouldn't have any noticeable impact on performance when actually rendering templates as only one of these inlined objects will actually get evaluated.
Diffstat (limited to 'lib/handlebars/compiler/compiler.js')
-rw-r--r--lib/handlebars/compiler/compiler.js90
1 files changed, 46 insertions, 44 deletions
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 259c543..00353a8 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -156,12 +156,13 @@ Compiler.prototype = {
inverse = this.compileProgram(inverse);
}
- var type = this.classifyMustache(mustache);
+ var sexpr = mustache.sexpr;
+ var type = this.classifySexpr(sexpr);
if (type === "helper") {
- this.helperMustache(mustache, program, inverse);
+ this.helperSexpr(sexpr, program, inverse);
} else if (type === "simple") {
- this.simpleMustache(mustache);
+ this.simpleSexpr(sexpr);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
@@ -170,7 +171,7 @@ Compiler.prototype = {
this.opcode('emptyHash');
this.opcode('blockValue');
} else {
- this.ambiguousMustache(mustache, program, inverse);
+ this.ambiguousSexpr(sexpr, program, inverse);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
@@ -198,6 +199,12 @@ Compiler.prototype = {
}
this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', val.stringModeValue, val.type);
+
+ if (val.type === 'sexpr') {
+ // Subexpressions get evaluated and passed in
+ // in string params mode.
+ this.sexpr(val);
+ }
} else {
this.accept(val);
}
@@ -226,26 +233,17 @@ Compiler.prototype = {
},
mustache: function(mustache) {
- var options = this.options;
- var type = this.classifyMustache(mustache);
-
- if (type === "simple") {
- this.simpleMustache(mustache);
- } else if (type === "helper") {
- this.helperMustache(mustache);
- } else {
- this.ambiguousMustache(mustache);
- }
+ this.sexpr(mustache.sexpr);
- if(mustache.escaped && !options.noEscape) {
+ if(mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
} else {
this.opcode('append');
}
},
- ambiguousMustache: function(mustache, program, inverse) {
- var id = mustache.id,
+ ambiguousSexpr: function(sexpr, program, inverse) {
+ var id = sexpr.id,
name = id.parts[0],
isBlock = program != null || inverse != null;
@@ -257,8 +255,8 @@ Compiler.prototype = {
this.opcode('invokeAmbiguous', name, isBlock);
},
- simpleMustache: function(mustache) {
- var id = mustache.id;
+ simpleSexpr: function(sexpr) {
+ var id = sexpr.id;
if (id.type === 'DATA') {
this.DATA(id);
@@ -274,9 +272,9 @@ Compiler.prototype = {
this.opcode('resolvePossibleLambda');
},
- helperMustache: function(mustache, program, inverse) {
- var params = this.setupFullMustacheParams(mustache, program, inverse),
- name = mustache.id.parts[0];
+ helperSexpr: function(sexpr, program, inverse) {
+ var params = this.setupFullMustacheParams(sexpr, program, inverse),
+ name = sexpr.id.parts[0];
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
@@ -287,6 +285,18 @@ Compiler.prototype = {
}
},
+ sexpr: function(sexpr) {
+ var type = this.classifySexpr(sexpr);
+
+ if (type === "simple") {
+ this.simpleSexpr(sexpr);
+ } else if (type === "helper") {
+ this.helperSexpr(sexpr);
+ } else {
+ this.ambiguousSexpr(sexpr);
+ }
+ },
+
ID: function(id) {
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
@@ -349,14 +359,14 @@ Compiler.prototype = {
}
},
- classifyMustache: function(mustache) {
- var isHelper = mustache.isHelper;
- var isEligible = mustache.eligibleHelper;
+ classifySexpr: function(sexpr) {
+ var isHelper = sexpr.isHelper;
+ var isEligible = sexpr.eligibleHelper;
var options = this.options;
// if ambiguous, we can possibly resolve the ambiguity now
if (isEligible && !isHelper) {
- var name = mustache.id.parts[0];
+ var name = sexpr.id.parts[0];
if (options.knownHelpers[name]) {
isHelper = true;
@@ -383,35 +393,27 @@ Compiler.prototype = {
this.opcode('getContext', param.depth || 0);
this.opcode('pushStringParam', param.stringModeValue, param.type);
+
+ if (param.type === 'sexpr') {
+ // Subexpressions get evaluated and passed in
+ // in string params mode.
+ this.sexpr(param);
+ }
} else {
this[param.type](param);
}
}
},
- setupMustacheParams: function(mustache) {
- var params = mustache.params;
- this.pushParams(params);
-
- if(mustache.hash) {
- this.hash(mustache.hash);
- } else {
- this.opcode('emptyHash');
- }
-
- return params;
- },
-
- // this will replace setupMustacheParams when we're done
- setupFullMustacheParams: function(mustache, program, inverse) {
- var params = mustache.params;
+ setupFullMustacheParams: function(sexpr, program, inverse) {
+ var params = sexpr.params;
this.pushParams(params);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
- if(mustache.hash) {
- this.hash(mustache.hash);
+ if (sexpr.hash) {
+ this.hash(sexpr.hash);
} else {
this.opcode('emptyHash');
}