diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java
index 02e81feb4..0d6411a67 100644
--- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java
+++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java
@@ -7,6 +7,7 @@
import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException;
+import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
import gov.nist.secauto.metaschema.core.metapath.function.library.ArrayGet;
import gov.nist.secauto.metaschema.core.metapath.function.library.MapGet;
import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue;
@@ -84,7 +85,11 @@ public ISequence extends IItem> accept(DynamicContext dynamicContext, ISequenc
.collect(Collectors.toUnmodifiableList())),
dynamicContext,
focus);
- }
+ } else if (collection instanceof IFunction) {
+ return ((IFunction) collection).execute( ObjectUtils.notNull(getArguments().stream()
+ .map(expr -> expr.accept(dynamicContext, focus))
+ .collect(Collectors.toUnmodifiableList())), dynamicContext, focus);
+ }
// the value to find, which will be the key for a map or the index for an array
IExpression argument = getArguments().stream().findFirst()
diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java
index cc9997fcd..b14f2b658 100644
--- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java
+++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java
@@ -91,6 +91,8 @@ public DefaultFunctionLibrary() { // NOPMD - intentional
// P2: https://www.w3.org/TR/xpath-functions-31/#func-format-integer
// P2: https://www.w3.org/TR/xpath-functions-31/#func-format-number
// P2: https://www.w3.org/TR/xpath-functions-31/#func-format-time
+ // https://www.w3.org/TR/xpath-functions-31/#func-function-lookup
+ registerFunction(FnFunctionLookup.SIGNATURE);
// P1: https://www.w3.org/TR/xpath-functions-31/#func-generate-id
// https://www.w3.org/TR/xpath-functions-31/#func-has-children
registerFunction(FnHasChildren.SIGNATURE_NO_ARG);
diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookup.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookup.java
new file mode 100644
index 000000000..b5083be3b
--- /dev/null
+++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookup.java
@@ -0,0 +1,86 @@
+/*
+ * SPDX-FileCopyrightText: none
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+package gov.nist.secauto.metaschema.core.metapath.function.library;
+
+import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
+import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
+import gov.nist.secauto.metaschema.core.metapath.StaticContext;
+import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException;
+import gov.nist.secauto.metaschema.core.metapath.function.FunctionLibrary;
+import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
+import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
+import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
+import gov.nist.secauto.metaschema.core.metapath.item.IItem;
+import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
+import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem;
+import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
+import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem;
+import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
+import gov.nist.secauto.metaschema.core.metapath.type.IItemType;
+import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
+import gov.nist.secauto.metaschema.core.util.ObjectUtils;
+
+import java.util.List;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * /** Implements
+ * fn:function-lookup
+ * functions.
+ */
+public final class FnFunctionLookup {
+ @NonNull
+ private static final String NAME = "function-lookup";
+
+ @NonNull
+ static final IFunction SIGNATURE= IFunction.builder()
+ .name(NAME)
+ .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
+ .deterministic()
+ .contextIndependent()
+ .focusIndependent()
+ .argument(IArgument.builder()
+ .name("name")
+ .type(IStringItem.type())
+ .one()
+ .build())
+ .argument(IArgument.builder()
+ .name("arity")
+ .type(IIntegerItem.type())
+ .one()
+ .build())
+ .returnType(IItemType.function())
+ .returnZeroOrOne()
+ .functionHandler(FnFunctionLookup::execute)
+ .build();
+
+ @SuppressWarnings("unused")
+ @NonNull
+ private static ISequence execute(@NonNull IFunction function,
+ @NonNull List> arguments,
+ @NonNull DynamicContext dynamicContext,
+ IItem focus) {
+ IStringItem name = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0).getFirstItem(true)));
+ IIntegerItem arity = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true)));
+ IFunction matchingFunction;
+
+ try {
+ matchingFunction = dynamicContext.getStaticContext().lookupFunction(name.asString(), arity.asInteger().intValueExact());
+ } catch (StaticMetapathException ex) {
+ if (ex.getCode() != StaticMetapathException.NO_FUNCTION_MATCH) {
+ throw ex;
+ }
+ matchingFunction = null;
+ }
+
+ return ISequence.of(matchingFunction);
+ }
+
+ private FnFunctionLookup() {
+ // disable construction
+ }
+}
diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookupTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookupTest.java
new file mode 100644
index 000000000..d214aa49a
--- /dev/null
+++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookupTest.java
@@ -0,0 +1,46 @@
+/*
+ * SPDX-FileCopyrightText: none
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+package gov.nist.secauto.metaschema.core.metapath.function.library;
+
+import static gov.nist.secauto.metaschema.core.metapath.TestUtils.string;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase;
+import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression;
+import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression.ResultType;
+import gov.nist.secauto.metaschema.core.metapath.MetapathException;
+import gov.nist.secauto.metaschema.core.metapath.function.regex.RegularExpressionMetapathException;
+import gov.nist.secauto.metaschema.core.metapath.item.IItem;
+import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
+import gov.nist.secauto.metaschema.core.util.ObjectUtils;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+class FnFunctionLookupTest
+ extends ExpressionTestBase {
+
+ private static Stream provideValues() { // NOPMD - false positive
+ return Stream.of(
+ Arguments.of(
+ string("bcd"),
+ "function-lookup('substring', 2)('abcd', 2)"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideValues")
+ void test(@NonNull IItem expected, @NonNull String metapath) {
+ assertEquals(expected, IMetapathExpression.compile(metapath).evaluateAs(null, ResultType.ITEM, newDynamicContext()));
+ }
+}