summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwycats <wycats@gmail.com>2010-11-25 00:48:55 -0800
committerwycats <wycats@gmail.com>2010-11-25 00:50:57 -0800
commit85fa2cb65f4d62d7e48d62d9f6072bf6610b0fa3 (patch)
tree477e1e7b2847b74f6c204211d8a1340a8bc42f96
downloadhandlebars.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--Rakefile6
-rw-r--r--lib/handlebars.js30
-rw-r--r--lib/handlebars/ast.js53
-rw-r--r--lib/handlebars/handlebars_lexer.js71
-rw-r--r--lib/handlebars/jison_ext.js58
-rw-r--r--lib/handlebars/printer.js92
-rw-r--r--src/handlebars.yy65
-rw-r--r--test.js8
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) }
+ ;
+
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..7a2b363
--- /dev/null
+++ b/test.js
@@ -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);