diff options
-rw-r--r-- | lib/handlebars/compiler/ast.js | 50 | ||||
-rw-r--r-- | lib/handlebars/compiler/base.js | 11 | ||||
-rw-r--r-- | lib/handlebars/compiler/helpers.js | 41 | ||||
-rw-r--r-- | spec/ast.js | 47 | ||||
-rw-r--r-- | spec/parser.js | 4 | ||||
-rw-r--r-- | spec/tokenizer.js | 8 | ||||
-rw-r--r-- | src/handlebars.l | 2 | ||||
-rw-r--r-- | src/handlebars.yy | 62 |
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 |