package org.basex.query;
import static org.basex.query.QueryError.*;
import java.util.*;
import org.basex.query.func.*;
import org.basex.query.scope.*;
import org.basex.query.util.*;
import org.basex.query.value.item.*;
import org.basex.query.var.*;
import org.basex.util.*;
import org.basex.util.list.*;
/**
* This class compiles all components of the query that are needed in an order that
* maximizes the amount of inlining possible.
*
* @author BaseX Team 2005-17, BSD License
* @author Leo Woerteler
*/
final class QueryCompiler {
/** Number of scopes from which on linear search is replaced by a hash map. */
private static final int MAP_THRESHOLD = 16;
/** Compilation context. */
private final CompileContext cc;
/** Result list. */
private final ArrayList<Scope[]> result = new ArrayList<>();
/** Node stack. */
private final IntList stack = new IntList();
/** Index and lowlink list. */
private final IntList list = new IntList();
/** Counter for the next free index. */
private int next;
/** Adjacency list. */
private final ArrayList<int[]> adjacent = new ArrayList<>();
/** Declaration list. */
private final ArrayList<Scope> scopes = new ArrayList<>();
/** Declaration list. */
private IdentityHashMap<Scope, Integer> ids;
/**
* Constructor.
* @param cc compilation context
* @param root root expression
*/
private QueryCompiler(final CompileContext cc, final Scope root) {
this.cc = cc;
add(root);
}
/**
* Gathers all declarations (functions and static variables) used by the given main module.
* @param main the main module to start from
* @return list of all declarations that the main module uses
*/
public static List<StaticDecl> usedDecls(final MainModule main) {
final List<StaticDecl> scopes = new ArrayList<>();
final IdentityHashMap<Scope, Object> map = new IdentityHashMap<>();
main.visit(new ASTVisitor() {
@Override
public boolean staticVar(final StaticVar var) {
if(map.put(var, var) == null) {
var.visit(this);
scopes.add(var);
}
return true;
}
@Override
public boolean staticFuncCall(final StaticFuncCall call) {
final StaticFunc f = call.func();
if(map.put(f, f) == null) {
f.visit(this);
scopes.add(f);
}
return true;
}
@Override
public boolean inlineFunc(final Scope sub) {
if(map.put(sub, sub) == null) sub.visit(this);
return true;
}
@Override
public boolean funcItem(final FuncItem func) {
if(map.put(func, func) == null) func.visit(this);
return true;
}
});
return scopes;
}
/**
* Compiles all necessary parts of this query.
* @param cc compilation context
* @param root root expression
* @throws QueryException compilation errors
*/
public static void compile(final CompileContext cc, final MainModule root) throws QueryException {
if(!root.compiled()) new QueryCompiler(cc, root).compile();
}
/**
* Compiles all necessary parts of this query.
* @throws QueryException compilation errors
*/
private void compile() throws QueryException {
// compile the used scopes only
for(final Scope[] comp : components(0)) circCheck(comp).comp(cc);
// check for circular variable declarations without compiling the unused scopes
for(final StaticVar v : cc.qc.vars) {
if(id(v) == -1) for(final Scope[] comp : components(add(v))) circCheck(comp);
}
}
/**
* Checks if the given component contains a static variable that depends on itself.
* @param comp component to check
* @return scope to be compiled, the others are compiled recursively
* @throws QueryException query exception
*/
private static Scope circCheck(final Scope[] comp) throws QueryException {
if(comp.length > 1) {
for(final Scope scp : comp) {
if(scp instanceof StaticVar) {
final StaticVar var = (StaticVar) scp;
throw CIRCVAR_X.get(var.info, var.id());
}
}
}
return comp[0];
}
/**
* Returns the strongly connected components of the dependency graph.
* @param p ID of the starting point
* @return the components
* @throws QueryException if a variable directly calls itself
*/
private Iterable<Scope[]> components(final int p) throws QueryException {
result.clear();
tarjan(p);
return result;
}
/**
* Algorithm of Tarjan for computing the strongly connected components of a graph.
* @param v current node
* @throws QueryException if a variable directly calls itself
*/
private void tarjan(final int v) throws QueryException {
final int ixv = 2 * v, llv = ixv + 1, idx = next++;
while(list.size() <= llv) list.add(-1);
list.set(ixv, idx);
list.set(llv, idx);
stack.push(v);
for(final int w : adjacentTo(v)) {
final int ixw = 2 * w, llw = ixw + 1;
if(list.size() <= ixw || list.get(ixw) < 0) {
// Successor w has not yet been visited; recurse on it
tarjan(w);
list.set(llv, Math.min(list.get(llv), list.get(llw)));
} else if(stack.contains(w)) {
// Successor w is in stack S and hence in the current SCC
list.set(llv, Math.min(list.get(llv), list.get(ixw)));
}
}
// If v is a root node, pop the stack and generate an SCC
if(list.get(llv) == list.get(ixv)) {
int w;
Scope[] out = null;
do {
w = stack.pop();
final Scope scp = scopes.get(w);
out = out == null ? new Scope[] { scp } : Array.add(out, scp);
} while(w != v);
result.add(out);
}
}
/**
* Gets the ID of the given scope.
* @param scp scope
* @return id if existing, {@code null} otherwise
*/
private int id(final Scope scp) {
if(ids != null) {
final Integer id = ids.get(scp);
return id == null ? -1 : id;
}
final int ss = scopes.size();
for(int s = 0; s < ss; s++) if(scopes.get(s) == scp) return s;
return -1;
}
/**
* Adds a new scope and returns its ID.
* @param scp scope to add
* @return the scope's ID
*/
private int add(final Scope scp) {
final int id = scopes.size();
if(id == MAP_THRESHOLD) {
ids = new IdentityHashMap<>();
for(final Scope s : scopes) ids.put(s, ids.size());
}
scopes.add(scp);
adjacent.add(null);
if(ids != null) ids.put(scp, id);
return id;
}
/**
* Returns the indices of all scopes called by the given one.
* @param node source node index
* @return destination node indices
* @throws QueryException if a variable directly calls itself
*/
private int[] adjacentTo(final int node) throws QueryException {
int[] adj = adjacent.get(node);
if(adj == null) {
adj = neighbors(scopes.get(node));
adjacent.set(node, adj);
}
return adj;
}
/**
* Fills in all used scopes of the given one.
* @param curr current scope
* @return IDs of all directly reachable scopes
* @throws QueryException if a variable directly calls itself
*/
private int[] neighbors(final Scope curr) throws QueryException {
final IntList adj = new IntList(0);
final boolean ok = curr.visit(new ASTVisitor() {
@Override
public boolean staticVar(final StaticVar var) { return var != curr && neighbor(var); }
@Override
public boolean staticFuncCall(final StaticFuncCall call) { return neighbor(call.func()); }
@Override
public boolean inlineFunc(final Scope sub) { return sub.visit(this); }
@Override
public boolean funcItem(final FuncItem func) { return neighbor(func); }
/**
* Adds a neighbor of the currently inspected scope.
* @param scp the neighbor
* @return {@code true} for convenience
*/
private boolean neighbor(final Scope scp) {
final int old = id(scp), id = old == -1 ? add(scp) : old;
if(old == -1 || !adj.contains(id)) adj.add(id);
return true;
}
});
if(!ok) {
final StaticVar var = (StaticVar) curr;
throw CIRCREF_X.get(var.info, "$" + var.name);
}
return adj.finish();
}
}