summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/handlebars/compiler/ast.js50
-rw-r--r--lib/handlebars/compiler/base.js11
-rw-r--r--lib/handlebars/compiler/helpers.js41
-rw-r--r--spec/ast.js47
-rw-r--r--spec/parser.js4
-rw-r--r--spec/tokenizer.js8
-rw-r--r--src/handlebars.l2
-rw-r--r--src/handlebars.yy62
8 files changed, 103 insertions, 122 deletions
diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index e388e54..5a3057f 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -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;
@@ -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) {
@@ -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;
@@ -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) {
diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js
index 722f09a..7ab1843 100644
--- a/lib/handlebars/compiler/base.js
+++ b/lib/handlebars/compiler/base.js
@@ -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);
}
diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js
new file mode 100644
index 0000000..1a2bd26
--- /dev/null
+++ b/lib/handlebars/compiler/helpers.js
@@ -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);
+ }
+}
diff --git a/spec/ast.js b/spec/ast.js
index 17a982c..f703054 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -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");
});
@@ -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);
});
});
@@ -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);
});
});
});
diff --git a/spec/parser.js b/spec/parser.js
index ebde171..34f6a1d 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -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() {
diff --git a/spec/tokenizer.js b/spec/tokenizer.js
index 36a632b..0f0dc0b 100644
--- a/spec/tokenizer.js
+++ b/spec/tokenizer.js
@@ -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() {
diff --git a/src/handlebars.l b/src/handlebars.l
index cafdd72..006f2c7 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -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';
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 51796ec..112c1ad 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -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