Skip to content

Commit

Permalink
Implement recursive field lookup in compat mode
Browse files Browse the repository at this point in the history
Provides the mustache behavior of recursive lookup without the use of depthed paths.

Note that this does incur a fairly dramatic performance penalty for depthed queries.
  • Loading branch information
kpdecker committed Aug 14, 2014
1 parent 625b00e commit c98613b
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 9 deletions.
8 changes: 8 additions & 0 deletions bench/throughput.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ function makeSuite(bench, name, template, handlebarsOnly) {
partials = template.partials,

handlebarsOut,
compatOut,
dustOut,
ecoOut,
mustacheOut;

var handlebar = Handlebars.compile(template.handlebars, {data: false}),
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
options = {helpers: template.helpers};
_.each(template.partials && template.partials.handlebars, function(partial, name) {
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
Expand All @@ -43,6 +45,11 @@ function makeSuite(bench, name, template, handlebarsOnly) {
handlebar(context, options);
});

compatOut = compat(context, options);
bench("compat", function() {
compat(context, options);
});

if (handlebarsOnly) {
return;
}
Expand Down Expand Up @@ -107,6 +114,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
}
}

compare(compatOut, 'compat');
compare(dustOut, 'dust');
compare(ecoOut, 'eco');
compare(mustacheOut, 'mustache');
Expand Down
8 changes: 7 additions & 1 deletion lib/handlebars/compiler/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ Compiler.prototype = {
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
this.opcode('pushContext');
} else {
this.opcode('lookupOnContext', id.parts, id.falsy);
this.opcode('lookupOnContext', id.parts, id.falsy, id.isScoped);
}
},

Expand Down Expand Up @@ -424,6 +424,9 @@ export function precompile(input, options, env) {
if (!('data' in options)) {
options.data = true;
}
if (options.compat) {
options.useDepths = true;
}

var ast = env.parse(input);
var environment = new env.Compiler().compile(ast, options);
Expand All @@ -440,6 +443,9 @@ export function compile(input, options, env) {
if (!('data' in options)) {
options.data = true;
}
if (options.compat) {
options.useDepths = true;
}

var compiled;

Expand Down
24 changes: 17 additions & 7 deletions lib/handlebars/compiler/javascript-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ JavaScriptCompiler.prototype = {
return parent + "['" + name + "']";
}
},
depthedLookup: function(name) {
this.aliases.lookup = 'this.lookup';

return 'lookup(depths, "' + name + '")';
},

compilerInfo: function() {
var revision = COMPILER_REVISION,
Expand Down Expand Up @@ -69,7 +74,7 @@ JavaScriptCompiler.prototype = {

this.compileChildren(environment, options);

this.useDepths = this.useDepths || environment.depths.list.length;
this.useDepths = this.useDepths || environment.depths.list.length || this.options.compat;

var opcodes = environment.opcodes,
opcode,
Expand Down Expand Up @@ -356,15 +361,20 @@ JavaScriptCompiler.prototype = {
//
// Looks up the value of `name` on the current context and pushes
// it onto the stack.
lookupOnContext: function(parts, falsy) {
lookupOnContext: function(parts, falsy, scoped) {
/*jshint -W083 */
this.pushContext();
if (!parts) {
return;
var i = 0,
len = parts.length;

if (!scoped && this.isChild && this.options.compat && !this.lastContext) {
// The depthed query is expected to handle the undefined logic for the root level that
// is implemented below, so we evaluate that directly in compat mode
this.pushStackLiteral(this.depthedLookup(parts[i++]));
} else {
this.pushContext();
}

var len = parts.length;
for (var i = 0; i < len; i++) {
for (; i < len; i++) {
this.replaceStack(function(current) {
var lookup = this.nameLookup(current, parts[i], 'context');
// We want to ensure that zero and false are handled properly for the first element
Expand Down
8 changes: 8 additions & 0 deletions lib/handlebars/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export function template(templateSpec, env) {

// Just add water
var container = {
lookup: function(depths, name) {
var len = depths.length;
for (var i = 0; i < len; i++) {
if (depths[i] && depths[i][name] != null) {
return depths[i][name];
}
}
},
lambda: function(current, context) {
return typeof current === 'function' ? current.call(context) : current;
},
Expand Down
21 changes: 21 additions & 0 deletions spec/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,25 @@ describe('blocks', function() {
'No people\n');
});
});

describe('compat mode', function() {
it("block with deep recursive lookup lookup", function() {
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}";
var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };

shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!");
});
it("block with deep recursive pathed lookup", function() {
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}";
var hash = {omg: {yes: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] };

shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!");
});
it("block with missed recursive lookup", function() {
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}";
var hash = {omg: {no: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] };

shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel ");
});
});
});
2 changes: 1 addition & 1 deletion spec/env/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
ary = [];
ary.push(hashOrArray[0]);
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2], compat: hashOrArray[3] });
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
options = {compat: hashOrArray[3]};
} else {
ary = [hashOrArray];
Expand Down

0 comments on commit c98613b

Please sign in to comment.