Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

More cleanup

* Make block and inverse use the main helper path
* Eliminate separate inverse AST node
  • Loading branch information...
commit 175c6fae0f704b2f1088728136e238186fbb1cf3 1 parent 3486b53
tomhuda authored
10 lib/handlebars/compiler/ast.js
View
@@ -48,18 +48,12 @@ var Handlebars = require('./base');
}
};
- Handlebars.AST.BlockNode = function(mustache, program, close) {
+ Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
verifyMatch(mustache.id, close);
this.type = "block";
this.mustache = mustache;
this.program = program;
- };
-
- Handlebars.AST.InverseNode = function(mustache, program, close) {
- verifyMatch(mustache.id, close);
- this.type = "inverse";
- this.mustache = mustache;
- this.program = program;
+ this.inverse = inverse;
};
Handlebars.AST.ContentNode = function(string) {
220 lib/handlebars/compiler/compiler.js
View
@@ -106,32 +106,42 @@ Handlebars.JavaScriptCompiler = function() {};
},
block: function(block) {
- var mustache = block.mustache;
- var depth, child, inverse, inverseGuid;
+ var mustache = block.mustache,
+ program = block.program,
+ inverse = block.inverse;
- var params = this.setupStackForMustache(mustache);
-
- var programGuid = this.compileProgram(block.program);
+ if (program) {
+ program = this.compileProgram(program);
+ }
- if(block.program.inverse) {
- inverseGuid = this.compileProgram(block.program.inverse);
- this.declare('inverse', inverseGuid);
+ if (inverse) {
+ inverse = this.compileProgram(inverse);
}
- this.opcode('invokeProgram', programGuid, params.length, !!mustache.hash);
- this.declare('inverse', null);
- this.opcode('append');
- },
+ var type = this.classifyMustache(mustache);
- inverse: function(block) {
- var params = this.setupStackForMustache(block.mustache);
+ if (type === "helper") {
+ this.helperMustache(mustache, program, inverse);
+ } else if (type === "simple") {
+ this.simpleMustache(mustache);
- var programGuid = this.compileProgram(block.program);
+ // now that the simple mustache is resolved, we need to
+ // evaluate it by executing `blockHelperMissing`
+ this.opcode('pushProgram', program);
+ this.opcode('pushProgram', inverse);
+ this.opcode('pushLiteral', '{}');
+ this.opcode('blockValue');
+ } else {
+ this.ambiguousMustache(mustache, program, inverse);
- this.declare('inverse', programGuid);
+ // now that the simple mustache is resolved, we need to
+ // evaluate it by executing `blockHelperMissing`
+ this.opcode('pushProgram', program);
+ this.opcode('pushProgram', inverse);
+ this.opcode('pushLiteral', '{}');
+ this.opcode('ambiguousBlockValue');
+ }
- this.opcode('invokeProgram', null, params.length, !!block.mustache.hash);
- this.declare('inverse', null);
this.opcode('append');
},
@@ -168,24 +178,12 @@ Handlebars.JavaScriptCompiler = function() {};
},
mustache: function(mustache) {
- var isHelper = mustache.isHelper;
- var isEligible = mustache.eligibleHelper;
- var options = this.options;
+ var options = this.options;
+ var type = this.classifyMustache(mustache);
- // if ambiguous, we can possibly resolve the ambiguity now
- if (isEligible && !isHelper) {
- var name = mustache.id.parts[0];
-
- if (options.knownHelpers[name]) {
- isHelper = true;
- } else if (options.knownHelpersOnly) {
- isEligible = false;
- }
- }
-
- if (!isEligible) {
+ if (type === "simple") {
this.simpleMustache(mustache);
- } else if (isHelper) {
+ } else if (type === "helper") {
this.helperMustache(mustache);
} else {
this.ambiguousMustache(mustache);
@@ -198,14 +196,18 @@ Handlebars.JavaScriptCompiler = function() {};
}
},
- ambiguousMustache: function(mustache) {
- var params = this.setupStackForMustache(mustache),
- id = mustache.id, name = id.parts[0];
+ ambiguousMustache: function(mustache, program, inverse) {
+ var id = mustache.id, name = id.parts[0];
- this.opcode('invokeMustache', params.length, id.original);
+ this.opcode('getContext', id.depth);
+
+ this.opcode('pushProgram', program);
+ this.opcode('pushProgram', inverse);
+
+ this.opcode('invokeAmbiguous', name);
},
- simpleMustache: function(mustache) {
+ simpleMustache: function(mustache, program, inverse) {
var id = mustache.id;
this.addDepth(id.depth);
@@ -222,8 +224,8 @@ Handlebars.JavaScriptCompiler = function() {};
this.opcode('resolvePossibleLambda');
},
- helperMustache: function(mustache) {
- var params = this.setupMustacheParams(mustache),
+ helperMustache: function(mustache, program, inverse) {
+ var params = this.setupFullMustacheParams(mustache, program, inverse),
name = mustache.id.parts[0];
if (this.options.knownHelpers[name]) {
@@ -264,11 +266,11 @@ Handlebars.JavaScriptCompiler = function() {};
},
INTEGER: function(integer) {
- this.opcode('push', integer.integer);
+ this.opcode('pushLiteral', integer.integer);
},
BOOLEAN: function(bool) {
- this.opcode('push', bool.bool);
+ this.opcode('pushLiteral', bool.bool);
},
comment: function() {},
@@ -291,6 +293,27 @@ Handlebars.JavaScriptCompiler = function() {};
}
},
+ classifyMustache: function(mustache) {
+ var isHelper = mustache.isHelper;
+ var isEligible = mustache.eligibleHelper;
+ var options = this.options;
+
+ // if ambiguous, we can possibly resolve the ambiguity now
+ if (isEligible && !isHelper) {
+ var name = mustache.id.parts[0];
+
+ if (options.knownHelpers[name]) {
+ isHelper = true;
+ } else if (options.knownHelpersOnly) {
+ isEligible = false;
+ }
+ }
+
+ if (isHelper) { return "helper"; }
+ else if (isEligible) { return "ambiguous"; }
+ else { return "simple"; }
+ },
+
pushParams: function(params) {
var i = params.length, param;
@@ -323,6 +346,23 @@ Handlebars.JavaScriptCompiler = function() {};
return params;
},
+ // this will replace setupMustacheParams when we're done
+ setupFullMustacheParams: function(mustache, program, inverse) {
+ var params = mustache.params;
+ this.pushParams(params);
+
+ this.opcode('pushProgram', program);
+ this.opcode('pushProgram', inverse);
+
+ if(mustache.hash) {
+ this.hash(mustache.hash);
+ } else {
+ this.opcode('pushLiteral', '{}');
+ }
+
+ return params;
+ },
+
setupStackForMustache: function(mustache) {
var params = this.setupMustacheParams(mustache);
@@ -485,6 +525,30 @@ Handlebars.JavaScriptCompiler = function() {};
}
},
+ blockValue: function() {
+ this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+
+ var params = ["depth0"];
+ this.setupParams(0, params);
+
+ this.replaceStack(function(current) {
+ params.splice(1, 0, current);
+ return current + " = blockHelperMissing.call(" + params.join(", ") + ")";
+ });
+ },
+
+ ambiguousBlockValue: function() {
+ this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
+
+ var params = ["depth0"];
+ this.setupParams(0, params);
+
+ var current = this.topStack();
+ params.splice(1, 0, current);
+
+ this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
+ },
+
appendContent: function(content) {
this.source.push(this.appendToBuffer(this.quotedString(content)));
},
@@ -536,7 +600,8 @@ Handlebars.JavaScriptCompiler = function() {};
},
lookupWithHelpers: function(name) {
- this.register('foundHelper', this.nameLookup('helpers', name, 'helper'));
+ var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
+ this.register('foundHelper', helperName);
this.pushStack("foundHelper || " + this.nameLookup('depth' + this.lastContext, name, 'context'));
},
@@ -563,25 +628,6 @@ Handlebars.JavaScriptCompiler = function() {};
this.pushStackLiteral(value);
},
- // The rules for mustaches are:
- //
- // If the first parameter resolves to a function, call the function with the remaining parameters
- // If the first parameter resolves to undefined, call helperMissing with the string form of the
- // first parameter and the remaining parameters
- // Otherwise, if there is only one parameter, the expression evaluates to the value of the first
- // parameter
- invokeMustache: function(paramSize, original) {
- this.populateParams(paramSize, this.quotedString(original), [], true, function(nextStack, helperMissingString, id) {
- this.context.aliases.helperMissing = 'helpers.helperMissing';
- this.context.aliases.undef = 'void 0';
-
- this.source.push("else if(" + id + "=== undef) { " + nextStack + " = helperMissing.call(" + helperMissingString + "); }");
- if (nextStack !== id) {
- this.source.push("else { " + nextStack + " = " + id + "; }");
- }
- });
- },
-
// The rules for mustaches containing a block are:
//
// If the first parameter resolves to a function, call the function with the remaining parameters
@@ -604,10 +650,18 @@ Handlebars.JavaScriptCompiler = function() {};
});
},
+ pushProgram: function(guid) {
+ if (guid != null) {
+ this.pushStackLiteral(this.programExpression(guid));
+ } else {
+ this.pushStackLiteral(null);
+ }
+ },
+
invokeHelper: function(paramSize, name) {
this.context.aliases.helperMissing = 'helpers.helperMissing';
- var helper = this.setupHelper(paramSize, name);
+ var helper = this.lastHelper = this.setupHelper(paramSize, name);
this.register('foundHelper', helper.name);
this.pushStack("foundHelper ? foundHelper.call(" +
@@ -620,6 +674,22 @@ Handlebars.JavaScriptCompiler = function() {};
this.pushStack(helper.name + ".call(" + helper.callParams + ")");
},
+ invokeAmbiguous: function(name) {
+ this.context.aliases.functionType = '"function"';
+
+ this.pushStackLiteral('{}');
+ var helper = this.setupHelper(0, name);
+
+ var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
+ this.register('foundHelper', helperName);
+
+ var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
+ var nextStack = this.nextStack();
+
+ this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
+ this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '() : ' + nextStack + '; }');
+ },
+
setupHelper: function(paramSize, name) {
var params = [];
this.setupParams(paramSize, params);
@@ -636,10 +706,30 @@ Handlebars.JavaScriptCompiler = function() {};
// the params and contexts arguments are passed in arrays
// to fill in
setupParams: function(paramSize, params) {
- var options = [], contexts = [], param;
+ var options = [], contexts = [], param, inverse, program;
options.push("hash:" + this.popStack());
+ inverse = this.popStack();
+ program = this.popStack();
+
+ // Avoid setting fn and inverse if neither are set. This allows
+ // helpers to do a check for `if (options.fn)`
+ if (program || inverse) {
+ if (!program) {
+ this.context.aliases.self = "this";
+ program = "self.noop";
+ }
+
+ if (!inverse) {
+ this.context.aliases.self = "this";
+ inverse = "self.noop";
+ }
+
+ options.push("inverse:" + inverse);
+ options.push("fn:" + program);
+ }
+
for(var i=0; i<paramSize; i++) {
param = this.popStack();
params.push(param);
8 spec/qunit_spec.js
View
@@ -376,10 +376,10 @@ test("block helper inverted sections", function() {
module("helpers hash");
test("providing a helpers hash", function() {
- shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!",
+ shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",
"helpers hash is available");
- shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}],
+ shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}],
"Goodbye cruel world!", "helpers hash is available inside other blocks");
});
@@ -888,8 +888,8 @@ test("helpers can take an optional hash with booleans", function() {
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE CRUEL WORLD", "Helper output hash");
- var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
- var result = template(context, {helpers: helpers});
+ template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
+ result = template(context, {helpers: helpers});
equals(result, "NOT PRINTING", "Boolean helper parameter honored");
});
4 src/handlebars.yy
View
@@ -18,8 +18,8 @@ statements
;
statement
- : openInverse program closeBlock { $$ = new yy.InverseNode($1, $2, $3); }
- | openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $3); }
+ : openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); }
+ | openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); }
| mustache { $$ = $1; }
| partial { $$ = $1; }
| CONTENT { $$ = new yy.ContentNode($1); }
Please sign in to comment.
Something went wrong with that request. Please try again.