diff --git a/exist-core/src/main/java/org/exist/xquery/Function.java b/exist-core/src/main/java/org/exist/xquery/Function.java
index f812391988c..a7f6bae07e1 100644
--- a/exist-core/src/main/java/org/exist/xquery/Function.java
+++ b/exist-core/src/main/java/org/exist/xquery/Function.java
@@ -517,6 +517,37 @@ public boolean isCalledAs(final String localName) {
return localName.equals(mySignature.getName().getLocalPart());
}
+ /**
+ * Indicates whether this function's behaviour depends on the dynamic
+ * context (focus, current dateTime, default collation, static base URI,
+ * implicit timezone, ...).
+ *
+ * Used by {@link FunctionFactory#wrap(XQueryContext, Function)} to decide
+ * whether the lifted wrapper around a built-in must forward the caller's
+ * focus into the wrapped body when the built-in is used as a function
+ * item -- e.g. when retrieved via {@code fn:function-lookup} or as a
+ * named function reference. F&O 3.1 section 16.1.1 requires the
+ * static and dynamic context of the lookup site to form part of the
+ * closure of the returned function.
+ *
+ * Default is {@code false}. Overridden to {@code true} in each
+ * context-dependent built-in (the 0-arg forms of {@code fn:position},
+ * {@code fn:last}, {@code fn:node-name}, {@code fn:string},
+ * {@code fn:data}, {@code fn:base-uri}, {@code fn:string-length},
+ * {@code fn:normalize-space}, {@code fn:has-children},
+ * {@code fn:document-uri}, {@code fn:nilled}; the focus-using forms of
+ * {@code fn:lang}, {@code fn:id}, {@code fn:idref},
+ * {@code fn:element-with-id}; and {@code fn:current-dateTime},
+ * {@code fn:current-date}, {@code fn:current-time},
+ * {@code fn:implicit-timezone}, {@code fn:default-collation},
+ * {@code fn:default-language}, {@code fn:static-base-uri}).
+ *
+ * @return {@code true} if this function depends on the dynamic context
+ */
+ public boolean isContextDependent() {
+ return false;
+ }
+
@Override
public int getDependencies() {
return Dependency.CONTEXT_ITEM | Dependency.CONTEXT_SET;
diff --git a/exist-core/src/main/java/org/exist/xquery/FunctionFactory.java b/exist-core/src/main/java/org/exist/xquery/FunctionFactory.java
index 87b721751af..b8ff10c7444 100644
--- a/exist-core/src/main/java/org/exist/xquery/FunctionFactory.java
+++ b/exist-core/src/main/java/org/exist/xquery/FunctionFactory.java
@@ -508,6 +508,20 @@ public static FunctionCall wrap(XQueryContext context, Function call) throws XPa
newSignature.setArgumentTypes(newParamArray);
final UserDefinedFunction func = new UserDefinedFunction(context, newSignature);
+ // This wrapper exists to lift a built-in Function into a FunctionCall
+ // so that it can be used as a function item. Per F&O 3.1 section
+ // 16.1.1, the static and dynamic context of the call to
+ // fn:function-lookup -- and of named function references -- forms
+ // part of the closure of the returned function. When the wrapped
+ // built-in is itself context-dependent (fn:position#0,
+ // fn:node-name#0, fn:lang#1, fn:default-collation,
+ // fn:static-base-uri, ...), the wrapper must forward that captured
+ // focus into the body. Each Function subclass declares its own
+ // context-dependency via Function.isContextDependent(); the default
+ // is false, so non-context-dependent built-ins (fn:concat,
+ // fn:string-length#1, ...) and user-defined functions do not pay
+ // the propagation cost.
+ func.setPropagateContextToBody(call.isContextDependent());
for (final QName varName: variables) {
func.addVariable(varName);
}
diff --git a/exist-core/src/main/java/org/exist/xquery/InternalFunctionCall.java b/exist-core/src/main/java/org/exist/xquery/InternalFunctionCall.java
index 247c787d79d..d68d40f8a90 100644
--- a/exist-core/src/main/java/org/exist/xquery/InternalFunctionCall.java
+++ b/exist-core/src/main/java/org/exist/xquery/InternalFunctionCall.java
@@ -83,6 +83,11 @@ public int getArgumentCount() {
return function.getArgumentCount();
}
+ @Override
+ public boolean isContextDependent() {
+ return function.isContextDependent();
+ }
+
@Override
public QName getName() {
return function.getName();
diff --git a/exist-core/src/main/java/org/exist/xquery/UserDefinedFunction.java b/exist-core/src/main/java/org/exist/xquery/UserDefinedFunction.java
index 839cbb6bc44..bb7962cf73b 100644
--- a/exist-core/src/main/java/org/exist/xquery/UserDefinedFunction.java
+++ b/exist-core/src/main/java/org/exist/xquery/UserDefinedFunction.java
@@ -48,6 +48,18 @@ public class UserDefinedFunction extends Function implements Cloneable {
private boolean hasBeenReset = false;
private List closureVariables = null;
+ // When true, propagate the caller's focus (context sequence + item) to the
+ // body. Set by FunctionFactory.wrap so that named-function-reference and
+ // fn:function-lookup wrappers around context-dependent built-ins
+ // (fn:node-name#0, fn:string#0, fn:position#0, fn:lang#1, ...) can see a
+ // focus. Plain user-defined functions are not supposed to depend on the
+ // caller's focus, so this flag stays false for them. See F&O 3.1 ยง16.1.1.
+ private boolean propagateContextToBody = false;
+
+ public void setPropagateContextToBody(final boolean propagateContextToBody) {
+ this.propagateContextToBody = propagateContextToBody;
+ }
+
public UserDefinedFunction(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
@@ -155,7 +167,11 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
", got " + currentArguments[j].getItemCount());
}
}
- result = body.eval(null, null);
+ if (propagateContextToBody) {
+ result = body.eval(contextSequence, contextItem);
+ } else {
+ result = body.eval(null, null);
+ }
return result;
} finally {
// restore the local variable stack
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnDefaultLanguage.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnDefaultLanguage.java
index dd2e6ad1c55..eb603675602 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnDefaultLanguage.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnDefaultLanguage.java
@@ -41,6 +41,11 @@ public FnDefaultLanguage(final XQueryContext context) {
super(context, FS_DEFAULT_LANGUAGE);
}
+ @Override
+ public boolean isContextDependent() {
+ return true;
+ }
+
@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnHasChildren.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnHasChildren.java
index 8e3cd3e2eec..cb28163baca 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnHasChildren.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnHasChildren.java
@@ -51,6 +51,11 @@ public FnHasChildren(final XQueryContext context, final FunctionSignature signat
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
final NodeValue node;
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunBaseURI.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunBaseURI.java
index 4bc9c42a071..a44a002982a 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunBaseURI.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunBaseURI.java
@@ -77,6 +77,15 @@ public FunBaseURI(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ // 0-arg base-uri() reads the focus; static-base-uri() reads the
+ // call-site's static base URI from the XQueryContext. Both forms
+ // must close over the lookup-site context when wrapped via
+ // fn:function-lookup or a named function reference.
+ return isCalledAs(FS_STATIC_BASE_URI) || getArgumentCount() == 0;
+ }
+
/* (non-Javadoc)
* @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[],
* org.exist.xquery.value.Sequence)
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunCurrentDateTime.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunCurrentDateTime.java
index 84e3bf0e7e3..a88a65bfefa 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunCurrentDateTime.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunCurrentDateTime.java
@@ -82,6 +82,11 @@ public FunCurrentDateTime(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return true;
+ }
+
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
context.getProfiler().start(this);
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunData.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunData.java
index 8594c7ea63b..15ddd04e30b 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunData.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunData.java
@@ -68,6 +68,11 @@ public FunData(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
/* (non-Javadoc)
* @see org.exist.xquery.Expression#eval(org.exist.xquery.value.Sequence,
* org.exist.xquery.value.Item)
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunDefaultCollation.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunDefaultCollation.java
index bcd5446dbbe..cea49c47916 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunDefaultCollation.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunDefaultCollation.java
@@ -55,6 +55,11 @@ public FunDefaultCollation(XQueryContext context) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return true;
+ }
+
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
if (context.getProfiler().isEnabled()) {
context.getProfiler().start(this);
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunDocumentURI.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunDocumentURI.java
index ec81779ade3..e39dbf40f04 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunDocumentURI.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunDocumentURI.java
@@ -60,6 +60,11 @@ public FunDocumentURI(final XQueryContext context, final FunctionSignature signa
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunElementWithId.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunElementWithId.java
index dd289251062..e1caa1dbf8e 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunElementWithId.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunElementWithId.java
@@ -59,6 +59,11 @@ public FunElementWithId(final XQueryContext context, final FunctionSignature sig
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 1;
+ }
+
@Override
public Sequence eval(final Sequence[] args, Sequence contextSequence) throws XPathException {
if (getArgumentCount() < 1) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunGenerateId.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunGenerateId.java
index 1f46d3593f2..99689c0c062 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunGenerateId.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunGenerateId.java
@@ -50,6 +50,11 @@ public FunGenerateId(final XQueryContext context, final FunctionSignature signat
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
final NodeValue node;
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunId.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunId.java
index ba330cfb4c4..8f1dc78dc4f 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunId.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunId.java
@@ -71,6 +71,11 @@ public FunId(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 1;
+ }
+
/**
* @see org.exist.xquery.Expression#eval(Sequence, Item)
*/
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunIdRef.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunIdRef.java
index 19637562f7e..7e00291c161 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunIdRef.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunIdRef.java
@@ -92,6 +92,11 @@ public FunIdRef(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 1;
+ }
+
/**
* @see org.exist.xquery.Expression#eval(Sequence, Item)
*/
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunImplicitTimezone.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunImplicitTimezone.java
index fcb4e8b1b09..64278942f4b 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunImplicitTimezone.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunImplicitTimezone.java
@@ -54,6 +54,11 @@ public FunImplicitTimezone(XQueryContext context) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return true;
+ }
+
public Sequence eval(Sequence contextSequence, Item contextItem)
throws XPathException {
if (context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLang.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLang.java
index 9df5559dbe6..4fe22008812 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLang.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLang.java
@@ -97,6 +97,11 @@ public FunLang(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 1;
+ }
+
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
super.analyze(contextInfo);
try {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLast.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLast.java
index f5b6ad971e3..feb07c6d0dc 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLast.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLast.java
@@ -63,6 +63,11 @@ public int getDependencies() {
Dependency.CONTEXT_POSITION;
}
+ @Override
+ public boolean isContextDependent() {
+ return true;
+ }
+
/* (non-Javadoc)
* @see org.exist.xquery.functions.Function#eval(org.exist.xquery.StaticContext, org.exist.dom.persistent.DocumentSet, org.exist.xquery.value.Sequence, org.exist.xquery.value.Item)
*/
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLocalName.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLocalName.java
index df9a253d2bf..b81760b07dc 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLocalName.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunLocalName.java
@@ -90,6 +90,11 @@ public FunLocalName(final XQueryContext context, final FunctionSignature signatu
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunName.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunName.java
index 9d32f9f432a..74e4f6a8fda 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunName.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunName.java
@@ -96,6 +96,11 @@ public FunName(final XQueryContext context, final FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNamespaceURI.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNamespaceURI.java
index 09b579593c9..3251d11ec5c 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNamespaceURI.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNamespaceURI.java
@@ -90,6 +90,11 @@ public FunNamespaceURI(final XQueryContext context, final FunctionSignature sign
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNilled.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNilled.java
index 17aa71d6e66..ac57e5791fe 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNilled.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNilled.java
@@ -58,6 +58,11 @@ public FunNilled(final XQueryContext context, final FunctionSignature signature)
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNodeName.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNodeName.java
index d45bb74be1f..e3d364beee5 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNodeName.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNodeName.java
@@ -73,6 +73,11 @@ public FunNodeName(final XQueryContext context, final FunctionSignature signatur
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNormalizeSpace.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNormalizeSpace.java
index cd922e65a86..d22f3d674b9 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNormalizeSpace.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNormalizeSpace.java
@@ -93,6 +93,11 @@ public FunNormalizeSpace(final XQueryContext context, final FunctionSignature si
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public int returnsType() {
return Type.STRING;
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNumber.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNumber.java
index ca421938d3e..112fb60f0a4 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNumber.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunNumber.java
@@ -97,6 +97,11 @@ public FunNumber(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
if(context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunOnFunctions.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunOnFunctions.java
index b6844b0d0cd..27d0df014bd 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunOnFunctions.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunOnFunctions.java
@@ -94,7 +94,21 @@ public Sequence eval(Sequence[] args, Sequence contextSequence)
}
throw e;
}
- return call == null ? Sequence.EMPTY_SEQUENCE : new FunctionReference(this, call);
+ if (call == null) {
+ return Sequence.EMPTY_SEQUENCE;
+ }
+ final FunctionReference ref = new FunctionReference(this, call);
+ // F&O 3.1 section 16.1.1: the static and dynamic context of the
+ // call to fn:function-lookup forms part of the closure of the
+ // returned function. Capture the focus so that context-dependent
+ // built-ins (fn:position#0, fn:node-name#0, fn:lang#1, ...) run
+ // against the focus that was in scope at the lookup site.
+ final org.exist.xquery.value.Item capturedItem =
+ (contextSequence != null && !contextSequence.isEmpty())
+ ? contextSequence.itemAt(0)
+ : null;
+ ref.setCapturedContext(contextSequence, capturedItem);
+ return ref;
} else if (isCalledAs("function-name")) {
final FunctionReference ref = (FunctionReference) args[0].itemAt(0);
final QName qname = ref.getSignature().getName();
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunPath.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunPath.java
index a78600ea214..98a49b0d36a 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunPath.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunPath.java
@@ -55,6 +55,11 @@ public FunPath(final XQueryContext context, final FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
final Sequence result;
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunPosition.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunPosition.java
index f19c5a7d210..936df38b1ee 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunPosition.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunPosition.java
@@ -62,6 +62,11 @@ public int getDependencies() {
Dependency.CONTEXT_POSITION;
}
+ @Override
+ public boolean isContextDependent() {
+ return true;
+ }
+
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
context.getProfiler().start(this);
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunRoot.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunRoot.java
index fcc444c6cfb..13b2a051f9f 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunRoot.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunRoot.java
@@ -84,6 +84,11 @@ public FunRoot(final XQueryContext context, final FunctionSignature signature) {
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunStrLength.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunStrLength.java
index f56586b006f..db27888a03c 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunStrLength.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunStrLength.java
@@ -67,6 +67,11 @@ public FunStrLength(final XQueryContext context, final FunctionSignature signatu
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
protected Tuple2 strictCheckArgumentType(Expression argument,
@Nullable final SequenceType argType, final AnalyzeContextInfo argContextInfo, final int argPosition,
diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunString.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunString.java
index af79dd4ac02..70c8a42cc30 100644
--- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FunString.java
+++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FunString.java
@@ -67,6 +67,11 @@ public FunString(final XQueryContext context, final FunctionSignature signature)
super(context, signature);
}
+ @Override
+ public boolean isContextDependent() {
+ return getArgumentCount() == 0;
+ }
+
@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
diff --git a/exist-core/src/main/java/org/exist/xquery/value/FunctionReference.java b/exist-core/src/main/java/org/exist/xquery/value/FunctionReference.java
index bbac6e112b5..07eaaf72d9f 100644
--- a/exist-core/src/main/java/org/exist/xquery/value/FunctionReference.java
+++ b/exist-core/src/main/java/org/exist/xquery/value/FunctionReference.java
@@ -43,6 +43,15 @@ public class FunctionReference extends AtomicValue implements AutoCloseable {
protected final FunctionCall functionCall;
+ // Focus captured at the time of fn:function-lookup. When non-null, this
+ // focus is substituted for the function body's evaluation, while the
+ // function's argument expressions are still evaluated in the outer focus.
+ // See F&O 3.1 section 16.1.1 (fn:function-lookup), which states that the
+ // implementation of a returned context-dependent built-in is associated
+ // with the dynamic context of the call to fn:function-lookup.
+ private Sequence capturedContextSequence;
+ private Item capturedContextItem;
+
public FunctionReference(final FunctionCall functionCall) {
this(null, functionCall);
}
@@ -52,6 +61,15 @@ public FunctionReference(final Expression expression, final FunctionCall functio
this.functionCall = functionCall;
}
+ public void setCapturedContext(final Sequence contextSequence, final Item contextItem) {
+ this.capturedContextSequence = contextSequence;
+ this.capturedContextItem = contextItem;
+ }
+
+ private boolean hasCapturedContext() {
+ return capturedContextSequence != null || capturedContextItem != null;
+ }
+
public FunctionCall getCall() {
return functionCall;
}
@@ -110,7 +128,7 @@ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
* @throws XPathException in case of dynamic error
*/
public Sequence eval(Sequence contextSequence) throws XPathException {
- return functionCall.eval(contextSequence, null);
+ return eval(contextSequence, null);
}
/**
@@ -122,6 +140,18 @@ public Sequence eval(Sequence contextSequence) throws XPathException {
* @throws XPathException in case of dynamic error
*/
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
+ if (hasCapturedContext()) {
+ // Per F&O 3.1 section 16.1.1, when fn:function-lookup returns a
+ // context-dependent built-in, the function body must see the focus
+ // captured at lookup time. Argument expressions, however, still
+ // evaluate against the outer focus of the dynamic call site.
+ final int n = functionCall.getArgumentCount();
+ final Sequence[] seq = new Sequence[n];
+ for (int i = 0; i < n; i++) {
+ seq[i] = functionCall.getArgument(i).eval(contextSequence, contextItem);
+ }
+ return functionCall.evalFunction(capturedContextSequence, capturedContextItem, seq);
+ }
return functionCall.eval(contextSequence, contextItem);
}
@@ -135,6 +165,9 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
* @throws XPathException in case of dynamic error
*/
public Sequence evalFunction(Sequence contextSequence, Item contextItem, Sequence[] seq) throws XPathException {
+ if (hasCapturedContext()) {
+ return functionCall.evalFunction(capturedContextSequence, capturedContextItem, seq);
+ }
return functionCall.evalFunction(contextSequence, contextItem, seq);
}
diff --git a/exist-core/src/test/xquery/xquery3/higher-order.xml b/exist-core/src/test/xquery/xquery3/higher-order.xml
deleted file mode 100644
index e22d6bf95f1..00000000000
--- a/exist-core/src/test/xquery/xquery3/higher-order.xml
+++ /dev/null
@@ -1,762 +0,0 @@
-
-
-
- xquery3 higher-order functions
-
- Higher-order functions in XQuery 3.0
- Wolfgang Meier
-
-
-
-
-
-
-
-
-
- Named function reference
-
-
- 70
-
-
- Named function reference in "local" namespace
-
-
- 70
-
-
- Named function reference on internal standard function
-
-
- 3
-
-
- Backwards compatibility test 1
-
-
- 70
-
-
- Backwards compatibility test 2
-
-
- 70
-
-
- Annotation support test
-
-
- 70
-
-
- Inline function
-
-
- HELLO WORLD!
-
-
- Inline function with annotation
-
-
- HELLO WORLD!
-
-
- Inline function as parameter in function call
-
-
- HELLO WORLD!
-
-
- Immediate execution of inline function
-
-
- Hello
-
-
- Combine named function reference, dynamic calls and inline functions
-
-
- HELLO WORLD!
-
-
- Function sequence type: any function
-
-
- HELLO WORLD!
-
-
- Function sequence type: one parameter
-
-
- HELLO WORLD!
-
-
- Function sequence type: two parameters 1
-
-
- 15
-
-
- Function sequence type: two parameters 2
-
-
- 210
-
-
- Function sequence type: two parameters 3
-
-
- 1 4 9 16 25
-
-
- Function sequence type: parenthesized type
-
-
- OK
-
-
- Calling function in imported module
-
-
- 210
-
-
- Dynamic function call syntax test 1
-
- 2
-
-
- Dynamic function call syntax test 2
-
- 3
-
-
- Dynamic function call syntax test 3
-
- 1
-
-
- Dynamic function call syntax test 4
- 1]
-]]>
- 2 3
-
-
- Dynamic function call syntax test 5
-
- Hello you
-
-
- Dynamic function call syntax test 6
-
- HELLO
-
-
- fn:function-lookup
-
-
- 70
-
-
- fn:function-lookup on unknown function
-
-
-
-
-
- fn:function-lookup on function in imported module
-
-
- 210
-
-
- fn:function-arity
-
-
- 2
-
-
- fn:function-name
-
-
- ex:add
-
-
- fn:function-name on anonymous function
-
-
-
-
-
- fn:for-each function
- fn:for-each(1 to 5, function($a) { $a * $a })
- 1 4 9 16 25
-
-
- fn:for-each on strings
- fn:for-each(("john", "jane"), fn:string-to-codepoints#1)
- 106 111 104 110 106 97 110 101
-
-
- fn:for-each with cast
- fn:for-each(("23", "29"), xs:int#1)
- 23 29
-
-
- fn:filter function (wrong argument order: deprecated)
- fn:filter(function($a) {$a mod 2 = 0}, (1 to 10))
- XPTY0004
-
-
- fn:filter function
- fn:filter(1 to 10, function($a) {$a mod 2 = 0})
- 2 4 6 8 10
-
-
- fn:fold-left function 1
- fn:fold-left(1 to 5, (), function($a, $b) {($b, $a)})
- 5 4 3 2 1
-
-
- fn:fold-left function 2
- fold-left((2,3,5,7), 1, function($a, $b) { $a * $b })
- 210
-
-
- fn:fold-left function 3
- fn:fold-left(1 to 5, "$zero", fn:concat("$f(", ?, ", ", ?, ")"))
- $f($f($f($f($f($zero, 1), 2), 3), 4), 5)
-
-
- fn:fold-left with partial application
- fn:fold-left(1 to 5, "", fn:concat(?, ".", ?))
- .1.2.3.4.5
-
-
- fn:fold-left non-associative
- fold-left(1 to 1000000, 10, function($a, $b){ ($a - ($a - 1)) div $b })
- 0.000001
-
-
- fn:fold-right function 1
- fn:fold-right(1 to 5, 0, function($a, $b) { $a + $b })
- 15
-
-
- fn:fold-right function 2
- fn:fold-right(1 to 5, "", function($a, $b) { concat($a, ".", $b) })
- 1.2.3.4.5.
-
-
- fn:fold-right function 3
- fn:fold-right(1 to 5, "$zero", concat("$f(", ?, ", ", ?, ")"))
- $f(1, $f(2, $f(3, $f(4, $f(5, $zero)))))
-
-
- fn:fold-right non-associative
- fold-right(1 to 1000000, 10, function($a, $b){ ($a - ($a - 1)) div $b })
- 10
-
-
- fn:for-each-pair function
- fn:for-each-pair(1 to 5, 1 to 5, function($a, $b){10*$a + $b})
- 11 22 33 44 55
-
-
- fn:for-each-pair with strings
- fn:for-each-pair(("a", "b", "c"), ("x", "y", "z"), concat#2)
- ax by cz
-
-
-
- Partial function with fn:for-each
-
- 10 20 30 40 50 60 70 80 90 100
-
-
- Partial function with flwor
-
- 10 20 30 40 50 60 70 80 90 100
-
-
- Partial function application on dynamic call using named function reference
-
- 13
-
-
- Partial function application on dynamic call with function parameter
-
-)
-
-let $f := $a(?, function($x){ true() })
-
-return $f($list)]]>
- true
-
-
- Partial function application: dynamic call with partial application
-
- 18
-
-
- Partial function application on inline function using map
-
- 5 10
-
-
- Partial function application on internal function
-
- a:b
-
-
- Partial function application on internal overloaded function
-
- Hello world! Hello Wolf!
-
-
-
- Simple closure
- {$p}
}
-return
-$f($b)]]>
- Hello world!
-
-
- Closure test: function passed to other function
- {$b} }
-return
- local:test($f)]]>
- Hello world!
-
-
- Closure test: inline function created in other function
- { $b } }
-};
-
-let $f := local:create()
-return
- local:test($f)]]>
- Hello world!
-
-
- Closure test: variable dependencies
- {$b} }
-};
-
-let $f := local:create("Hello")
-return
- local:test($f)]]>
- Hello world!
-
-
- Closure test: ensure variables are properly copied
-
- 1 2 3 4 5 6 7 8 9 10
-
-
- Closure test: redefined variable
-
- b
-
-
diff --git a/exist-core/src/test/xquery/xquery3/higher-order.xqm b/exist-core/src/test/xquery/xquery3/higher-order.xqm
new file mode 100644
index 00000000000..eb58bb50bb3
--- /dev/null
+++ b/exist-core/src/test/xquery/xquery3/higher-order.xqm
@@ -0,0 +1,912 @@
+(:
+ : eXist-db Open Source Native XML Database
+ : Copyright (C) 2001 The eXist-db Authors
+ :
+ : info@exist-db.org
+ : http://www.exist-db.org
+ :
+ : This library is free software; you can redistribute it and/or
+ : modify it under the terms of the GNU Lesser General Public
+ : License as published by the Free Software Foundation; either
+ : version 2.1 of the License, or (at your option) any later version.
+ :
+ : This library is distributed in the hope that it will be useful,
+ : but WITHOUT ANY WARRANTY; without even the implied warranty of
+ : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ : Lesser General Public License for more details.
+ :
+ : You should have received a copy of the GNU Lesser General Public
+ : License along with this library; if not, write to the Free Software
+ : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ :)
+xquery version "3.1";
+
+(:~
+ : Higher-order function tests, originally authored by Wolfgang Meier.
+ : Converted from the legacy XML format to an XQSuite module
+ : per duncdrum's review on PR #6352 so the entire suite uses a single
+ : annotation-based assertion format. Each scenario is wrapped in
+ : util:eval to preserve the per-test compilation isolation of the
+ : original format (each test had its own xquery version prologue and
+ : local:/ex: module-level function declarations).
+ :)
+module namespace ho = "http://exist-db.org/xquery/test/higher-order";
+
+declare namespace test = "http://exist-db.org/xquery/xqsuite";
+
+declare
+ %test:setUp
+function ho:setup() {
+ let $col := xmldb:create-collection("/db", "xq3-test")
+ return
+ xmldb:store($col, "test1.xql",
+ ``[module namespace ex2="http://exist-db.org/xquery/ex2";
+
+(: copied from the XQuery spec :)
+declare function ex2:fold-left(
+ $f as function(item()*, item()) as item()*,
+ $zero as item()*,
+ $seq as item()*) as item()* {
+ if (fn:empty($seq)) then $zero
+ else ex2:fold-left($f, $f($zero, $seq[1]), subsequence($seq, 2))
+};]``,
+ "application/xquery")
+};
+
+declare
+ %test:tearDown
+function ho:cleanup() {
+ xmldb:remove("/db/xq3-test")
+};
+
+declare
+ %test:assertEquals("70")
+function ho:named-function-reference() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:add($a, $b) {
+ $a + $b
+};
+
+let $f1 := ex:add#2
+return
+ $f1(20, 50)]``)
+};
+
+declare
+ %test:assertEquals("70")
+function ho:named-function-reference-local-namespace() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:add($a, $b) {
+ $a + $b
+};
+
+let $f1 := local:add#2
+return
+ $f1(20, 50)]``)
+};
+
+declare
+ %test:assertEquals("3")
+function ho:named-function-reference-internal-standard-function() {
+ util:eval(``[xquery version "1.0";
+
+let $f := sum#1
+return
+ $f((1, 2))]``)
+};
+
+declare
+ %test:assertEquals("70")
+function ho:backwards-compatibility-1() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:add($a, $b) {
+ $a + $b
+};
+
+let $f1 := util:function(xs:QName("local:add"), 2)
+return
+ $f1(20, 50)]``)
+};
+
+declare
+ %test:assertEquals("70")
+function ho:backwards-compatibility-2() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:add($a, $b) {
+ $a + $b
+};
+
+let $f1 := local:add#2
+return
+ util:call($f1, 20, 50)]``)
+};
+
+declare
+ %test:assertEquals("70")
+function ho:annotation-support() {
+ util:eval(``[xquery version "3.0";
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare %ex:annotest function ex:add($a, $b) {
+ $a + $b
+};
+
+let $f1 := ex:add#2
+return
+ $f1(20, 50)]``)
+};
+
+declare
+ %test:assertEquals("HELLO", "WORLD!")
+function ho:inline-function() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:apply($func, $list) {
+ for $item in $list return $func($item)
+};
+
+let $f2 := function($a) { upper-case($a) }
+return
+ ex:apply($f2, ("Hello", "world!"))]``)
+};
+
+declare
+ %test:assertEquals("HELLO", "WORLD!")
+function ho:inline-function-with-annotation() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:apply($func, $list) {
+ for $item in $list return $func($item)
+};
+
+let $f2 := %ex:annoex function($a) { upper-case($a) }
+return
+ ex:apply($f2, ("Hello", "world!"))]``)
+};
+
+declare
+ %test:assertEquals("HELLO", "WORLD!")
+function ho:inline-function-as-parameter() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:apply($func, $list) {
+ for $item in $list return $func($item)
+};
+
+ex:apply(function($a) { upper-case($a) }, ("Hello", "world!"))]``)
+};
+
+declare
+ %test:assertEquals("Hello")
+function ho:immediate-execution-of-inline-function() {
+ util:eval(``[xquery version "3.0";
+
+function($x) { $x }("Hello")]``)
+};
+
+declare
+ %test:assertEquals("HELLO", "WORLD!")
+function ho:combine-named-reference-dynamic-calls-inline() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:apply($func, $list) {
+ for $item in $list return $func($item)
+};
+
+let $f1 := ex:apply#2
+return
+ $f1(function($a) { upper-case($a) }, ("Hello", "world!"))]``)
+};
+
+declare
+ %test:assertEquals("HELLO", "WORLD!")
+function ho:function-sequence-type-any-function() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:apply($func as function(*), $list) {
+ for $item in $list return $func($item)
+};
+
+ex:apply(function($a) { upper-case($a) }, ("Hello", "world!"))]``)
+};
+
+declare
+ %test:assertEquals("HELLO", "WORLD!")
+function ho:function-sequence-type-one-parameter() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:apply($func as function(xs:string*) as xs:string*, $list) {
+ for $item in $list return $func($item)
+};
+
+ex:apply(function($a) { upper-case($a) }, ("Hello", "world!"))]``)
+};
+
+declare
+ %test:assertEquals("15")
+function ho:function-sequence-type-two-parameters-1() {
+ util:eval(``[
+(: copied from the XQuery spec :)
+declare function local:fold-left(
+ $f as function(item()*, item()) as item()*,
+ $zero as item()*,
+ $seq as item()*) as item()* {
+ if (fn:empty($seq)) then $zero
+ else local:fold-left($f, $f($zero, $seq[1]), subsequence($seq, 2))
+};
+
+local:fold-left(function($a, $b) { $a + $b }, 0, 1 to 5)]``)
+};
+
+declare
+ %test:assertEquals("210")
+function ho:function-sequence-type-two-parameters-2() {
+ util:eval(``[
+declare function local:fold-left(
+ $f as function(item()*, item()) as item()*,
+ $zero as item()*,
+ $seq as item()*) as item()* {
+ if (fn:empty($seq)) then $zero
+ else local:fold-left($f, $f($zero, $seq[1]), subsequence($seq, 2))
+};
+
+local:fold-left(function($a, $b) { $a * $b }, 1, (2,3,5,7))]``)
+};
+
+declare
+ %test:assertEquals("1", "4", "9", "16", "25")
+function ho:function-sequence-type-two-parameters-3() {
+ util:eval(``[declare function local:map($f, $seq) {
+ if (fn:empty($seq))
+ then ()
+ else ($f($seq[1]), local:map($f, subsequence($seq, 2)))
+};
+
+local:map(function($a) { $a * $a }, 1 to 5)]``)
+};
+
+declare
+ %test:assertEquals("OK")
+function ho:function-sequence-type-parenthesized-type() {
+ util:eval(``[
+declare function local:test(
+ $f as (function(item()*) as item()*)?) {
+ "OK"
+};
+
+local:test(())]``)
+};
+
+declare
+ %test:assertEquals("210")
+function ho:calling-function-in-imported-module() {
+ util:eval(``[
+import module namespace ex2="http://exist-db.org/xquery/ex2"
+at "xmldb:exist:///db/xq3-test/test1.xql";
+
+let $f1 := ex2:fold-left#3
+return
+ $f1(function($a, $b) { $a * $b }, 1, (2,3,5,7))]``)
+};
+
+declare
+ %test:assertEquals("2")
+function ho:dynfcall1() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:seq() {
+ (1, 2, 3)
+};
+
+let $seq := local:seq#0
+return
+ $seq()[2]]``)
+};
+
+declare
+ %test:assertEquals("3")
+function ho:dynfcall2() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:seq() {
+ (1, 2, 3)
+};
+
+let $seq := local:seq#0
+return
+ $seq[1]()[3]]``)
+};
+
+declare
+ %test:assertEquals("1")
+function ho:dynfcall3() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:seq() {
+ (1, 2, 3)
+};
+
+let $seq := local:seq#0
+return
+ ($seq)()[1]]``)
+};
+
+declare
+ %test:assertEquals("2", "3")
+function ho:dynfcall4() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:seq() {
+ (1, 2, 3)
+};
+
+let $seq := local:seq#0
+return
+ $seq()[. > 1]]``)
+};
+
+declare
+ %test:assertEquals("Hello you")
+function ho:dynfcall5() {
+ util:eval(``[xquery version "3.0";
+(: Immediate function evaluation :)
+(function($a) { "Hello " || $a })("you")]``)
+};
+
+declare
+ %test:assertEquals("HELLO")
+function ho:dynfcall6() {
+ util:eval(``[xquery version "3.0";
+declare function local:get-function() as function(xs:string) as xs:string {
+ function($str as xs:string) { upper-case($str) }
+};
+
+local:get-function()("Hello")]``)
+};
+
+declare
+ %test:assertEquals("70")
+function ho:fn-function-lookup() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:add($a, $b) {
+ $a + $b
+};
+
+let $f1 := function-lookup(xs:QName("ex:add"), 2)
+return
+ $f1(20, 50)]``)
+};
+
+declare
+ %test:assertEmpty
+function ho:lookup-unknown() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+let $f1 := function-lookup(xs:QName("ex:add"), 2)
+return
+ if (exists($f1)) then
+ $f1(20, 50)
+ else
+ ()]``)
+};
+
+declare
+ %test:assertEquals("210")
+function ho:fn-function-lookup-on-imported-module() {
+ util:eval(``[xquery version "3.0";
+
+import module namespace ex2="http://exist-db.org/xquery/ex2"
+at "xmldb:exist:///db/xq3-test/test1.xql";
+
+let $f1 := function-lookup(xs:QName("ex2:fold-left"), 3)
+return
+ $f1(function($a, $b) { $a * $b }, 1, (2,3,5,7))]``)
+};
+
+declare
+ %test:assertEquals("2")
+function ho:fn-function-arity() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:add($a, $b) {
+ $a + $b
+};
+
+let $f1 := ex:add#2
+return
+ function-arity($f1)]``)
+};
+
+declare
+ %test:assertEquals("ex:add")
+function ho:fn-function-name() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:add($a, $b) {
+ $a + $b
+};
+
+let $f1 := ex:add#2
+return
+ xs:string(function-name($f1))]``)
+};
+
+declare
+ %test:assertEmpty
+function ho:fn-function-name-anonymous() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+let $f1 := function($a, $b) {
+ $a + $b
+}
+return
+ function-name($f1)]``)
+};
+
+declare
+ %test:assertEquals("1", "4", "9", "16", "25")
+function ho:fn-for-each-function() {
+ util:eval("fn:for-each(1 to 5, function($a) { $a * $a })")
+};
+
+declare
+ %test:assertEquals("106", "111", "104", "110", "106", "97", "110", "101")
+function ho:fn-for-each-on-strings() {
+ util:eval(``[fn:for-each(("john", "jane"), fn:string-to-codepoints#1)]``)
+};
+
+declare
+ %test:assertEquals("23", "29")
+function ho:fn-for-each-with-cast() {
+ util:eval(``[fn:for-each(("23", "29"), xs:int#1)]``)
+};
+
+declare
+ %test:assertError("err:XPTY0004")
+function ho:fn-filter-wrong-argument-order-deprecated() {
+ util:eval("fn:filter(function($a) {$a mod 2 = 0}, (1 to 10))")
+};
+
+declare
+ %test:assertEquals("2", "4", "6", "8", "10")
+function ho:fn-filter-function() {
+ util:eval("fn:filter(1 to 10, function($a) {$a mod 2 = 0})")
+};
+
+declare
+ %test:assertEquals("5", "4", "3", "2", "1")
+function ho:fn-fold-left-1() {
+ util:eval("fn:fold-left(1 to 5, (), function($a, $b) {($b, $a)})")
+};
+
+declare
+ %test:assertEquals("210")
+function ho:fn-fold-left-2() {
+ util:eval("fold-left((2,3,5,7), 1, function($a, $b) { $a * $b })")
+};
+
+declare
+ %test:assertEquals("$f($f($f($f($f($zero, 1), 2), 3), 4), 5)")
+function ho:fn-fold-left-3() {
+ util:eval(``[fn:fold-left(1 to 5, "$zero", fn:concat("$f(", ?, ", ", ?, ")"))]``)
+};
+
+declare
+ %test:assertEquals(".1.2.3.4.5")
+function ho:fn-fold-left-with-partial-application() {
+ util:eval(``[fn:fold-left(1 to 5, "", fn:concat(?, ".", ?))]``)
+};
+
+declare
+ %test:assertEquals("0.000001")
+function ho:fn-fold-left-non-associative() {
+ util:eval("fold-left(1 to 1000000, 10, function($a, $b){ ($a - ($a - 1)) div $b })")
+};
+
+declare
+ %test:assertEquals("15")
+function ho:fn-fold-right-1() {
+ util:eval("fn:fold-right(1 to 5, 0, function($a, $b) { $a + $b })")
+};
+
+declare
+ %test:assertEquals("1.2.3.4.5.")
+function ho:fn-fold-right-2() {
+ util:eval(``[fn:fold-right(1 to 5, "", function($a, $b) { concat($a, ".", $b) })]``)
+};
+
+declare
+ %test:assertEquals("$f(1, $f(2, $f(3, $f(4, $f(5, $zero)))))")
+function ho:fn-fold-right-3() {
+ util:eval(``[fn:fold-right(1 to 5, "$zero", concat("$f(", ?, ", ", ?, ")"))]``)
+};
+
+declare
+ %test:assertEquals("10")
+function ho:fn-fold-right-non-associative() {
+ util:eval("fold-right(1 to 1000000, 10, function($a, $b){ ($a - ($a - 1)) div $b })")
+};
+
+declare
+ %test:assertEquals("11", "22", "33", "44", "55")
+function ho:fn-for-each-pair-function() {
+ util:eval("fn:for-each-pair(1 to 5, 1 to 5, function($a, $b){10*$a + $b})")
+};
+
+declare
+ %test:assertEquals("ax", "by", "cz")
+function ho:fn-for-each-pair-with-strings() {
+ util:eval(``[fn:for-each-pair(("a", "b", "c"), ("x", "y", "z"), concat#2)]``)
+};
+
+declare
+ %test:assertEquals("10", "20", "30", "40", "50", "60", "70", "80", "90", "100")
+function ho:partial1() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:multiply($base, $number) {
+ $base * $number
+};
+
+(: Use function reference literal to find function at compile time :)
+let $fMultiply := ex:multiply(10, ?)
+return
+ for-each(1 to 10, $fMultiply)]``)
+};
+
+declare
+ %test:assertEquals("10", "20", "30", "40", "50", "60", "70", "80", "90", "100")
+function ho:partial2() {
+ util:eval(``[xquery version "3.0";
+
+declare namespace ex="http://exist-db.org/xquery/ex";
+
+declare function ex:multiply($base, $number) {
+ $base * $number
+};
+
+(: Use function reference literal to find function at compile time :)
+let $fMultiply := ex:multiply(10, ?)
+for $i in 1 to 10
+return
+ $fMultiply($i)]``)
+};
+
+declare
+ %test:assertEquals("13")
+function ho:partial3() {
+ util:eval(``[xquery version "1.0";
+
+declare function local:func($a, $b, $c) {
+ $a + $b + $c
+};
+
+let $f := local:func#3
+let $ff := $f(5, ?, ?)
+return
+ $ff(5, 3)]``)
+};
+
+declare
+ %test:assertEquals("true")
+function ho:partial4() {
+ util:eval(``[xquery version "3.0";
+
+declare variable $a := function($x as item()*, $f as function(item()*) as
+xs:boolean) as xs:boolean {
+ $f($x)
+};
+
+let $list := (
+
+)
+
+let $f := $a(?, function($x){ true() })
+
+return $f($list)]``)
+};
+
+declare
+ %test:assertEquals("18")
+function ho:partiald1() {
+ util:eval(``[xquery version "1.0";
+
+declare function local:func($a, $b, $c) {
+ $a + $b + $c
+};
+
+let $f := local:func(10, ?, ?)
+let $ff := $f(5, ?)
+return
+ $ff(3)]``)
+};
+
+declare
+ %test:assertEquals("5", "10")
+function ho:partiald2() {
+ util:eval(``[xquery version "1.0";
+
+let $f := function ($a, $b) { $a * $b }
+let $ff := $f(5, ?)
+return
+ for-each((1, 2), $ff)]``)
+};
+
+declare
+ %test:assertEquals("a:b")
+function ho:internal1() {
+ util:eval(``[xquery version "1.0";
+
+let $f := string-join(?, ":")
+return
+ $f(("a", "b"))]``)
+};
+
+declare
+ %test:assertEquals("Hello world!", "Hello Wolf!")
+function ho:internal2() {
+ util:eval(``[xquery version "1.0";
+
+let $f := concat("Hello ", ?)
+return
+ for-each(("world!", "Wolf!"), $f)]``)
+};
+
+declare
+ %test:assertEquals("Hello world!
")
+function ho:simple-closure() {
+ util:eval(``[xquery version "3.0";
+
+let $a := "Hello"
+let $b := "Hello" || " world!"
+let $f := function ($p) { {$p}
}
+return
+$f($b)]``)
+};
+
+declare
+ %test:assertEquals("Hello world!
")
+function ho:closure-function-passed-to-other-function() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:test($f as function() as element()) {
+ $f()
+};
+
+let $a := "Hello"
+let $b := "Hello" || " world!"
+let $f := function () { {$b}
}
+return
+ local:test($f)]``)
+};
+
+declare
+ %test:assertEquals("Hello world!
")
+function ho:closure-inline-function-created-in-other-function() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:test($f as function() as element()) {
+ $f()
+};
+
+declare function local:create() {
+ let $a := "Hello"
+ let $b := "Hello" || " world!"
+ return function () { { $b }
}
+};
+
+let $f := local:create()
+return
+ local:test($f)]``)
+};
+
+declare
+ %test:assertEquals("Hello world!
")
+function ho:closure-variable-dependencies() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:test($f as function() as element()) {
+ $f()
+};
+
+declare function local:create($a) {
+ let $b := $a || " world!"
+ return function () { {$b}
}
+};
+
+let $f := local:create("Hello")
+return
+ local:test($f)]``)
+};
+
+declare
+ %test:assertEquals("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
+function ho:closure-variables-properly-copied() {
+ util:eval(``[xquery version "3.0";
+
+declare function local:create() {
+ for $i in 1 to 10
+ return
+ function () { $i }
+};
+
+for $f in local:create()
+return
+ $f()]``)
+};
+
+declare
+ %test:assertEquals("root")
+function ho:lookup-captures-focus-node-name() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { }
+return
+ $doc/root/function-lookup(xs:QName('fn:node-name'), 0)()]``)
+};
+
+declare
+ %test:assertEquals("root")
+function ho:lookup-captures-focus-xqts-002-shape() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { }
+return
+ ($doc/root)/function-lookup(xs:QName('fn:node-name'), 0)()]``)
+};
+
+declare
+ %test:assertEquals("hello")
+function ho:lookup-captures-focus-string() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { hello }
+return
+ $doc/root/child/function-lookup(xs:QName('fn:string'), 0)()]``)
+};
+
+declare
+ %test:assertEquals("true")
+function ho:lookup-captures-focus-lang() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { }
+return
+ $doc/root/function-lookup(xs:QName('fn:lang'), 1)('en')]``)
+};
+
+declare
+ %test:assertEquals("true")
+function ho:lookup-captures-focus-has-children() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { }
+return
+ $doc/function-lookup(xs:QName('fn:has-children'), 0)()]``)
+};
+
+declare
+ %test:assertEquals("0")
+function ho:lookup-captures-focus-id-1-arg() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { }
+return
+ count($doc/function-lookup(xs:QName('fn:id'), 1)('nope'))]``)
+};
+
+declare
+ %test:assertEquals("ex:greeting")
+function ho:lookup-captures-focus-name() {
+ util:eval(``[xquery version "3.0";
+declare namespace ex="http://exist-db.org/xquery/ex";
+let $doc := document { }
+return
+ $doc/*/function-lookup(xs:QName('fn:name'), 0)()]``)
+};
+
+declare
+ %test:assertEquals("child")
+function ho:lookup-captures-focus-local-name() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { }
+return
+ $doc/root/child/function-lookup(xs:QName('fn:local-name'), 0)()]``)
+};
+
+declare
+ %test:assertEquals("true")
+function ho:lookup-captures-focus-base-uri() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { }
+let $direct := $doc/root/fn:base-uri()
+let $viaLookup := $doc/root/function-lookup(xs:QName('fn:base-uri'), 0)()
+return
+ ($direct = $viaLookup) or (empty($direct) and empty($viaLookup))]``)
+};
+
+declare
+ %test:assertEquals("/Q{}root[1]/Q{}child[1]")
+function ho:lookup-captures-focus-path() {
+ util:eval(``[xquery version "3.0";
+let $doc := document { }
+return
+ $doc/root/child/function-lookup(xs:QName('fn:path'), 0)()]``)
+};
+
+declare
+ %test:assertEquals("http://www.w3.org/2005/xpath-functions/collation/codepoint")
+function ho:lookup-default-collation-closes-over-static-context() {
+ util:eval(``[xquery version "3.0";
+function-lookup(xs:QName('fn:default-collation'), 0)()]``)
+};
+
+declare
+ %test:assertEquals("true")
+function ho:lookup-static-base-uri-closes-over-static-context() {
+ util:eval(``[xquery version "3.0";
+let $direct := fn:static-base-uri()
+let $viaLookup := function-lookup(xs:QName('fn:static-base-uri'), 0)()
+return
+ ($direct = $viaLookup) or (empty($direct) and empty($viaLookup))]``)
+};
+
+declare
+ %test:assertEquals("hello world")
+function ho:lookup-fn-concat-not-context-dependent() {
+ util:eval(``[xquery version "3.0";
+function-lookup(xs:QName('fn:concat'), 2)('hello', ' world')]``)
+};
+
+declare
+ %test:assertEquals("5")
+function ho:lookup-fn-string-length-1-arg-not-context-dependent() {
+ util:eval(``[xquery version "3.0";
+function-lookup(xs:QName('fn:string-length'), 1)('hello')]``)
+};
+
+declare
+ %test:assertEquals("5")
+function ho:lookup-named-function-reference-no-captured-focus() {
+ util:eval(``[xquery version "3.0";
+declare namespace ex="http://exist-db.org/xquery/ex";
+declare function ex:add($a, $b) { $a + $b };
+let $f := function-lookup(xs:QName('ex:add'), 2)
+return $f(2, 3)]``)
+};
+
+declare
+ %test:assertEquals("b")
+function ho:closure-redefined-variable() {
+ util:eval(``[xquery version "3.0";
+
+let $a := ()
+let $a := if ($a) then $a else "b"
+return
+ function() { $a }()]``)
+};