Skip to content

Commit eee2c4d

Browse files
committed
Refactor blocks, programs and inverses
1 parent 271106d commit eee2c4d

File tree

8 files changed

+103
-122
lines changed

8 files changed

+103
-122
lines changed

lib/handlebars/compiler/ast.js

+6-44
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Exception from "../exception";
22

3-
function LocationInfo(locInfo){
3+
function LocationInfo(locInfo) {
44
locInfo = locInfo || {};
55
this.firstLine = locInfo.first_line;
66
this.firstColumn = locInfo.first_column;
@@ -9,38 +9,11 @@ function LocationInfo(locInfo){
99
}
1010

1111
var AST = {
12-
ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
13-
var inverseLocationInfo, firstInverseNode;
14-
if (arguments.length === 3) {
15-
locInfo = inverse;
16-
inverse = null;
17-
} else if (arguments.length === 2) {
18-
locInfo = inverseStrip;
19-
inverseStrip = null;
20-
}
21-
12+
ProgramNode: function(statements, strip, locInfo) {
2213
LocationInfo.call(this, locInfo);
2314
this.type = "program";
2415
this.statements = statements;
25-
this.strip = {};
26-
27-
if(inverse) {
28-
firstInverseNode = inverse[0];
29-
if (firstInverseNode) {
30-
inverseLocationInfo = {
31-
first_line: firstInverseNode.firstLine,
32-
last_line: firstInverseNode.lastLine,
33-
last_column: firstInverseNode.lastColumn,
34-
first_column: firstInverseNode.firstColumn
35-
};
36-
this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
37-
} else {
38-
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
39-
}
40-
this.strip.right = inverseStrip.left;
41-
} else if (inverseStrip) {
42-
this.strip.left = inverseStrip.right;
43-
}
16+
this.strip = strip;
4417
},
4518

4619
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
@@ -106,25 +79,14 @@ var AST = {
10679
this.strip = strip;
10780
},
10881

109-
BlockNode: function(mustache, program, inverse, close, locInfo) {
82+
BlockNode: function(mustache, program, inverse, strip, locInfo) {
11083
LocationInfo.call(this, locInfo);
11184

112-
if(mustache.sexpr.id.original !== close.path.original) {
113-
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
114-
}
115-
11685
this.type = 'block';
11786
this.mustache = mustache;
11887
this.program = program;
11988
this.inverse = inverse;
120-
121-
this.strip = {
122-
left: mustache.strip.left,
123-
right: close.strip.right
124-
};
125-
126-
(program || inverse).strip.left = mustache.strip.right;
127-
(inverse || program).strip.right = close.strip.left;
89+
this.strip = strip;
12890

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

143105
this.type = 'block';
144106
this.mustache = mustache;
145-
this.program = new AST.ProgramNode([content], locInfo);
107+
this.program = new AST.ProgramNode([content], {}, locInfo);
146108
},
147109

148110
ContentNode: function(string, locInfo) {

lib/handlebars/compiler/base.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import parser from "./parser";
22
import AST from "./ast";
3+
import { stripFlags, prepareBlock } from "./helpers";
34

45
export { parser };
56

67
export function parse(input) {
78
// Just return if an already-compile AST was passed in.
8-
if(input.constructor === AST.ProgramNode) { return input; }
9+
if (input.constructor === AST.ProgramNode) { return input; }
10+
11+
for (var key in AST) {
12+
parser.yy[key] = AST[key];
13+
}
14+
15+
parser.yy.stripFlags = stripFlags;
16+
parser.yy.prepareBlock = prepareBlock;
917

10-
parser.yy = AST;
1118
return parser.parse(input);
1219
}

lib/handlebars/compiler/helpers.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Exception from "../exception";
2+
import AST from "./ast";
3+
4+
export function stripFlags(open, close) {
5+
return {
6+
left: open.charAt(2) === '~',
7+
right: close.charAt(close.length-3) === '~'
8+
};
9+
}
10+
11+
export function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) {
12+
if (mustache.sexpr.id.original !== close.path.original) {
13+
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, mustache);
14+
}
15+
16+
var inverse, strip;
17+
18+
strip = {
19+
left: mustache.strip.left,
20+
right: close.strip.right
21+
};
22+
23+
if (inverseAndProgram) {
24+
inverse = inverseAndProgram.program;
25+
var inverseStrip = inverseAndProgram.strip;
26+
27+
program.strip.left = mustache.strip.right;
28+
program.strip.right = inverseStrip.left;
29+
inverse.strip.left = inverseStrip.right;
30+
inverse.strip.right = close.strip.left;
31+
} else {
32+
program.strip.left = mustache.strip.right;
33+
program.strip.right = close.strip.left;
34+
}
35+
36+
if (inverted) {
37+
return new AST.BlockNode(mustache, inverse, program, strip, locInfo);
38+
} else {
39+
return new AST.BlockNode(mustache, program, inverse, strip, locInfo);
40+
}
41+
}

spec/ast.js

+13-34
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,9 @@ describe('ast', function() {
6868
});
6969
});
7070
describe('BlockNode', function() {
71-
it('should throw on mustache mismatch (old sexpr-less version)', function() {
72-
shouldThrow(function() {
73-
var mustacheNode = new handlebarsEnv.AST.MustacheNode([{ original: 'foo'}], null, '{{', {});
74-
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
75-
}, Handlebars.Exception, "foo doesn't match bar");
76-
});
7771
it('should throw on mustache mismatch', function() {
7872
shouldThrow(function() {
79-
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
80-
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
81-
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}}, {first_line: 2, first_column: 2});
73+
handlebarsEnv.parse("\n {{#foo}}{{/bar}}")
8274
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
8375
});
8476

@@ -197,32 +189,12 @@ describe('ast', function() {
197189
testLocationInfoStorage(pn);
198190
});
199191
});
192+
200193
describe("ProgramNode", function(){
201194

202-
describe("storing location info", function(){
203-
it("stores when `inverse` argument isn't passed", function(){
204-
var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
205-
testLocationInfoStorage(pn);
206-
});
207-
208-
it("stores when `inverse` or `stripInverse` arguments passed", function(){
209-
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
210-
testLocationInfoStorage(pn);
211-
212-
var clone = {
213-
strip: {},
214-
firstLine: 0,
215-
lastLine: 0,
216-
firstColumn: 0,
217-
lastColumn: 0
218-
};
219-
pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
220-
testLocationInfoStorage(pn);
221-
222-
// Assert that the newly created ProgramNode has the same location
223-
// information as the inverse
224-
testLocationInfoStorage(pn.inverse);
225-
});
195+
it("storing location info", function(){
196+
var pn = new handlebarsEnv.AST.ProgramNode([], {}, LOCATION_INFO);
197+
testLocationInfoStorage(pn);
226198
});
227199
});
228200

@@ -265,11 +237,18 @@ describe('ast', function() {
265237
testColumns(blockHelperNode, 3, 7, 8, 23);
266238
});
267239

240+
it('correctly records the line numbers the program of a block helper', function(){
241+
var blockHelperNode = statements[5],
242+
program = blockHelperNode.program;
243+
244+
testColumns(program, 3, 5, 8, 5);
245+
});
246+
268247
it('correctly records the line numbers of an inverse of a block helper', function(){
269248
var blockHelperNode = statements[5],
270249
inverse = blockHelperNode.inverse;
271250

272-
testColumns(inverse, 5, 6, 13, 0);
251+
testColumns(inverse, 5, 7, 5, 0);
273252
});
274253
});
275254
});

