1'use strict'; 2 3var nodes = require('./nodes'); 4var lib = require('./lib'); 5var sym = 0; 6function gensym() { 7 return 'hole_' + sym++; 8} 9 10// copy-on-write version of map 11function mapCOW(arr, func) { 12 var res = null; 13 for (var i = 0; i < arr.length; i++) { 14 var item = func(arr[i]); 15 if (item !== arr[i]) { 16 if (!res) { 17 res = arr.slice(); 18 } 19 res[i] = item; 20 } 21 } 22 return res || arr; 23} 24function walk(ast, func, depthFirst) { 25 if (!(ast instanceof nodes.Node)) { 26 return ast; 27 } 28 if (!depthFirst) { 29 var astT = func(ast); 30 if (astT && astT !== ast) { 31 return astT; 32 } 33 } 34 if (ast instanceof nodes.NodeList) { 35 var children = mapCOW(ast.children, function (node) { 36 return walk(node, func, depthFirst); 37 }); 38 if (children !== ast.children) { 39 ast = new nodes[ast.typename](ast.lineno, ast.colno, children); 40 } 41 } else if (ast instanceof nodes.CallExtension) { 42 var args = walk(ast.args, func, depthFirst); 43 var contentArgs = mapCOW(ast.contentArgs, function (node) { 44 return walk(node, func, depthFirst); 45 }); 46 if (args !== ast.args || contentArgs !== ast.contentArgs) { 47 ast = new nodes[ast.typename](ast.extName, ast.prop, args, contentArgs); 48 } 49 } else { 50 var props = ast.fields.map(function (field) { 51 return ast[field]; 52 }); 53 var propsT = mapCOW(props, function (prop) { 54 return walk(prop, func, depthFirst); 55 }); 56 if (propsT !== props) { 57 ast = new nodes[ast.typename](ast.lineno, ast.colno); 58 propsT.forEach(function (prop, i) { 59 ast[ast.fields[i]] = prop; 60 }); 61 } 62 } 63 return depthFirst ? func(ast) || ast : ast; 64} 65function depthWalk(ast, func) { 66 return walk(ast, func, true); 67} 68function _liftFilters(node, asyncFilters, prop) { 69 var children = []; 70 var walked = depthWalk(prop ? node[prop] : node, function (descNode) { 71 var symbol; 72 if (descNode instanceof nodes.Block) { 73 return descNode; 74 } else if (descNode instanceof nodes.Filter && lib.indexOf(asyncFilters, descNode.name.value) !== -1 || descNode instanceof nodes.CallExtensionAsync) { 75 symbol = new nodes.Symbol(descNode.lineno, descNode.colno, gensym()); 76 children.push(new nodes.FilterAsync(descNode.lineno, descNode.colno, descNode.name, descNode.args, symbol)); 77 } 78 return symbol; 79 }); 80 if (prop) { 81 node[prop] = walked; 82 } else { 83 node = walked; 84 } 85 if (children.length) { 86 children.push(node); 87 return new nodes.NodeList(node.lineno, node.colno, children); 88 } else { 89 return node; 90 } 91} 92function liftFilters(ast, asyncFilters) { 93 return depthWalk(ast, function (node) { 94 if (node instanceof nodes.Output) { 95 return _liftFilters(node, asyncFilters); 96 } else if (node instanceof nodes.Set) { 97 return _liftFilters(node, asyncFilters, 'value'); 98 } else if (node instanceof nodes.For) { 99 return _liftFilters(node, asyncFilters, 'arr'); 100 } else if (node instanceof nodes.If) { 101 return _liftFilters(node, asyncFilters, 'cond'); 102 } else if (node instanceof nodes.CallExtension) { 103 return _liftFilters(node, asyncFilters, 'args'); 104 } else { 105 return undefined; 106 } 107 }); 108} 109function liftSuper(ast) { 110 return walk(ast, function (blockNode) { 111 if (!(blockNode instanceof nodes.Block)) { 112 return; 113 } 114 var hasSuper = false; 115 var symbol = gensym(); 116 blockNode.body = walk(blockNode.body, function (node) { 117 // eslint-disable-line consistent-return 118 if (node instanceof nodes.FunCall && node.name.value === 'super') { 119 hasSuper = true; 120 return new nodes.Symbol(node.lineno, node.colno, symbol); 121 } 122 }); 123 if (hasSuper) { 124 blockNode.body.children.unshift(new nodes.Super(0, 0, blockNode.name, new nodes.Symbol(0, 0, symbol))); 125 } 126 }); 127} 128function convertStatements(ast) { 129 return depthWalk(ast, function (node) { 130 if (!(node instanceof nodes.If) && !(node instanceof nodes.For)) { 131 return undefined; 132 } 133 var async = false; 134 walk(node, function (child) { 135 if (child instanceof nodes.FilterAsync || child instanceof nodes.IfAsync || child instanceof nodes.AsyncEach || child instanceof nodes.AsyncAll || child instanceof nodes.CallExtensionAsync) { 136 async = true; 137 // Stop iterating by returning the node 138 return child; 139 } 140 return undefined; 141 }); 142 if (async) { 143 if (node instanceof nodes.If) { 144 return new nodes.IfAsync(node.lineno, node.colno, node.cond, node.body, node.else_); 145 } else if (node instanceof nodes.For && !(node instanceof nodes.AsyncAll)) { 146 return new nodes.AsyncEach(node.lineno, node.colno, node.arr, node.name, node.body, node.else_); 147 } 148 } 149 return undefined; 150 }); 151} 152function cps(ast, asyncFilters) { 153 return convertStatements(liftSuper(liftFilters(ast, asyncFilters))); 154} 155function transform(ast, asyncFilters) { 156 return cps(ast, asyncFilters || []); 157} 158 159// var parser = require('./parser'); 160// var src = 'hello {% foo %}{% endfoo %} end'; 161// var ast = transform(parser.parse(src, [new FooExtension()]), ['bar']); 162// nodes.printNodes(ast); 163 164module.exports = { 165 transform: transform 166};