Merge branch 'refactor-parser' of github.com:mmun/handlebars.js into mmun-refactor-parser
Conflicts: lib/handlebars/compiler/ast.js spec/ast.js src/handlebars.yy
This commit is contained in:
@@ -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,41 +9,11 @@ function LocationInfo(locInfo){
|
||||
}
|
||||
|
||||
var AST = {
|
||||
ProgramNode: function(isRoot, statements, inverseStrip, inverse, locInfo) {
|
||||
var inverseLocationInfo, firstInverseNode;
|
||||
if (arguments.length === 4) {
|
||||
locInfo = inverse;
|
||||
inverse = null;
|
||||
} else if (arguments.length === 3) {
|
||||
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(isRoot, inverse, inverseStrip, inverseLocationInfo);
|
||||
} else {
|
||||
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);
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
|
||||
@@ -111,43 +81,14 @@ var AST = {
|
||||
this.strip.inlineStandalone = true;
|
||||
},
|
||||
|
||||
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;
|
||||
|
||||
var firstChild = program || inverse,
|
||||
lastChild = inverse || program;
|
||||
|
||||
this.strip = {
|
||||
left: mustache.strip.left,
|
||||
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)
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
this.strip = strip;
|
||||
|
||||
if (inverse && !program) {
|
||||
this.isInverse = true;
|
||||
@@ -165,7 +106,7 @@ var AST = {
|
||||
|
||||
this.type = 'block';
|
||||
this.mustache = mustache;
|
||||
this.program = new AST.ProgramNode(false, [content], locInfo);
|
||||
this.program = new AST.ProgramNode([content], {}, locInfo);
|
||||
},
|
||||
|
||||
ContentNode: function(string, locInfo) {
|
||||
@@ -269,113 +210,6 @@ var AST = {
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import parser from "./parser";
|
||||
import AST from "./ast";
|
||||
module Helpers from "./helpers";
|
||||
import { extend } from "../utils";
|
||||
|
||||
export { parser };
|
||||
|
||||
var yy = {};
|
||||
extend(yy, Helpers, AST);
|
||||
|
||||
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; }
|
||||
|
||||
parser.yy = yy;
|
||||
|
||||
parser.yy = AST;
|
||||
return parser.parse(input);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import Exception from "../exception";
|
||||
|
||||
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) {
|
||||
/*jshint -W040 */
|
||||
if (mustache.sexpr.id.original !== close.path.original) {
|
||||
throw new Exception(mustache.sexpr.id.original + ' doesn\'t match ' + close.path.original, mustache);
|
||||
}
|
||||
|
||||
var inverse = inverseAndProgram && inverseAndProgram.program;
|
||||
|
||||
var strip = {
|
||||
left: mustache.strip.left,
|
||||
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(program.statements),
|
||||
closeStandalone: isPrevWhitespace((inverse || program).statements)
|
||||
};
|
||||
|
||||
if (inverse) {
|
||||
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;
|
||||
|
||||
// Find standalone else statments
|
||||
if (isPrevWhitespace(program.statements)
|
||||
&& isNextWhitespace(inverse.statements)) {
|
||||
|
||||
omitLeft(program.statements);
|
||||
omitRight(inverse.statements);
|
||||
}
|
||||
} else {
|
||||
program.strip.left = mustache.strip.right;
|
||||
program.strip.right = close.strip.left;
|
||||
}
|
||||
|
||||
if (inverted) {
|
||||
return new this.BlockNode(mustache, inverse, program, strip, locInfo);
|
||||
} else {
|
||||
return new this.BlockNode(mustache, program, inverse, strip, locInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function prepareProgram(statements, isRoot) {
|
||||
for (var i = 0, l = statements.length; i < l; i++) {
|
||||
var current = statements[i],
|
||||
strip = current.strip;
|
||||
|
||||
if (!strip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var _isPrevWhitespace = isPrevWhitespace(statements, i, isRoot, current.type === 'partial'),
|
||||
_isNextWhitespace = isNextWhitespace(statements, i, isRoot),
|
||||
|
||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
||||
inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
||||
|
||||
if (inlineStandalone) {
|
||||
omitRight(statements, i);
|
||||
|
||||
if (omitLeft(statements, i)) {
|
||||
// If we are on a standalone node, save the indent info for partials
|
||||
if (current.type === 'partial') {
|
||||
current.indent = statements[i-1].original;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openStandalone) {
|
||||
omitRight((current.program || current.inverse).statements);
|
||||
|
||||
// Strip out the previous content node if it's whitespace only
|
||||
omitLeft(statements, i);
|
||||
}
|
||||
if (closeStandalone) {
|
||||
// Always strip the next node
|
||||
omitRight(statements, i);
|
||||
|
||||
omitLeft((current.inverse || current.program).statements);
|
||||
}
|
||||
}
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
function isPrevWhitespace(statements, i, isRoot, disallowIndent) {
|
||||
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(statements, i, isRoot) {
|
||||
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(statements, i) {
|
||||
var first = 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(statements, i) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
+13
-35
@@ -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,11 @@ 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(false, [], LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
|
||||
it("stores when `inverse` or `stripInverse` arguments passed", function(){
|
||||
var pn = new handlebarsEnv.AST.ProgramNode(false, [], {strip: {}}, undefined, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
|
||||
var clone = {
|
||||
strip: {},
|
||||
firstLine: 0,
|
||||
lastLine: 0,
|
||||
firstColumn: 0,
|
||||
lastColumn: 0
|
||||
};
|
||||
pn = new handlebarsEnv.AST.ProgramNode(false, [], {strip: {}}, [ clone ], LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
|
||||
// Assert that the newly created ProgramNode has the same location
|
||||
// information as the inverse
|
||||
testLocationInfoStorage(pn.inverse);
|
||||
});
|
||||
describe('ProgramNode', function(){
|
||||
it('storing location info', function(){
|
||||
var pn = new handlebarsEnv.AST.ProgramNode([], {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -265,11 +236,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[7],
|
||||
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[7],
|
||||
inverse = blockHelperNode.inverse;
|
||||
|
||||
testColumns(inverse, 5, 6, 13, 0);
|
||||
testColumns(inverse, 5, 7, 5, 0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ describe('compiler', function() {
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
equal(Handlebars.compile(new Handlebars.AST.ProgramNode(true, [ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');
|
||||
equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")], {}))(), 'Hello');
|
||||
});
|
||||
|
||||
it("can pass through an empty string", function() {
|
||||
@@ -60,7 +60,7 @@ describe('compiler', function() {
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
equal(/return "Hello"/.test(Handlebars.precompile(new Handlebars.AST.ProgramNode(true, [ new Handlebars.AST.ContentNode("Hello")]))), true);
|
||||
equal(/return "Hello"/.test(Handlebars.precompile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]), {})), true);
|
||||
});
|
||||
|
||||
it("can pass through an empty string", function() {
|
||||
|
||||
+6
-3
@@ -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() {
|
||||
@@ -147,6 +147,9 @@ describe('parser', function() {
|
||||
it('parses a standalone inverse section', function() {
|
||||
equals(ast_for("{{^foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
|
||||
});
|
||||
it('parses a standalone inverse section', function() {
|
||||
equals(ast_for("{{else foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
|
||||
});
|
||||
|
||||
it("raises if there's a Parse error", function() {
|
||||
shouldThrow(function() {
|
||||
@@ -184,7 +187,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(false, [ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
|
||||
equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+4
-4
@@ -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() {
|
||||
|
||||
@@ -70,6 +70,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';
|
||||
|
||||
+25
-39
@@ -2,78 +2,64 @@
|
||||
|
||||
%ebnf
|
||||
|
||||
%{
|
||||
|
||||
function stripFlags(open, close) {
|
||||
return {
|
||||
left: open.charAt(2) === '~',
|
||||
right: close.charAt(0) === '~' || close.charAt(1) === '~'
|
||||
};
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%%
|
||||
|
||||
root
|
||||
: statements EOF { return new yy.ProgramNode(true, $1, @$); }
|
||||
| EOF { return new yy.ProgramNode(true, [], @$); }
|
||||
: program EOF { yy.prepareProgram($1.statements, true); return $1; }
|
||||
;
|
||||
|
||||
program
|
||||
: 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
|
||||
: statement -> [$1]
|
||||
| statements statement { $1.push($2); $$ = $1; }
|
||||
: statement* -> new yy.ProgramNode(yy.prepareProgram($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), @$)
|
||||
;
|
||||
|
||||
simpleInverse
|
||||
: OPEN_INVERSE CLOSE -> stripFlags($1, $2)
|
||||
: 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), @$)
|
||||
;
|
||||
|
||||
sexpr
|
||||
|
||||
Reference in New Issue
Block a user