diff options
Diffstat (limited to 'lib/handlebars/compiler')
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 65 | ||||
-rw-r--r-- | lib/handlebars/compiler/javascript-compiler.js | 82 |
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; }, |