package loop;
import loop.ast.*;
import loop.ast.script.FunctionDecl;
import java.util.ArrayList;
import java.util.List;
/**
* Takes an AST generated by the parser and strips it of unnecessary elements. Also optimizes the
* AST to be in its most compact form. This is sometimes referred to as reducing the "parse tree"
* to an AST (abstract syntax tree).
*
* Nothing done in this phase affects the semantics of the program.
*/
public class Reducer {
private Node ast;
public Reducer(Node ast) {
this.ast = ast;
}
public Node reduce() {
// Unwrap top level.
if (shouldUnwrap(ast)) {
ast = onlyChildOf(ast);
}
return reduce(ast, true);
}
private Node reduce(Node bloated, boolean tailPath) {
List<Node> reduced = new ArrayList<Node>();
List<Node> children = bloated.children();
int childrenSize = children.size();
for (int i = 0; i < childrenSize; i++) {
Node node = children.get(i);
node = reduce(node, tailPath && isTailPath(i, childrenSize, bloated));
// Unwrap any redundant wrappers.
if (shouldUnwrap(node)) {
reduced.add(onlyChildOf(node));
continue;
}
reduced.add(node);
}
// Use this special tree-detection to tell if there are any tail calls.
if (tailPath && bloated instanceof CallChain) {
Node last = children.get(childrenSize - 1);
if (last instanceof Call) {
// A "last" call is not a tail call if it is preceded by a binary operator.
((Call) last).tailCall(true);
}
}
// Reduce non-children sub-nodes (many of these in various special cases).
if (bloated instanceof IndexIntoList) {
IndexIntoList indexIntoList = (IndexIntoList) bloated;
// See if the nodes themselves merit reduction.
if (null != indexIntoList.from()) {
reduce(indexIntoList.from(), false);
if (shouldUnwrap(indexIntoList.from())) {
indexIntoList.from(onlyChildOf(indexIntoList.from()));
}
}
if (null != indexIntoList.to()) {
reduce(indexIntoList.to(), false);
if (shouldUnwrap(indexIntoList.to())) {
indexIntoList.to(onlyChildOf(indexIntoList.to()));
}
}
} else if (bloated instanceof Comprehension) {
Comprehension comprehension = (Comprehension) bloated;
reduce(comprehension.inList(), false);
if (shouldUnwrap(comprehension.inList())) {
comprehension.inList(onlyChildOf(comprehension.inList()));
}
// the filter clause is optional.
if (null != comprehension.filter()) {
reduce(comprehension.filter(), false);
if (shouldUnwrap(comprehension.filter())) {
comprehension.filter(onlyChildOf(comprehension.filter()));
}
}
} else if (bloated instanceof Call) {
Call call = (Call) bloated;
if (call.args() != null) {
reduce(call.args(), false);
}
} else if (bloated instanceof ConstructorCall) {
ConstructorCall call = (ConstructorCall) bloated;
if (call.args() != null) {
reduce(call.args(), false);
}
} else if (bloated instanceof PatternRule) {
PatternRule rule = (PatternRule) bloated;
if (null != rule.rhs)
reduce(rule.rhs, true);
} else if (bloated instanceof Guard) {
Guard guard = (Guard) bloated;
reduce(guard.expression, false);
reduce(guard.line, true);
} else if (bloated instanceof FunctionDecl) {
FunctionDecl decl = (FunctionDecl) bloated;
if (!decl.whereBlock().isEmpty()) {
for (Node node : decl.whereBlock()) {
reduce(node, true);
}
}
}
bloated.children().clear();
bloated.children().addAll(reduced);
// Run through the entire list again and compress list comprehension nodes.
reduceComprehension(reduced);
return bloated;
}
private static boolean isTailPath(int i, int childrenSize, Node node) {
// It's the tail path, if this is the last node and it is not a descendent of a binary op.
return (i == childrenSize - 1 && (!(node instanceof BinaryOp)))
// Or this is the "then" part of an if-then-else
|| (node instanceof TernaryIfExpression && i == 1);
}
private void reduceComprehension(List<Node> reduced) {
for (Node node : reduced) {
List<Node> children = node.children();
if (!children.isEmpty()) {
Node last = children.get(children.size() - 1);
if (last instanceof Comprehension) {
// Assign all preceding siblings as the "project expression"
// E.g. (x) (+ 2) (for x in ls) becomes ((x + 2) for x in ls)
List<Node> nodes = new ArrayList<Node>(children.subList(0, children.size() - 1));
((Comprehension) last).projection(nodes);
// Delete the compressed nodes from the original parent.
children.removeAll(nodes);
}
}
}
}
public static Node onlyChildOf(Node node) {
return node.children().get(0);
}
public static boolean shouldUnwrap(Node node) {
return (node instanceof CallChain || node instanceof Computation)
&& node.children().size() == 1;
}
}