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