Skip to content

Commit

Permalink
Merge pull request #309 from wolfgangmm/develop
Browse files Browse the repository at this point in the history
[bugfix] Fix higher-order utility functions to comply with latest XQuery...
  • Loading branch information
dizzzz committed Aug 23, 2014
2 parents 56a22e7 + 0ecdf80 commit dd91664
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 31 deletions.
2 changes: 2 additions & 0 deletions src/org/exist/xquery/functions/fn/FnModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ public class FnModule extends AbstractInternalModule {
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),
Expand Down
122 changes: 100 additions & 22 deletions src/org/exist/xquery/functions/fn/FunHigherOrderFun.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -30,13 +57,16 @@ public class FunHigherOrderFun extends BasicFunction {
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")),
Expand All @@ -45,9 +75,9 @@ public class FunHigherOrderFun extends BasicFunction {
"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")),
Expand All @@ -56,9 +86,9 @@ public class FunHigherOrderFun extends BasicFunction {
"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")),
Expand All @@ -72,7 +102,10 @@ public class FunHigherOrderFun extends BasicFunction {
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;
Expand All @@ -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);
Expand All @@ -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() });
Expand All @@ -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;
}
Expand Down
53 changes: 44 additions & 9 deletions test/src/xquery/xquery3/higher-order.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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">
Expand Down

0 comments on commit dd91664

Please sign in to comment.