166 lines
5.1 KiB
JavaScript
166 lines
5.1 KiB
JavaScript
'use strict';
|
|
|
|
var nodes = require('./nodes');
|
|
var lib = require('./lib');
|
|
var sym = 0;
|
|
function gensym() {
|
|
return 'hole_' + sym++;
|
|
}
|
|
|
|
// copy-on-write version of map
|
|
function mapCOW(arr, func) {
|
|
var res = null;
|
|
for (var i = 0; i < arr.length; i++) {
|
|
var item = func(arr[i]);
|
|
if (item !== arr[i]) {
|
|
if (!res) {
|
|
res = arr.slice();
|
|
}
|
|
res[i] = item;
|
|
}
|
|
}
|
|
return res || arr;
|
|
}
|
|
function walk(ast, func, depthFirst) {
|
|
if (!(ast instanceof nodes.Node)) {
|
|
return ast;
|
|
}
|
|
if (!depthFirst) {
|
|
var astT = func(ast);
|
|
if (astT && astT !== ast) {
|
|
return astT;
|
|
}
|
|
}
|
|
if (ast instanceof nodes.NodeList) {
|
|
var children = mapCOW(ast.children, function (node) {
|
|
return walk(node, func, depthFirst);
|
|
});
|
|
if (children !== ast.children) {
|
|
ast = new nodes[ast.typename](ast.lineno, ast.colno, children);
|
|
}
|
|
} else if (ast instanceof nodes.CallExtension) {
|
|
var args = walk(ast.args, func, depthFirst);
|
|
var contentArgs = mapCOW(ast.contentArgs, function (node) {
|
|
return walk(node, func, depthFirst);
|
|
});
|
|
if (args !== ast.args || contentArgs !== ast.contentArgs) {
|
|
ast = new nodes[ast.typename](ast.extName, ast.prop, args, contentArgs);
|
|
}
|
|
} else {
|
|
var props = ast.fields.map(function (field) {
|
|
return ast[field];
|
|
});
|
|
var propsT = mapCOW(props, function (prop) {
|
|
return walk(prop, func, depthFirst);
|
|
});
|
|
if (propsT !== props) {
|
|
ast = new nodes[ast.typename](ast.lineno, ast.colno);
|
|
propsT.forEach(function (prop, i) {
|
|
ast[ast.fields[i]] = prop;
|
|
});
|
|
}
|
|
}
|
|
return depthFirst ? func(ast) || ast : ast;
|
|
}
|
|
function depthWalk(ast, func) {
|
|
return walk(ast, func, true);
|
|
}
|
|
function _liftFilters(node, asyncFilters, prop) {
|
|
var children = [];
|
|
var walked = depthWalk(prop ? node[prop] : node, function (descNode) {
|
|
var symbol;
|
|
if (descNode instanceof nodes.Block) {
|
|
return descNode;
|
|
} else if (descNode instanceof nodes.Filter && lib.indexOf(asyncFilters, descNode.name.value) !== -1 || descNode instanceof nodes.CallExtensionAsync) {
|
|
symbol = new nodes.Symbol(descNode.lineno, descNode.colno, gensym());
|
|
children.push(new nodes.FilterAsync(descNode.lineno, descNode.colno, descNode.name, descNode.args, symbol));
|
|
}
|
|
return symbol;
|
|
});
|
|
if (prop) {
|
|
node[prop] = walked;
|
|
} else {
|
|
node = walked;
|
|
}
|
|
if (children.length) {
|
|
children.push(node);
|
|
return new nodes.NodeList(node.lineno, node.colno, children);
|
|
} else {
|
|
return node;
|
|
}
|
|
}
|
|
function liftFilters(ast, asyncFilters) {
|
|
return depthWalk(ast, function (node) {
|
|
if (node instanceof nodes.Output) {
|
|
return _liftFilters(node, asyncFilters);
|
|
} else if (node instanceof nodes.Set) {
|
|
return _liftFilters(node, asyncFilters, 'value');
|
|
} else if (node instanceof nodes.For) {
|
|
return _liftFilters(node, asyncFilters, 'arr');
|
|
} else if (node instanceof nodes.If) {
|
|
return _liftFilters(node, asyncFilters, 'cond');
|
|
} else if (node instanceof nodes.CallExtension) {
|
|
return _liftFilters(node, asyncFilters, 'args');
|
|
} else {
|
|
return undefined;
|
|
}
|
|
});
|
|
}
|
|
function liftSuper(ast) {
|
|
return walk(ast, function (blockNode) {
|
|
if (!(blockNode instanceof nodes.Block)) {
|
|
return;
|
|
}
|
|
var hasSuper = false;
|
|
var symbol = gensym();
|
|
blockNode.body = walk(blockNode.body, function (node) {
|
|
// eslint-disable-line consistent-return
|
|
if (node instanceof nodes.FunCall && node.name.value === 'super') {
|
|
hasSuper = true;
|
|
return new nodes.Symbol(node.lineno, node.colno, symbol);
|
|
}
|
|
});
|
|
if (hasSuper) {
|
|
blockNode.body.children.unshift(new nodes.Super(0, 0, blockNode.name, new nodes.Symbol(0, 0, symbol)));
|
|
}
|
|
});
|
|
}
|
|
function convertStatements(ast) {
|
|
return depthWalk(ast, function (node) {
|
|
if (!(node instanceof nodes.If) && !(node instanceof nodes.For)) {
|
|
return undefined;
|
|
}
|
|
var async = false;
|
|
walk(node, function (child) {
|
|
if (child instanceof nodes.FilterAsync || child instanceof nodes.IfAsync || child instanceof nodes.AsyncEach || child instanceof nodes.AsyncAll || child instanceof nodes.CallExtensionAsync) {
|
|
async = true;
|
|
// Stop iterating by returning the node
|
|
return child;
|
|
}
|
|
return undefined;
|
|
});
|
|
if (async) {
|
|
if (node instanceof nodes.If) {
|
|
return new nodes.IfAsync(node.lineno, node.colno, node.cond, node.body, node.else_);
|
|
} else if (node instanceof nodes.For && !(node instanceof nodes.AsyncAll)) {
|
|
return new nodes.AsyncEach(node.lineno, node.colno, node.arr, node.name, node.body, node.else_);
|
|
}
|
|
}
|
|
return undefined;
|
|
});
|
|
}
|
|
function cps(ast, asyncFilters) {
|
|
return convertStatements(liftSuper(liftFilters(ast, asyncFilters)));
|
|
}
|
|
function transform(ast, asyncFilters) {
|
|
return cps(ast, asyncFilters || []);
|
|
}
|
|
|
|
// var parser = require('./parser');
|
|
// var src = 'hello {% foo %}{% endfoo %} end';
|
|
// var ast = transform(parser.parse(src, [new FooExtension()]), ['bar']);
|
|
// nodes.printNodes(ast);
|
|
|
|
module.exports = {
|
|
transform: transform
|
|
}; |