diff options
author | kpdecker <kpdecker@gmail.com> | 2013-06-03 23:27:35 -0500 |
---|---|---|
committer | kpdecker <kpdecker@gmail.com> | 2013-06-03 23:27:35 -0500 |
commit | c3ff57318452c731f9af280a6183a20cad9e03f7 (patch) | |
tree | 61f2f4e80e4d123ca1fa2b130732a5fd108b5710 /lib/handlebars/compiler/compiler.js | |
parent | f11aaa7b7c754c8185c0a1f6a12a503447e7be34 (diff) | |
download | handlebars.js-c3ff57318452c731f9af280a6183a20cad9e03f7.zip handlebars.js-c3ff57318452c731f9af280a6183a20cad9e03f7.tar.gz handlebars.js-c3ff57318452c731f9af280a6183a20cad9e03f7.tar.bz2 |
Break JavascriptCompiler into standalone file
Diffstat (limited to 'lib/handlebars/compiler/compiler.js')
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 846 |
1 files changed, 2 insertions, 844 deletions
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 8bb1fc5..98e12b1 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -8,7 +8,6 @@ compilerbase.attach(Handlebars); /*jshint eqnull:true*/ var Compiler = Handlebars.Compiler = function() {}; -var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; // the foundHelper register will disambiguate helper lookup from finding a // function in a context. This is necessary for mustache compatibility, which @@ -416,845 +415,6 @@ Compiler.prototype = { } }; -var Literal = function(value) { - this.value = value; -}; - -JavaScriptCompiler.prototype = { - // PUBLIC API: You can override these methods in a subclass to provide - // alternative compiled forms for name lookup and buffering semantics - nameLookup: function(parent, name /* , type*/) { - if (/^[0-9]+$/.test(name)) { - return parent + "[" + name + "]"; - } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { - return parent + "." + name; - } - else { - return parent + "['" + name + "']"; - } - }, - - appendToBuffer: function(string) { - if (this.environment.isSimple) { - return "return " + string + ";"; - } else { - return { - appendToBuffer: true, - content: string, - toString: function() { return "buffer += " + string + ";"; } - }; - } - }, - - initializeBuffer: function() { - return this.quotedString(""); - }, - - namespace: "Handlebars", - // END PUBLIC API - - compile: function(environment, options, context, asObject) { - this.environment = environment; - this.options = options || {}; - - Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n"); - - this.name = this.environment.name; - this.isChild = !!context; - this.context = context || { - programs: [], - environments: [], - aliases: { } - }; - - this.preamble(); - - this.stackSlot = 0; - this.stackVars = []; - this.registers = { list: [] }; - this.compileStack = []; - this.inlineStack = []; - - this.compileChildren(environment, options); - - var opcodes = environment.opcodes, opcode; - - this.i = 0; - - for(l=opcodes.length; this.i<l; this.i++) { - opcode = opcodes[this.i]; - - if(opcode.opcode === 'DECLARE') { - this[opcode.name] = opcode.value; - } else { - this[opcode.opcode].apply(this, opcode.args); - } - } - - return this.createFunctionContext(asObject); - }, - - nextOpcode: function() { - var opcodes = this.environment.opcodes; - return opcodes[this.i + 1]; - }, - - eat: function() { - this.i = this.i + 1; - }, - - preamble: function() { - var out = []; - - if (!this.isChild) { - var namespace = this.namespace; - - var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);"; - if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; } - if (this.options.data) { copies = copies + " data = data || {};"; } - out.push(copies); - } else { - out.push(''); - } - - if (!this.environment.isSimple) { - out.push(", buffer = " + this.initializeBuffer()); - } else { - out.push(""); - } - - // track the last context pushed into place to allow skipping the - // getContext opcode when it would be a noop - this.lastContext = 0; - this.source = out; - }, - - createFunctionContext: function(asObject) { - var locals = this.stackVars.concat(this.registers.list); - - if(locals.length > 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } - - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - if (this.context.aliases.hasOwnProperty(alias)) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } - } - - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } - - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } - - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "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]); - } - - // Perform a second pass over the output to merge content when possible - var source = this.mergeSource(); - - if (!this.isChild) { - var revision = Handlebars.COMPILER_REVISION, - versions = Handlebars.REVISION_CHANGES[revision]; - source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source; - } - - if (asObject) { - params.push(source); - - return Function.apply(this, params); - } else { - var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}'; - Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n"); - return functionSource; - } - }, - mergeSource: function() { - // WARN: We are not handling the case where buffer is still populated as the source should - // not have buffer append operations as their final action. - var source = '', - buffer; - for (var i = 0, len = this.source.length; i < len; i++) { - var line = this.source[i]; - if (line.appendToBuffer) { - if (buffer) { - buffer = buffer + '\n + ' + line.content; - } else { - buffer = line.content; - } - } else { - if (buffer) { - source += 'buffer += ' + buffer + ';\n '; - buffer = undefined; - } - source += line + '\n '; - } - } - return source; - }, - - // [blockValue] - // - // On stack, before: hash, inverse, program, value - // On stack, after: return value of blockHelperMissing - // - // The purpose of this opcode is to take a block of the form - // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and - // replace it on the stack with the result of properly - // invoking blockHelperMissing. - blockValue: function() { - this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; - - var params = ["depth0"]; - this.setupParams(0, params); - - this.replaceStack(function(current) { - params.splice(1, 0, current); - return "blockHelperMissing.call(" + params.join(", ") + ")"; - }); - }, - - // [ambiguousBlockValue] - // - // On stack, before: hash, inverse, program, value - // Compiler value, before: lastHelper=value of last found helper, if any - // On stack, after, if no lastHelper: same as [blockValue] - // On stack, after, if lastHelper: value - ambiguousBlockValue: function() { - this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; - - var params = ["depth0"]; - this.setupParams(0, params); - - var current = this.topStack(); - params.splice(1, 0, current); - - // Use the options value generated from the invocation - params[params.length-1] = 'options'; - - this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }"); - }, - - // [appendContent] - // - // On stack, before: ... - // On stack, after: ... - // - // Appends the string value of `content` to the current buffer - appendContent: function(content) { - this.source.push(this.appendToBuffer(this.quotedString(content))); - }, - - // [append] - // - // On stack, before: value, ... - // On stack, after: ... - // - // Coerces `value` to a String and appends it to the current buffer. - // - // If `value` is truthy, or 0, it is coerced into a string and appended - // Otherwise, the empty string is appended - append: function() { - // Force anything that is inlined onto the stack so we don't have duplication - // when we examine local - this.flushInline(); - var local = this.popStack(); - this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }"); - if (this.environment.isSimple) { - this.source.push("else { " + this.appendToBuffer("''") + " }"); - } - }, - - // [appendEscaped] - // - // On stack, before: value, ... - // On stack, after: ... - // - // Escape `value` and append it to the buffer - appendEscaped: function() { - this.context.aliases.escapeExpression = 'this.escapeExpression'; - - this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); - }, - - // [getContext] - // - // On stack, before: ... - // On stack, after: ... - // Compiler value, after: lastContext=depth - // - // Set the value of the `lastContext` compiler value to the depth - getContext: function(depth) { - if(this.lastContext !== depth) { - this.lastContext = depth; - } - }, - - // [lookupOnContext] - // - // On stack, before: ... - // On stack, after: currentContext[name], ... - // - // Looks up the value of `name` on the current context and pushes - // it onto the stack. - lookupOnContext: function(name) { - this.push(this.nameLookup('depth' + this.lastContext, name, 'context')); - }, - - // [pushContext] - // - // On stack, before: ... - // On stack, after: currentContext, ... - // - // Pushes the value of the current context onto the stack. - pushContext: function() { - this.pushStackLiteral('depth' + this.lastContext); - }, - - // [resolvePossibleLambda] - // - // On stack, before: value, ... - // On stack, after: resolved value, ... - // - // If the `value` is a lambda, replace it on the stack by - // the return value of the lambda - resolvePossibleLambda: function() { - this.context.aliases.functionType = '"function"'; - - this.replaceStack(function(current) { - return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current; - }); - }, - - // [lookup] - // - // On stack, before: value, ... - // On stack, after: value[name], ... - // - // Replace the value on the stack with the result of looking - // up `name` on `value` - lookup: function(name) { - this.replaceStack(function(current) { - return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context'); - }); - }, - - // [lookupData] - // - // On stack, before: ... - // On stack, after: data[id], ... - // - // Push the result of looking up `id` on the current data - lookupData: function(id) { - this.push('data'); - }, - - // [pushStringParam] - // - // On stack, before: ... - // On stack, after: string, currentContext, ... - // - // This opcode is designed for use in string mode, which - // 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.pushString(type); - - if (typeof string === 'string') { - this.pushString(string); - } else { - this.pushStackLiteral(string); - } - }, - - emptyHash: function() { - this.pushStackLiteral('{}'); - - if (this.options.stringParams) { - this.register('hashTypes', '{}'); - this.register('hashContexts', '{}'); - } - }, - pushHash: function() { - this.hash = {values: [], types: [], contexts: []}; - }, - popHash: function() { - var hash = this.hash; - this.hash = undefined; - - if (this.options.stringParams) { - this.register('hashContexts', '{' + hash.contexts.join(',') + '}'); - this.register('hashTypes', '{' + hash.types.join(',') + '}'); - } - this.push('{\n ' + hash.values.join(',\n ') + '\n }'); - }, - - // [pushString] - // - // On stack, before: ... - // On stack, after: quotedString(string), ... - // - // Push a quoted version of `string` onto the stack - pushString: function(string) { - this.pushStackLiteral(this.quotedString(string)); - }, - - // [push] - // - // On stack, before: ... - // On stack, after: expr, ... - // - // Push an expression onto the stack - push: function(expr) { - this.inlineStack.push(expr); - return expr; - }, - - // [pushLiteral] - // - // On stack, before: ... - // On stack, after: value, ... - // - // Pushes a value onto the stack. This operation prevents - // the compiler from creating a temporary variable to hold - // it. - pushLiteral: function(value) { - this.pushStackLiteral(value); - }, - - // [pushProgram] - // - // On stack, before: ... - // On stack, after: program(guid), ... - // - // Push a program expression onto the stack. This takes - // a compile-time guid and converts it into a runtime-accessible - // expression. - pushProgram: function(guid) { - if (guid != null) { - this.pushStackLiteral(this.programExpression(guid)); - } else { - this.pushStackLiteral(null); - } - }, - - // [invokeHelper] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of helper invocation - // - // Pops off the helper's parameters, invokes the helper, - // and pushes the helper's return value onto the stack. - // - // If the helper is not found, `helperMissing` is called. - invokeHelper: function(paramSize, name) { - this.context.aliases.helperMissing = 'helpers.helperMissing'; - - 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 + ")"; - }); - }, - - // [invokeKnownHelper] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of helper invocation - // - // This operation is used when the helper is known to exist, - // so a `helperMissing` fallback is not required. - invokeKnownHelper: function(paramSize, name) { - var helper = this.setupHelper(paramSize, name); - this.push(helper.name + ".call(" + helper.callParams + ")"); - }, - - // [invokeAmbiguous] - // - // On stack, before: hash, inverse, program, params..., ... - // On stack, after: result of disambiguation - // - // This operation is used when an expression like `{{foo}}` - // is provided, but we don't know at compile-time whether it - // is a helper or a path. - // - // This operation emits more code than the other options, - // and can be avoided by passing the `knownHelpers` and - // `knownHelpersOnly` flags at compile-time. - invokeAmbiguous: function(name, helperCall) { - this.context.aliases.functionType = '"function"'; - - this.pushStackLiteral('{}'); // Hash value - var helper = this.setupHelper(0, name, helperCall); - - var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); - - var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); - var nextStack = this.nextStack(); - - this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }'); - this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }'); - }, - - // [invokePartial] - // - // On stack, before: context, ... - // On stack after: result of partial invocation - // - // 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(), "helpers", "partials"]; - - if (this.options.data) { - params.push("data"); - } - - this.context.aliases.self = "this"; - this.push("self.invokePartial(" + params.join(", ") + ")"); - }, - - // [assignToHash] - // - // On stack, before: value, hash, ... - // On stack, after: hash, ... - // - // Pops a value and hash off the stack, assigns `hash[key] = value` - // and pushes the hash back onto the stack. - assignToHash: function(key) { - var value = this.popStack(), - context, - type; - - if (this.options.stringParams) { - type = this.popStack(); - context = this.popStack(); - } - - var hash = this.hash; - if (context) { - hash.contexts.push("'" + key + "': " + context); - } - if (type) { - hash.types.push("'" + key + "': " + type); - } - hash.values.push("'" + key + "': (" + value + ")"); - }, - - // HELPERS - - compiler: JavaScriptCompiler, - - compileChildren: function(environment, options) { - var children = environment.children, child, compiler; - - for(var i=0, l=children.length; i<l; i++) { - child = children[i]; - compiler = new this.compiler(); - - var index = this.matchExistingProgram(child); - - if (index == null) { - this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children - index = this.context.programs.length; - child.index = index; - child.name = 'program' + index; - this.context.programs[index] = compiler.compile(child, options, this.context); - this.context.environments[index] = child; - } else { - child.index = index; - child.name = 'program' + index; - } - } - }, - matchExistingProgram: function(child) { - for (var i = 0, len = this.context.environments.length; i < len; i++) { - var environment = this.context.environments[i]; - if (environment && environment.equals(child)) { - return i; - } - } - }, - - programExpression: function(guid) { - this.context.aliases.self = "this"; - - if(guid == null) { - return "self.noop"; - } - - var child = this.environment.children[guid], - depths = child.depths.list, depth; - - var programParams = [child.index, child.name, "data"]; - - for(var i=0, l = depths.length; i<l; i++) { - depth = depths[i]; - - if(depth === 1) { programParams.push("depth0"); } - else { programParams.push("depth" + (depth - 1)); } - } - - return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")"; - }, - - register: function(name, val) { - this.useRegister(name); - this.source.push(name + " = " + val + ";"); - }, - - useRegister: function(name) { - if(!this.registers[name]) { - this.registers[name] = true; - this.registers.list.push(name); - } - }, - - pushStackLiteral: function(item) { - return this.push(new Literal(item)); - }, - - pushStack: function(item) { - this.flushInline(); - - var stack = this.incrStack(); - if (item) { - this.source.push(stack + " = " + item + ";"); - } - this.compileStack.push(stack); - return stack; - }, - - replaceStack: function(callback) { - var prefix = '', - inline = this.isInline(), - stack; - - // 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); - - if (top instanceof Literal) { - // Literals do not need to be inlined - stack = top.value; - } else { - // Get or create the current stack name for use by the inline - var name = this.stackSlot ? this.topStackName() : this.incrStack(); - - prefix = '(' + this.push(name) + ' = ' + top + '),'; - stack = this.topStack(); - } - } else { - stack = this.topStack(); - } - - var item = callback.call(this, stack); - - if (inline) { - if (this.inlineStack.length || this.compileStack.length) { - this.popStack(); - } - this.push('(' + prefix + item + ')'); - } else { - // Prevent modification of the context depth variable. Through replaceStack - if (!/^stack/.test(stack)) { - stack = this.nextStack(); - } - - this.source.push(stack + " = (" + prefix + item + ");"); - } - return stack; - }, - - nextStack: function() { - return this.pushStack(); - }, - - incrStack: function() { - this.stackSlot++; - if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } - } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; - } - return item; - } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; - } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 - .replace(/\u2029/g, '\\u2029') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } - - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } - - options.push("inverse:" + inverse); - options.push("fn:" + program); - } - - for(var i=0; i<paramSize; i++) { - param = this.popStack(); - params.push(param); - - if(this.options.stringParams) { - types.push(this.popStack()); - contexts.push(this.popStack()); - } - } - - 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) { - options.push("data:data"); - } - - options = "{" + options.join(",") + "}"; - if (useRegister) { - this.register('options', options); - params.push('options'); - } else { - params.push(options); - } - return params.join(", "); - } -}; - -var reservedWords = ( - "break else new var" + - " case finally return void" + - " catch for switch while" + - " continue function this with" + - " default if throw" + - " delete in try" + - " do instanceof typeof" + - " abstract enum int short" + - " boolean export interface static" + - " byte extends long super" + - " char final native synchronized" + - " class float package throws" + - " const goto private transient" + - " debugger implements protected volatile" + - " double import public let yield" -).split(" "); - -var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; - -for(var i=0, l=reservedWords.length; i<l; i++) { - compilerWords[reservedWords[i]] = true; -} - -JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { - if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) { - return true; - } - return false; -}; - Handlebars.precompile = function(input, options) { if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input); @@ -1266,7 +426,7 @@ Handlebars.precompile = function(input, options) { } var ast = Handlebars.parse(input); var environment = new Compiler().compile(ast, options); - return new JavaScriptCompiler().compile(environment, options); + return new Handlebars.JavaScriptCompiler().compile(environment, options); }; Handlebars.compile = function(input, options) { @@ -1282,7 +442,7 @@ Handlebars.compile = function(input, options) { function compile() { var ast = Handlebars.parse(input); var environment = new Compiler().compile(ast, options); - var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true); + var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); return Handlebars.template(templateSpec); } @@ -1301,5 +461,3 @@ Handlebars.compile = function(input, options) { return Handlebars; }; - - |