Convert template spec to object literal

This allows for metadata to be associated with the template and a simplification of the template init logic.
This commit is contained in:
kpdecker
2014-01-19 17:32:25 -06:00
parent 8f07bbabea
commit 2812fe2775
4 changed files with 96 additions and 90 deletions
+56 -58
View File
@@ -35,7 +35,7 @@ JavaScriptCompiler.prototype = {
compilerInfo: function() { compilerInfo: function() {
var revision = COMPILER_REVISION, var revision = COMPILER_REVISION,
versions = REVISION_CHANGES[revision]; versions = REVISION_CHANGES[revision];
return "this.compilerInfo = ["+revision+",'"+versions+"'];\n"; return [revision, versions];
}, },
appendToBuffer: function(string) { appendToBuffer: function(string) {
@@ -62,6 +62,7 @@ JavaScriptCompiler.prototype = {
this.options = options || {}; this.options = options || {};
this.stringParams = this.options.stringParams; this.stringParams = this.options.stringParams;
this.trackIds = this.options.trackIds; this.trackIds = this.options.trackIds;
this.precompile = !asObject;
log('debug', this.environment.disassemble() + "\n\n"); log('debug', this.environment.disassemble() + "\n\n");
@@ -69,14 +70,14 @@ JavaScriptCompiler.prototype = {
this.isChild = !!context; this.isChild = !!context;
this.context = context || { this.context = context || {
programs: [], programs: [],
environments: [], environments: []
aliases: { }
}; };
this.preamble(); this.preamble();
this.stackSlot = 0; this.stackSlot = 0;
this.stackVars = []; this.stackVars = [];
this.aliases = {};
this.registers = { list: [] }; this.registers = { list: [] };
this.hashes = []; this.hashes = [];
this.compileStack = []; this.compileStack = [];
@@ -110,23 +111,39 @@ JavaScriptCompiler.prototype = {
throw new Exception('Compile completed with content left on stack'); throw new Exception('Compile completed with content left on stack');
} }
return this.createFunctionContext(asObject); var fn = this.createFunctionContext(asObject);
if (!this.isChild) {
var ret = {
compiler: this.compilerInfo(),
main: fn
};
this.context.programs.map(function(program, index) {
if (program) {
ret[index] = program;
}
});
if (this.environment.usePartial) {
ret.usePartial = true;
}
if (this.options.data) {
ret.useData = true;
}
if (!asObject) {
ret.compiler = JSON.stringify(ret.compiler);
ret = this.objectLiteral(ret);
}
return ret;
} else {
return fn;
}
}, },
preamble: function() { preamble: function() {
var out = []; var out = [];
if (!this.isChild) {
var namespace = this.namespace;
var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
if (this.options.data) { copies = copies + " data = this.initData(depth0, data);"; }
out.push(copies);
} else {
out.push('');
}
if (!this.environment.isSimple) { if (!this.environment.isSimple) {
out.push(", buffer = " + this.initializeBuffer()); out.push(", buffer = " + this.initializeBuffer());
} else { } else {
@@ -143,32 +160,25 @@ JavaScriptCompiler.prototype = {
var locals = this.stackVars.concat(this.registers.list); var locals = this.stackVars.concat(this.registers.list);
if(locals.length > 0) { if(locals.length > 0) {
this.source[1] = this.source[1] + ", " + locals.join(", "); this.source[0] += ", " + locals.join(", ");
} }
// Generate minimizer alias mappings // Generate minimizer alias mappings
if (!this.isChild) { for (var alias in this.aliases) {
for (var alias in this.context.aliases) { if (this.aliases.hasOwnProperty(alias)) {
if (this.context.aliases.hasOwnProperty(alias)) { this.source[0] += ', ' + alias + '=' + this.aliases[alias];
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
}
} }
} }
if (this.source[1]) { if (this.source[0]) {
this.source[1] = "var " + this.source[1].substring(2) + ";"; this.source[0] = "var " + this.source[0].substring(2) + ";";
}
// Merge children
if (!this.isChild) {
this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
} }
if (!this.environment.isSimple) { if (!this.environment.isSimple) {
this.pushSource("return buffer;"); this.pushSource("return buffer;");
} }
var params = this.isChild ? ["depth0", "data"] : [this.namespace, "depth0", "helpers", "partials", "data"]; var params = ["depth0", "helpers", "partials", "data"];
for(var i=0, l=this.environment.depths.list.length; i<l; i++) { for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
params.push("depth" + this.environment.depths.list[i]); params.push("depth" + this.environment.depths.list[i]);
@@ -177,18 +187,12 @@ JavaScriptCompiler.prototype = {
// Perform a second pass over the output to merge content when possible // Perform a second pass over the output to merge content when possible
var source = this.mergeSource(); var source = this.mergeSource();
if (!this.isChild) {
source = this.compilerInfo()+source;
}
if (asObject) { if (asObject) {
params.push(source); params.push(source);
return Function.apply(this, params); return Function.apply(this, params);
} else { } else {
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}'; return 'function(' + params.join(',') + ') {\n ' + source + '}';
log('debug', functionSource + "\n\n");
return functionSource;
} }
}, },
mergeSource: function() { mergeSource: function() {
@@ -225,7 +229,7 @@ JavaScriptCompiler.prototype = {
// replace it on the stack with the result of properly // replace it on the stack with the result of properly
// invoking blockHelperMissing. // invoking blockHelperMissing.
blockValue: function(name) { blockValue: function(name) {
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
var params = ["depth0"]; var params = ["depth0"];
this.setupParams(name, 0, params); this.setupParams(name, 0, params);
@@ -243,7 +247,7 @@ JavaScriptCompiler.prototype = {
// On stack, after, if no lastHelper: same as [blockValue] // On stack, after, if no lastHelper: same as [blockValue]
// On stack, after, if lastHelper: value // On stack, after, if lastHelper: value
ambiguousBlockValue: function() { ambiguousBlockValue: function() {
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
// We're being a bit cheeky and reusing the options value from the prior exec // We're being a bit cheeky and reusing the options value from the prior exec
var params = ["depth0"]; var params = ["depth0"];
@@ -315,7 +319,7 @@ JavaScriptCompiler.prototype = {
// //
// Escape `value` and append it to the buffer // Escape `value` and append it to the buffer
appendEscaped: function() { appendEscaped: function() {
this.context.aliases.escapeExpression = 'this.escapeExpression'; this.aliases.escapeExpression = 'this.escapeExpression';
this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
}, },
@@ -362,7 +366,7 @@ JavaScriptCompiler.prototype = {
// If the `value` is a lambda, replace it on the stack by // If the `value` is a lambda, replace it on the stack by
// the return value of the lambda // the return value of the lambda
resolvePossibleLambda: function() { resolvePossibleLambda: function() {
this.context.aliases.functionType = '"function"'; this.aliases.functionType = '"function"';
this.replaceStack(function(current) { this.replaceStack(function(current) {
return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current; return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
@@ -507,7 +511,7 @@ JavaScriptCompiler.prototype = {
// //
// If the helper is not found, `helperMissing` is called. // If the helper is not found, `helperMissing` is called.
invokeHelper: function(paramSize, name, isRoot) { invokeHelper: function(paramSize, name, isRoot) {
this.context.aliases.helperMissing = 'helpers.helperMissing'; this.aliases.helperMissing = 'helpers.helperMissing';
this.useRegister('helper'); this.useRegister('helper');
var nonHelper = this.popStack(); var nonHelper = this.popStack();
@@ -553,7 +557,7 @@ JavaScriptCompiler.prototype = {
// and can be avoided by passing the `knownHelpers` and // and can be avoided by passing the `knownHelpers` and
// `knownHelpersOnly` flags at compile-time. // `knownHelpersOnly` flags at compile-time.
invokeAmbiguous: function(name, helperCall) { invokeAmbiguous: function(name, helperCall) {
this.context.aliases.functionType = '"function"'; this.aliases.functionType = '"function"';
this.useRegister('helper'); this.useRegister('helper');
this.emptyHash(); this.emptyHash();
@@ -582,8 +586,7 @@ JavaScriptCompiler.prototype = {
params.push("data"); params.push("data");
} }
this.context.aliases.self = "this"; this.push("this.invokePartial(" + params.join(", ") + ")");
this.push("self.invokePartial(" + params.join(", ") + ")");
}, },
// [assignToHash] // [assignToHash]
@@ -648,7 +651,7 @@ JavaScriptCompiler.prototype = {
index = this.context.programs.length; index = this.context.programs.length;
child.index = index; child.index = index;
child.name = 'program' + index; child.name = 'program' + index;
this.context.programs[index] = compiler.compile(child, options, this.context); this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
this.context.environments[index] = child; this.context.environments[index] = child;
} else { } else {
child.index = index; child.index = index;
@@ -666,25 +669,22 @@ JavaScriptCompiler.prototype = {
}, },
programExpression: function(guid) { programExpression: function(guid) {
this.context.aliases.self = "this";
if(guid == null) { if(guid == null) {
return "self.noop"; return 'this.noop';
} }
var child = this.environment.children[guid], var child = this.environment.children[guid],
depths = child.depths.list, depth; depths = child.depths.list, depth;
var programParams = [child.index, child.name, "data"]; var programParams = [child.index, 'data'];
for(var i=0, l = depths.length; i<l; i++) { for(var i=0, l = depths.length; i<l; i++) {
depth = depths[i]; depth = depths[i];
if(depth === 1) { programParams.push("depth0"); } programParams.push('depth' + (depth - 1));
else { programParams.push("depth" + (depth - 1)); }
} }
return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")"; return (depths.length === 0 ? 'this.program(' : 'this.programWithDepth(') + programParams.join(', ') + ')';
}, },
register: function(name, val) { register: function(name, val) {
@@ -842,7 +842,7 @@ JavaScriptCompiler.prototype = {
.replace(/\u2029/g, '\\u2029') + '"'; .replace(/\u2029/g, '\\u2029') + '"';
}, },
setupHash: function(obj) { objectLiteral: function(obj) {
var pairs = []; var pairs = [];
for (var key in obj) { for (var key in obj) {
@@ -888,13 +888,11 @@ JavaScriptCompiler.prototype = {
// helpers to do a check for `if (options.fn)` // helpers to do a check for `if (options.fn)`
if (program || inverse) { if (program || inverse) {
if (!program) { if (!program) {
this.context.aliases.self = "this"; program = 'this.noop';
program = "self.noop";
} }
if (!inverse) { if (!inverse) {
this.context.aliases.self = "this"; inverse = 'this.noop';
inverse = "self.noop";
} }
options.fn = program; options.fn = program;
@@ -932,7 +930,7 @@ JavaScriptCompiler.prototype = {
// the params and contexts arguments are passed in arrays // the params and contexts arguments are passed in arrays
// to fill in // to fill in
setupParams: function(helperName, paramSize, params, useRegister) { setupParams: function(helperName, paramSize, params, useRegister) {
var options = this.setupHash(this.setupOptions(helperName, paramSize, params)); var options = this.objectLiteral(this.setupOptions(helperName, paramSize, params));
if (useRegister) { if (useRegister) {
this.useRegister('options'); this.useRegister('options');
+37 -25
View File
@@ -29,6 +29,8 @@ export function template(templateSpec, env) {
// Note: Using env.VM references rather than local var references throughout this section to allow // Note: Using env.VM references rather than local var references throughout this section to allow
// for external users to override these as psuedo-supported APIs. // for external users to override these as psuedo-supported APIs.
env.VM.checkRevision(templateSpec.compiler);
var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) { var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) {
if (hash) { if (hash) {
context = Utils.extend({}, context, hash); context = Utils.extend({}, context, hash);
@@ -50,16 +52,24 @@ export function template(templateSpec, env) {
var container = { var container = {
escapeExpression: Utils.escapeExpression, escapeExpression: Utils.escapeExpression,
invokePartial: invokePartialWrapper, invokePartial: invokePartialWrapper,
fn: function(i) {
return templateSpec[i];
},
programs: [], programs: [],
program: function(i, fn, data) { program: function(i, data) {
var programWrapper = this.programs[i]; var programWrapper = this.programs[i],
fn = this.fn(i);
if(data) { if(data) {
programWrapper = program(i, fn, data); programWrapper = program(this, i, fn, data);
} else if (!programWrapper) { } else if (!programWrapper) {
programWrapper = this.programs[i] = program(i, fn); programWrapper = this.programs[i] = program(this, i, fn);
} }
return programWrapper; return programWrapper;
}, },
programWithDepth: env.VM.programWithDepth,
initData: function(context, data) { initData: function(context, data) {
if (!data || !('root' in data)) { if (!data || !('root' in data)) {
data = data ? createFrame(data) : {}; data = data ? createFrame(data) : {};
@@ -76,54 +86,56 @@ export function template(templateSpec, env) {
return ret; return ret;
}, },
programWithDepth: env.VM.programWithDepth,
noop: env.VM.noop, noop: env.VM.noop,
compilerInfo: null compilerInfo: templateSpec.compiler
}; };
return function(context, options) { return function(context, options) {
options = options || {}; options = options || {};
var namespace = options.partial ? options : env, var namespace = options.partial ? options : env,
helpers, helpers,
partials; partials,
data = options.data;
if (!options.partial) { if (!options.partial) {
helpers = options.helpers; helpers = container.helpers = container.merge(options.helpers, namespace.helpers);
partials = options.partials;
}
var result = templateSpec.call(
container,
namespace, context,
helpers,
partials,
options.data);
if (!options.partial) { if (templateSpec.usePartial) {
env.VM.checkRevision(container.compilerInfo); partials = container.partials = container.merge(options.partials, namespace.partials);
}
if (templateSpec.useData) {
data = container.initData(context, data);
}
} else {
helpers = container.helpers = options.helpers;
partials = container.partials = options.partials;
} }
return templateSpec.main.call(container, context, helpers, partials, data);
return result;
}; };
} }
export function programWithDepth(i, fn, data /*, $depth */) { export function programWithDepth(i, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3); /*jshint -W040 */
var args = Array.prototype.slice.call(arguments, 2),
container = this,
fn = container.fn(i);
var prog = function(context, options) { var prog = function(context, options) {
options = options || {}; options = options || {};
return fn.apply(this, [context, options.data || data].concat(args)); return fn.apply(container, [context, container.helpers, container.partials, options.data || data].concat(args));
}; };
prog.program = i; prog.program = i;
prog.depth = args.length; prog.depth = args.length;
return prog; return prog;
} }
export function program(i, fn, data) { export function program(container, i, fn, data) {
var prog = function(context, options) { var prog = function(context, options) {
options = options || {}; options = options || {};
return fn(context, options.data || data); return fn.call(container, context, container.helpers, container.partials, options.data || data);
}; };
prog.program = i; prog.program = i;
prog.depth = 0; prog.depth = 0;
+2 -6
View File
@@ -1,11 +1,7 @@
define(['handlebars.runtime'], function(Handlebars) { define(['handlebars.runtime'], function(Handlebars) {
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
return templates['empty'] = template(function (Handlebars,depth0,helpers,partials,data) { return templates['empty'] = template({"compiler":[4,">= 1.0.0"],"main":function(depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = this.initData(depth0, data);
var buffer = ""; var buffer = "";
return buffer; return buffer;
}); },"useData":true});
}); });
+1 -1
View File
@@ -32,7 +32,7 @@ describe('javascript-compiler api', function() {
}); });
it('should allow compilerInfo override', function() { it('should allow compilerInfo override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() { handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() {
return 'this.compilerInfo = "crazy";'; return 'crazy';
}; };
handlebarsEnv.VM.checkRevision = function(compilerInfo) { handlebarsEnv.VM.checkRevision = function(compilerInfo) {
if (compilerInfo !== 'crazy') { if (compilerInfo !== 'crazy') {