diff options
Diffstat (limited to 'lib/handlebars')
-rw-r--r-- | lib/handlebars/ast.js | 13 | ||||
-rw-r--r-- | lib/handlebars/base.js | 26 | ||||
-rw-r--r-- | lib/handlebars/compiler.js (renamed from lib/handlebars/vm.js) | 127 | ||||
-rw-r--r-- | lib/handlebars/printer.js | 8 | ||||
-rw-r--r-- | lib/handlebars/runtime.js | 267 | ||||
-rw-r--r-- | lib/handlebars/utils.js | 11 |
6 files changed, 126 insertions, 326 deletions
diff --git a/lib/handlebars/ast.js b/lib/handlebars/ast.js index a266fe7..d7cc150 100644 --- a/lib/handlebars/ast.js +++ b/lib/handlebars/ast.js @@ -60,7 +60,7 @@ var Handlebars = require("handlebars"); Handlebars.AST.IdNode = function(parts) { this.type = "ID"; - this.original = parts.join("/"); + this.original = parts.join("."); var dig = [], depth = 0; @@ -73,6 +73,7 @@ var Handlebars = require("handlebars"); } this.parts = dig; + this.string = dig.join('.'); this.depth = depth; this.isSimple = (dig.length === 1) && (depth === 0); }; @@ -82,6 +83,16 @@ var Handlebars = require("handlebars"); this.string = string; }; + Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.integer = integer; + }; + + Handlebars.AST.BooleanNode = function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + }; + Handlebars.AST.CommentNode = function(comment) { this.type = "comment"; this.comment = comment; diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 387ff76..b1cc4ba 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -3,6 +3,8 @@ var handlebars = require("handlebars/parser").parser; // BEGIN(BROWSER) var Handlebars = {}; +Handlebars.VERSION = "1.0.beta.2"; + Handlebars.Parser = handlebars; Handlebars.parse = function(string) { @@ -14,22 +16,6 @@ Handlebars.print = function(ast) { return new Handlebars.PrintVisitor().accept(ast); }; -Handlebars.Runtime = {}; - -Handlebars.Runtime.compile = function(string) { - var ast = Handlebars.parse(string); - - return function(context, helpers, partials) { - helpers = helpers || Handlebars.helpers; - partials = partials || Handlebars.partials; - - var internalContext = new Handlebars.Context(context, helpers, partials); - var runtime = new Handlebars.Runtime(internalContext); - runtime.accept(ast); - return runtime.buffer; - }; -}; - Handlebars.helpers = {}; Handlebars.partials = {}; @@ -42,6 +28,14 @@ Handlebars.registerPartial = function(name, str) { this.partials[name] = str; }; +Handlebars.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Error("Could not find property '" + arg + "'"); + } +}); + Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) { inverse = inverse || function() {}; diff --git a/lib/handlebars/vm.js b/lib/handlebars/compiler.js index 3dbd8f4..6f52482 100644 --- a/lib/handlebars/vm.js +++ b/lib/handlebars/compiler.js @@ -20,7 +20,8 @@ Handlebars.JavaScriptCompiler = function() {}; invokePartial: 12, push: 13, invokeInverse: 14, - assignToHash: 15 + assignToHash: 15, + pushStringParam: 16 }; Compiler.MULTI_PARAM_OPCODES = { @@ -36,7 +37,8 @@ Handlebars.JavaScriptCompiler = function() {}; invokePartial: 1, push: 1, invokeInverse: 1, - assignToHash: 1 + assignToHash: 1, + pushStringParam: 1 }; Compiler.DISASSEMBLE_MAP = {}; @@ -51,6 +53,8 @@ Handlebars.JavaScriptCompiler = function() {}; }; Compiler.prototype = { + compiler: Compiler, + disassemble: function() { var opcodes = this.opcodes, opcode, nextCode; var out = [], str, name, value; @@ -89,9 +93,10 @@ Handlebars.JavaScriptCompiler = function() {}; guid: 0, - compile: function(program) { + compile: function(program, options) { this.children = []; this.depths = {list: []}; + this.options = options || {}; return this.program(program); }, @@ -116,7 +121,7 @@ Handlebars.JavaScriptCompiler = function() {}; }, compileProgram: function(program) { - var result = new Compiler().compile(program); + var result = new this.compiler().compile(program, this.options); var guid = this.guid++; this.usePartial = this.usePartial || result.usePartial; @@ -219,6 +224,14 @@ Handlebars.JavaScriptCompiler = function() {}; this.opcode('pushString', string.string); }, + INTEGER: function(integer) { + this.opcode('push', integer.integer); + }, + + BOOLEAN: function(bool) { + this.opcode('push', bool.bool); + }, + comment: function() {}, // HELPERS @@ -227,7 +240,17 @@ Handlebars.JavaScriptCompiler = function() {}; while(i--) { param = params[i]; - this[param.type](param); + + if(this.options.stringParams) { + if(param.depth) { + this.addDepth(param.depth); + } + + this.opcode('getContext', param.depth || 0); + this.opcode('pushStringParam', param.string); + } else { + this[param.type](param); + } } }, @@ -273,8 +296,10 @@ Handlebars.JavaScriptCompiler = function() {}; // 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(JavaScriptCompiler.RESERVED_WORDS[name]) { + if(JavaScriptCompiler.RESERVED_WORDS[name] || name.indexOf('-') !== -1 || !isNaN(name)) { return parent + "['" + name + "']"; + } else if (/^[0-9]+$/.test(name)) { + return parent + "[" + name + "]"; } else { return parent + "." + name; } @@ -289,9 +314,9 @@ Handlebars.JavaScriptCompiler = function() {}; }, // END PUBLIC API - compile: function(environment, data) { + compile: function(environment, options) { this.environment = environment; - this.data = data; + this.options = options || {}; this.preamble(); @@ -299,7 +324,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.stackVars = []; this.registers = {list: []}; - this.compileChildren(environment, data); + this.compileChildren(environment, options); Handlebars.log(Handlebars.logger.DEBUG, environment.disassemble() + "\n\n"); @@ -393,13 +418,12 @@ Handlebars.JavaScriptCompiler = function() {}; var params = ["Handlebars", "context", "helpers", "partials"]; - if(this.data) { params.push("data"); } + if(this.options.data) { params.push("data"); } for(var i=0, l=this.environment.depths.list.length; i<l; i++) { params.push("depth" + this.environment.depths.list[i]); } - if(params.length === 4 && !this.environment.usePartial) { params.pop(); } params.push(this.source.join("\n")); @@ -413,10 +437,12 @@ Handlebars.JavaScriptCompiler = function() {}; container.children = this.environment.children; - return function(context, helpers, partials, data, $depth) { + return function(context, options, $depth) { try { - var args = Array.prototype.slice.call(arguments); - args.unshift(Handlebars); + options = options || {}; + var args = [Handlebars, context, options.helpers, options.partials, options.data]; + var depth = Array.prototype.slice.call(arguments, 2); + args = args.concat(depth); return container.render.apply(container, args); } catch(e) { throw e; @@ -477,6 +503,11 @@ Handlebars.JavaScriptCompiler = function() {}; this.source.push(topStack + " = " + this.nameLookup(topStack, name, 'context') + ";"); }, + pushStringParam: function(string) { + this.pushStack("currentContext"); + this.pushString(string); + }, + pushString: function(string) { this.pushStack(this.quotedString(string)); }, @@ -503,24 +534,32 @@ Handlebars.JavaScriptCompiler = function() {}; populateParams: function(paramSize, helperId, program, inverse, fn) { var id = this.popStack(), nextStack; - var params = []; + var params = [], param, stringParam; var hash = this.popStack(); + this.register('tmp1', program); + this.source.push('tmp1.hash = ' + hash + ';'); + + if(this.options.stringParams) { + this.source.push('tmp1.contexts = [];'); + } + for(var i=0; i<paramSize; i++) { - var param = this.popStack(); + param = this.popStack(); params.push(param); - } - this.register('tmp1', program); - this.source.push('tmp1.hash = ' + hash + ';'); + if(this.options.stringParams) { + this.source.push('tmp1.contexts.push(' + this.popStack() + ');'); + } + } if(inverse) { this.source.push('tmp1.fn = tmp1;'); this.source.push('tmp1.inverse = ' + inverse + ';'); } - if(this.data) { + if(this.options.data) { this.source.push('tmp1.data = data;'); } @@ -566,7 +605,7 @@ Handlebars.JavaScriptCompiler = function() {}; compiler: JavaScriptCompiler, - compileChildren: function(environment, data) { + compileChildren: function(environment, options) { var children = environment.children, child, compiler; var compiled = []; @@ -574,7 +613,7 @@ Handlebars.JavaScriptCompiler = function() {}; child = children[i]; compiler = new this.compiler(); - compiled[i] = compiler.compile(child, data); + compiled[i] = compiler.compile(child, options); } environment.rawChildren = children; @@ -588,7 +627,7 @@ Handlebars.JavaScriptCompiler = function() {}; var depths = this.environment.rawChildren[guid].depths.list; - if(this.data) { programParams.push("data"); } + if(this.options.data) { programParams.push("data"); } for(var i=0, l = depths.length; i<l; i++) { depth = depths[i]; @@ -646,7 +685,7 @@ Handlebars.JavaScriptCompiler = function() {}; quotedString: function(str) { return '"' + str - .replace(/\\/, '\\\\') + .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') + '"'; @@ -666,34 +705,46 @@ Handlebars.JavaScriptCompiler = function() {}; })(Handlebars.Compiler, Handlebars.JavaScriptCompiler); Handlebars.VM = { - programWithDepth: function(fn) { - var args = Array.prototype.slice.call(arguments, 1); - return function(context, helpers, partials, data) { - args[0] = helpers || args[0]; - args[1] = partials || args[1]; - args[2] = data || args[2]; - return fn.apply(this, [context].concat(args)); + programWithDepth: function(fn, helpers, partials, data, $depth) { + var args = Array.prototype.slice.call(arguments, 4); + + return function(context, options) { + options = options || {}; + + options = { + helpers: options.helpers || helpers, + partials: options.partials || partials, + data: options.data || data + }; + + return fn.apply(this, [context, options].concat(args)); }; }, program: function(fn, helpers, partials, data) { - return function(context, h2, p2, d2) { - return fn(context, h2 || helpers, p2 || partials, d2 || data); + return function(context, options) { + options = options || {}; + + return fn(context, { + helpers: options.helpers || helpers, + partials: options.partials || partials, + data: options.data || data + }); }; }, noop: function() { return ""; }, - compile: function(string, data) { + compile: function(string, options) { var ast = Handlebars.parse(string); - var environment = new Handlebars.Compiler().compile(ast); - return new Handlebars.JavaScriptCompiler().compile(environment, data); + var environment = new Handlebars.Compiler().compile(ast, options); + return new Handlebars.JavaScriptCompiler().compile(environment, options); }, invokePartial: function(partial, name, context, helpers, partials) { if(partial === undefined) { throw new Handlebars.Exception("The partial " + name + " could not be found"); } else if(partial instanceof Function) { - return partial(context, helpers, partials); + return partial(context, {helpers: helpers, partials: partials}); } else { partials[name] = Handlebars.VM.compile(partial); - return partials[name](context, helpers, partials); + return partials[name](context, {helpers: helpers, partials: partials}); } } }; diff --git a/lib/handlebars/printer.js b/lib/handlebars/printer.js index 2da7bcc..7be3f98 100644 --- a/lib/handlebars/printer.js +++ b/lib/handlebars/printer.js @@ -109,6 +109,14 @@ Handlebars.PrintVisitor.prototype.STRING = function(string) { return '"' + string.string + '"'; }; +Handlebars.PrintVisitor.prototype.INTEGER = function(integer) { + return "INTEGER{" + integer.integer + "}"; +}; + +Handlebars.PrintVisitor.prototype.BOOLEAN = function(bool) { + return "BOOLEAN{" + bool.bool + "}"; +}; + Handlebars.PrintVisitor.prototype.ID = function(id) { var path = id.parts.join("/"); if(id.parts.length > 1) { diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js deleted file mode 100644 index 29ace86..0000000 --- a/lib/handlebars/runtime.js +++ /dev/null @@ -1,267 +0,0 @@ -var inspect = function(obj) { - require("sys").print(require("sys").inspect(obj) + "\n"); -}; - -var Handlebars = require("handlebars"); - -// BEGIN(BROWSER) -// A Context wraps data, and makes it possible to extract a -// new Context given a path. For instance, if the data -// is { person: { name: "Alan" } }, a Context wrapping -// "Alan" can be extracted by searching for "person/name" -Handlebars.Context = function(data, helpers, partials) { - this.data = data; - this.helpers = helpers || {}; - this.partials = partials || {}; -}; - -Handlebars.Context.prototype = { - isContext: true, - - // Make a shallow copy of the Context - clone: function() { - return new Handlebars.Context(this.data, this.helpers, this.partials); - }, - - // Search for an object inside the Context's data. The - // path parameter is an object with parts - // ("person/name" represented as ["person", "name"]), - // and depth (the amount of levels to go up the stack, - // originally represented as ..). The stack parameter - // is the objects already searched from the root of - // the original Context in order to get to this point. - // - // Return a new Context wrapping the data found in - // the search. - evaluate: function(path, stack) { - var context = this.clone(); - var depth = path.depth, parts = path.parts; - - if(depth > stack.length) { context.data = null; } - else if(depth > 0) { context = stack[stack.length - depth].clone(); } - - for(var i=0,l=parts.length; i<l && context.data != null; i++) { - context.data = context.data[parts[i]]; - } - - if(context.data !== undefined) { return context; } - - if(parts.length === 1 && context.data === undefined) { - context.data = context.helpers[parts[0]]; - } - - return context; - } -}; - -Handlebars.K = function() { return this; }; - -Handlebars.proxy = function(obj) { - var Proxy = this.K; - Proxy.prototype = obj; - return new Proxy(); -}; - -Handlebars.Runtime = function(context, stack) { - this.stack = stack || []; - this.buffer = ""; - - this.context = context; -}; - -Handlebars.Runtime.prototype = { - accept: Handlebars.Visitor.prototype.accept, - - ID: function(path) { - return this.context.evaluate(path, this.stack); - }, - - STRING: function(string) { - return { data: string.string }; - }, - - program: function(program) { - var statements = program.statements; - - for(var i=0, l=statements.length; i<l; i++) { - var statement = statements[i]; - this[statement.type](statement); - } - - return this.buffer; - }, - - mustache: function(mustache) { - var idObj = this.ID(mustache.id); - var params = mustache.params.slice(0); - var buf; - - for(var i=0, l=params.length; i<l; i++) { - var param = params[i]; - params[i] = this[param.type](param).data; - } - - var data = idObj.data; - - var type = Object.prototype.toString.call(data); - var functionType = (type === "[object Function]"); - - if(!functionType && params.length) { - params = params.slice(0); - params.unshift(data || mustache.id.original); - data = this.context.helpers.helperMissing; - functionType = true; - } - - if(functionType) { - buf = data.apply(this.wrapContext(), params); - } else { - buf = data; - } - - if(buf && mustache.escaped) { buf = Handlebars.Utils.escapeExpression(buf); } - - this.buffer = this.buffer + ((!buf && buf !== 0) ? '' : buf); - }, - - block: function(block) { - var mustache = block.mustache, data; - - var id = mustache.id, - idObj = this.ID(mustache.id), - data = idObj.data; - - var result; - - if(typeof data === "function") { - params = this.evaluateParams(mustache.params); - } else { - params = [data]; - data = this.context.helpers.blockHelperMissing; - } - - params.push(this.wrapProgram(block.program)); - result = data.apply(this.wrapContext(), params); - this.buffer = this.buffer + ((result === undefined) ? "" : result); - - if(block.program.inverse) { - params.pop(); - params.push(this.wrapProgram(block.program.inverse)); - result = data.not ? data.not.apply(this.wrapContext(), params) : ""; - this.buffer = this.buffer + result; - } - }, - - partial: function(partial) { - var partials = this.context.partials || {}; - var id = partial.id.original; - - var partialBody = partials[partial.id.original]; - var program, context; - - if(!partialBody) { - throw new Handlebars.Exception("The partial " + partial.id.original + " does not exist"); - } - - if(typeof partialBody === "string") { - program = Handlebars.parse(partialBody); - partials[id] = program; - } else { - program = partialBody; - } - - if(partial.context) { - context = this.ID(partial.context); - } else { - context = this.context; - } - var runtime = new Handlebars.Runtime(context, this.stack); - this.buffer = this.buffer + runtime.program(program); - }, - - not: function(context, fn) { - return fn(context); - }, - - // TODO: Write down the actual spec for inverse sections... - inverse: function(block) { - var mustache = block.mustache, - id = mustache.id, - not; - - var idObj = this.ID(id), - data = idObj.data, - isInverse = Handlebars.Utils.isEmpty(data); - - - var context = this.wrapContext(); - - if(Object.prototype.toString.call(data) === "[object Function]") { - params = this.evaluateParams(mustache.params); - id = id.parts.join("/"); - - data = data.apply(context, params); - if(Handlebars.Utils.isEmpty(data)) { isInverse = true; } - if(data.not) { not = data.not; } else { not = this.not; } - } else { - not = this.not; - } - - var result = not(context, this.wrapProgram(block.program)); - if(result != null) { this.buffer = this.buffer + result; } - return; - }, - - content: function(content) { - this.buffer += content.string; - }, - - comment: function() {}, - - evaluateParams: function(params) { - var ret = []; - - for(var i=0, l=params.length; i<l; i++) { - var param = params[i]; - ret[i] = this[param.type](param).data; - } - - if(ret.length === 0) { ret = [this.wrapContext()]; } - return ret; - }, - - wrapContext: function() { - var data = this.context.data; - var proxy = Handlebars.proxy(data); - var context = proxy.__context__ = this.context; - var stack = proxy.__stack__ = this.stack.slice(0); - - proxy.__get__ = function(path) { - path = new Handlebars.AST.IdNode(path.split("/")); - return context.evaluate(path, stack).data; - }; - - proxy.isWrappedContext = true; - proxy.__data__ = data; - - return proxy; - }, - - wrapProgram: function(program) { - var currentContext = this.context; - var stack = this.stack.slice(0); - - return function(context) { - if(context && context.isWrappedContext) { context = context.__data__; } - - stack.push(currentContext); - var newContext = new Handlebars.Context(context, currentContext.helpers, currentContext.partials); - var runtime = new Handlebars.Runtime(newContext, stack); - runtime.program(program); - return runtime.buffer; - }; - } - -}; -// END(BROWSER) - diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 981bb1f..4202c77 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -16,14 +16,17 @@ Handlebars.SafeString.prototype.toString = function() { (function() { var escape = { "<": "<", - ">": ">" + ">": ">", + '"': """, + "'": "'", + "`": "`" }; - var badChars = /&(?!\w+;)|[<>]/g; - var possible = /[&<>]/ + var badChars = /&(?!\w+;)|[<>"'`]/g; + var possible = /[&<>"'`]/; var escapeChar = function(chr) { - return escape[chr] || "&" + return escape[chr] || "&"; }; Handlebars.Utils = { |