Skip to content

Commit

Permalink
Refactor blocks, programs and inverses
Browse files Browse the repository at this point in the history
  • Loading branch information
mmun committed Jul 29, 2014
1 parent 271106d commit eee2c4d
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 122 deletions.
50 changes: 6 additions & 44 deletions lib/handlebars/compiler/ast.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Exception from "../exception";

function LocationInfo(locInfo){
function LocationInfo(locInfo) {
locInfo = locInfo || {};
this.firstLine = locInfo.first_line;
this.firstColumn = locInfo.first_column;
Expand All @@ -9,38 +9,11 @@ function LocationInfo(locInfo){
}

var AST = {
ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
var inverseLocationInfo, firstInverseNode;
if (arguments.length === 3) {
locInfo = inverse;
inverse = null;
} else if (arguments.length === 2) {
locInfo = inverseStrip;
inverseStrip = null;
}

ProgramNode: function(statements, strip, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "program";
this.statements = statements;
this.strip = {};

if(inverse) {
firstInverseNode = inverse[0];
if (firstInverseNode) {
inverseLocationInfo = {
first_line: firstInverseNode.firstLine,
last_line: firstInverseNode.lastLine,
last_column: firstInverseNode.lastColumn,
first_column: firstInverseNode.firstColumn
};
this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
} else {
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
}
this.strip.right = inverseStrip.left;
} else if (inverseStrip) {
this.strip.left = inverseStrip.right;
}
this.strip = strip;
},

MustacheNode: function(rawParams, hash, open, strip, locInfo) {
Expand Down Expand Up @@ -106,25 +79,14 @@ var AST = {
this.strip = strip;
},

BlockNode: function(mustache, program, inverse, close, locInfo) {
BlockNode: function(mustache, program, inverse, strip, locInfo) {
LocationInfo.call(this, locInfo);

if(mustache.sexpr.id.original !== close.path.original) {
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
}

this.type = 'block';
this.mustache = mustache;
this.program = program;
this.inverse = inverse;

this.strip = {
left: mustache.strip.left,
right: close.strip.right
};

(program || inverse).strip.left = mustache.strip.right;
(inverse || program).strip.right = close.strip.left;
this.strip = strip;

if (inverse && !program) {
this.isInverse = true;
Expand All @@ -142,7 +104,7 @@ var AST = {

this.type = 'block';
this.mustache = mustache;
this.program = new AST.ProgramNode([content], locInfo);
this.program = new AST.ProgramNode([content], {}, locInfo);
},

ContentNode: function(string, locInfo) {
Expand Down
11 changes: 9 additions & 2 deletions lib/handlebars/compiler/base.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import parser from "./parser";
import AST from "./ast";
import { stripFlags, prepareBlock } from "./helpers";

export { parser };

export function parse(input) {
// Just return if an already-compile AST was passed in.
if(input.constructor === AST.ProgramNode) { return input; }
if (input.constructor === AST.ProgramNode) { return input; }

for (var key in AST) {
parser.yy[key] = AST[key];
}

parser.yy.stripFlags = stripFlags;
parser.yy.prepareBlock = prepareBlock;

parser.yy = AST;
return parser.parse(input);
}
41 changes: 41 additions & 0 deletions lib/handlebars/compiler/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Exception from "../exception";
import AST from "./ast";

export function stripFlags(open, close) {
return {
left: open.charAt(2) === '~',
right: close.charAt(close.length-3) === '~'
};
}

export function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) {
if (mustache.sexpr.id.original !== close.path.original) {
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, mustache);
}

var inverse, strip;

strip = {
left: mustache.strip.left,
right: close.strip.right
};

if (inverseAndProgram) {
inverse = inverseAndProgram.program;
var inverseStrip = inverseAndProgram.strip;

program.strip.left = mustache.strip.right;
program.strip.right = inverseStrip.left;
inverse.strip.left = inverseStrip.right;
inverse.strip.right = close.strip.left;
} else {
program.strip.left = mustache.strip.right;
program.strip.right = close.strip.left;
}

if (inverted) {
return new AST.BlockNode(mustache, inverse, program, strip, locInfo);
} else {
return new AST.BlockNode(mustache, program, inverse, strip, locInfo);
}
}
47 changes: 13 additions & 34 deletions spec/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,9 @@ describe('ast', function() {
});
});
describe('BlockNode', function() {
it('should throw on mustache mismatch (old sexpr-less version)', function() {
shouldThrow(function() {
var mustacheNode = new handlebarsEnv.AST.MustacheNode([{ original: 'foo'}], null, '{{', {});
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
}, Handlebars.Exception, "foo doesn't match bar");
});
it('should throw on mustache mismatch', function() {
shouldThrow(function() {
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}}, {first_line: 2, first_column: 2});
handlebarsEnv.parse("\n {{#foo}}{{/bar}}")
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
});

Expand Down Expand Up @@ -197,32 +189,12 @@ describe('ast', function() {
testLocationInfoStorage(pn);
});
});

describe("ProgramNode", function(){

describe("storing location info", function(){
it("stores when `inverse` argument isn't passed", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
testLocationInfoStorage(pn);
});

it("stores when `inverse` or `stripInverse` arguments passed", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
testLocationInfoStorage(pn);

var clone = {
strip: {},
firstLine: 0,
lastLine: 0,
firstColumn: 0,
lastColumn: 0
};
pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
testLocationInfoStorage(pn);

// Assert that the newly created ProgramNode has the same location
// information as the inverse
testLocationInfoStorage(pn.inverse);
});
it("storing location info", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], {}, LOCATION_INFO);
testLocationInfoStorage(pn);
});
});

