diff options
author | tomhuda <tomhuda@tilde.io> | 2012-05-28 17:00:41 -0700 |
---|---|---|
committer | tomhuda <tomhuda@tilde.io> | 2012-05-28 17:00:41 -0700 |
commit | 175c6fae0f704b2f1088728136e238186fbb1cf3 (patch) | |
tree | d539e2275a589f998ef02ed128976401247f0809 /lib/handlebars/compiler/compiler.js | |
parent | 3486b530beb9a9516bb8c67470b3515133259559 (diff) | |
download | handlebars.js-175c6fae0f704b2f1088728136e238186fbb1cf3.zip handlebars.js-175c6fae0f704b2f1088728136e238186fbb1cf3.tar.gz handlebars.js-175c6fae0f704b2f1088728136e238186fbb1cf3.tar.bz2 |
More cleanup
* Make block and inverse use the main helper path
* Eliminate separate inverse AST node
Diffstat (limited to 'lib/handlebars/compiler/compiler.js')
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 220 |
1 files changed, 155 insertions, 65 deletions
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 12f1695..74b0b0b 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -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); |