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.js81
1 files changed, 63 insertions, 18 deletions
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index d920d52..cd75af9 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -1,4 +1,5 @@
import { COMPILER_REVISION, REVISION_CHANGES, log } from "../base";
+import Exception from "../exception";
function Literal(value) {
this.value = value;
@@ -76,6 +77,7 @@ JavaScriptCompiler.prototype = {
this.stackSlot = 0;
this.stackVars = [];
this.registers = { list: [] };
+ this.hashes = [];
this.compileStack = [];
this.inlineStack = [];
@@ -103,6 +105,10 @@ JavaScriptCompiler.prototype = {
// Flush any trailing content that might be pending.
this.pushSource('');
+ if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
+ throw new Exception('Compile completed with content left on stack');
+ }
+
return this.createFunctionContext(asObject);
},
@@ -379,7 +385,7 @@ JavaScriptCompiler.prototype = {
//
// Push the data lookup operator
lookupData: function() {
- this.push('data');
+ this.pushStackLiteral('data');
},
// [pushStringParam]
@@ -415,11 +421,14 @@ JavaScriptCompiler.prototype = {
}
},
pushHash: function() {
+ if (this.hash) {
+ this.hashes.push(this.hash);
+ }
this.hash = {values: [], types: [], contexts: []};
},
popHash: function() {
var hash = this.hash;
- this.hash = undefined;
+ this.hash = this.hashes.pop();
if (this.options.stringParams) {
this.push('{' + hash.contexts.join(',') + '}');
@@ -487,18 +496,31 @@ JavaScriptCompiler.prototype = {
// and pushes the helper's return value onto the stack.
//
// If the helper is not found, `helperMissing` is called.
- invokeHelper: function(paramSize, name) {
+ invokeHelper: function(paramSize, name, isRoot) {
this.context.aliases.helperMissing = 'helpers.helperMissing';
+ this.useRegister('helper');
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
- this.push(helper.name + ' || ' + nonHelper);
- this.replaceStack(function(name) {
- return name + ' ? ' + name + '.call(' +
- helper.callParams + ") " + ": helperMissing.call(" +
- helper.helperMissingParams + ")";
- });
+ var lookup = 'helper = ' + helper.name + ' || ' + nonHelper;
+ if (helper.paramsInit) {
+ lookup += ',' + helper.paramsInit;
+ }
+
+ this.push(
+ '('
+ + lookup
+ + ',helper '
+ + '? helper.call(' + helper.callParams + ') '
+ + ': helperMissing.call(' + helper.helperMissingParams + '))');
+
+ // 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]
@@ -527,6 +549,7 @@ JavaScriptCompiler.prototype = {
// `knownHelpersOnly` flags at compile-time.
invokeAmbiguous: function(name, helperCall) {
this.context.aliases.functionType = '"function"';
+ this.useRegister('helper');
this.emptyHash();
var helper = this.setupHelper(0, name, helperCall);
@@ -536,8 +559,11 @@ JavaScriptCompiler.prototype = {
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
var nextStack = this.nextStack();
- this.pushSource('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
- this.pushSource('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(' + helper.callParams + ') : ' + nextStack + '; }');
+ if (helper.paramsInit) {
+ this.pushSource(helper.paramsInit);
+ }
+ this.pushSource('if (helper = ' + helperName + ') { ' + nextStack + ' = helper.call(' + helper.callParams + '); }');
+ this.pushSource('else { helper = ' + nonHelper + '; ' + nextStack + ' = typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper; }');
},
// [invokePartial]
@@ -683,7 +709,9 @@ JavaScriptCompiler.prototype = {
replaceStack: function(callback) {
var prefix = '',
inline = this.isInline(),
- stack;
+ stack,
+ createdStack,
+ usedLiteral;
// If we are currently inline then we want to merge the inline statement into the
// replacement statement via ','
@@ -693,9 +721,11 @@ JavaScriptCompiler.prototype = {
if (top instanceof Literal) {
// Literals do not need to be inlined
stack = top.value;
+ usedLiteral = true;
} else {
// Get or create the current stack name for use by the inline
- var name = this.stackSlot ? this.topStackName() : this.incrStack();
+ createdStack = !this.stackSlot;
+ var name = !createdStack ? this.topStackName() : this.incrStack();
prefix = '(' + this.push(name) + ' = ' + top + '),';
stack = this.topStack();
@@ -707,9 +737,12 @@ JavaScriptCompiler.prototype = {
var item = callback.call(this, stack);
if (inline) {
- if (this.inlineStack.length || this.compileStack.length) {
+ if (!usedLiteral) {
this.popStack();
}
+ if (createdStack) {
+ this.stackSlot--;
+ }
this.push('(' + prefix + item + ')');
} else {
// Prevent modification of the context depth variable. Through replaceStack
@@ -760,6 +793,9 @@ JavaScriptCompiler.prototype = {
return item.value;
} else {
if (!inline) {
+ if (!this.stackSlot) {
+ throw new Exception('Invalid stack pop');
+ }
this.stackSlot--;
}
return item;
@@ -788,12 +824,13 @@ JavaScriptCompiler.prototype = {
},
setupHelper: function(paramSize, name, missingParams) {
- var params = [];
- this.setupParams(paramSize, params, missingParams);
+ var params = [],
+ paramsInit = this.setupParams(paramSize, params, missingParams);
var foundHelper = this.nameLookup('helpers', name, 'helper');
return {
params: params,
+ paramsInit: paramsInit,
name: foundHelper,
callParams: ["depth0"].concat(params).join(", "),
helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
@@ -824,7 +861,7 @@ JavaScriptCompiler.prototype = {
}
if (!inverse) {
- this.context.aliases.self = "this";
+ this.context.aliases.self = "this";
inverse = "self.noop";
}
@@ -851,7 +888,15 @@ JavaScriptCompiler.prototype = {
options.push("data:data");
}
- params.push("{" + options.join(",") + "}");
+ options = "{" + options.join(",") + "}";
+ if (useRegister) {
+ this.useRegister('options');
+ params.push('options');
+ return 'options=' + options;
+ } else {
+ params.push(options);
+ return '';
+ }
}
};