import { COMPILER_REVISION, REVISION_CHANGES, log } from "../base"; import Exception from "../exception"; function Literal(value) { this.value = value; } function JavaScriptCompiler() {} 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*/) { var wrap, ret; if (parent.indexOf('depth') === 0) { wrap = true; } if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { ret = parent + "." + name; } else { ret = parent + "['" + name + "']"; } if (wrap) { return '(' + parent + ' && ' + ret + ')'; } else { return ret; } }, compilerInfo: function() { var revision = COMPILER_REVISION, versions = REVISION_CHANGES[revision]; return [revision, versions]; }, 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 || {}; this.stringParams = this.options.stringParams; this.trackIds = this.options.trackIds; this.precompile = !asObject; log('debug', this.environment.disassemble() + "\n\n"); this.name = this.environment.name; this.isChild = !!context; this.context = context || { programs: [], environments: [] }; this.preamble(); this.stackSlot = 0; this.stackVars = []; this.aliases = {}; this.registers = { list: [] }; this.hashes = []; this.compileStack = []; this.inlineStack = []; this.compileChildren(environment, options); var opcodes = environment.opcodes, opcode; this.i = 0; for(var l=opcodes.length; this.i 0) { varDeclarations += ", " + locals.join(", "); } // Generate minimizer alias mappings for (var alias in this.aliases) { if (this.aliases.hasOwnProperty(alias)) { varDeclarations += ', ' + alias + '=' + this.aliases[alias]; } } var params = ["depth0", "helpers", "partials", "data"]; for(var i=0, l=this.environment.depths.list.length; i 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) { if (!this.stackSlot) { throw new Exception('Invalid stack pop'); } 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') + '"'; }, objectLiteral: function(obj) { var pairs = []; for (var key in obj) { if (obj.hasOwnProperty(key)) { pairs.push(this.quotedString(key) + ':' + obj[key]); } } return '{' + pairs.join(',') + '}'; }, setupHelper: function(paramSize, name, blockHelper) { var params = [], paramsInit = this.setupParams(name, paramSize, params, blockHelper); var foundHelper = this.nameLookup('helpers', name, 'helper'); return { params: params, paramsInit: paramsInit, name: foundHelper, callParams: ["depth0"].concat(params).join(", ") }; }, setupOptions: function(helper, paramSize, params) { var options = {}, contexts = [], types = [], ids = [], param, inverse, program; options.name = this.quotedString(helper); options.hash = this.popStack(); if (this.trackIds) { options.hashIds = this.popStack(); } if (this.stringParams) { options.hashTypes = this.popStack(); options.hashContexts = 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) { program = 'this.noop'; } if (!inverse) { inverse = 'this.noop'; } options.fn = program; options.inverse = inverse; } for (var i = 0; i < paramSize; i++) { param = this.popStack(); params.push(param); if (this.trackIds) { ids.push(this.popStack()); } if (this.stringParams) { types.push(this.popStack()); contexts.push(this.popStack()); } } if (this.trackIds) { options.ids = "[" + ids.join(",") + "]"; } if (this.stringParams) { options.types = "[" + types.join(",") + "]"; options.contexts = "[" + contexts.join(",") + "]"; } if (this.options.data) { options.data = "data"; } return options; }, // the params and contexts arguments are passed in arrays // to fill in setupParams: function(helperName, paramSize, params, useRegister) { var options = this.objectLiteral(this.setupOptions(helperName, paramSize, params)); if (useRegister) { this.useRegister('options'); params.push('options'); return 'options=' + options; } else { params.push(options); return ''; } } }; 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