'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 };