Expand Down Expand Up @@ -265,11 +237,18 @@ describe('ast', function() {
testColumns(blockHelperNode, 3, 7, 8, 23);
});

it('correctly records the line numbers the program of a block helper', function(){
var blockHelperNode = statements[5],
program = blockHelperNode.program;

testColumns(program, 3, 5, 8, 5);
});

it('correctly records the line numbers of an inverse of a block helper', function(){
var blockHelperNode = statements[5],
inverse = blockHelperNode.inverse;

testColumns(inverse, 5, 6, 13, 0);
testColumns(inverse, 5, 7, 5, 0);
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions spec/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,11 @@ describe('parser', function() {
});

it('parses empty blocks with empty inverse section', function() {
equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n");
});

it('parses empty blocks with empty inverse (else-style) section', function() {
equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n");
});

it('parses non-empty blocks with empty inverse section', function() {
Expand Down
8 changes: 4 additions & 4 deletions spec/tokenizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,10 @@ describe('Tokenizer', function() {
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
});

it('tokenizes inverse sections as "OPEN_INVERSE CLOSE"', function() {
shouldMatchTokens(tokenize("{{^}}"), ['OPEN_INVERSE', 'CLOSE']);
shouldMatchTokens(tokenize("{{else}}"), ['OPEN_INVERSE', 'CLOSE']);
shouldMatchTokens(tokenize("{{ else }}"), ['OPEN_INVERSE', 'CLOSE']);
it('tokenizes inverse sections as "INVERSE"', function() {
shouldMatchTokens(tokenize("{{^}}"), ['INVERSE']);
shouldMatchTokens(tokenize("{{else}}"), ['INVERSE']);
shouldMatchTokens(tokenize("{{ else }}"), ['INVERSE']);
});

it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function() {
Expand Down
2 changes: 2 additions & 0 deletions src/handlebars.l
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
<mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
<mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK';
<mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
<mu>"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
<mu>"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
<mu>"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE';
<mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE';
<mu>"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED';
Expand Down
62 changes: 26 additions & 36 deletions src/handlebars.yy
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,68 @@

%ebnf

%{

function stripFlags(open, close) {
return {
left: open.charAt(2) === '~',
right: close.charAt(0) === '~' || close.charAt(1) === '~'
};
}

%}

%%

root
: statements EOF { return new yy.ProgramNode($1, @$); }
| EOF { return new yy.ProgramNode([], @$); }
: program EOF { return $1; }
;

program
: simpleInverse statements -> new yy.ProgramNode([], $1, $2, @$)
| statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3, @$)
| statements simpleInverse -> new yy.ProgramNode($1, $2, [], @$)
| statements -> new yy.ProgramNode($1, @$)
| simpleInverse -> new yy.ProgramNode([], @$)
| "" -> new yy.ProgramNode([], @$)
;

statements
: statement -> [$1]
| statements statement { $1.push($2); $$ = $1; }
: statement* -> new yy.ProgramNode($1, {}, @$)
;

statement
: openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$)
| openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$)
| openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$)
| mustache -> $1
: mustache -> $1
| block -> $1
| rawBlock -> $1
| partial -> $1
| CONTENT -> new yy.ContentNode($1, @$)
| COMMENT -> new yy.CommentNode($1, @$)
;

rawBlock
: openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$)
;

openRawBlock
: OPEN_RAW_BLOCK sexpr CLOSE_RAW_BLOCK -> new yy.MustacheNode($2, null, '', '', @$)
;

block
: openBlock program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$)
| openInverse program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, true, @$)
;

openBlock
: OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
: OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
;

openInverse
: OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
: OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
;

inverseAndProgram
: INVERSE program -> { strip: yy.stripFlags($1, $1), program: $2 }
;

closeBlock
: OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: stripFlags($1, $3)}
: OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: yy.stripFlags($1, $3)}
;

mustache
// Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node.
// This also allows for handler unification as all mustache node instances can utilize the same handler
: OPEN sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
| OPEN_UNESCAPED sexpr CLOSE_UNESCAPED -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
: OPEN sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
| OPEN_UNESCAPED sexpr CLOSE_UNESCAPED -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
;

partial
: OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, stripFlags($1, $5), @$)
| OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, stripFlags($1, $4), @$)
: OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, yy.stripFlags($1, $5), @$)
| OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, yy.stripFlags($1, $4), @$)
;

simpleInverse
: OPEN_INVERSE CLOSE -> stripFlags($1, $2)
: INVERSE -> yy.stripFlags($1, $1)
;

sexpr
Expand Down

0 comments on commit eee2c4d

Please sign in to comment.