spec/parser.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@ describe('parser', function() {
121121
});
122122

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

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

131131
it('parses non-empty blocks with empty inverse section', function() {

spec/tokenizer.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,10 @@ describe('Tokenizer', function() {
237237
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
238238
});
239239

240-
it('tokenizes inverse sections as "OPEN_INVERSE CLOSE"', function() {
241-
shouldMatchTokens(tokenize("{{^}}"), ['OPEN_INVERSE', 'CLOSE']);
242-
shouldMatchTokens(tokenize("{{else}}"), ['OPEN_INVERSE', 'CLOSE']);
243-
shouldMatchTokens(tokenize("{{ else }}"), ['OPEN_INVERSE', 'CLOSE']);
240+
it('tokenizes inverse sections as "INVERSE"', function() {
241+
shouldMatchTokens(tokenize("{{^}}"), ['INVERSE']);
242+
shouldMatchTokens(tokenize("{{else}}"), ['INVERSE']);
243+
shouldMatchTokens(tokenize("{{ else }}"), ['INVERSE']);
244244
});
245245

246246
it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function() {

src/handlebars.l

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
7575
<mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
7676
<mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK';
7777
<mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
78+
<mu>"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
79+
<mu>"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
7880
<mu>"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE';
7981
<mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE';
8082
<mu>"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED';

src/handlebars.yy

+26-36
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,68 @@
22

33
%ebnf
44

5-
%{
6-
7-
function stripFlags(open, close) {
8-
return {
9-
left: open.charAt(2) === '~',
10-
right: close.charAt(0) === '~' || close.charAt(1) === '~'
11-
};
12-
}
13-
14-
%}
15-
165
%%
176

187
root
19-
: statements EOF { return new yy.ProgramNode($1, @$); }
20-
| EOF { return new yy.ProgramNode([], @$); }
8+
: program EOF { return $1; }
219
;
2210

2311
program
24-
: simpleInverse statements -> new yy.ProgramNode([], $1, $2, @$)
25-
| statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3, @$)
26-
| statements simpleInverse -> new yy.ProgramNode($1, $2, [], @$)
27-
| statements -> new yy.ProgramNode($1, @$)
28-
| simpleInverse -> new yy.ProgramNode([], @$)
29-
| "" -> new yy.ProgramNode([], @$)
30-
;
31-
32-
statements
33-
: statement -> [$1]
34-
| statements statement { $1.push($2); $$ = $1; }
12+
: statement* -> new yy.ProgramNode($1, {}, @$)
3513
;
3614

3715
statement
38-
: openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$)
39-
| openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$)
40-
| openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$)
41-
| mustache -> $1
16+
: mustache -> $1
17+
| block -> $1
18+
| rawBlock -> $1
4219
| partial -> $1
4320
| CONTENT -> new yy.ContentNode($1, @$)
4421
| COMMENT -> new yy.CommentNode($1, @$)
4522
;
4623

24+
rawBlock
25+
: openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$)
26+
;
27+
4728
openRawBlock
4829
: OPEN_RAW_BLOCK sexpr CLOSE_RAW_BLOCK -> new yy.MustacheNode($2, null, '', '', @$)
4930
;
5031

32+
block
33+
: openBlock program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$)
34+
| openInverse program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, true, @$)
35+
;
36+
5137
openBlock
52-
: OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
38+
: OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
5339
;
5440

5541
openInverse
56-
: OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
42+
: OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
43+
;
44+
45+
inverseAndProgram
46+
: INVERSE program -> { strip: yy.stripFlags($1, $1), program: $2 }
5747
;
5848

5949
closeBlock
60-
: OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: stripFlags($1, $3)}
50+
: OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: yy.stripFlags($1, $3)}
6151
;
6252

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

7060
partial
71-
: OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, stripFlags($1, $5), @$)
72-
| OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, stripFlags($1, $4), @$)
61+
: OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, yy.stripFlags($1, $5), @$)
62+
| OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, yy.stripFlags($1, $4), @$)
7363
;
7464

7565
simpleInverse
76-
: OPEN_INVERSE CLOSE -> stripFlags($1, $2)
66+
: INVERSE -> yy.stripFlags($1, $1)
7767
;
7868

7969
sexpr

0 commit comments

Comments
 (0)