package org.basex.query.item;
import java.io.IOException;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import static org.basex.query.QueryText.*;
import org.basex.query.expr.Expr;
import org.basex.query.expr.VarRef;
import org.basex.query.func.DynamicFunc;
import org.basex.query.iter.Iter;
import org.basex.query.util.Err;
import org.basex.query.util.Var;
import org.basex.query.util.VarStack;
import org.basex.util.InputInfo;
import static org.basex.util.Token.*;
import org.basex.util.Util;
/**
* Function item.
*
* @author BaseX Team 2005-12, BSD License
* @author Leo Woerteler
*/
public final class FuncItem extends FItem {
/** Variables. */
private final Var[] vars;
/** Function expression. */
private final Expr expr;
/** Function name. */
private final QNm name;
/** Optional type to cast to. */
private final SeqType cast;
/** The closure of this function item. */
private final VarStack closure = new VarStack();
/**
* Constructor.
* @param n function name
* @param arg function arguments
* @param body function body
* @param t function type
* @param cst cast flag
*/
public FuncItem(final QNm n, final Var[] arg, final Expr body,
final FuncType t, final boolean cst) {
super(t);
name = n;
vars = arg;
expr = body;
cast = cst && t.ret != null ? t.ret : null;
}
/**
* Constructor for anonymous functions.
* @param arg function arguments
* @param body function body
* @param t function type
* @param cl variables in the closure
* @param cst cast flag
*/
public FuncItem(final Var[] arg, final Expr body, final FuncType t,
final VarStack cl, final boolean cst) {
this(null, arg, body, t, cst);
if(cl != null) {
for(int i = cl.size; --i >= 0;) {
final Var v = cl.vars[i];
if(body.count(v) != 0 && !closure.contains(v)) closure.add(v.copy());
}
}
}
@Override
public int arity() {
return vars.length;
}
@Override
public QNm fName() {
return name;
}
/**
* Binds all variables to the context.
* @param ctx query context
* @param args argument values
* @throws QueryException query exception
*/
private void bindVars(final QueryContext ctx, final Value[] args)
throws QueryException {
for(int v = closure.size; --v >= 0;)
ctx.vars.add(closure.vars[v].copy());
for(int v = vars.length; --v >= 0;)
ctx.vars.add(vars[v].bind(args[v], ctx).copy());
}
@Override
public Value invValue(final QueryContext ctx, final InputInfo ii,
final Value... args) throws QueryException {
// bind variables and cache context
final VarStack cs = ctx.vars.cache(args.length);
final Value cv = ctx.value;
try {
bindVars(ctx, args);
ctx.value = null;
final Value v = ctx.value(expr);
// optionally cast return value to target type
return cast != null ? cast.promote(v, ctx, ii) : v;
} finally {
ctx.value = cv;
ctx.vars.reset(cs);
}
}
@Override
public Iter invIter(final QueryContext ctx, final InputInfo ii,
final Value... args) throws QueryException {
// [LW] make result streamable
return invValue(ctx, ii, args).iter();
}
@Override
public Item invItem(final QueryContext ctx, final InputInfo ii,
final Value... args) throws QueryException {
// bind variables and cache context
final VarStack cs = ctx.vars.cache(args.length);
final Value cv = ctx.value;
try {
bindVars(ctx, args);
ctx.value = null;
final Item it = expr.item(ctx, ii);
// optionally cast return value to target type
return cast != null ? cast.cast(it, expr, false, ctx, ii) : it;
} finally {
ctx.value = cv;
ctx.vars.reset(cs);
}
}
@Override
public String toString() {
final FuncType ft = (FuncType) type;
final StringBuilder sb = new StringBuilder(FUNCTION).append('(');
for(final Var v : vars)
sb.append(v).append(v == vars[vars.length - 1] ? "" : ", ");
return sb.append(')').append(ft.ret != null ? " as " + ft.ret :
"").append(" { ").append(expr).append(" }").toString();
}
@Override
public boolean uses(final Use u) {
return expr.uses(u);
}
@Override
public int count(final Var v) {
return expr.count(v);
}
/**
* Coerces a function item to the given type.
* @param ctx query context
* @param ii input info
* @param fun function item to coerce
* @param t type to coerce to
* @return coerced function item
*/
private static FuncItem coerce(final QueryContext ctx, final InputInfo ii,
final FuncItem fun, final FuncType t) {
final Var[] vars = new Var[fun.vars.length];
final Expr[] refs = new Expr[vars.length];
for(int i = vars.length; i-- > 0;) {
vars[i] = ctx.uniqueVar(ii, t.args[i]);
refs[i] = new VarRef(ii, vars[i]);
}
return new FuncItem(fun.name, vars, new DynamicFunc(ii, fun, refs), t,
fun.cast != null);
}
@Override
public FItem coerceTo(final FuncType ft, final QueryContext ctx,
final InputInfo ii) throws QueryException {
if(vars.length != ft.args.length) throw Err.cast(ii, ft, this);
return type.instanceOf(ft) ? this : coerce(ctx, ii, this, ft);
}
@Override
public void plan(final Serializer ser) throws IOException {
ser.openElement(token(Util.name(this)), token(TYPE),
token(type.toString()));
for(final Var v : vars) v.plan(ser);
expr.plan(ser);
ser.closeElement();
}
}