Skip to content

Commit

Permalink
Implement block parameters
Browse files Browse the repository at this point in the history
Fixes #907
  • Loading branch information
kpdecker committed Dec 26, 2014
1 parent 9e907e6 commit 396795c
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 52 deletions.
6 changes: 5 additions & 1 deletion lib/handlebars/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,11 @@ function registerDefaultHelpers(instance) {
data.contextPath = contextPath + key;
}
}
ret = ret + fn(context[key], { data: data });

ret = ret + fn(context[key], {
data: data,
blockParams: Utils.blockParams([context[key], key], [contextPath + key, null])
});
}

if(context && typeof context === 'object') {
Expand Down
65 changes: 49 additions & 16 deletions lib/handlebars/compiler/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Compiler.prototype = {
this.stringParams = options.stringParams;
this.trackIds = options.trackIds;

options.blockParams = options.blockParams || [];

// These changes will propagate to the other compiler components
var knownHelpers = options.knownHelpers;
options.knownHelpers = {
Expand Down Expand Up @@ -92,14 +94,17 @@ Compiler.prototype = {
},

Program: function(program) {
var body = program.body;
this.options.blockParams.unshift(program.blockParams);

var body = program.body;
for(var i=0, l=body.length; i<l; i++) {
this.accept(body[i]);
}

this.isSimple = l === 1;
this.options.blockParams.shift();

this.isSimple = l === 1;
this.blockParams = program.blockParams ? program.blockParams.length : 0;

return this;
},
Expand Down Expand Up @@ -231,15 +236,20 @@ Compiler.prototype = {
this.addDepth(path.depth);
this.opcode('getContext', path.depth);

var name = path.parts[0];
if (!name) {
var name = path.parts[0],
scoped = AST.helpers.scopedId(path),
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);

if (blockParamId) {
this.opcode('lookupBlockParam', blockParamId, path.parts);
} else if (!name) {
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
this.opcode('pushContext');
} else if (path.data) {
this.options.data = true;
this.opcode('lookupData', path.depth, path.parts);
} else {
this.opcode('lookupOnContext', path.parts, path.falsy, AST.helpers.scopedId(path));
this.opcode('lookupOnContext', path.parts, path.falsy, scoped);
}
},

Expand Down Expand Up @@ -283,14 +293,18 @@ Compiler.prototype = {
},

classifySexpr: function(sexpr) {
var isSimple = AST.helpers.simpleId(sexpr.path);

var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);

// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
var isHelper = AST.helpers.helperExpression(sexpr);
var isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);

// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
var isEligible = isHelper || AST.helpers.simpleId(sexpr.path);
var isEligible = !isBlockParam && (isHelper || isSimple);

var options = this.options;

Expand Down Expand Up @@ -345,14 +359,23 @@ Compiler.prototype = {
}
} else {
if (this.trackIds) {
value = val.original || value;
if (value.replace) {
value = value
.replace(/^\.\//g, '')
.replace(/^\.$/g, '');
var blockParamIndex;
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
blockParamIndex = this.blockParamIndex(val.parts[0]);
}
if (blockParamIndex) {
var blockParamChild = val.parts.slice(1).join('.');
this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
} else {
value = val.original || value;
if (value.replace) {
value = value
.replace(/^\.\//g, '')
.replace(/^\.$/g, '');
}

this.opcode('pushId', val.type, value);
}

this.opcode('pushId', val.type, value);
}
this.accept(val);
}
Expand All @@ -372,6 +395,16 @@ Compiler.prototype = {
}

return params;
},

blockParamIndex: function(name) {
for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
var blockParams = this.options.blockParams[depth],
param = blockParams && blockParams.indexOf(name);
if (blockParams && param >= 0) {
return [depth, param];
}
}
}
};

