diff options
author | Kevin Decker <kpdecker@gmail.com> | 2014-08-13 09:16:08 -0500 |
---|---|---|
committer | Kevin Decker <kpdecker@gmail.com> | 2014-08-13 09:16:08 -0500 |
commit | 867322adf28a2babfa22c46558d388091b5756e8 (patch) | |
tree | 0acee3600b07bc15edc6e6531b756dcc32bfe96d | |
parent | 3288f525f3600c7501f521ad0e97b3126c5ed201 (diff) | |
parent | f2a2914d4fb38095105c963086425ba75121ae7b (diff) | |
download | handlebars.js-867322adf28a2babfa22c46558d388091b5756e8.zip handlebars.js-867322adf28a2babfa22c46558d388091b5756e8.tar.gz handlebars.js-867322adf28a2babfa22c46558d388091b5756e8.tar.bz2 |
Merge pull request #787 from wycats/standalone-mustaches
Remove whitespace surrounding standalone statements
-rw-r--r-- | lib/handlebars/compiler/ast.js | 153 | ||||
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 6 | ||||
-rw-r--r-- | lib/handlebars/compiler/javascript-compiler.js | 4 | ||||
-rw-r--r-- | lib/handlebars/runtime.js | 21 | ||||
-rw-r--r-- | spec/ast.js | 166 | ||||
-rw-r--r-- | spec/blocks.js | 11 | ||||
-rw-r--r-- | spec/parser.js | 2 | ||||
-rw-r--r-- | spec/partials.js | 20 | ||||
-rw-r--r-- | spec/regressions.js | 24 | ||||
-rw-r--r-- | src/handlebars.l | 4 | ||||
-rw-r--r-- | src/handlebars.yy | 16 |
11 files changed, 386 insertions, 41 deletions
diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index e388e54..ae5ea63 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -9,12 +9,12 @@ function LocationInfo(locInfo){ } var AST = { - ProgramNode: function(statements, inverseStrip, inverse, locInfo) { + ProgramNode: function(isRoot, statements, inverseStrip, inverse, locInfo) { var inverseLocationInfo, firstInverseNode; - if (arguments.length === 3) { + if (arguments.length === 4) { locInfo = inverse; inverse = null; - } else if (arguments.length === 2) { + } else if (arguments.length === 3) { locInfo = inverseStrip; inverseStrip = null; } @@ -33,14 +33,17 @@ var AST = { last_column: firstInverseNode.lastColumn, first_column: firstInverseNode.firstColumn }; - this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo); + this.inverse = new AST.ProgramNode(isRoot, inverse, inverseStrip, inverseLocationInfo); } else { - this.inverse = new AST.ProgramNode(inverse, inverseStrip); + this.inverse = new AST.ProgramNode(isRoot, inverse, inverseStrip); } this.strip.right = inverseStrip.left; } else if (inverseStrip) { this.strip.left = inverseStrip.right; } + + // Scan all children to complete the standalone analysis + checkStandalone(this, isRoot, statements); }, MustacheNode: function(rawParams, hash, open, strip, locInfo) { @@ -104,6 +107,8 @@ var AST = { this.context = context; this.hash = hash; this.strip = strip; + + this.strip.inlineStandalone = true; }, BlockNode: function(mustache, program, inverse, close, locInfo) { @@ -118,13 +123,31 @@ var AST = { this.program = program; this.inverse = inverse; + var firstChild = program || inverse, + lastChild = inverse || program; + this.strip = { left: mustache.strip.left, - right: close.strip.right + right: close.strip.right, + + // Determine the standalone candiacy. Basically flag our content as being possibly standalone + // so our parent can determine if we actually are standalone + openStandalone: isNextWhitespace(firstChild), + closeStandalone: isPrevWhitespace(lastChild) }; - (program || inverse).strip.left = mustache.strip.right; - (inverse || program).strip.right = close.strip.left; + // Calculate stripping for any else statements + firstChild.strip.left = mustache.strip.right; + lastChild.strip.right = close.strip.left; + + // Find standalone else statments + if (program && inverse + && isPrevWhitespace(program) + && isNextWhitespace(inverse)) { + + omitLeft(program); + omitRight(inverse); + } if (inverse && !program) { this.isInverse = true; @@ -142,7 +165,7 @@ var AST = { this.type = 'block'; this.mustache = mustache; - this.program = new AST.ProgramNode([content], locInfo); + this.program = new AST.ProgramNode(false, [content], locInfo); }, ContentNode: function(string, locInfo) { @@ -238,9 +261,121 @@ var AST = { LocationInfo.call(this, locInfo); this.type = "comment"; this.comment = comment; + + this.strip = { + inlineStandalone: true + }; } }; + +function checkStandalone(program, isRoot, statements) { + for (var i = 0, l = statements.length; i < l; i++) { + var current = statements[i], + strip = current.strip; + + if (!strip) { + continue; + } + + var _isPrevWhitespace = isPrevWhitespace(program, i, isRoot, current.type === 'partial'), + _isNextWhitespace = isNextWhitespace(program, i, isRoot); + strip.openStandalone = strip.openStandalone && _isPrevWhitespace; + strip.closeStandalone = strip.closeStandalone && _isNextWhitespace; + strip.inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; + + if (strip.inlineStandalone) { + omitRight(program, i); + + if (omitLeft(program, i)) { + // If we are on a standalone node, save the indent info for partials + if (current.type === 'partial') { + current.indent = statements[i-1].string; + } + } + } + if (strip.openStandalone) { + omitRight(current.program || current.inverse); + + // Strip out the previous content node if it's whitespace only + omitLeft(program, i); + } + if (strip.closeStandalone) { + // Always strip the next node + omitRight(program, i); + + omitLeft(current.inverse || current.program); + } + } +} +function isPrevWhitespace(parent, i, isRoot, disallowIndent) { + var statements = parent.statements; + if (i === undefined) { + i = statements.length; + } + + // Nodes that end with newlines are considered whitespace (but are special + // cased for strip operations) + var prev = statements[i-1]; + if (prev && /\n$/.test(prev.string)) { + return true; + } + + return checkWhitespace(isRoot, prev, statements[i-2]); +} +function isNextWhitespace(parent, i, isRoot) { + var statements = parent.statements; + if (i === undefined) { + i = -1; + } + + return checkWhitespace(isRoot, statements[i+1], statements[i+2]); +} +function checkWhitespace(isRoot, next1, next2, disallowIndent) { + if (!next1) { + return isRoot; + } else if (next1.type === 'content') { + // Check if the previous node is empty or whitespace only + if (disallowIndent ? !next1.string : /^[\s]*$/.test(next1.string)) { + if (next2) { + return next2.type === 'content' || /\n$/.test(next1.string); + } else { + return isRoot || (next1.string.indexOf('\n') >= 0); + } + } + } +} + +// Marks the node to the right of the position as omitted. +// I.e. " "{{foo}} will mark the " " node as omitted. +// +// If i is undefined, then the first child will be marked as such. +function omitRight(program, i) { + var first = program.statements[i == null ? 0 : i + 1]; + if (first) { + first.omit = true; + } +} + +// Marks the node to the left of the position as omitted. +// I.e. " "{{foo}} will mark the " " node as omitted. +// +// If i is undefined then the last child will be marked as such. +function omitLeft(program, i) { + var statements = program.statements; + if (i === undefined) { + i = statements.length; + } + + var last = statements[i-1], + prev = statements[i-2]; + + // We omit the last node if it's whitespace only and not preceeded by a non-content node. + if (last && /^[\s]*$/.test(last.string) && (!prev || prev.type === 'content')) { + return last.omit = true; + } +} + // Must be exported as an object rather than the root of the module as the jison lexer // most modify the object to operate properly. export default AST; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 3e83a62..131cb98 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -217,12 +217,14 @@ Compiler.prototype = { this.opcode('push', 'depth0'); } - this.opcode('invokePartial', partialName.name); + this.opcode('invokePartial', partialName.name, partial.indent || ''); this.opcode('append'); }, content: function(content) { - this.opcode('appendContent', content.string); + if (!content.omit) { + this.opcode('appendContent', content.string); + } }, mustache: function(mustache) { diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index bc9d3c1..c92cf58 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -596,8 +596,8 @@ JavaScriptCompiler.prototype = { // // This operation pops off a context, invokes a partial with that context, // and pushes the result of the invocation back. - invokePartial: function(name) { - var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"]; + invokePartial: function(name, indent) { + var params = [this.nameLookup('partials', name, 'partial'), "'" + indent + "'", "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"]; if (this.options.data) { params.push("data"); diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index b9fc77d..9b61afe 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -31,18 +31,31 @@ export function template(templateSpec, env) { // for external users to override these as psuedo-supported APIs. env.VM.checkRevision(templateSpec.compiler); - var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) { + var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data) { if (hash) { context = Utils.extend({}, context, hash); } var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data); - if (result != null) { return result; } - if (env.compile) { + if (result == null && env.compile) { var options = { helpers: helpers, partials: partials, data: data }; partials[name] = env.compile(partial, { data: data !== undefined }, env); - return partials[name](context, options); + result = partials[name](context, options); + } + if (result != null) { + if (indent) { + var lines = result.split('\n'); + for (var i = 0, l = lines.length; i < l; i++) { + if (!lines[i] && i + 1 === l) { + break; + } + + lines[i] = indent + lines[i]; + } + result = lines.join('\n'); + } + return result; } else { throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); } diff --git a/spec/ast.js b/spec/ast.js index 17a982c..316d4a3 100644 --- a/spec/ast.js +++ b/spec/ast.js @@ -86,7 +86,7 @@ describe('ast', function() { var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null); var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {}); var block = new handlebarsEnv.AST.BlockNode(mustacheNode, - {strip: {}}, {strip: {}}, + {statements: [], strip: {}}, {statements: [], strip: {}}, { strip: {}, path: {original: 'foo'} @@ -201,12 +201,12 @@ describe('ast', function() { describe("storing location info", function(){ it("stores when `inverse` argument isn't passed", function(){ - var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO); + var pn = new handlebarsEnv.AST.ProgramNode(false, [], LOCATION_INFO); testLocationInfoStorage(pn); }); it("stores when `inverse` or `stripInverse` arguments passed", function(){ - var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO); + var pn = new handlebarsEnv.AST.ProgramNode(false, [], {strip: {}}, undefined, LOCATION_INFO); testLocationInfoStorage(pn); var clone = { @@ -216,7 +216,7 @@ describe('ast', function() { firstColumn: 0, lastColumn: 0 }; - pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO); + pn = new handlebarsEnv.AST.ProgramNode(false, [], {strip: {}}, [ clone ], LOCATION_INFO); testLocationInfoStorage(pn); // Assert that the newly created ProgramNode has the same location @@ -251,26 +251,174 @@ describe('ast', function() { }); it('gets line numbers correct when newlines appear', function(){ - var secondContentNode = statements[2]; - testColumns(secondContentNode, 1, 2, 21, 8); + testColumns(statements[2], 1, 2, 21, 0); + testColumns(statements[3], 2, 2, 0, 8); }); it('gets MustacheNode line numbers correct across newlines', function(){ - var secondMustacheNode = statements[3]; + var secondMustacheNode = statements[4]; testColumns(secondMustacheNode, 2, 2, 8, 22); }); it('gets the block helper information correct', function(){ - var blockHelperNode = statements[5]; + var blockHelperNode = statements[7]; testColumns(blockHelperNode, 3, 7, 8, 23); }); it('correctly records the line numbers of an inverse of a block helper', function(){ - var blockHelperNode = statements[5], + var blockHelperNode = statements[7], inverse = blockHelperNode.inverse; testColumns(inverse, 5, 6, 13, 0); }); }); + + describe('standalone flags', function(){ + describe('mustache', function() { + it('does not mark mustaches as standalone', function() { + var ast = Handlebars.parse(' {{comment}} '); + equals(ast.statements[0].omit, undefined); + equals(ast.statements[2].omit, undefined); + }); + }); + describe('blocks', function() { + it('marks block mustaches as standalone', function() { + var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '), + block = ast.statements[1]; + + equals(ast.statements[0].omit, true); + + equals(block.program.statements[0].omit, true); + equals(block.program.statements[1].string, 'foo\n'); + equals(block.program.statements[2].omit, true); + + equals(block.inverse.statements[0].omit, true); + equals(block.inverse.statements[1].string, ' bar \n'); + equals(block.inverse.statements[2].omit, true); + + equals(ast.statements[2].omit, true); + }); + it('marks initial block mustaches as standalone', function() { + var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'), + block = ast.statements[0]; + + equals(block.program.statements[0].omit, true); + equals(block.program.statements[1].string, 'foo\n'); + equals(block.program.statements[2].omit, true); + }); + it('marks mustaches with children as standalone', function() { + var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'), + block = ast.statements[0]; + + equals(block.program.statements[0].omit, true); + equals(block.program.statements[1].id.original, 'foo'); + equals(block.program.statements[2].omit, undefined); + equals(block.program.statements[3].omit, true); + }); + it('marks nested block mustaches as standalone', function() { + var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'), + statements = ast.statements[0].program.statements, + block = statements[1]; + + equals(statements[0].omit, true); + + equals(block.program.statements[0].omit, true); + equals(block.program.statements[1].string, 'foo\n'); + equals(block.program.statements[2].omit, true); + + equals(block.inverse.statements[0].omit, true); + equals(block.inverse.statements[1].string, ' bar \n'); + equals(block.inverse.statements[2].omit, true); + + equals(statements[0].omit, true); + }); + it('does not mark nested block mustaches as standalone', function() { + var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'), + statements = ast.statements[0].program.statements, + block = statements[1]; + + equals(statements[0].omit, undefined); + + equals(block.program.statements[0].omit, undefined); + equals(block.program.statements[1].string, 'foo\n'); + equals(block.program.statements[2].omit, true); + + equals(block.inverse.statements[0].omit, true); + equals(block.inverse.statements[1].string, ' bar \n'); + equals(block.inverse.statements[2].omit, undefined); + + equals(statements[0].omit, undefined); + }); + it('does not mark nested initial block mustaches as standalone', function() { + var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'), + statements = ast.statements[0].program.statements, + block = statements[0]; + + equals(block.program.statements[0].omit, undefined); + equals(block.program.statements[1].string, 'foo\n'); + equals(block.program.statements[2].omit, true); + + equals(block.inverse.statements[0].omit, true); + equals(block.inverse.statements[1].string, ' bar \n'); + equals(block.inverse.statements[2].omit, undefined); + + equals(statements[0].omit, undefined); + }); + + it('marks column 0 block mustaches as standalone', function() { + var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '), + block = ast.statements[1]; + + equals(ast.statements[0].omit, undefined); + + equals(block.program.statements[0].omit, true); + equals(block.program.statements[1].string, 'foo\n'); + equals(block.program.statements[2].omit, true); + + equals(block.inverse.statements[0].omit, true); + equals(block.inverse.statements[1].string, ' bar \n'); + equals(block.inverse.statements[2].omit, true); + + equals(ast.statements[2].omit, true); + }); + }); + describe('partials', function() { + it('marks partial as standalone', function() { + var ast = Handlebars.parse('{{> partial }} '); + equals(ast.statements[1].omit, true); + }); + it('marks indented partial as standalone', function() { + var ast = Handlebars.parse(' {{> partial }} '); + equals(ast.statements[0].omit, true); + equals(ast.statements[1].indent, ' '); + equals(ast.statements[2].omit, true); + }); + it('marks those around content as not standalone', function() { + var ast = Handlebars.parse('a{{> partial }}'); + equals(ast.statements[0].omit, undefined); + + ast = Handlebars.parse('{{> partial }}a'); + equals(ast.statements[1].omit, undefined); + }); + }); + describe('comments', function() { + it('marks comment as standalone', function() { + var ast = Handlebars.parse('{{! comment }} '); + equals(ast.statements[1].omit, true); + }); + it('marks indented comment as standalone', function() { + var ast = Handlebars.parse(' {{! comment }} '); + equals(ast.statements[0].omit, true); + equals(ast.statements[2].omit, true); + }); + it('marks those around content as not standalone', function() { + var ast = Handlebars.parse('a{{! comment }}'); + equals(ast.statements[0].omit, undefined); + + ast = Handlebars.parse('{{! comment }}a'); + equals(ast.statements[1].omit, undefined); + }); + }); + }); }); diff --git a/spec/blocks.js b/spec/blocks.js index 718d316..c57a67e 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -83,4 +83,15 @@ describe('blocks', function() { "No people"); }); }); + + describe('standalone sections', function() { + it('block standalone else sections', function() { + shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'}, + 'No people\n'); + shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'}, + 'No people\n'); + shouldCompileTo('\n{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'}, + 'No people\n'); + }); + }); }); diff --git a/spec/parser.js b/spec/parser.js index ebde171..ff12cc4 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -184,7 +184,7 @@ describe('parser', function() { describe('externally compiled AST', function() { it('can pass through an already-compiled AST', function() { - equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n"); + equals(ast_for(new Handlebars.AST.ProgramNode(false, [ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n"); }); }); }); diff --git a/spec/partials.js b/spec/partials.js index 732436a..816fd1e 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -1,4 +1,4 @@ -/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */ +/*global CompilerContext, Handlebars, handlebarsEnv, shouldCompileTo, shouldCompileToWithPartials, shouldThrow */ describe('partials', function() { it("basic partials", function() { var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}"; @@ -137,4 +137,22 @@ describe('partials', function() { var partial = ""; var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: "); }); + + describe('standalone partials', function() { + it("indented partials", function() { + var string = "Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}"; + var dude = "{{name}}\n"; + var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true, + "Dudes:\n Yehuda\n Alan\n"); + }); + it("nested indented partials", function() { + var string = "Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}"; + var dude = "{{name}}\n {{> url}}"; + var url = "{{url}}!\n"; + var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, + "Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n"); + }); + }); }); diff --git a/spec/regressions.js b/spec/regressions.js index c633a21..dd4eedd 100644 --- a/spec/regressions.js +++ b/spec/regressions.js @@ -24,7 +24,19 @@ describe('Regressions', function() { }); it("bug reported by @fat where lambdas weren't being properly resolved", function() { - var string = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\n{{/hasThings}}"; + var string = '<strong>This is a slightly more complicated {{thing}}.</strong>.\n' + + '{{! Just ignore this business. }}\n' + + 'Check this out:\n' + + '{{#hasThings}}\n' + + '<ul>\n' + + '{{#things}}\n' + + '<li class={{className}}>{{word}}</li>\n' + + '{{/things}}</ul>.\n' + + '{{/hasThings}}\n' + + '{{^hasThings}}\n' + + '\n' + + '<small>Nothing to check out...</small>\n' + + '{{/hasThings}}'; var data = { thing: function() { return "blah"; @@ -39,7 +51,13 @@ describe('Regressions', function() { } }; - var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n"; + var output = '<strong>This is a slightly more complicated blah.</strong>.\n' + + 'Check this out:\n' + + '<ul>\n' + + '<li class=one>@fat</li>\n' + + '<li class=two>@dhg</li>\n' + + '<li class=three>@sayrer</li>\n' + + '</ul>.\n'; shouldCompileTo(string, data, output); }); @@ -128,7 +146,7 @@ describe('Regressions', function() { if (Handlebars.AST) { it("can pass through an already-compiled AST via compile/precompile", function() { - equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello'); + equal(Handlebars.compile(new Handlebars.AST.ProgramNode(true, [ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello'); }); it("can pass through an empty string", function() { diff --git a/src/handlebars.l b/src/handlebars.l index cafdd72..0a531d5 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -28,7 +28,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} %% -[^\x00]*?/("{{") { +[^\x00\n]*?\n?/("{{") { if(yytext.slice(-2) === "\\\\") { strip(0,1); this.begin("mu"); @@ -41,7 +41,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} if(yytext) return 'CONTENT'; } -[^\x00]+ return 'CONTENT'; +([^\x00\n]+\n?|\n) return 'CONTENT'; // marks CONTENT up to the next mustache or escaped mustache <emu>[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<<EOF>>) { diff --git a/src/handlebars.yy b/src/handlebars.yy index 51796ec..fa69f73 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -16,17 +16,17 @@ function stripFlags(open, close) { %% root - : statements EOF { return new yy.ProgramNode($1, @$); } - | EOF { return new yy.ProgramNode([], @$); } + : statements EOF { return new yy.ProgramNode(true, $1, @$); } + | EOF { return new yy.ProgramNode(true, [], @$); } ; 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([], @$) + : simpleInverse statements -> new yy.ProgramNode(false, [], $1, $2, @$) + | statements simpleInverse statements -> new yy.ProgramNode(false, $1, $2, $3, @$) + | statements simpleInverse -> new yy.ProgramNode(false, $1, $2, [], @$) + | statements -> new yy.ProgramNode(false, $1, @$) + | simpleInverse -> new yy.ProgramNode(false, [], @$) + | "" -> new yy.ProgramNode(false, [], @$) ; statements |