summaryrefslogtreecommitdiffstats
path: root/lib/handlebars/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'lib/handlebars/compiler')
-rw-r--r--lib/handlebars/compiler/compiler.js65
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js82
2 files changed, 106 insertions, 41 deletions
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 7be96cb..13143cc 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -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 = {
@@ -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;
},
@@ -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);
}
},
@@ -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;
@@ -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);
}
@@ -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];
+ }
+ }
}
};
@@ -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;
}
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 3e4f5e1..9f40792 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -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,
@@ -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;
}
@@ -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');
}
@@ -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
@@ -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]
@@ -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];
+ }
});
}
},
@@ -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');
@@ -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;
}
}
},
@@ -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');
}
@@ -943,7 +972,10 @@ JavaScriptCompiler.prototype = {
}
if (this.options.data) {
- options.data = "data";
+ options.data = 'data';
+ }
+ if (this.useBlockParams) {
+ options.blockParams = 'blockParams';
}
return options;
},