summaryrefslogtreecommitdiffstats
path: root/lib/handlebars/compiler/javascript-compiler.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/handlebars/compiler/javascript-compiler.js')
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js192
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(", ")
};
},