Permalink
Browse files

[bugfix] Fix higher-order utility functions to comply with latest XQu…

…ery spec. Order of arguments to filter, fold-left and fold-right has changed. Added hack to preserve backwards compatibility for fn:filter: dashboard would crash if users update.
  • Loading branch information...
1 parent 56a22e7 commit 0ecdf809530818ae1f8c74d5e7cb1ac80fa70d47 @wolfgangmm wolfgangmm committed Aug 23, 2014
View
2 src/org/exist/xquery/functions/fn/FnModule.java
@@ -220,6 +220,8 @@
new FunctionDef(FunHigherOrderFun.signatures[2], FunHigherOrderFun.class),
new FunctionDef(FunHigherOrderFun.signatures[3], FunHigherOrderFun.class),
new FunctionDef(FunHigherOrderFun.signatures[4], FunHigherOrderFun.class),
+ new FunctionDef(FunHigherOrderFun.signatures[5], FunHigherOrderFun.class),
+ new FunctionDef(FunHigherOrderFun.signatures[6], FunHigherOrderFun.class),
new FunctionDef(FunEnvironment.signature[0], FunEnvironment.class),
new FunctionDef(FunEnvironment.signature[1], FunEnvironment.class),
new FunctionDef(ParsingFunctions.signatures[0], ParsingFunctions.class),
View
122 src/org/exist/xquery/functions/fn/FunHigherOrderFun.java
@@ -20,6 +20,33 @@
public class FunHigherOrderFun extends BasicFunction {
+ public final static FunctionSignature FN_FOR_EACH =
+ new FunctionSignature(
+ new QName("for-each", Function.BUILTIN_FUNCTION_NS),
+ "Applies the function item $function to every item from the sequence " +
+ "$sequence in turn, returning the concatenation of the resulting sequences in order.",
+ new SequenceType[] {
+ new FunctionParameterSequenceType("sequence", Type.ITEM, Cardinality.ZERO_OR_MORE, "the sequence on which to apply the function"),
+ new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "the function to call")
+ },
+ new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE,
+ "result of applying the function to each item of the sequence")
+ );
+
+ public final static FunctionSignature FN_FOR_EACH_PAIR =
+ new FunctionSignature(
+ new QName("for-each-pair", Function.BUILTIN_FUNCTION_NS),
+ "Applies the function item $f to successive pairs of items taken one from $seq1 and one from $seq2, " +
+ "returning the concatenation of the resulting sequences in order.",
+ new SequenceType[] {
+ new FunctionParameterSequenceType("seq1", Type.ITEM, Cardinality.ZERO_OR_MORE, "first sequence to take items from"),
+ new FunctionParameterSequenceType("seq2", Type.ITEM, Cardinality.ZERO_OR_MORE, "second sequence to take items from"),
+ new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "the function to call")
+ },
+ new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE,
+ "concatenation of resulting sequences")
+ );
+
public final static FunctionSignature signatures[] = {
new FunctionSignature(
new QName("map", Function.BUILTIN_FUNCTION_NS),
@@ -30,13 +57,16 @@
new FunctionParameterSequenceType("sequence", Type.ITEM, Cardinality.ZERO_OR_MORE, "the sequence on which to apply the function")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE,
- "result of applying the function to each item of the sequence")),
+ "result of applying the function to each item of the sequence"),
+ FN_FOR_EACH
+ ),
+ FN_FOR_EACH,
new FunctionSignature(
new QName("filter", Function.BUILTIN_FUNCTION_NS),
"Returns those items from the sequence $sequence for which the supplied function $function returns true.",
new SequenceType[] {
- new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "the function to call"),
- new FunctionParameterSequenceType("sequence", Type.ITEM, Cardinality.ZERO_OR_MORE, "the sequence to filter")
+ new FunctionParameterSequenceType("sequence", Type.ITEM, Cardinality.ZERO_OR_MORE, "the sequence to filter"),
+ new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "the function to call")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE,
"result of filtering the sequence")),
@@ -45,9 +75,9 @@
"Processes the supplied sequence from left to right, applying the supplied function repeatedly to each " +
"item in turn, together with an accumulated result value.",
new SequenceType[] {
- new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "the function to call"),
- new FunctionParameterSequenceType("zero", Type.ITEM, Cardinality.ZERO_OR_MORE, "initial value to start with"),
- new FunctionParameterSequenceType("sequence", Type.ITEM, Cardinality.ZERO_OR_MORE, "the sequence to filter")
+ new FunctionParameterSequenceType("sequence", Type.ITEM, Cardinality.ZERO_OR_MORE, "the sequence to filter"),
+ new FunctionParameterSequenceType("zero", Type.ITEM, Cardinality.ZERO_OR_MORE, "initial value to start with"),
+ new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "the function to call")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE,
"result of the fold-left operation")),
@@ -56,9 +86,9 @@
"Processes the supplied sequence from right to left, applying the supplied function repeatedly to each " +
"item in turn, together with an accumulated result value.",
new SequenceType[] {
+ new FunctionParameterSequenceType("sequence", Type.ITEM, Cardinality.ZERO_OR_MORE, "the sequence to filter"),
+ new FunctionParameterSequenceType("zero", Type.ITEM, Cardinality.ZERO_OR_MORE, "initial value to start with"),
new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "the function to call"),
- new FunctionParameterSequenceType("zero", Type.ITEM, Cardinality.ZERO_OR_MORE, "initial value to start with"),
- new FunctionParameterSequenceType("sequence", Type.ITEM, Cardinality.ZERO_OR_MORE, "the sequence to filter")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE,
"result of the fold-right operation")),
@@ -72,7 +102,10 @@
new FunctionParameterSequenceType("seq2", Type.ITEM, Cardinality.ZERO_OR_MORE, "second sequence to take items from")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE,
- "result of the map-pairs operation"))
+ "result of the map-pairs operation"),
+ FN_FOR_EACH_PAIR
+ ),
+ FN_FOR_EACH_PAIR
};
private AnalyzeContextInfo cachedContextInfo;
@@ -81,7 +114,18 @@ public FunHigherOrderFun(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
- @Override
+ @Override
+ protected void checkArguments() throws XPathException {
+ // hack: order of parameters for filter and other functions has changed
+ // in final XQ3 spec. This would cause some core apps (dashboard) to stop
+ // working. We thus switch parameters dynamically until all users can be expected to
+ // have updated to 2.2.
+ if (!isCalledAs("filter")) {
+ super.checkArguments();
+ }
+ }
+
+ @Override
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
cachedContextInfo = new AnalyzeContextInfo(contextInfo);
super.analyze(cachedContextInfo);
@@ -90,27 +134,47 @@ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
@Override
public Sequence eval(Sequence[] args, Sequence contextSequence)
throws XPathException {
- final FunctionReference ref = (FunctionReference)args[0];
-
- ref.analyze(cachedContextInfo);
-
Sequence result = new ValueSequence();
if (isCalledAs("map")) {
- for (final SequenceIterator i = args[1].iterate(); i.hasNext(); ) {
- final Item item = i.nextItem();
- final Sequence r = ref.evalFunction(contextSequence, null, new Sequence[] { item.toSequence() });
- result.addAll(r);
- }
+ final FunctionReference ref = (FunctionReference) args[0];
+ ref.analyze(cachedContextInfo);
+ for (final SequenceIterator i = args[1].iterate(); i.hasNext(); ) {
+ final Item item = i.nextItem();
+ final Sequence r = ref.evalFunction(contextSequence, null, new Sequence[]{item.toSequence()});
+ result.addAll(r);
+ }
+ } else if (isCalledAs("for-each")) {
+ final FunctionReference ref = (FunctionReference) args[1];
+ ref.analyze(cachedContextInfo);
+ for (final SequenceIterator i = args[0].iterate(); i.hasNext(); ) {
+ final Item item = i.nextItem();
+ final Sequence r = ref.evalFunction(contextSequence, null, new Sequence[]{item.toSequence()});
+ result.addAll(r);
+ }
} else if (isCalledAs("filter")) {
- for (final SequenceIterator i = args[1].iterate(); i.hasNext(); ) {
+ FunctionReference ref;
+ Sequence seq;
+ // Hack: switch parameters for backwards compatibility
+ if (Type.subTypeOf(args[1].getItemType(), Type.FUNCTION_REFERENCE)) {
+ ref = (FunctionReference) args[1];
+ seq = args[0];
+ } else {
+ ref = (FunctionReference) args[0];
+ seq = args[1];
+ }
+
+ ref.analyze(cachedContextInfo);
+ for (final SequenceIterator i = seq.iterate(); i.hasNext(); ) {
final Item item = i.nextItem();
final Sequence r = ref.evalFunction(contextSequence, null, new Sequence[] { item.toSequence() });
if (r.effectiveBooleanValue())
{result.add(item);}
}
} else if (isCalledAs("fold-left")) {
+ final FunctionReference ref = (FunctionReference) args[2];
+ ref.analyze(cachedContextInfo);
Sequence zero = args[1];
- Sequence input = args[2];
+ Sequence input = args[0];
while (!input.isEmpty()) {
final SequenceIterator i = input.iterate();
zero = ref.evalFunction(contextSequence, null, new Sequence[] { zero, i.nextItem().toSequence() });
@@ -122,17 +186,31 @@ public Sequence eval(Sequence[] args, Sequence contextSequence)
}
result = zero;
} else if (isCalledAs("fold-right")) {
+ final FunctionReference ref = (FunctionReference) args[2];
+ ref.analyze(cachedContextInfo);
final Sequence zero = args[1];
- final Sequence input = args[2];
+ final Sequence input = args[0];
result = foldRight(ref, zero, input, contextSequence);
} else if (isCalledAs("map-pairs")) {
+ final FunctionReference ref = (FunctionReference) args[0];
+ ref.analyze(cachedContextInfo);
final SequenceIterator i1 = args[1].iterate();
final SequenceIterator i2 = args[2].iterate();
while (i1.hasNext() && i2.hasNext()) {
final Sequence r = ref.evalFunction(contextSequence, null,
new Sequence[] { i1.nextItem().toSequence(), i2.nextItem().toSequence() });
result.addAll(r);
}
+ } else if (isCalledAs("for-each-pair")) {
+ final FunctionReference ref = (FunctionReference) args[2];
+ ref.analyze(cachedContextInfo);
+ final SequenceIterator i1 = args[0].iterate();
+ final SequenceIterator i2 = args[1].iterate();
+ while (i1.hasNext() && i2.hasNext()) {
+ final Sequence r = ref.evalFunction(contextSequence, null,
+ new Sequence[] { i1.nextItem().toSequence(), i2.nextItem().toSequence() });
+ result.addAll(r);
+ }
}
return result;
}
View
53 test/src/xquery/xquery3/higher-order.xml
@@ -451,43 +451,78 @@ return
<expected></expected>
</test>
<test output="text">
- <task>fn:map function</task>
+ <task>fn:map function (deprecated)</task>
<code>fn:map(function($a) { $a * $a }, 1 to 5)</code>
<expected>1 4 9 16 25</expected>
</test>
<test output="text">
- <task>fn:filter function</task>
+ <task>fn:for-each function</task>
+ <code>fn:for-each(1 to 5, function($a) { $a * $a })</code>
+ <expected>1 4 9 16 25</expected>
+ </test>
+ <test output="text">
+ <task>fn:for-each on strings</task>
+ <code>fn:for-each(("john", "jane"), fn:string-to-codepoints#1)</code>
+ <expected>106 111 104 110 106 97 110 101</expected>
+ </test>
+ <test output="text">
+ <task>fn:for-each with cast</task>
+ <code>fn:for-each(("23", "29"), xs:int#1)</code>
+ <expected>23 29</expected>
+ </test>
+ <test output="text">
+ <task>fn:filter function (wrong argument order: deprecated)</task>
<code>fn:filter(function($a) {$a mod 2 = 0}, 1 to 10)</code>
<expected>2 4 6 8 10</expected>
</test>
<test output="text">
+ <task>fn:filter function (correct argument order)</task>
+ <code>fn:filter(1 to 10, function($a) {$a mod 2 = 0})</code>
+ <expected>2 4 6 8 10</expected>
+ </test>
+ <test output="text">
<task>fn:fold-left function 1</task>
- <code> fn:fold-left(function($a, $b) {($b, $a)}, (), 1 to 5)</code>
+ <code> fn:fold-left(1 to 5, (), function($a, $b) {($b, $a)})</code>
<expected>5 4 3 2 1</expected>
</test>
<test output="text">
<task>fn:fold-left function 2</task>
- <code>fold-left(function($a, $b) { $a * $b }, 1, (2,3,5,7))</code>
+ <code>fold-left((2,3,5,7), 1, function($a, $b) { $a * $b })</code>
<expected>210</expected>
</test>
<test output="text">
+ <task>fn:fold-left with partial application</task>
+ <code>fn:fold-left(1 to 5, "", fn:concat(?, ".", ?))</code>
+ <expected>.1.2.3.4.5</expected>
+ </test>
+ <test output="text">
<task>fn:fold-right function 1</task>
- <code>fn:fold-right(function($a, $b) { $a + $b }, 0, 1 to 5)</code>
+ <code>fn:fold-right(1 to 5, 0, function($a, $b) { $a + $b })</code>
<expected>15</expected>
</test>
<test output="text">
<task>fn:fold-right function 2</task>
- <code>fn:fold-right(function($a, $b) { concat($a, ".", $b) }, "", 1 to 5)</code>
+ <code>fn:fold-right(1 to 5, "", function($a, $b) { concat($a, ".", $b) })</code>
<expected>1.2.3.4.5.</expected>
</test>
<test output="text">
- <task>fn:map-pairs function</task>
+ <task>fn:map-pairs function (deprecated)</task>
<code>fn:map-pairs(function($a, $b){10*$a + $b}, 1 to 5, 1 to 5)</code>
<expected>11 22 33 44 55</expected>
</test>
+ <test output="text">
+ <task>fn:for-each-pair function</task>
+ <code>fn:for-each-pair(1 to 5, 1 to 5, function($a, $b){10*$a + $b})</code>
+ <expected>11 22 33 44 55</expected>
+ </test>
+ <test output="text">
+ <task>fn:for-each-pair with strings</task>
+ <code>fn:for-each-pair(("a", "b", "c"), ("x", "y", "z"), concat#2)</code>
+ <expected>ax by cz</expected>
+ </test>
<!-- Partial function applications -->
<test output="text" id="partial1">
- <task>Partial function with fn:map</task>
+ <task>Partial function with fn:for-each</task>
<code><![CDATA[xquery version "3.0";
declare namespace ex="http://exist-db.org/xquery/ex";
@@ -499,7 +534,7 @@ declare function ex:multiply($base, $number) {
(: Use function reference literal to find function at compile time :)
let $fMultiply := ex:multiply(10, ?)
return
- map($fMultiply, 1 to 10)]]></code>
+ for-each(1 to 10, $fMultiply)]]></code>
<expected>10 20 30 40 50 60 70 80 90 100</expected>
</test>
<test output="text" id="partial2">

0 comments on commit 0ecdf80

Please sign in to comment.