diff options
Diffstat (limited to 'lib/handlebars/compiler/javascript-compiler.js')
-rw-r--r-- | lib/handlebars/compiler/javascript-compiler.js | 192 |
1 files changed, 91 insertions, 101 deletions
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index c4f3c27..25d45d1 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -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, @@ -69,6 +74,8 @@ JavaScriptCompiler.prototype = { this.compileChildren(environment, options); + this.useDepths = this.useDepths || environment.depths.list.length || this.options.compat; + var opcodes = environment.opcodes, opcode, i, @@ -77,11 +84,7 @@ JavaScriptCompiler.prototype = { for (i = 0, l = opcodes.length; i < l; i++) { opcode = opcodes[i]; - if(opcode.opcode === 'DECLARE') { - this[opcode.name] = opcode.value; - } else { - this[opcode.opcode].apply(this, opcode.args); - } + this[opcode.opcode].apply(this, opcode.args); // Reset the stripNext flag if it was not set by this operation. if (opcode.opcode !== this.stripNext) { @@ -92,6 +95,7 @@ JavaScriptCompiler.prototype = { // Flush any trailing content that might be pending. this.pushSource(''); + /* istanbul ignore next */ if (this.stackSlot || this.inlineStack.length || this.compileStack.length) { throw new Exception('Compile completed with content left on stack'); } @@ -115,6 +119,12 @@ JavaScriptCompiler.prototype = { if (this.options.data) { ret.useData = true; } + if (this.useDepths) { + ret.useDepths = true; + } + if (this.options.compat) { + ret.compat = true; + } if (!asObject) { ret.compiler = JSON.stringify(ret.compiler); @@ -151,8 +161,8 @@ JavaScriptCompiler.prototype = { var params = ["depth0", "helpers", "partials", "data"]; - for(var i=0, l=this.environment.depths.list.length; i<l; i++) { - params.push("depth" + this.environment.depths.list[i]); + if (this.useDepths) { + params.push('depths'); } // Perform a second pass over the output to merge content when possible @@ -230,13 +240,13 @@ JavaScriptCompiler.prototype = { blockValue: function(name) { this.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; - var params = ["depth0"]; + var params = [this.contextName(0)]; this.setupParams(name, 0, params); - this.replaceStack(function(current) { - params.splice(1, 0, current); - return "blockHelperMissing.call(" + params.join(", ") + ")"; - }); + var blockName = this.popStack(); + params.splice(1, 0, blockName); + + this.push('blockHelperMissing.call(' + params.join(', ') + ')'); }, // [ambiguousBlockValue] @@ -249,7 +259,7 @@ JavaScriptCompiler.prototype = { this.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; // We're being a bit cheeky and reusing the options value from the prior exec - var params = ["depth0"]; + var params = [this.contextName(0)]; this.setupParams('', 0, params, true); this.flushInline(); @@ -341,7 +351,7 @@ JavaScriptCompiler.prototype = { // // Pushes the value of the current context onto the stack. pushContext: function() { - this.pushStackLiteral('depth' + this.lastContext); + this.pushStackLiteral(this.contextName(this.lastContext)); }, // [lookupOnContext] @@ -351,21 +361,25 @@ 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.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.push(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 - // of non-chained elements, if the context (falsy flag) needs to have the special - // handling for these values. - if (!falsy && !i && len === 1) { + // 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 @@ -388,9 +402,6 @@ JavaScriptCompiler.prototype = { } else { this.pushStackLiteral('this.data(data, ' + depth + ')'); } - if (!parts) { - return; - } var len = parts.length; for (var i = 0; i < len; i++) { @@ -410,7 +421,7 @@ JavaScriptCompiler.prototype = { resolvePossibleLambda: function() { this.aliases.lambda = 'this.lambda'; - this.push('lambda(' + this.popStack() + ', depth0)'); + this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')'); }, // [pushStringParam] @@ -422,8 +433,7 @@ JavaScriptCompiler.prototype = { // provides the string value of a parameter along with its // depth rather than resolving it immediately. pushStringParam: function(string, type) { - this.pushStackLiteral('depth' + this.lastContext); - + this.pushContext(); this.pushString(type); // If it's a subexpression, the string result @@ -534,18 +544,7 @@ JavaScriptCompiler.prototype = { var helper = this.setupHelper(paramSize, name); var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing'; - if (helper.paramsInit) { - lookup += ',' + helper.paramsInit; - } - this.push('((' + lookup + ').call(' + helper.callParams + '))'); - - // Always flush subexpressions. This is both to prevent the compounding size issue that - // occurs when the code has to be duplicated for inlining and also to prevent errors - // due to the incorrect options object being passed due to the shared register. - if (!isRoot) { - this.flushInline(); - } }, // [invokeKnownHelper] @@ -596,11 +595,16 @@ JavaScriptCompiler.prototype = { // // This operation pops off a context, invokes a partial with that context, // and pushes the result of the invocation back. - invokePartial: function(name) { - var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"]; + invokePartial: function(name, indent) { + var params = [this.nameLookup('partials', name, 'partial'), "'" + indent + "'", "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"]; if (this.options.data) { params.push("data"); + } else if (this.options.compat) { + params.push('undefined'); + } + if (this.options.compat) { + params.push('depths'); } this.push("this.invokePartial(" + params.join(", ") + ")"); @@ -669,6 +673,8 @@ JavaScriptCompiler.prototype = { child.name = 'program' + index; this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile); this.context.environments[index] = child; + + this.useDepths = this.useDepths || compiler.useDepths; } else { child.index = index; child.name = 'program' + index; @@ -685,27 +691,18 @@ JavaScriptCompiler.prototype = { }, programExpression: function(guid) { - if(guid == null) { - return 'this.noop'; - } - var child = this.environment.children[guid], - depths = child.depths.list, depth; + depths = child.depths.list, + useDepths = this.useDepths, + depth; var programParams = [child.index, 'data']; - for(var i=0, l = depths.length; i<l; i++) { - depth = depths[i]; - - programParams.push('depth' + (depth - 1)); + if (useDepths) { + programParams.push('depths'); } - return (depths.length === 0 ? 'this.program(' : 'this.programWithDepth(') + programParams.join(', ') + ')'; - }, - - register: function(name, val) { - this.useRegister(name); - this.pushSource(name + " = " + val + ";"); + return 'this.program(' + programParams.join(', ') + ')'; }, useRegister: function(name) { @@ -734,9 +731,7 @@ JavaScriptCompiler.prototype = { this.flushInline(); var stack = this.incrStack(); - if (item) { - this.pushSource(stack + " = " + item + ";"); - } + this.pushSource(stack + " = " + item + ";"); this.compileStack.push(stack); return stack; }, @@ -748,54 +743,40 @@ JavaScriptCompiler.prototype = { createdStack, usedLiteral; - // If we are currently inline then we want to merge the inline statement into the - // replacement statement via ',' - if (inline) { - var top = this.popStack(true); + /* istanbul ignore next */ + if (!this.isInline()) { + throw new Exception('replaceStack on non-inline'); + } - if (top instanceof Literal) { - // Literals do not need to be inlined - stack = top.value; - usedLiteral = true; + // We want to merge the inline statement into the replacement statement via ',' + var top = this.popStack(true); - if (chain) { - prefix = stack; - } - } else { - // Get or create the current stack name for use by the inline - createdStack = !this.stackSlot; - var name = !createdStack ? this.topStackName() : this.incrStack(); + if (top instanceof Literal) { + // Literals do not need to be inlined + stack = top.value; + usedLiteral = true; - prefix = '(' + this.push(name) + ' = ' + top + (chain ? ')' : '),'); - stack = this.topStack(); + if (chain) { + prefix = stack; } } else { + // Get or create the current stack name for use by the inline + createdStack = !this.stackSlot; + var name = !createdStack ? this.topStackName() : this.incrStack(); + + prefix = '(' + this.push(name) + ' = ' + top + (chain ? ')' : '),'); stack = this.topStack(); } var item = callback.call(this, stack); - if (inline) { - if (!usedLiteral) { - this.popStack(); - } - if (createdStack) { - this.stackSlot--; - } - this.push('(' + prefix + item + ')'); - } else { - // Prevent modification of the context depth variable. Through replaceStack - if (!/^stack/.test(stack)) { - stack = this.nextStack(); - } - - this.pushSource(stack + " = (" + prefix + item + ");"); + if (!usedLiteral) { + this.popStack(); } - return stack; - }, - - nextStack: function() { - return this.pushStack(); + if (createdStack) { + this.stackSlot--; + } + this.push('(' + prefix + item + ')'); }, incrStack: function() { @@ -832,6 +813,7 @@ JavaScriptCompiler.prototype = { return item.value; } else { if (!inline) { + /* istanbul ignore next */ if (!this.stackSlot) { throw new Exception('Invalid stack pop'); } @@ -841,17 +823,25 @@ JavaScriptCompiler.prototype = { } }, - topStack: function(wrapped) { + topStack: function() { var stack = (this.isInline() ? this.inlineStack : this.compileStack), item = stack[stack.length - 1]; - if (!wrapped && (item instanceof Literal)) { + if (item instanceof Literal) { return item.value; } else { return item; } }, + contextName: function(context) { + if (this.useDepths && context) { + return 'depths[' + context + ']'; + } else { + return 'depth' + context; + } + }, + quotedString: function(str) { return '"' + str .replace(/\\/g, '\\\\') @@ -883,7 +873,7 @@ JavaScriptCompiler.prototype = { params: params, paramsInit: paramsInit, name: foundHelper, - callParams: ["depth0"].concat(params).join(", ") + callParams: [this.contextName(0)].concat(params).join(", ") }; }, |