diff options
author | tomhuda <tomhuda@strobecorp.com> | 2011-06-01 22:33:48 -0700 |
---|---|---|
committer | tomhuda <tomhuda@strobecorp.com> | 2011-06-01 22:33:48 -0700 |
commit | 0f78345d0c3399edd27b897485c7f190fe4e8bf6 (patch) | |
tree | 64f4be14a5d25349ad81b31ff9d3d729249daf27 | |
parent | 4eeda34ad2bd051b8d686f1b9086054f89284ec1 (diff) | |
download | handlebars.js-0f78345d0c3399edd27b897485c7f190fe4e8bf6.zip handlebars.js-0f78345d0c3399edd27b897485c7f190fe4e8bf6.tar.gz handlebars.js-0f78345d0c3399edd27b897485c7f190fe4e8bf6.tar.bz2 |
Add support for INTEGER expressions
-rw-r--r-- | lib/handlebars/ast.js | 5 | ||||
-rw-r--r-- | lib/handlebars/compiler.js | 4 | ||||
-rw-r--r-- | lib/handlebars/printer.js | 4 | ||||
-rw-r--r-- | spec/parser_spec.rb | 18 | ||||
-rw-r--r-- | spec/qunit_spec.js | 58 | ||||
-rw-r--r-- | spec/tokenizer_spec.rb | 9 | ||||
-rw-r--r-- | src/handlebars.l | 1 | ||||
-rw-r--r-- | src/handlebars.yy | 2 |
8 files changed, 71 insertions, 30 deletions
diff --git a/lib/handlebars/ast.js b/lib/handlebars/ast.js index 1194694..86960ab 100644 --- a/lib/handlebars/ast.js +++ b/lib/handlebars/ast.js @@ -83,6 +83,11 @@ var Handlebars = require("handlebars"); this.string = string; }; + Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.integer = integer; + }; + Handlebars.AST.CommentNode = function(comment) { this.type = "comment"; this.comment = comment; diff --git a/lib/handlebars/compiler.js b/lib/handlebars/compiler.js index f3ce602..d66b1db 100644 --- a/lib/handlebars/compiler.js +++ b/lib/handlebars/compiler.js @@ -224,6 +224,10 @@ Handlebars.JavaScriptCompiler = function() {}; this.opcode('pushString', string.string); }, + INTEGER: function(integer) { + this.opcode('push', integer.integer); + }, + comment: function() {}, // HELPERS diff --git a/lib/handlebars/printer.js b/lib/handlebars/printer.js index 2da7bcc..6388f01 100644 --- a/lib/handlebars/printer.js +++ b/lib/handlebars/printer.js @@ -109,6 +109,10 @@ Handlebars.PrintVisitor.prototype.STRING = function(string) { return '"' + string.string + '"'; }; +Handlebars.PrintVisitor.prototype.INTEGER = function(integer) { + return "INTEGER{" + integer.integer + "}"; +} + Handlebars.PrintVisitor.prototype.ID = function(id) { var path = id.parts.join("/"); if(id.parts.length > 1) { diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 78ffa1e..941887d 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -92,6 +92,10 @@ describe "Parser" do string.inspect end + def integer(string) + "INTEGER{#{string}}" + end + def hash(*pairs) "HASH{" + pairs.map {|k,v| "#{k}=#{v}" }.join(", ") + "}" end @@ -127,7 +131,11 @@ describe "Parser" do it "parses mustaches with hash arguments" do ast_for("{{foo bar=baz}}").should == program do - mustache id("foo"), [], hash(["bar", "ID:baz"]) + mustache id("foo"), [], hash(["bar", id("baz")]) + end + + ast_for("{{foo bar=1}}").should == program do + mustache id("foo"), [], hash(["bar", integer("1")]) end ast_for("{{foo bar=baz bat=bam}}").should == program do @@ -141,12 +149,20 @@ describe "Parser" do ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == program do mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")]) end + + ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == program do + mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")]) + end end it "parses mustaches with string parameters" do ast_for("{{foo bar \"baz\" }}").should == program { mustache id("foo"), [id("bar"), string("baz")] } end + it "parses mustaches with INTEGER parameters" do + ast_for("{{foo 1}}").should == program { mustache id("foo"), [integer("1")] } + end + it "parses contents followed by a mustache" do ast_for("foo bar {{baz}}").should == program do content "foo bar " diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index c766b69..64fee91 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -139,7 +139,7 @@ test("--- TODO --- bad idea nested paths", function() { shouldCompileTo(string, hash, "world world world ", "Same context (.) is ignored in paths"); }); -test("that current context path ({{.}}) doesn't hit fallback", function() { +test("that current context path ({{.}}) doesn't hit helpers", function() { shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: "); }); @@ -231,16 +231,16 @@ test("block with complex lookup", function() { test("helper with complex lookup", function() { var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}" var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]}; - var fallback = {link: function(prefix) { + var helpers = {link: function(prefix) { return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>" }}; - shouldCompileTo(string, [hash, fallback], "<a href='/root/goodbye'>Goodbye</a>") + shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>") }); test("helper block with complex lookup expression", function() { var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}" var hash = {name: "Alan"}; - var fallback = {goodbyes: function(fn) { + var helpers = {goodbyes: function(fn) { var out = ""; var byes = ["Goodbye", "goodbye", "GOODBYE"]; for (var i = 0,j = byes.length; i < j; i++) { @@ -248,16 +248,16 @@ test("helper block with complex lookup expression", function() { } return out; }}; - shouldCompileTo(string, [hash, fallback], "Goodbye Alan! goodbye Alan! GOODBYE Alan! "); + shouldCompileTo(string, [hash, helpers], "Goodbye Alan! goodbye Alan! GOODBYE Alan! "); }); test("helper with complex lookup and nested template", function() { var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}"; var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]}; - var fallback = {link: function (prefix, fn) { + var helpers = {link: function (prefix, fn) { return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>"; }}; - shouldCompileTo(string, [hash, fallback], "<a href='/root/goodbye'>Goodbye</a>") + shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>") }); test("block with deep nested complex lookup", function() { @@ -373,21 +373,21 @@ test("block helper inverted sections", function() { shouldCompileTo(messageString, rootMessage, "<p>Nobody's here</p>", "the context of an inverse is the parent of the block"); }); -module("fallback hash"); +module("helpers hash"); -test("providing a fallback hash", function() { +test("providing a helpers hash", function() { shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!", - "Fallback hash is available"); + "helpers hash is available"); shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}], - "Goodbye cruel world!", "Fallback hash is available inside other blocks"); + "Goodbye cruel world!", "helpers hash is available inside other blocks"); }); test("in cases of conflict, the explicit hash wins", function() { }); -test("the fallback hash is available is nested contexts", function() { +test("the helpers hash is available is nested contexts", function() { }); @@ -434,10 +434,10 @@ test("GH-14: a partial preceding a selector", function() { module("String literal parameters"); test("simple literals work", function() { - var string = 'Message: {{hello "world"}}'; + var string = 'Message: {{hello "world" 12}}'; var hash = {}; - var fallback = {hello: function(param) { return "Hello " + param; }} - shouldCompileTo(string, [hash, fallback], "Message: Hello world", "template with a simple String literal"); + var helpers = {hello: function(param, times) { return "Hello " + param + " " + times + " times"; }} + shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times", "template with a simple String literal"); }); test("using a quote in the middle of a parameter raises an error", function() { @@ -450,15 +450,15 @@ test("using a quote in the middle of a parameter raises an error", function() { test("escaping a String is possible", function(){ var string = 'Message: {{{hello "\\"world\\""}}}'; var hash = {} - var fallback = {hello: function(param) { return "Hello " + param; }} - shouldCompileTo(string, [hash, fallback], "Message: Hello \"world\"", "template with an escaped String literal"); + var helpers = {hello: function(param) { return "Hello " + param; }} + shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal"); }); test("it works with ' marks", function() { var string = 'Message: {{{hello "Alan\'s world"}}}'; var hash = {} - var fallback = {hello: function(param) { return "Hello " + param; }} - shouldCompileTo(string, [hash, fallback], "Message: Hello Alan's world", "template with a ' mark"); + var helpers = {hello: function(param) { return "Hello " + param; }} + shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark"); }); module("multiple parameters"); @@ -466,17 +466,17 @@ module("multiple parameters"); test("simple multi-params work", function() { var string = 'Message: {{goodbye cruel world}}'; var hash = {cruel: "cruel", world: "world"} - var fallback = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }} - shouldCompileTo(string, [hash, fallback], "Message: Goodbye cruel world", "regular helpers with multiple params"); + var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }} + shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params"); }); test("block multi-params work", function() { var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}'; var hash = {cruel: "cruel", world: "world"} - var fallback = {goodbye: function(cruel, world, fn) { + var helpers = {goodbye: function(cruel, world, fn) { return fn({greeting: "Goodbye", adj: cruel, noun: world}); }} - shouldCompileTo(string, [hash, fallback], "Message: Goodbye cruel world", "block helpers with multiple params"); + shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params"); }) module("safestring"); @@ -681,31 +681,31 @@ test("helpers take precedence over same-named context properties", function() { }); test("helpers can take an optional hash", function() { - var template = Handlebars.compile('{{goodbye cruel="CRUEL" world="WORLD"}}'); + var template = Handlebars.compile('{{goodbye cruel="CRUEL" world="WORLD" times=12}}'); var helpers = { goodbye: function(options) { - return "GOODBYE " + options.hash.cruel + " " + options.hash.world; + return "GOODBYE " + options.hash.cruel + " " + options.hash.world + " " + options.hash.times + " TIMES"; } }; var context = {}; var result = template(context, {helpers: helpers}); - equals(result, "GOODBYE CRUEL WORLD"); + equals(result, "GOODBYE CRUEL WORLD 12 TIMES"); }); test("block helpers can take an optional hash", function() { - var template = Handlebars.compile('{{#goodbye cruel="CRUEL"}}world{{/goodbye}}'); + var template = Handlebars.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'); var helpers = { goodbye: function(options) { - return "GOODBYE " + options.hash.cruel + " " + options.fn(this); + return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES"; } }; var result = template({}, {helpers: helpers}); - equals(result, "GOODBYE CRUEL world"); + equals(result, "GOODBYE CRUEL world 12 TIMES"); }); test("arguments to helpers can be retrieved from options hash in string form", function() { diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index 286c244..d98ee3d 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -171,6 +171,12 @@ describe "Tokenizer" do result[2].should be_token("STRING", %{bar"baz}) end + it "tokenizes numbers" do + result = tokenize(%|{{ foo 1 }}|) + result.should match_tokens(%w(OPEN ID INTEGER CLOSE)) + result[2].should be_token("INTEGER", "1") + end + it "tokenizes hash arguments" do result = tokenize("{{ foo bar=baz }}") result.should match_tokens %w(OPEN ID ID EQUALS ID CLOSE) @@ -178,6 +184,9 @@ describe "Tokenizer" do result = tokenize("{{ foo bar baz=bat }}") result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE) + result = tokenize("{{ foo bar baz=1 }}") + result.should match_tokens %w(OPEN ID ID ID EQUALS INTEGER CLOSE) + result = tokenize("{{ foo bar\n baz=bat }}") result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE) diff --git a/src/handlebars.l b/src/handlebars.l index 83014c7..895974a 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -24,6 +24,7 @@ <mu>"}}}" { this.begin("INITIAL"); return 'CLOSE'; } <mu>"}}" { this.begin("INITIAL"); return 'CLOSE'; } <mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; } +<mu>[0-9]+/[}\s] { return 'INTEGER' } <mu>[a-zA-Z0-9_$-]+/[=}\s/.] { return 'ID'; } <mu>. { return 'INVALID'; } diff --git a/src/handlebars.yy b/src/handlebars.yy index f119df2..02f8e21 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -68,6 +68,7 @@ params param : path { $$ = $1 } | STRING { $$ = new yy.StringNode($1) } + | INTEGER { $$ = new yy.IntegerNode($1) } ; hash @@ -82,6 +83,7 @@ hashSegments hashSegment : ID EQUALS path { $$ = [$1, $3] } | ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)] } + | ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)] } ; path |