package org.basex.query.func;
import static org.basex.query.util.Err.*;
import static org.basex.query.QueryText.*;
import java.io.IOException;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.Expr;
import org.basex.query.expr.Single;
import org.basex.query.item.AtomType;
import org.basex.query.item.Item;
import org.basex.query.item.QNm;
import org.basex.query.item.SeqType;
import org.basex.query.item.Value;
import org.basex.query.iter.Iter;
import org.basex.query.util.Var;
import org.basex.query.util.VarStack;
import org.basex.util.Atts;
import org.basex.util.InputInfo;
import org.basex.util.Token;
import org.basex.util.TokenBuilder;
/**
* User-defined function.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public class UserFunc extends Single {
/** Function name. */
public final QNm name;
/** Arguments. */
public final Var[] args;
/** Declaration flag. */
public final boolean declared;
/** Return type. */
public final SeqType ret;
/** Updating flag. */
public boolean updating;
/** Cast flag. */
private boolean cast;
/** Compilation flag. */
private boolean compiled;
/**
* Function constructor.
* @param ii input info
* @param n function name
* @param a arguments
* @param r return type
* @param d declaration flag
*/
public UserFunc(final InputInfo ii, final QNm n, final Var[] a,
final SeqType r, final boolean d) {
super(ii, null);
name = n;
ret = r;
args = a;
declared = d;
cast = r != null;
}
/**
* Checks the function for updating behavior.
* @throws QueryException query exception
*/
final void checkUp() throws QueryException {
final boolean u = expr.uses(Use.UPD);
if(updating) {
// updating function
if(ret != null) UPFUNCTYPE.thrw(input);
if(!u && !expr.isVacuous()) UPEXPECTF.thrw(input);
} else if(u) {
// uses updates, but is not declared as such
UPNOT.thrw(input, description());
}
}
@Override
public Expr comp(final QueryContext ctx) throws QueryException {
comp(ctx, true);
return this;
}
/**
* Compiles the expression.
* @param ctx query context
* @param cache cache variables
* @throws QueryException query exception
*/
void comp(final QueryContext ctx, final boolean cache)
throws QueryException {
if(compiled) return;
compiled = true;
final int vs = ctx.vars.size();
final VarStack vl = cache ? ctx.vars.cache(args.length) : null;
for(final Var v : args) ctx.vars.add(v);
expr = expr.comp(ctx);
if(cache) ctx.vars.reset(vl);
else ctx.vars.size(vs);
// convert all function calls in tail position to proper tail calls
if(tco()) expr = expr.markTailCalls();
// remove redundant cast
if(ret != null && (ret.type == AtomType.BLN || ret.type == AtomType.FLT ||
ret.type == AtomType.DBL || ret.type == AtomType.QNM ||
ret.type == AtomType.URI) && ret.eq(expr.type())) {
ctx.compInfo(OPTCAST, ret);
cast = false;
}
}
@Override
public Item item(final QueryContext ctx, final InputInfo ii)
throws QueryException {
// reset context and evaluate function
final Value cv = ctx.value;
final Atts ns = ctx.sc.ns.reset();
ctx.value = null;
try {
final Item it = expr.item(ctx, ii);
// optionally promote return value to target type
return cast ? ret.cast(it, this, false, ctx, input) : it;
} finally {
ctx.value = cv;
ctx.sc.ns.stack(ns);
}
}
@Override
public Value value(final QueryContext ctx) throws QueryException {
// reset context and evaluate function
final Value cv = ctx.value;
final Atts ns = ctx.sc.ns.reset();
ctx.value = null;
try {
final Value v = ctx.value(expr);
// optionally promote return value to target type
return cast ? ret.promote(v, ctx, input) : v;
} finally {
ctx.value = cv;
ctx.sc.ns.stack(ns);
}
}
@Override
public Iter iter(final QueryContext ctx) throws QueryException {
return value(ctx).iter();
}
@Override
public void plan(final Serializer ser) throws IOException {
ser.openElement(this);
ser.attribute(NAM, name.string());
for(int i = 0; i < args.length; ++i) {
ser.attribute(Token.token(ARG + i), args[i].name.string());
}
expr.plan(ser);
ser.closeElement();
}
@Override
public String toString() {
final TokenBuilder tb = new TokenBuilder(name.string());
tb.add(PAR1).addSep(args, SEP).add(PAR2);
if(ret != null) tb.add(' ' + AS + ' ' + ret);
if(expr != null) tb.add(" { " + expr + " }; ");
return tb.toString();
}
/**
* Checks if this function is tail-call optimizable.
* @return {@code true} if it is optimizable, {@code false} otherwise
*/
boolean tco() {
return true;
}
}