diff options
author | tomhuda <tomhuda@tilde.io> | 2012-05-26 14:51:41 -0700 |
---|---|---|
committer | tomhuda <tomhuda@tilde.io> | 2012-05-26 14:51:41 -0700 |
commit | facefe8fedd2411bf8ce696b072d15041f0265a0 (patch) | |
tree | 48194489e0f5fdbb5b217c4dae3ee598e224126c /lib/handlebars/compiler/compiler.js | |
parent | 8e70e3b58b7416b7002f2c16e5daa8e9d39c4807 (diff) | |
download | handlebars.js-facefe8fedd2411bf8ce696b072d15041f0265a0.zip handlebars.js-facefe8fedd2411bf8ce696b072d15041f0265a0.tar.gz handlebars.js-facefe8fedd2411bf8ce696b072d15041f0265a0.tar.bz2 |
Several improvements to compiled output:
* Eliminate legacy support for an options hash
that doubles as a function. This prevented us
from building the hash as a literal, and added
a bunch of code weight
* Create a new "stack literal" construct, that
allows an opcode to push a literal expression
onto the stack. This will not allocate a new
stack variable, and when popped, will simply
return the literal expression as a String.
Diffstat (limited to 'lib/handlebars/compiler/compiler.js')
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 148 |
1 files changed, 94 insertions, 54 deletions
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 2cdc7c0..8fccc90 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -52,6 +52,11 @@ Handlebars.JavaScriptCompiler = function() {}; return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]]; }; + // the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + Compiler.prototype = { compiler: Compiler, @@ -314,6 +319,10 @@ Handlebars.JavaScriptCompiler = function() {}; } }; + var Literal = function(value) { + this.value = value; + }; + JavaScriptCompiler.prototype = { // PUBLIC API: You can override these methods in a subclass to provide // alternative compiled forms for name lookup and buffering semantics @@ -347,11 +356,13 @@ Handlebars.JavaScriptCompiler = function() {}; this.environment = environment; this.options = options || {}; + Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n"); + this.name = this.environment.name; this.isChild = !!context; this.context = context || { programs: [], - aliases: { self: 'this' }, + aliases: { }, registers: {list: []} }; @@ -359,6 +370,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.stackSlot = 0; this.stackVars = []; + this.compileStack = []; this.compileChildren(environment, options); @@ -410,12 +422,6 @@ Handlebars.JavaScriptCompiler = function() {}; preamble: function() { var out = []; - // this register will disambiguate helper lookup from finding a function in - // a context. This is necessary for mustache compatibility, which requires - // that context functions in blocks are evaluated by blockHelperMissing, and - // then proceed as if the resulting value was provided to blockHelperMissing. - this.useRegister('foundHelper'); - if (!this.isChild) { var namespace = this.namespace; var copies = "helpers = helpers || " + namespace + ".helpers;"; @@ -517,41 +523,36 @@ Handlebars.JavaScriptCompiler = function() {}; lookupWithHelpers: function(name, isScoped) { if(name) { - var topStack = this.nextStack(); - this.usingKnownHelper = false; var toPush; if (!isScoped && this.options.knownHelpers[name]) { - toPush = topStack + " = " + this.nameLookup('helpers', name, 'helper'); this.usingKnownHelper = true; + this.pushStackLiteral(this.nameLookup('helpers', name, 'helper')); } else if (isScoped || this.options.knownHelpersOnly) { - toPush = topStack + " = " + this.nameLookup('depth' + this.lastContext, name, 'context'); + this.pushStackLiteral(this.nameLookup('depth' + this.lastContext, name, 'context')); } else { this.register('foundHelper', this.nameLookup('helpers', name, 'helper')); - toPush = topStack + " = foundHelper || " + this.nameLookup('depth' + this.lastContext, name, 'context'); + this.pushStack("foundHelper || " + this.nameLookup('depth' + this.lastContext, name, 'context')); } - - toPush += ';'; - this.source.push(toPush); } else { - this.pushStack('depth' + this.lastContext); + this.pushStackLiteral('depth' + this.lastContext); } }, lookup: function(name) { - var topStack = this.topStack(); - this.source.push(topStack + " = (" + topStack + " === null || " + topStack + " === undefined || " + topStack + " === false ? " + - topStack + " : " + this.nameLookup(topStack, name, 'context') + ");"); + this.replaceStack(function(current) { + return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context'); + }); }, pushStringParam: function(string) { - this.pushStack('depth' + this.lastContext); + this.pushStackLiteral('depth' + this.lastContext); this.pushString(string); }, pushString: function(string) { - this.pushStack(this.quotedString(string)); + this.pushStackLiteral(this.quotedString(string)); }, push: function(name) { @@ -559,7 +560,7 @@ Handlebars.JavaScriptCompiler = function() {}; }, invokeMustache: function(paramSize, original, hasHash) { - this.populateParams(paramSize, this.quotedString(original), "{}", null, hasHash, function(nextStack, helperMissingString, id) { + this.populateParams(paramSize, this.quotedString(original), null, null, hasHash, function(nextStack, helperMissingString, id) { if (!this.usingKnownHelper) { this.context.aliases.helperMissing = 'helpers.helperMissing'; this.context.aliases.undef = 'void 0'; @@ -583,25 +584,17 @@ Handlebars.JavaScriptCompiler = function() {}; }); }, - populateParams: function(paramSize, helperId, program, inverse, hasHash, fn) { + populateParams: function(paramSize, helperId, program, inverse, hasHash, callback) { var needsRegister = hasHash || this.options.stringParams || inverse || this.options.data; var id = this.popStack(), nextStack; var params = [], param, stringParam, stringOptions; - if (needsRegister) { - this.register('tmp1', program); - stringOptions = 'tmp1'; - } else { - stringOptions = '{ hash: {} }'; - } + var options = [], contexts = []; - if (needsRegister) { - var hash = (hasHash ? this.popStack() : '{}'); - this.source.push('tmp1.hash = ' + hash + ';'); - } - - if(this.options.stringParams) { - this.source.push('tmp1.contexts = [];'); + if (hasHash) { + options.push("hash:" + this.popStack()); + } else { + options.push("hash:{}"); } for(var i=0; i<paramSize; i++) { @@ -609,38 +602,48 @@ Handlebars.JavaScriptCompiler = function() {}; params.push(param); if(this.options.stringParams) { - this.source.push('tmp1.contexts.push(' + this.popStack() + ');'); + contexts.push(this.popStack()); } } - if(inverse) { - this.source.push('tmp1.fn = tmp1;'); - this.source.push('tmp1.inverse = ' + inverse + ';'); + if (this.options.stringParams) { + options.push("contexts:[" + contexts.join(",") + "]"); + } + + if (program) { + options.push("fn:" + program); + } + + if (inverse) { + options.push("inverse:" + inverse); } if(this.options.data) { - this.source.push('tmp1.data = data;'); + options.push("data:data"); } - params.push(stringOptions); + this.register("params", "{" + options.join(",") + "}"); - this.populateCall(params, id, helperId || id, fn, program !== '{}'); + params.push("params"); + + this.populateCall(params, id, helperId || id, callback, program); }, - populateCall: function(params, id, helperId, fn, program) { - var paramString = ["depth0"].concat(params).join(", "); + populateCall: function(params, id, helperId, callback, program) { + var paramString = ["depth0"].concat(params).join(", "), nextStack; var helperMissingString = ["depth0"].concat(helperId).concat(params).join(", "); - var nextStack = this.nextStack(); - if (this.usingKnownHelper) { - this.source.push(nextStack + " = " + id + ".call(" + paramString + ");"); + nextStack = this.pushStack(id + ".call(" + paramString + ")"); } else { + this.useRegister('foundHelper'); + + nextStack = this.nextStack(); this.context.aliases.functionType = '"function"'; var condition = program ? "foundHelper && " : ""; this.source.push("if(" + condition + "typeof " + id + " === functionType) { " + nextStack + " = " + id + ".call(" + paramString + "); }"); } - fn.call(this, nextStack, helperMissingString, id); + callback.call(this, nextStack, helperMissingString, id); this.usingKnownHelper = false; }, @@ -651,6 +654,7 @@ Handlebars.JavaScriptCompiler = function() {}; params.push("data"); } + this.context.aliases.self = "this"; this.pushStack("self.invokePartial(" + params.join(", ") + ");"); }, @@ -681,7 +685,11 @@ Handlebars.JavaScriptCompiler = function() {}; }, programExpression: function(guid) { - if(guid == null) { return "self.noop"; } + this.context.aliases.self = "this"; + + if(guid == null) { + return "self.noop"; + } var child = this.environment.children[guid], depths = child.depths.list, depth; @@ -715,23 +723,55 @@ Handlebars.JavaScriptCompiler = function() {}; } }, + pushStackLiteral: function(item) { + this.compileStack.push(new Literal(item)); + return item; + }, + pushStack: function(item) { - this.source.push(this.nextStack() + " = " + item + ";"); + this.source.push(this.incrStack() + " = " + item + ";"); + this.compileStack.push("stack" + this.stackSlot); + return "stack" + this.stackSlot; + }, + + replaceStack: function(callback) { + var item = callback.call(this, this.topStack()); + + this.source.push(this.topStack() + " = " + item + ";"); return "stack" + this.stackSlot; }, - nextStack: function() { + nextStack: function(skipCompileStack) { + var name = this.incrStack(); + this.compileStack.push("stack" + this.stackSlot); + return name; + }, + + incrStack: function() { this.stackSlot++; if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } return "stack" + this.stackSlot; }, popStack: function() { - return "stack" + this.stackSlot--; + var item = this.compileStack.pop(); + + if (item instanceof Literal) { + return item.value; + } else { + this.stackSlot--; + return item; + } }, topStack: function() { - return "stack" + this.stackSlot; + var item = this.compileStack[this.compileStack.length - 1]; + + if (item instanceof Literal) { + return item.value; + } else { + return item; + } }, quotedString: function(str) { |