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.

This commit is contained in:
wycats
2010-11-25 00:48:55 -08:00
commit 85fa2cb65f
8 changed files with 383 additions and 0 deletions
+6
View File
@@ -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"
+30
View File
@@ -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);
}
+53
View File
@@ -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;
}
+71
View File
@@ -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; }
+58
View File
@@ -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;
}
+92
View File
@@ -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;
}
+65
View File
@@ -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) }
;
+8
View File
@@ -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);