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.js109
1 files changed, 76 insertions, 33 deletions
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 607b030..7539068 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);
},
@@ -244,9 +250,6 @@ JavaScriptCompiler.prototype = {
var current = this.topStack();
params.splice(1, 0, current);
- // Use the options value generated from the invocation
- params[params.length-1] = 'options';
-
this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
},
@@ -382,7 +385,7 @@ JavaScriptCompiler.prototype = {
//
// Push the data lookup operator
lookupData: function() {
- this.push('data');
+ this.pushStackLiteral('data');
},
// [pushStringParam]
@@ -398,10 +401,14 @@ JavaScriptCompiler.prototype = {
this.pushString(type);
- if (typeof string === 'string') {
- this.pushString(string);
- } else {
- this.pushStackLiteral(string);
+ // If it's a subexpression, the string result
+ // will be pushed after this opcode.
+ if (type !== 'sexpr') {
+ if (typeof string === 'string') {
+ this.pushString(string);
+ } else {
+ this.pushStackLiteral(string);
+ }
}
},
@@ -409,21 +416,25 @@ JavaScriptCompiler.prototype = {
this.pushStackLiteral('{}');
if (this.options.stringParams) {
- this.register('hashTypes', '{}');
- this.register('hashContexts', '{}');
+ this.push('{}'); // hashContexts
+ this.push('{}'); // hashTypes
}
},
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.register('hashContexts', '{' + hash.contexts.join(',') + '}');
- this.register('hashTypes', '{' + hash.types.join(',') + '}');
+ this.push('{' + hash.contexts.join(',') + '}');
+ this.push('{' + hash.types.join(',') + '}');
}
+
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
},
@@ -485,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]
@@ -525,8 +549,9 @@ JavaScriptCompiler.prototype = {
// `knownHelpersOnly` flags at compile-time.
invokeAmbiguous: function(name, helperCall) {
this.context.aliases.functionType = '"function"';
+ this.useRegister('helper');
- this.pushStackLiteral('{}'); // Hash value
+ this.emptyHash();
var helper = this.setupHelper(0, name, helperCall);
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
@@ -534,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]
@@ -681,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 ','
@@ -691,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();
@@ -705,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
@@ -758,6 +793,9 @@ JavaScriptCompiler.prototype = {
return item.value;
} else {
if (!inline) {
+ if (!this.stackSlot) {
+ throw new Exception('Invalid stack pop');
+ }
this.stackSlot--;
}
return item;
@@ -786,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(", ")
@@ -803,6 +842,11 @@ JavaScriptCompiler.prototype = {
options.push("hash:" + this.popStack());
+ if (this.options.stringParams) {
+ options.push("hashTypes:" + this.popStack());
+ options.push("hashContexts:" + this.popStack());
+ }
+
inverse = this.popStack();
program = this.popStack();
@@ -815,7 +859,7 @@ JavaScriptCompiler.prototype = {
}
if (!inverse) {
- this.context.aliases.self = "this";
+ this.context.aliases.self = "this";
inverse = "self.noop";
}
@@ -836,8 +880,6 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
options.push("contexts:[" + contexts.join(",") + "]");
options.push("types:[" + types.join(",") + "]");
- options.push("hashContexts:hashContexts");
- options.push("hashTypes:hashTypes");
}
if(this.options.data) {
@@ -853,12 +895,13 @@ JavaScriptCompiler.prototype = {
var options = '{' + this.setupOptions(paramSize, params).join(',') + '}';
if (useRegister) {
- this.register('options', options);
+ this.useRegister('options');
params.push('options');
+ return 'options=' + options;
} else {
params.push(options);
+ return '';
}
- return params.join(', ');
}
};