Expand Down Expand Up @@ -429,11 +462,11 @@ export function compile(input, options, env) {
}
return compiled._setup(options);
};
ret._child = function(i, data, depths) {
ret._child = function(i, data, blockParams, depths) {
if (!compiled) {
compiled = compileInput();
}
return compiled._child(i, data, depths);
return compiled._child(i, data, blockParams, depths);
};
return ret;
}
Expand Down
82 changes: 57 additions & 25 deletions lib/handlebars/compiler/javascript-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ JavaScriptCompiler.prototype = {
this.hashes = [];
this.compileStack = [];
this.inlineStack = [];
this.blockParams = [];

this.compileChildren(environment, options);

this.useDepths = this.useDepths || environment.useDepths || this.options.compat;
this.useBlockParams = this.useBlockParams || environment.useBlockParams;

var opcodes = environment.opcodes,
opcode,
Expand Down Expand Up @@ -127,6 +129,9 @@ JavaScriptCompiler.prototype = {
if (this.useDepths) {
ret.useDepths = true;
}
if (this.useBlockParams) {
ret.useBlockParams = true;
}
if (this.options.compat) {
ret.compat = true;
}
Expand Down Expand Up @@ -186,6 +191,9 @@ JavaScriptCompiler.prototype = {

var params = ["depth0", "helpers", "partials", "data"];

if (this.useBlockParams || this.useDepths) {
params.push('blockParams');
}
if (this.useDepths) {
params.push('depths');
}
Expand Down Expand Up @@ -385,9 +393,7 @@ JavaScriptCompiler.prototype = {
// Looks up the value of `name` on the current context and pushes
// it onto the stack.
lookupOnContext: function(parts, falsy, scoped) {
/*jshint -W083 */
var i = 0,
len = parts.length;
var i = 0;

if (!scoped && this.options.compat && !this.lastContext) {
// The depthed query is expected to handle the undefined logic for the root level that
Expand All @@ -397,19 +403,21 @@ JavaScriptCompiler.prototype = {
this.pushContext();
}

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 if the context (falsy flag)
// needs to have the special handling for these values.
if (!falsy) {
return [' != null ? ', lookup, ' : ', current];
} else {
// Otherwise we can use generic falsy handling
return [' && ', lookup];
}
});
}
this.resolvePath('context', parts, i, falsy);
},

// [lookupBlockParam]
//
// On stack, before: ...
// On stack, after: blockParam[name], ...
//
// Looks up the value of `parts` on the given block param and pushes
// it onto the stack.
lookupBlockParam: function(blockParamId, parts) {
this.useBlockParams = true;

this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
this.resolvePath('context', parts, 1);
},

// [lookupData]
Expand All @@ -426,9 +434,23 @@ JavaScriptCompiler.prototype = {
this.pushStackLiteral('this.data(data, ' + depth + ')');
}

for (var i = 0, len = parts.length; i < len; i++) {
this.resolvePath('data', parts, 0, true);
},

resolvePath: function(type, parts, i, falsy) {
/*jshint -W083 */
var len = parts.length;
for (; i < len; i++) {
this.replaceStack(function(current) {
return [' && ', this.nameLookup(current, parts[i], 'data')];
var lookup = this.nameLookup(current, parts[i], type);
// We want to ensure that zero and false are handled properly if the context (falsy flag)
// needs to have the special handling for these values.
if (!falsy) {
return [' != null ? ', lookup, ' : ', current];
} else {
// Otherwise we can use generic falsy handling
return [' && ', lookup];
}
});
}
},
Expand Down Expand Up @@ -661,8 +683,12 @@ JavaScriptCompiler.prototype = {
hash.values[key] = value;
},

pushId: function(type, name) {
if (type === 'PathExpression') {
pushId: function(type, name, child) {
if (type === 'BlockParam') {
this.pushStackLiteral(
'blockParams[' + name[0] + '].path[' + name[1] + ']'
+ (child ? ' + ' + JSON.stringify('.' + child) : ''));
} else if (type === 'PathExpression') {
this.pushString(name);
} else if (type === 'SubExpression') {
this.pushStackLiteral('true');
Expand Down Expand Up @@ -693,11 +719,13 @@ JavaScriptCompiler.prototype = {
this.context.environments[index] = child;

this.useDepths = this.useDepths || compiler.useDepths;
this.useBlockParams = this.useBlockParams || compiler.useBlockParams;
} else {
child.index = index;
child.name = 'program' + index;

this.useDepths = this.useDepths || child.useDepths;
this.useBlockParams = this.useBlockParams || child.useBlockParams;
}
}
},
Expand All @@ -712,11 +740,12 @@ JavaScriptCompiler.prototype = {

programExpression: function(guid) {
var child = this.environment.children[guid],
useDepths = this.useDepths;
programParams = [child.index, 'data', child.blockParams];

var programParams = [child.index, 'data'];

if (useDepths) {
if (this.useBlockParams || this.useDepths) {
programParams.push('blockParams');
}
if (this.useDepths) {
programParams.push('depths');
}

Expand Down Expand Up @@ -943,7 +972,10 @@ JavaScriptCompiler.prototype = {
}

if (this.options.data) {
options.data = "data";
options.data = 'data';
}
if (this.useBlockParams) {
options.blockParams = 'blockParams';
}
return options;
},
Expand Down
28 changes: 19 additions & 9 deletions lib/handlebars/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ export function template(templateSpec, env) {
},

programs: [],
program: function(i, data, depths) {
program: function(i, data, declaredBlockParams, blockParams, depths) {
var programWrapper = this.programs[i],
fn = this.fn(i);
if (data || depths) {
programWrapper = program(this, i, fn, data, depths);
if (data || depths || blockParams || declaredBlockParams) {
programWrapper = program(this, i, fn, data, declaredBlockParams, blockParams, depths);
} else if (!programWrapper) {
programWrapper = this.programs[i] = program(this, i, fn);
}
Expand Down Expand Up @@ -128,12 +128,13 @@ export function template(templateSpec, env) {
if (!options.partial && templateSpec.useData) {
data = initData(context, data);
}
var depths;
var depths,
blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) {
depths = options.depths ? [context].concat(options.depths) : [context];
}

return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
return templateSpec.main.call(container, context, container.helpers, container.partials, data, blockParams, depths);
};
ret.isTop = true;

Expand All @@ -150,24 +151,33 @@ export function template(templateSpec, env) {
}
};

ret._child = function(i, data, depths) {
ret._child = function(i, data, blockParams, depths) {
if (templateSpec.useBlockParams && !blockParams) {
throw new Exception('must pass block params');
}
if (templateSpec.useDepths && !depths) {
throw new Exception('must pass parent depths');
}

return program(container, i, templateSpec[i], data, depths);
return program(container, i, templateSpec[i], data, 0, blockParams, depths);
};
return ret;
}

export function program(container, i, fn, data, depths) {
export function program(container, i, fn, data, declaredBlockParams, blockParams, depths) {
var prog = function(context, options) {
options = options || {};

return fn.call(container, context, container.helpers, container.partials, options.data || data, depths && [context].concat(depths));
return fn.call(container,
context,
container.helpers, container.partials,
options.data || data,
blockParams && [options.blockParams].concat(blockParams),
depths && [context].concat(depths));
};
prog.program = i;
prog.depth = depths ? depths.length : 0;
prog.blockParams = declaredBlockParams || 0;
return prog;
}

Expand Down
5 changes: 5 additions & 0 deletions lib/handlebars/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ export function isEmpty(value) {
}
}

export function blockParams(params, ids) {
params.path = ids;
return params;
}

export function appendContextPath(contextPath, id) {
return (contextPath ? contextPath + '.' : '') + id;
}
Loading

0 comments on commit 396795c

Please sign in to comment.