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