diff options
author | wycats <wycats@gmail.com> | 2010-11-25 00:48:55 -0800 |
---|---|---|
committer | wycats <wycats@gmail.com> | 2010-11-25 00:50:57 -0800 |
commit | 85fa2cb65f4d62d7e48d62d9f6072bf6610b0fa3 (patch) | |
tree | 477e1e7b2847b74f6c204211d8a1340a8bc42f96 | |
download | handlebars.js-85fa2cb65f4d62d7e48d62d9f6072bf6610b0fa3.zip handlebars.js-85fa2cb65f4d62d7e48d62d9f6072bf6610b0fa3.tar.gz handlebars.js-85fa2cb65f4d62d7e48d62d9f6072bf6610b0fa3.tar.bz2 |
Initial commit. Note that I'm using CommonJS modules and node purely to help me develop this. If this ends up being useful, I will likely distribute the entire package as a single JS file for easier consumption in the browser.
-rw-r--r-- | Rakefile | 6 | ||||
-rw-r--r-- | lib/handlebars.js | 30 | ||||
-rw-r--r-- | lib/handlebars/ast.js | 53 | ||||
-rw-r--r-- | lib/handlebars/handlebars_lexer.js | 71 | ||||
-rw-r--r-- | lib/handlebars/jison_ext.js | 58 | ||||
-rw-r--r-- | lib/handlebars/printer.js | 92 | ||||
-rw-r--r-- | src/handlebars.yy | 65 | ||||
-rw-r--r-- | test.js | 8 |
8 files changed, 383 insertions, 0 deletions
diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..6093674 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +file "lib/handlebars/parser.js" => "src/handlebars.yy" do + system "jison src/handlebars.yy" + sh "mv handlebars.js lib/handlebars/parser.js" +end + +task :default => "lib/handlebars/parser.js" diff --git a/lib/handlebars.js b/lib/handlebars.js new file mode 100644 index 0000000..bb40f5f --- /dev/null +++ b/lib/handlebars.js @@ -0,0 +1,30 @@ +var ast = require("handlebars/ast"); +var Lexer = require("handlebars/handlebars_lexer").Lexer; +var printer = require("handlebars/printer"); +var parser = require("handlebars/parser").parser; + +parser.yy = { + ProgramNode: ast.ProgramNode, + MustacheNode: ast.MustacheNode, + ContentNode: ast.ContentNode, + IdNode: ast.IdNode, + StringNode: ast.StringNode, + PartialNode: ast.PartialNode, + CommentNode: ast.CommentNode, + BlockNode: ast.BlockNode, + inspect: function(obj) { + var sys = require("sys"); + sys.print(sys.inspect(obj) + "\n"); + } +} + +parser.lexer = new Lexer; + +exports.parse = function(string) { + return parser.parse(string); +}; + +exports.print = function(ast) { + var out = new PrintVisitor().accept(ast); + require("sys").print(out); +} diff --git a/lib/handlebars/ast.js b/lib/handlebars/ast.js new file mode 100644 index 0000000..d37d378 --- /dev/null +++ b/lib/handlebars/ast.js @@ -0,0 +1,53 @@ +var ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + this.inverse = inverse; +}; + +var MustacheNode = function(params) { + this.type = "mustache"; + this.id = params[0]; + this.params = params.slice(1); +}; + +var PartialNode = function(id) { + this.type = "partial"; + this.id = id; +}; + +var BlockNode = function(mustache, program) { + this.type = "block"; + this.mustache = mustache; + this.program = program; +}; + +var ContentNode = function(string) { + this.type = "content"; + this.string = string; +} + +var IdNode = function(id) { + this.type = "ID" + this.id = id; +} + +StringNode = function(string) { + this.type = "STRING"; + this.string = string; +} + +CommentNode = function(comment) { + this.type = "comment"; + this.comment = comment; +} + +if(exports) { + exports.ProgramNode = ProgramNode; + exports.MustacheNode = MustacheNode; + exports.ContentNode = ContentNode; + exports.IdNode = IdNode; + exports.StringNode = StringNode; + exports.PartialNode = PartialNode; + exports.CommentNode = CommentNode; + exports.BlockNode = BlockNode; +} diff --git a/lib/handlebars/handlebars_lexer.js b/lib/handlebars/handlebars_lexer.js new file mode 100644 index 0000000..f92fc17 --- /dev/null +++ b/lib/handlebars/handlebars_lexer.js @@ -0,0 +1,71 @@ +if(require) { + var Lexer = require("handlebars/jison_ext").Lexer +} + +var HandlebarsLexer = function() { + this.state = "CONTENT"; +}; +HandlebarsLexer.prototype = new Lexer; + +HandlebarsLexer.prototype.lex = function() { + if(this.input === "") return; + + this.setupLex(); + + var lookahead = this.peek(2); + var result = ''; + + if(this.state == "MUSTACHE") { + // chomp optional whitespace + while(this.peek() === " ") { this.readchar(); } + + if(this.peek(2) === "}}") { + this.state = "CONTENT" + this.getchar(2); + + if(this.peek() == "}") this.getchar(); + return "CLOSE"; + } else if(this.peek() === '"') { + this.getchar(); + while(this.peek() !== '"') { this.getchar() } + this.getchar(); + return "STRING"; + } else { + while(this.peek().match(/[A-Za-z]/)) { this.getchar() } + return "ID" + } + } else if(lookahead == "{{") { + this.state = "MUSTACHE"; + this.getchar(2); + + var peek = this.peek(); + + if(peek === ">") { + this.getchar(); + return "OPEN_PARTIAL"; + } else if(peek === "#") { + this.getchar(); + return "OPEN_BLOCK"; + } else if(peek === "/") { + this.getchar(); + return "OPEN_ENDBLOCK"; + } else if(peek === "^") { + this.getchar(); + return "OPEN_INVERSE" + } else if(peek === "!") { + this.getchar(); + this.setupLex(); + while(this.peek(2) !== "}}") { this.getchar(); }; + this.readchar(2); + this.state = "CONTENT" + return "COMMENT"; + } else { + return "OPEN"; + } + } else { + while(this.peek(2) !== "{{" && this.peek(2) !== "") { result = result + this.getchar(); } + return "CONTENT" + } +}; + +if(exports) { exports.Lexer = HandlebarsLexer; } diff --git a/lib/handlebars/jison_ext.js b/lib/handlebars/jison_ext.js new file mode 100644 index 0000000..0608034 --- /dev/null +++ b/lib/handlebars/jison_ext.js @@ -0,0 +1,58 @@ +var Lexer = function() {}; + +Lexer.prototype = { + setInput: function(input) { + this.input = input; + this.yylineno = 0; + }, + + setupLex: function() { + this.yyleng = 0; + this.yytext = ''; + }, + + getchar: function(n) { + n = n || 1; + var char = ""; + + for(var i=0; i<n; i++) { + char += this.input[0]; + this.yytext += this.input[0]; + this.yyleng++; + + if(char === "\n") this.yylineno++; + + this.input = this.input.slice(1); + } + return char; + }, + + readchar: function(n) { + n = n || 1; + var char; + + for(var i=0; i<n; i++) { + char = this.input[i]; + if(char === "\n") this.yylineno++; + } + + this.input = this.input.slice(n); + }, + + peek: function(n) { + return this.input.slice(0, n || 1); + } +}; + +var Visitor = function() {}; + +Visitor.prototype = { + accept: function(object) { + return this[object.type](object); + } +} + +if(exports) { + exports.Lexer = Lexer; + exports.Visitor = Visitor; +} diff --git a/lib/handlebars/printer.js b/lib/handlebars/printer.js new file mode 100644 index 0000000..542cfcf --- /dev/null +++ b/lib/handlebars/printer.js @@ -0,0 +1,92 @@ +if(exports) { var Visitor = require("handlebars/jison_ext").Visitor } + +PrintVisitor = function() { this.padding = 0; }; +PrintVisitor.prototype = new Visitor; + +PrintVisitor.prototype.pad = function(string, newline) { + var out = ""; + + for(var i=0,l=this.padding; i<l; i++) { + out = out + " "; + } + + out = out + string; + + if(newline !== false) { out = out + "\n" } + return out; +}; + +PrintVisitor.prototype.program = function(program) { + var out = this.pad("PROGRAM:"), + statements = program.statements, + inverse = program.inverse; + + this.padding++; + + for(var i=0, l=statements.length; i<l; i++) { + out = out + this.accept(statements[i]); + } + + this.padding--; + + if(inverse) { + out = out + this.pad("{{^}}"); + + this.padding++; + + for(var i=0, l=inverse.length; i<l; i++) { + out = out + this.accept(inverse[i]); + } + } + + this.padding--; + + return out; +}; + +PrintVisitor.prototype.block = function(block) { + var out = ""; + + out = out + this.pad("BLOCK:"); + this.padding++; + out = out + this.accept(block.mustache); + out = out + this.accept(block.program); + this.padding--; + + return out; +}; + +PrintVisitor.prototype.mustache = function(mustache) { + var params = mustache.params, paramStrings = []; + + for(var i=0, l=params.length; i<l; i++) { + paramStrings.push(this.accept(params[i])); + } + + var params = "[" + paramStrings.join(", ") + "]"; + return this.pad("{{ " + this.accept(mustache.id) + " " + params + "}}"); +}; + +PrintVisitor.prototype.partial = function(partial) { + return this.pad("{{> " + this.accept(partial.id) + " }}"); +}; + +PrintVisitor.prototype.STRING = function(string) { + return string.string; +}; + +PrintVisitor.prototype.ID = function(id) { + return "ID:" + id.id; +}; + +PrintVisitor.prototype.content = function(content) { + return this.pad("CONTENT[ '" + content.string + "' ]"); +}; + +PrintVisitor.prototype.comment = function(comment) { + return this.pad("{{! '" + comment.comment + "' }}"); +} + +if(exports) { + exports.PrintVisitor = PrintVisitor; +} diff --git a/src/handlebars.yy b/src/handlebars.yy new file mode 100644 index 0000000..e1ef1a7 --- /dev/null +++ b/src/handlebars.yy @@ -0,0 +1,65 @@ +%start root + +%% + +root + : program { return $1 } + ; + +program + : statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3) } + | statements { $$ = new yy.ProgramNode($1) } + ; + +statements + : statement { $$ = [$1] } + | statements statement { $1.push($2); $$ = $1 } + ; + +statement + : openBlock program closeBlock { $$ = new yy.BlockNode($1, $2) } + | mustache { $$ = $1 } + | partial { $$ = $1 } + | CONTENT { $$ = new yy.ContentNode($1) } + | COMMENT { $$ = new yy.CommentNode($1) } + ; + +openBlock + : OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2) } + ; + +closeBlock + : OPEN_ENDBLOCK id CLOSE { } + ; + +mustache + : OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2) } + ; + +partial + : OPEN_PARTIAL id CLOSE { $$ = new yy.PartialNode($2) } + ; + +simpleInverse + : OPEN_INVERSE CLOSE { } + ; + +inMustache + : id params { $$ = [$1].concat($2) } + | id { $$ = [$1] } + ; + +params + : params param { $1.push($2); $$ = $1; } + | param { $$ = [$1] } + ; + +param + : id { $$ = $1 } + | STRING { $$ = new yy.StringNode($1) } + ; + +id + : ID { $$ = new yy.IdNode($1) } + ; + @@ -0,0 +1,8 @@ +require.paths.push(__dirname + "/lib"); + +var Handlebars = require("handlebars"); + +var string = "foo {{ bar baz \"baz\" }}baz{{! foo bar baz }}{{#foo}} bar {{^}} baz {{/foo}}{{> partial }}{{# bar }}part1 {{^}} part2{{> foo }}{{/bar}}zomg" + +var ast = Handlebars.parse(string); +Handlebars.print(ast); |