diff options
author | kpdecker <kpdecker@gmail.com> | 2015-04-07 23:37:38 -0500 |
---|---|---|
committer | kpdecker <kpdecker@gmail.com> | 2015-04-07 23:37:38 -0500 |
commit | 2d149e779764cb373be0585c5e493da405062be6 (patch) | |
tree | 97ff4511e445c7d0a70161a0b3bbe02e7e5551e9 | |
parent | 81a4d50955b13e7d5e5052d904bb3e8c993ebb88 (diff) | |
download | handlebars.js-2d149e779764cb373be0585c5e493da405062be6.zip handlebars.js-2d149e779764cb373be0585c5e493da405062be6.tar.gz handlebars.js-2d149e779764cb373be0585c5e493da405062be6.tar.bz2 |
Add undefined and null literal support
This adds the UndefinedLiteral and NullLiteral to AST.
Fixes #990
-rw-r--r-- | docs/compiler-api.md | 8 | ||||
-rw-r--r-- | lib/handlebars/compiler/ast.js | 12 | ||||
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 8 | ||||
-rw-r--r-- | lib/handlebars/compiler/printer.js | 8 | ||||
-rw-r--r-- | lib/handlebars/compiler/visitor.js | 2 | ||||
-rw-r--r-- | spec/basic.js | 10 | ||||
-rw-r--r-- | spec/javascript-compiler.js | 2 | ||||
-rw-r--r-- | spec/parser.js | 4 | ||||
-rw-r--r-- | spec/tokenizer.js | 7 | ||||
-rw-r--r-- | spec/visitor.js | 2 | ||||
-rw-r--r-- | src/handlebars.l | 2 | ||||
-rw-r--r-- | src/handlebars.yy | 2 |
12 files changed, 65 insertions, 2 deletions
diff --git a/docs/compiler-api.md b/docs/compiler-api.md index 40ded8f..c09414f 100644 --- a/docs/compiler-api.md +++ b/docs/compiler-api.md @@ -164,6 +164,14 @@ interface NumberLiteral <: Literal { value: number; original: number; } + +interface UndefinedLiteral <: Literal { + type: "UndefinedLiteral"; +} + +interface NullLiteral <: Literal { + type: "NullLiteral"; +} ``` diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 45212b3..e5904c6 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -101,6 +101,18 @@ var AST = { this.value = bool === 'true'; }, + UndefinedLiteral: function(locInfo) { + this.loc = locInfo; + this.type = 'UndefinedLiteral'; + this.original = this.value = undefined; + }, + + NullLiteral: function(locInfo) { + this.loc = locInfo; + this.type = 'NullLiteral'; + this.original = this.value = null; + }, + Hash: function(pairs, locInfo) { this.loc = locInfo; this.type = 'Hash'; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index e1b4dfb..a39082d 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -273,6 +273,14 @@ Compiler.prototype = { this.opcode('pushLiteral', bool.value); }, + UndefinedLiteral: function() { + this.opcode('pushLiteral', 'undefined'); + }, + + NullLiteral: function() { + this.opcode('pushLiteral', 'null'); + }, + Hash: function(hash) { var pairs = hash.pairs, i, l; diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index 55232cc..dcc73c3 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -124,6 +124,14 @@ PrintVisitor.prototype.BooleanLiteral = function(bool) { return "BOOLEAN{" + bool.value + "}"; }; +PrintVisitor.prototype.UndefinedLiteral = function() { + return 'UNDEFINED'; +}; + +PrintVisitor.prototype.NullLiteral = function() { + return 'NULL'; +}; + PrintVisitor.prototype.Hash = function(hash) { var pairs = hash.pairs; var joinedPairs = []; diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js index 4101a4f..6e4168e 100644 --- a/lib/handlebars/compiler/visitor.js +++ b/lib/handlebars/compiler/visitor.js @@ -110,6 +110,8 @@ Visitor.prototype = { StringLiteral: function(/* string */) {}, NumberLiteral: function(/* number */) {}, BooleanLiteral: function(/* bool */) {}, + UndefinedLiteral: function(/* literal */) {}, + NullLiteral: function(/* literal */) {}, Hash: function(hash) { this.acceptArray(hash.pairs); diff --git a/spec/basic.js b/spec/basic.js index 9b6678a..11002e4 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -66,6 +66,16 @@ describe("basic context", function() { shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false'); }); + it('should handle undefined and null', function() { + shouldCompileTo('{{awesome undefined null}}', + { + awesome: function(_undefined, _null, options) { + return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options); + } + }, + 'true true object'); + }); + it("newlines", function() { shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest"); shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest"); diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js index fb78658..f47ddeb 100644 --- a/spec/javascript-compiler.js +++ b/spec/javascript-compiler.js @@ -23,7 +23,7 @@ describe('javascript-compiler api', function() { // Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases // to avoid errors in older browsers. it('should handle reserved words', function() { - shouldCompileTo("{{foo}} {{~null~}}", { foo: "food" }, "food"); + shouldCompileTo("{{foo}} {{~[null]~}}", { foo: "food" }, "food"); }); }); describe('#compilerInfo', function() { diff --git a/spec/parser.js b/spec/parser.js index 3d3dccf..6f5a660 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -58,6 +58,10 @@ describe('parser', function() { equals(ast_for("{{foo false}}"), "{{ PATH:foo [BOOLEAN{false}] }}\n"); }); + it('parses mustaches with undefined and null parameters', function() { + equals(ast_for("{{foo undefined null}}"), "{{ PATH:foo [UNDEFINED, NULL] }}\n"); + }); + it('parses mutaches with DATA parameters', function() { equals(ast_for("{{foo @bar}}"), "{{ PATH:foo [@PATH:bar] }}\n"); }); diff --git a/spec/tokenizer.js b/spec/tokenizer.js index 0a5143b..26a1c3b 100644 --- a/spec/tokenizer.js +++ b/spec/tokenizer.js @@ -322,6 +322,13 @@ describe('Tokenizer', function() { shouldBeToken(result[2], "BOOLEAN", "false"); }); + it('tokenizes undefined and null', function() { + var result = tokenize('{{ foo undefined null }}'); + shouldMatchTokens(result, ['OPEN', 'ID', 'UNDEFINED', 'NULL', 'CLOSE']); + shouldBeToken(result[2], 'UNDEFINED', 'undefined'); + shouldBeToken(result[3], 'NULL', 'null'); + }); + it('tokenizes hash arguments', function() { var result = tokenize("{{ foo bar=baz }}"); shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']); diff --git a/spec/visitor.js b/spec/visitor.js index 3217230..8b9ef9f 100644 --- a/spec/visitor.js +++ b/spec/visitor.js @@ -9,7 +9,7 @@ describe('Visitor', function() { // Simply run the thing and make sure it does not fail and that all of the // stub methods are executed var visitor = new Handlebars.Visitor(); - visitor.accept(Handlebars.parse('{{foo}}{{#foo (bar 1 "1" true) foo=@data}}{{!comment}}{{> bar }} {{/foo}}')); + visitor.accept(Handlebars.parse('{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}')); }); it('should traverse to stubs', function() { diff --git a/src/handlebars.l b/src/handlebars.l index ace0263..55d8efc 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -102,6 +102,8 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} <mu>"@" return 'DATA'; <mu>"true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; <mu>"false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; +<mu>"undefined"/{LITERAL_LOOKAHEAD} return 'UNDEFINED'; +<mu>"null"/{LITERAL_LOOKAHEAD} return 'NULL'; <mu>\-?[0-9]+(?:\.[0-9]+)?/{LITERAL_LOOKAHEAD} return 'NUMBER'; <mu>"as"\s+"|" return 'OPEN_BLOCK_PARAMS'; <mu>"|" return 'CLOSE_BLOCK_PARAMS'; diff --git a/src/handlebars.yy b/src/handlebars.yy index 18f03b9..d724578 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -83,6 +83,8 @@ partial param : helperName -> $1 | sexpr -> $1 + | UNDEFINED -> new yy.UndefinedLiteral(yy.locInfo(@$)) + | NULL -> new yy.NullLiteral(yy.locInfo(@$)) ; sexpr |