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:
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user