More cleanup
* Make block and inverse use the main helper path * Eliminate separate inverse AST node
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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(block.program.inverse) {
|
||||
inverseGuid = this.compileProgram(block.program.inverse);
|
||||
this.declare('inverse', inverseGuid);
|
||||
if (program) {
|
||||
program = this.compileProgram(program);
|
||||
}
|
||||
|
||||
this.opcode('invokeProgram', programGuid, params.length, !!mustache.hash);
|
||||
this.declare('inverse', null);
|
||||
this.opcode('append');
|
||||
},
|
||||
if (inverse) {
|
||||
inverse = this.compileProgram(inverse);
|
||||
}
|
||||
|
||||
inverse: function(block) {
|
||||
var params = this.setupStackForMustache(block.mustache);
|
||||
var type = this.classifyMustache(mustache);
|
||||
|
||||
var programGuid = this.compileProgram(block.program);
|
||||
if (type === "helper") {
|
||||
this.helperMustache(mustache, program, inverse);
|
||||
} else if (type === "simple") {
|
||||
this.simpleMustache(mustache);
|
||||
|
||||
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('blockValue');
|
||||
} else {
|
||||
this.ambiguousMustache(mustache, program, inverse);
|
||||
|
||||
// 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);
|
||||
|
||||
+4
-4
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user