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:
kpdecker
2014-08-23 16:29:22 -05:00
9 changed files with 229 additions and 257 deletions
+6 -172
View File
@@ -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;
+8 -2
View File
@@ -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);
}
+163
View File
@@ -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;
}
}