package client.net.sf.saxon.ce.style;
import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.LogController;
import client.net.sf.saxon.ce.expr.*;
import client.net.sf.saxon.ce.expr.instruct.Executable;
import client.net.sf.saxon.ce.expr.instruct.SlotManager;
import client.net.sf.saxon.ce.expr.instruct.UserFunction;
import client.net.sf.saxon.ce.expr.instruct.UserFunctionParameter;
import client.net.sf.saxon.ce.lib.NamespaceConstant;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.AxisIterator;
import client.net.sf.saxon.ce.value.SequenceType;
import client.net.sf.saxon.ce.value.Whitespace;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.google.gwt.logging.client.LogConfiguration;
/**
* Handler for xsl:function elements in stylesheet (XSLT 2.0). <BR>
* Attributes: <br>
* name gives the name of the function
*/
public class XSLFunction extends StyleElement implements StylesheetProcedure {
private String nameAtt = null;
private String asAtt = null;
private String overrideAtt = null;
private SequenceType resultType;
private String functionName;
private SlotManager stackFrameMap;
private boolean override = true;
private int numberOfArguments = -1; // -1 means not yet known
private UserFunction compiledFunction;
// List of UserFunctionCall objects that reference this XSLFunction
List references = new ArrayList(10);
/**
* Method called by UserFunctionCall to register the function call for
* subsequent fixup.
* @param ref the UserFunctionCall to be registered
*/
public void registerReference(UserFunctionCall ref) {
references.add(ref);
}
/**
* Ask whether this node is a declaration, that is, a permitted child of xsl:stylesheet
* (including xsl:include and xsl:import).
* @return true for this element
*/
@Override
public boolean isDeclaration() {
return true;
}
public void prepareAttributes() throws XPathException {
AttributeCollection atts = getAttributeList();
overrideAtt = "yes";
for (int a=0; a<atts.getLength(); a++) {
int nc = atts.getNameCode(a);
String f = getNamePool().getClarkName(nc);
if (f.equals(StandardNames.NAME)) {
nameAtt = Whitespace.trim(atts.getValue(a));
if (nameAtt.indexOf(':')<0) {
compileError("Function name must have a namespace prefix", "XTSE0740");
}
try {
setObjectName(makeQName(nameAtt));
} catch (NamespaceException err) {
compileError(err.getMessage(), "XTSE0280");
} catch (XPathException err) {
compileError(err);
}
} else if (f.equals(StandardNames.AS)) {
asAtt = atts.getValue(a);
} else if (f.equals(StandardNames.OVERRIDE)) {
overrideAtt = Whitespace.trim(atts.getValue(a));
if (overrideAtt.equals("yes")) {
override = true;
} else if (overrideAtt.equals("no")) {
override = false;
} else {
override = true;
compileError("override must be 'yes' or 'no'", "XTSE0020");
}
} else {
checkUnknownAttribute(nc);
}
}
if (nameAtt == null) {
reportAbsence("name");
nameAtt="xsl:unnamed-function";
}
if (asAtt == null) {
resultType = SequenceType.ANY_SEQUENCE;
} else {
resultType = makeSequenceType(asAtt);
}
functionName = nameAtt;
}
/**
* Get a name identifying the object of the expression, for example a function name, template name,
* variable name, key name, element name, etc. This is used only where the name is known statically.
* If there is no name, the value will be -1.
*/
public StructuredQName getObjectName() {
StructuredQName qn = super.getObjectName();
if (qn == null) {
nameAtt = Whitespace.trim(getAttributeValue("", "name"));
if (nameAtt == null) {
return new StructuredQName("saxon", NamespaceConstant.SAXON, "badly-named-function");
}
try {
qn = makeQName(nameAtt);
setObjectName(qn);
} catch (NamespaceException err) {
return new StructuredQName("saxon", NamespaceConstant.SAXON, "badly-named-function");
} catch (XPathException err) {
return new StructuredQName("saxon", NamespaceConstant.SAXON, "badly-named-function");
}
}
return qn;
}
/**
* Determine whether this type of element is allowed to contain a template-body.
* @return true: yes, it may contain a general template-body
*/
public boolean mayContainSequenceConstructor() {
return true;
}
protected boolean mayContainParam(String attName) {
return !"required".equals(attName);
}
/**
* Specify that xsl:param is a permitted child
*/
protected boolean isPermittedChild(StyleElement child) {
return (child instanceof XSLParam);
}
/**
* Is override="yes"?.
* @return true if override="yes" was specified, otherwise false
*/
public boolean isOverriding() {
if (overrideAtt == null) {
// this is a forwards reference
try {
prepareAttributes();
} catch (XPathException e) {
// no action: error will be caught later
}
}
return override;
}
protected void index(Declaration decl, PrincipalStylesheetModule top) throws XPathException {
top.indexFunction(decl);
}
/**
* Notify all references to this function of the data type.
* @throws XPathException
*/
public void fixupReferences() throws XPathException {
Iterator iter = references.iterator();
while (iter.hasNext()) {
((UserFunctionCall)iter.next()).setStaticType(resultType);
}
super.fixupReferences();
}
public void validate(Declaration decl) throws XPathException {
stackFrameMap = new SlotManager();
// check the element is at the top level of the stylesheet
checkTopLevel(null);
getNumberOfArguments();
}
/**
* Compile the function definition to create an executable representation
* @return an Instruction, or null. The instruction returned is actually
* rather irrelevant; the compile() method has the side-effect of binding
* all references to the function to the executable representation
* (a UserFunction object)
* @throws XPathException
*/
public Expression compile(Executable exec, Declaration decl) throws XPathException {
compileAsExpression(exec, decl);
return null;
}
/**
* Compile the function into a UserFunction object, which treats the function
* body as a single XPath expression. This involves recursively translating
* xsl:variable declarations into let expressions, withe the action part of the
* let expression containing the rest of the function body.
* The UserFunction that is created will be linked from all calls to
* this function, so nothing else needs to be done with the result. If there are
* no calls to it, the compiled function will be garbage-collected away.
* @param exec the Executable
* @param decl
* @throws XPathException
*/
private void compileAsExpression(Executable exec, Declaration decl) throws XPathException {
Expression exp = compileSequenceConstructor(exec, decl, iterateAxis(Axis.CHILD));
if (exp == null) {
exp = Literal.makeEmptySequence();
} else {
ExpressionVisitor visitor = makeExpressionVisitor();
exp = exp.simplify(visitor);
}
if (LogConfiguration.loggingIsEnabled() && LogController.traceIsEnabled()) {
TraceExpression trace = new TraceExpression(exp);
trace.setConstructType(StandardNames.XSL_FUNCTION);
trace.setObjectName(getObjectName());
exp = trace;
}
UserFunction fn = new UserFunction();
fn.setHostLanguage(Configuration.XSLT);
fn.setBody(exp);
fn.setFunctionName(getObjectName());
setParameterDefinitions(fn);
fn.setResultType(getResultType());
fn.setSourceLocator(this);
fn.setStackFrameMap(stackFrameMap);
fn.setExecutable(exec);
compiledFunction = fn;
fixupInstruction(fn);
}
public void typeCheckBody() throws XPathException {
Expression exp = compiledFunction.getBody();
Expression exp2 = exp;
ExpressionVisitor visitor = makeExpressionVisitor();
try {
// We've already done the typecheck of each XPath expression, but it's worth doing again at this
// level because we have more information now.
exp2 = visitor.typeCheck(exp, null);
if (resultType != null) {
RoleLocator role =
new RoleLocator(RoleLocator.FUNCTION_RESULT, functionName, 0);
role.setErrorCode("XTTE0780");
exp2 = TypeChecker.staticTypeCheck(exp2, resultType, false, role, visitor);
}
} catch (XPathException err) {
err.maybeSetLocation(this);
compileError(err);
}
if (exp2 != exp) {
compiledFunction.setBody(exp2);
}
}
public void optimize(Declaration declaration) throws XPathException {
Expression exp = compiledFunction.getBody();
ExpressionVisitor visitor = makeExpressionVisitor();
Expression exp2 = exp;
Optimizer opt = getConfiguration().getOptimizer();
try {
if (opt.getOptimizationLevel() != Optimizer.NO_OPTIMIZATION) {
exp2 = exp.optimize(visitor, null);
}
} catch (XPathException err) {
err.maybeSetLocation(this);
compileError(err);
}
allocateSlots(exp2);
if (exp2 != exp) {
compiledFunction.setBody(exp2);
}
int tailCalls = ExpressionTool.markTailFunctionCalls(exp2, getObjectName(), getNumberOfArguments());
if (tailCalls != 0) {
compiledFunction.setTailRecursive(tailCalls > 0, tailCalls > 1);
compiledFunction.setBody(new TailCallLoop(compiledFunction));
}
compiledFunction.computeEvaluationMode();
}
/**
* Fixup all function references.
* @param compiledFunction the Instruction representing this function in the compiled code
* @throws XPathException if an error occurs.
*/
private void fixupInstruction(UserFunction compiledFunction)
throws XPathException {
ExpressionVisitor visitor = makeExpressionVisitor();
try {
Iterator iter = references.iterator();
while (iter.hasNext()) {
UserFunctionCall call = ((UserFunctionCall)iter.next());
call.setFunction(compiledFunction);
call.checkFunctionCall(compiledFunction, visitor);
call.computeArgumentEvaluationModes();
}
} catch (XPathException err) {
compileError(err);
}
}
/**
* Get associated Procedure (for details of stack frame).
* @return the associated Procedure object
*/
public SlotManager getSlotManager() {
return stackFrameMap;
}
/**
* Get the type of value returned by this function
* @return the declared result type, or the inferred result type
* if this is more precise
*/
public SequenceType getResultType() {
return resultType;
}
/**
* Get the number of arguments declared by this function (that is, its arity).
* @return the arity of the function
*/
public int getNumberOfArguments() {
if (numberOfArguments == -1) {
numberOfArguments = 0;
AxisIterator kids = iterateAxis(Axis.CHILD);
while (true) {
Item child = kids.next();
if (child instanceof XSLParam) {
numberOfArguments++;
} else {
return numberOfArguments;
}
}
}
return numberOfArguments;
}
/**
* Set the definitions of the parameters in the compiled function, as an array.
* @param fn the compiled object representing the user-written function
*/
public void setParameterDefinitions(UserFunction fn) {
UserFunctionParameter[] params = new UserFunctionParameter[getNumberOfArguments()];
fn.setParameterDefinitions(params);
int count = 0;
AxisIterator kids = iterateAxis(Axis.CHILD);
while (true) {
NodeInfo node = (NodeInfo)kids.next();
if (node == null) {
return;
}
if (node instanceof XSLParam) {
UserFunctionParameter param = new UserFunctionParameter();
params[count++] = param;
param.setRequiredType(((XSLParam)node).getRequiredType());
param.setVariableQName(((XSLParam)node).getVariableQName());
param.setSlotNumber(((XSLParam)node).getSlotNumber());
((XSLParam)node).fixupBinding(param);
int refs = ExpressionTool.getReferenceCount(fn.getBody(), param, false);
param.setReferenceCount(refs);
}
}
}
/**
* Get the compiled function
* @return the object representing the compiled user-written function
*/
public UserFunction getCompiledFunction() {
return compiledFunction;
}
}
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.