Permalink
Browse files

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.
  • Loading branch information...
tomhuda
tomhuda committed May 26, 2012
1 parent 8e70e3b commit facefe8fedd2411bf8ce696b072d15041f0265a0
Showing with 144 additions and 104 deletions.
  1. +94 −54 lib/handlebars/compiler/compiler.js
  2. +50 −50 spec/qunit_spec.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,18 +356,21 @@ 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: []}
};
this.preamble();
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,49 +523,44 @@ 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) {
this.pushStack(name);
},
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,64 +584,66 @@ 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++) {
param = this.popStack();
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) {
Oops, something went wrong.

0 comments on commit facefe8

Please sign in to comment.