mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 08:52:58 +03:00
sort handles incomparable values (#5998)
* Update type ascriptions in some operators in Any
* Add @GenerateUncached to AnyToTextNode.
Will be used in another node with @GenerateUncached.
* Add tests for "sort handles incomparable types"
* Vector.sort handles incomparable types
* Implement sort handling for different comparators
* Comparison operators in Any do not throw Type_Error
* Fix some issues in Ordering_Spec
* Remove the remaining comparison operator overrides for numbers.
* Consolidate all sorting functionality into a single builtin node.
* Fix warnings attachment in sort
* PrimitiveValuesComparator handles other types than primitives
* Fix byFunc calling
* on function can be called from the builtin
* Fix build of native image
* Update changelog
* Add VectorSortTest
* Builtin method should not throw DataflowError.
If yes, the message is discarded (a bug?)
* TypeOfNode may not return only Type
* UnresolvedSymbol is not supported as `on` argument to Vector.sort_builtin
* Fix docs
* Fix bigint spec in LessThanNode
* Small fixes
* Small fixes
* Nothings and Nans are sorted at the end of default comparator group.
But not at the whole end of the resulting vector.
* Fix checking of `by` parameter - now accepts functions with default arguments.
* Fix changelog formatting
* Fix imports in DebuggingEnsoTest
* Remove Array.sort_builtin
* Add comparison operators to micro-distribution
* Remove Array.sort_builtin
* Replace Incomparable_Values by Type_Error in some tests
* Add on_incomparable argument to Vector.sort_builtin
* Fix after merge - Array.sort delegates to Vector.sort
* Add more tests for problem_behavior on Vector.sort
* SortVectorNode throws only Incomparable_Values.
* Delete Collections helper class
* Add test for expected failure for custom incomparable values
* Cosmetics.
* Fix test expecting different comparators warning
* isNothing is checked via interop
* Remove TruffleLogger from SortVectorNode
* Small review refactorings
* Revert "Remove the remaining comparison operator overrides for numbers."
This reverts commit 0df66b1080
.
* Improve bench_download.py tool's `--compare` functionality.
- Output table is sorted by benchmark labels.
- Do not fail when there are different benchmark labels in both runs.
* Wrap potential interop values with `HostValueToEnsoNode`
* Use alter function in Vector_Spec
* Update docs
* Invalid comparison throws Incomparable_Values rather than Type_Error
* Number comparison builtin methods return Nothing in case of incomparables
This commit is contained in:
parent
64ecaa85e3
commit
b42e910280
@ -684,6 +684,7 @@
|
||||
- [Ensure calls involving warnings remain instrumented][6067]
|
||||
- [One can define lazy atom fields][6151]
|
||||
- [Replace IOContexts with Execution Environment and generic Context][6171]
|
||||
- [Vector.sort handles incomparable types][5998]
|
||||
|
||||
[3227]: https://github.com/enso-org/enso/pull/3227
|
||||
[3248]: https://github.com/enso-org/enso/pull/3248
|
||||
@ -789,6 +790,7 @@
|
||||
[6067]: https://github.com/enso-org/enso/pull/6067
|
||||
[6151]: https://github.com/enso-org/enso/pull/6151
|
||||
[6171]: https://github.com/enso-org/enso/pull/6171
|
||||
[5998]: https://github.com/enso-org/enso/pull/5998
|
||||
|
||||
# Enso 2.0.0-alpha.18 (2021-10-12)
|
||||
|
||||
|
@ -481,11 +481,11 @@ type Any
|
||||
|
||||
## PRIVATE
|
||||
Checks if the comparators for the given objects are both of the same type. If so,
|
||||
proceeds with the given action, and if not, throws `Type_Error`.
|
||||
assert_same_comparators : Any -> Any -> (Any -> Any) -> Any ! Type_Error
|
||||
proceeds with the given action, and if not, throws `Incomparable_Values` error.
|
||||
assert_same_comparators : Any -> Any -> (Any -> Any) -> Any ! Incomparable_Values
|
||||
assert_same_comparators this that ~action =
|
||||
comp_this = Comparable.from this
|
||||
comp_that = Comparable.from that
|
||||
case Meta.is_same_object comp_this comp_that of
|
||||
True -> action
|
||||
False -> Error.throw (Type_Error.Error (Meta.type_of comp_this) comp_that "comp_that")
|
||||
False -> Error.throw (Incomparable_Values.Error this that)
|
||||
|
@ -14,6 +14,7 @@ import project.Error.Error
|
||||
import project.Errors.Common.Incomparable_Values
|
||||
import project.Errors.Common.Not_Found
|
||||
import project.Errors.Common.Index_Out_Of_Bounds
|
||||
import project.Errors.Problem_Behavior.Problem_Behavior
|
||||
import project.Meta
|
||||
import project.Nothing.Nothing
|
||||
import project.Panic.Panic
|
||||
@ -160,13 +161,17 @@ type Array
|
||||
Arguments:
|
||||
- order: The order in which the array elements are sorted.
|
||||
- on: A projection from the element type to the value of that element
|
||||
being sorted on.
|
||||
being sorted on. If set to `Nothing` (the default),
|
||||
identity function will be used.
|
||||
- by: A function that compares the result of applying `on` to two
|
||||
elements, returning an Ordering to compare them.
|
||||
elements, returning an an `Ordering` if the two elements are comparable
|
||||
or `Nothing` if they are not. If set to `Nothing` (the default argument),
|
||||
`Ordering.compare _ _` method will be used.
|
||||
- on_incomparable: A `Problem_Behavior` specifying what should happen if
|
||||
two incomparable values are encountered.
|
||||
|
||||
By default, elements are sorted in ascending order.
|
||||
|
||||
By default, elements are sorted in ascending order, using the comparator
|
||||
acquired from each element. A custom compare function may be passed to
|
||||
the sort method.
|
||||
|
||||
This is a stable sort, meaning that items that compare the same will not
|
||||
have their order changed by the sorting process.
|
||||
@ -178,12 +183,32 @@ type Array
|
||||
- *Average Time:* `O(n * log n)`
|
||||
- *Worst-Case Space:* `O(n)` additional
|
||||
|
||||
? Incomparable values
|
||||
Incomparable values are either values with different comparators or with
|
||||
the same comparator returning `Nothing` from its `compare` method.
|
||||
See the documentation of the `Ordering` module for more info.
|
||||
|
||||
? Implementation Note
|
||||
The sort implementation is based upon an adaptive, iterative mergesort
|
||||
that requires far fewer than `n * log(n)` comparisons when the array
|
||||
is partially sorted. When the array is randomly ordered, the
|
||||
performance is equivalent to a standard mergesort.
|
||||
|
||||
? Multiple comparators
|
||||
Elements with different comparators are incomparable by definition.
|
||||
This case is handled by first grouping the `self` array into groups
|
||||
with the same comparator, recursively sorting these groups, and then
|
||||
merging them back together. The order of the sorted groups in the
|
||||
resulting array is based on the order of fully qualified names of
|
||||
the comparators in the `self` array, with the exception of the group
|
||||
for the default comparator, which is always the first group.
|
||||
|
||||
Additionally, an `Incomparable_Values` dataflow error will be returned
|
||||
if the `on_incomparable` parameter is set to `Problem_Behavior.Report_Error`,
|
||||
or a warning attached if the `on_incomparable` parameter is set to
|
||||
`Problem_Behavior.Report_Warning` in case of encountering incomparable
|
||||
values.
|
||||
|
||||
It takes equal advantage of ascending and descending runs in the array,
|
||||
making it much simpler to merge two or more sorted arrays: simply
|
||||
concatenate them and sort.
|
||||
@ -197,8 +222,16 @@ type Array
|
||||
Sorting an array of `Pair`s on the first element, descending.
|
||||
|
||||
[Pair 1 2, Pair -1 8].to_array.sort Sort_Direction.Descending (_.first)
|
||||
sort : Sort_Direction -> (Any -> Any) -> (Any -> Any -> Ordering) -> Vector Any ! Incomparable_Values
|
||||
sort self (order = Sort_Direction.Ascending) (on = x -> x) (by = (Ordering.compare _ _)) = Vector.sort self order on by
|
||||
|
||||
> Example
|
||||
Sorting an array with elements with different comparators. Values `1`
|
||||
and `My_Type` have different comparators. `1` will be sorted before `My_Type`
|
||||
because it has the default comparator.
|
||||
|
||||
[My_Type.Value 'hello', 1].to_array.sort == [1, My_Type.Value 'hello'].to_array
|
||||
sort : Sort_Direction -> (Any -> Any)|Nothing -> (Any -> Any -> (Ordering|Nothing))|Nothing -> Problem_Behavior -> Vector Any ! Incomparable_Values
|
||||
sort self (order = Sort_Direction.Ascending) on=Nothing by=Nothing on_incomparable=Problem_Behavior.Ignore =
|
||||
Vector.sort self order on by on_incomparable
|
||||
|
||||
## Creates a new `Vector` with only the specified range of elements from the
|
||||
input, removing any elements outside the range.
|
||||
|
@ -117,7 +117,7 @@ invert_range_selection ranges length needs_sorting =
|
||||
Empty subranges are discarded.
|
||||
sort_and_merge_ranges : Vector Range -> Vector Range
|
||||
sort_and_merge_ranges ranges =
|
||||
sorted = ranges.filter (range-> range.is_empty.not) . sort on=(.start)
|
||||
sorted = ranges.filter (range-> range.is_empty.not) . sort on=(_.start)
|
||||
if sorted.is_empty then [] else
|
||||
current_ref = Ref.new sorted.first
|
||||
builder = Vector.new_builder
|
||||
|
@ -1,6 +1,7 @@
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Locale.Locale
|
||||
import project.Errors.Common.Arithmetic_Error
|
||||
import project.Errors.Common.Incomparable_Values
|
||||
import project.Error.Error
|
||||
import project.Nothing.Nothing
|
||||
import project.Panic.Panic
|
||||
@ -279,6 +280,9 @@ type Number
|
||||
|
||||
## Checks equality of numbers, using an `epsilon` value.
|
||||
|
||||
! Error Conditions
|
||||
If either of the arguments is `Number.nan`, an `Incomparable_Values` error is raised.
|
||||
|
||||
Arguments:
|
||||
- that: The number to check equality against.
|
||||
- epsilon: The value by which `self` and `that` can be separated by before
|
||||
@ -288,7 +292,7 @@ type Number
|
||||
Check if 1 is equal to 1.0000001 within 0.001.
|
||||
|
||||
1.equals 1.0000001 epsilon=0.001
|
||||
equals : Number -> Number -> Boolean
|
||||
equals : Number -> Number -> Boolean ! Incomparable_Values
|
||||
equals self that epsilon=0.0 =
|
||||
(self == that) || ((self - that).abs <= epsilon)
|
||||
|
||||
|
@ -170,7 +170,7 @@ type Ordering
|
||||
Greater
|
||||
|
||||
## Compares to values and returns an Ordering
|
||||
compare : Any -> Any -> Ordering ! (Incomparable_Values | Type_Error)
|
||||
compare : Any -> Any -> Ordering ! Incomparable_Values
|
||||
compare x y =
|
||||
if x < y then Ordering.Less else
|
||||
if x == y then Ordering.Equal else
|
||||
|
@ -4,7 +4,6 @@ import project.Data.Filter_Condition.Filter_Condition
|
||||
import project.Data.List.List
|
||||
import project.Data.Map.Map
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Sort_Direction.Sort_Direction
|
||||
import project.Data.Pair.Pair
|
||||
import project.Data.Range.Range
|
||||
@ -17,12 +16,20 @@ import project.Errors.Common.Not_Found
|
||||
import project.Errors.Common.Type_Error
|
||||
import project.Error.Error
|
||||
import project.Errors.Illegal_Argument.Illegal_Argument
|
||||
import project.Errors.Problem_Behavior.Problem_Behavior
|
||||
import project.Function.Function
|
||||
import project.Math
|
||||
import project.Meta
|
||||
import project.Nothing.Nothing
|
||||
import project.Panic.Panic
|
||||
import project.Random
|
||||
import project.Warning.Warning
|
||||
|
||||
import project.IO
|
||||
|
||||
## We have to import also conversion methods, therefore, we import all from the Ordering
|
||||
module
|
||||
from project.Data.Ordering import all
|
||||
from project.Data.Boolean import Boolean, True, False
|
||||
from project.Data.Index_Sub_Range import Index_Sub_Range, take_helper, drop_helper
|
||||
|
||||
@ -841,13 +848,17 @@ type Vector a
|
||||
Arguments:
|
||||
- order: The order in which the vector elements are sorted.
|
||||
- on: A projection from the element type to the value of that element
|
||||
being sorted on.
|
||||
being sorted on. If set to `Nothing` (the default),
|
||||
identity function will be used.
|
||||
- by: A function that compares the result of applying `on` to two
|
||||
elements, returning an Ordering to compare them.
|
||||
elements, returning an an `Ordering` if the two elements are comparable
|
||||
or `Nothing` if they are not. If set to `Nothing` (the default argument),
|
||||
`Ordering.compare _ _` method will be used.
|
||||
- on_incomparable: A `Problem_Behavior` specifying what should happen if
|
||||
two incomparable values are encountered.
|
||||
|
||||
By default, elements are sorted in ascending order.
|
||||
|
||||
By default, elements are sorted in ascending order, using the comparator
|
||||
acquired from each element. A custom compare function may be passed to
|
||||
the sort method.
|
||||
|
||||
This is a stable sort, meaning that items that compare the same will not
|
||||
have their order changed by the sorting process.
|
||||
@ -859,12 +870,32 @@ type Vector a
|
||||
- *Average Time:* `O(n * log n)`
|
||||
- *Worst-Case Space:* `O(n)` additional
|
||||
|
||||
? Incomparable values
|
||||
Incomparable values are either values with different comparators or with
|
||||
the same comparator returning `Nothing` from its `compare` method.
|
||||
See the documentation of the `Ordering` module for more info.
|
||||
|
||||
? Implementation Note
|
||||
The sort implementation is based upon an adaptive, iterative mergesort
|
||||
that requires far fewer than `n * log(n)` comparisons when the vector
|
||||
is partially sorted. When the vector is randomly ordered, the
|
||||
performance is equivalent to a standard mergesort.
|
||||
|
||||
? Multiple comparators
|
||||
Elements with different comparators are incomparable by definition.
|
||||
This case is handled by first grouping the `self` vector into groups
|
||||
with the same comparator, recursively sorting these groups, and then
|
||||
merging them back together. The order of the sorted groups in the
|
||||
resulting vector is based on the order of fully qualified names of
|
||||
the comparators in the `self` vector, with the exception of the group
|
||||
for the default comparator, which is always the first group.
|
||||
|
||||
Additionally, an `Incomparable_Values` dataflow error will be returned
|
||||
if the `on_incomparable` parameter is set to `Problem_Behavior.Report_Error`,
|
||||
or a warning attached if the `on_incomparable` parameter is set to
|
||||
`Problem_Behavior.Report_Warning` in case of encountering incomparable
|
||||
values.
|
||||
|
||||
It takes equal advantage of ascending and descending runs in the array,
|
||||
making it much simpler to merge two or more sorted arrays: simply
|
||||
concatenate them and sort.
|
||||
@ -878,16 +909,20 @@ type Vector a
|
||||
Sorting a vector of `Pair`s on the first element, descending.
|
||||
|
||||
[Pair 1 2, Pair -1 8].sort Sort_Direction.Descending (_.first)
|
||||
sort : Sort_Direction -> (Any -> Any) -> (Any -> Any -> Ordering) -> Vector Any ! Incomparable_Values
|
||||
sort self (order = Sort_Direction.Ascending) (on = x -> x) (by = (Ordering.compare _ _)) =
|
||||
comp_ascending l r = by (on l) (on r)
|
||||
comp_descending l r = by (on r) (on l)
|
||||
compare = if order == Sort_Direction.Ascending then comp_ascending else
|
||||
comp_descending
|
||||
|
||||
new_vec_arr = self.to_array.sort_builtin compare
|
||||
if new_vec_arr.is_error then Error.throw new_vec_arr else
|
||||
Vector.from_polyglot_array new_vec_arr
|
||||
> Example
|
||||
Sorting a vector with elements with different comparators. Values `1`
|
||||
and `My_Type` have different comparators. `1` will be sorted before `My_Type`
|
||||
because it has the default comparator.
|
||||
|
||||
[My_Type.Value 'hello', 1].sort == [1, My_Type.Value 'hello']
|
||||
sort : Sort_Direction -> (Any -> Any)|Nothing -> (Any -> Any -> (Ordering|Nothing))|Nothing -> Problem_Behavior -> Vector Any ! Incomparable_Values
|
||||
sort self (order = Sort_Direction.Ascending) on=Nothing by=Nothing on_incomparable=Problem_Behavior.Ignore =
|
||||
comps = case on == Nothing of
|
||||
True -> self.map it-> Comparable.from it
|
||||
False -> self.map it-> Comparable.from (on it)
|
||||
compare_funcs = comps.map (it-> it.compare)
|
||||
self.sort_builtin order.to_sign comps compare_funcs by on on_incomparable.to_number
|
||||
|
||||
## UNSTABLE
|
||||
Keeps only unique elements within the vector, removing any duplicates.
|
||||
|
@ -164,3 +164,11 @@ type Problem_Behavior
|
||||
warnings = Warning.get_all result . map .value
|
||||
cleared_result = Warning.set result []
|
||||
self.attach_problems_after cleared_result warnings
|
||||
|
||||
## PRIVATE
|
||||
Returns a mapping of Problem_Behavior constructors to an integer.
|
||||
Used for sending the number to Java, rather than sending the atom.
|
||||
to_number self = case self of
|
||||
Ignore -> 0
|
||||
Report_Warning -> 1
|
||||
Report_Error -> 2
|
||||
|
@ -3,7 +3,7 @@ from Standard.Base import all
|
||||
from project import Test
|
||||
import project.Extensions
|
||||
|
||||
## Returns values of warnings attached to the value.Nothing
|
||||
## Returns values of warnings attached to the value.
|
||||
get_attached_warnings v =
|
||||
Warning.get_all v . map .value
|
||||
|
||||
|
@ -9,7 +9,7 @@ public final class HostEnsoUtils {
|
||||
/**
|
||||
* Extracts a string representation for a polyglot exception.
|
||||
*
|
||||
* @param exexception the exception
|
||||
* @param ex the exception
|
||||
* @return message representing the exception
|
||||
*/
|
||||
public static String findExceptionMessage(Throwable ex) {
|
||||
|
@ -2,6 +2,7 @@ package org.enso.interpreter.node.expression.builtin.meta;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
@ -26,6 +27,7 @@ import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
name = "type_of",
|
||||
description = "Returns the type of a value.",
|
||||
autoRegister = false)
|
||||
@GenerateUncached
|
||||
public abstract class TypeOfNode extends Node {
|
||||
|
||||
public abstract Object execute(@AcceptsError Object value);
|
||||
|
@ -1,160 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.mutable;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.LoopNode;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.profiles.BranchProfile;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.callable.dispatch.CallOptimiserNode;
|
||||
import org.enso.interpreter.node.callable.dispatch.SimpleCallOptimiserNode;
|
||||
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.Array;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.error.PanicException;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
|
||||
@BuiltinMethod(type = "Array", name = "sort_builtin", description = "Returns a sorted array.")
|
||||
public abstract class SortNode extends Node {
|
||||
private @Child CallOptimiserNode callOptimiserNode = SimpleCallOptimiserNode.build();
|
||||
private @Child InvalidComparisonNode invalidComparisonNode = InvalidComparisonNode.build();
|
||||
private final BranchProfile invalidCompareResultProfile = BranchProfile.create();
|
||||
|
||||
abstract Object execute(State state, Object self, Object comparator);
|
||||
|
||||
static SortNode build() {
|
||||
return SortNodeGen.create();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Object doArray(State state, Array self, Function comparator) {
|
||||
EnsoContext context = EnsoContext.get(this);
|
||||
int size = self.getItems().length;
|
||||
Object[] newArr = new Object[size];
|
||||
System.arraycopy(self.getItems(), 0, newArr, 0, size);
|
||||
|
||||
try {
|
||||
return getComparatorAndSort(state, newArr, comparator, context);
|
||||
} catch (CompareException e) {
|
||||
return DataflowError.withoutTrace(
|
||||
incomparableValuesError(e.leftOperand, e.rightOperand), this);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = "arrays.hasArrayElements(self)")
|
||||
Object doPolyglotArray(
|
||||
State state,
|
||||
Object self,
|
||||
Function comparator,
|
||||
@CachedLibrary(limit = "3") InteropLibrary arrays,
|
||||
@Cached HostValueToEnsoNode hostValueToEnsoNode) {
|
||||
long size;
|
||||
Object[] newArray;
|
||||
try {
|
||||
size = arrays.getArraySize(self);
|
||||
newArray = new Object[(int) size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
newArray[i] = hostValueToEnsoNode.execute(arrays.readArrayElement(self, i));
|
||||
}
|
||||
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
return getComparatorAndSort(state, newArray, comparator, EnsoContext.get(this));
|
||||
} catch (CompareException e) {
|
||||
return DataflowError.withoutTrace(
|
||||
incomparableValuesError(e.leftOperand, e.rightOperand), this);
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
Object doOther(State state, Object self, Object comparator) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
var fun = EnsoContext.get(this).getBuiltins().function();
|
||||
throw new PanicException(
|
||||
EnsoContext.get(this).getBuiltins().error().makeTypeError(fun, comparator, "comparator"),
|
||||
this);
|
||||
}
|
||||
|
||||
private Object getComparatorAndSort(
|
||||
State state, Object[] rawItems, Function comparator, EnsoContext context) {
|
||||
Comparator<Object> compare = new SortComparator(comparator, context, this, state);
|
||||
runSort(compare, rawItems);
|
||||
return new Array(rawItems);
|
||||
}
|
||||
|
||||
Object[] runSort(Comparator<Object> compare, Object[] self) {
|
||||
doSort(self, compare);
|
||||
LoopNode.reportLoopCount(this, self.length);
|
||||
return self;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
void doSort(Object[] items, Comparator<Object> compare) {
|
||||
Arrays.sort(items, compare);
|
||||
}
|
||||
|
||||
private Object incomparableValuesError(Object left, Object right) {
|
||||
return EnsoContext.get(this).getBuiltins().error().makeIncomparableValues(left, right);
|
||||
}
|
||||
|
||||
private class SortComparator implements Comparator<Object> {
|
||||
private final Function compFn;
|
||||
private final EnsoContext context;
|
||||
private final Atom less;
|
||||
private final Atom equal;
|
||||
private final Atom greater;
|
||||
private final SortNode outerThis;
|
||||
private final State state;
|
||||
|
||||
SortComparator(Function compFn, EnsoContext context, SortNode outerThis, State state) {
|
||||
this.compFn = compFn;
|
||||
this.context = context;
|
||||
this.less = context.getBuiltins().ordering().newLess();
|
||||
this.equal = context.getBuiltins().ordering().newEqual();
|
||||
this.greater = context.getBuiltins().ordering().newGreater();
|
||||
this.outerThis = outerThis;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Object o1, Object o2) {
|
||||
Object res = callOptimiserNode.executeDispatch(compFn, null, state, new Object[] {o1, o2});
|
||||
if (res == less) {
|
||||
return -1;
|
||||
} else if (res == equal) {
|
||||
return 0;
|
||||
} else if (res == greater) {
|
||||
return 1;
|
||||
} else {
|
||||
invalidCompareResultProfile.enter();
|
||||
throw new CompareException(o1, o2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CompareException extends RuntimeException {
|
||||
final Object leftOperand;
|
||||
final Object rightOperand;
|
||||
|
||||
private CompareException(Object leftOperand, Object rightOperand) {
|
||||
this.leftOperand = leftOperand;
|
||||
this.rightOperand = rightOperand;
|
||||
}
|
||||
}
|
||||
}
|
@ -34,9 +34,9 @@ public abstract class GreaterNode extends Node {
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(EnsoBigInteger self, Object that) {
|
||||
Object doOther(EnsoBigInteger self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableValsErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableValsErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +34,9 @@ public abstract class GreaterOrEqualNode extends Node {
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(EnsoBigInteger self, Object that) {
|
||||
Object doOther(EnsoBigInteger self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableValsErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableValsErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +34,9 @@ public abstract class LessNode extends Node {
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(EnsoBigInteger self, Object that) {
|
||||
Object doOther(EnsoBigInteger self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableValsErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableValsErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +34,9 @@ public abstract class LessOrEqualNode extends Node {
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(EnsoBigInteger self, Object that) {
|
||||
Object doOther(EnsoBigInteger self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableValsErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableValsErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@ -19,24 +20,40 @@ public abstract class GreaterNode extends Node {
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doDouble(double self, double that) {
|
||||
return self > that;
|
||||
Object doDouble(double self, double that) {
|
||||
if (Double.isNaN(self) || Double.isNaN(that)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self > that;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doLong(double self, long that) {
|
||||
return self > (double) that;
|
||||
Object doLong(double self, long that) {
|
||||
if (Double.isNaN(self)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self > (double) that;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doBigInteger(double self, EnsoBigInteger that) {
|
||||
return self > BigIntegerOps.toDouble(that.getValue());
|
||||
Object doBigInteger(double self, EnsoBigInteger that) {
|
||||
if (Double.isNaN(self)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self > BigIntegerOps.toDouble(that.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(double self, Object that) {
|
||||
Object doOther(double self, Object that) {
|
||||
return incomparableError(self, that);
|
||||
}
|
||||
|
||||
private DataflowError incomparableError(Object self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@ -19,24 +20,40 @@ public abstract class GreaterOrEqualNode extends Node {
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doDouble(double self, double that) {
|
||||
return self >= that;
|
||||
Object doDouble(double self, double that) {
|
||||
if (Double.isNaN(self) || Double.isNaN(that)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self >= that;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doLong(double self, long that) {
|
||||
return self >= (double) that;
|
||||
Object doLong(double self, long that) {
|
||||
if (Double.isNaN(self)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self >= (double) that;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doBigInteger(double self, EnsoBigInteger that) {
|
||||
return self >= BigIntegerOps.toDouble(that.getValue());
|
||||
Object doBigInteger(double self, EnsoBigInteger that) {
|
||||
if (Double.isNaN(self)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self >= BigIntegerOps.toDouble(that.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(double self, Object that) {
|
||||
Object doOther(double self, Object that) {
|
||||
return incomparableError(self, that);
|
||||
}
|
||||
|
||||
private DataflowError incomparableError(Object self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@ -19,24 +20,40 @@ public abstract class LessNode extends Node {
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doDouble(double self, double that) {
|
||||
return self < that;
|
||||
Object doDouble(double self, double that) {
|
||||
if (Double.isNaN(self) || Double.isNaN(that)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self < that;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doLong(double self, long that) {
|
||||
return self < (double) that;
|
||||
Object doLong(double self, long that) {
|
||||
if (Double.isNaN(self)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self < (double) that;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doBigInteger(double self, EnsoBigInteger that) {
|
||||
return self < BigIntegerOps.toDouble(that.getValue());
|
||||
Object doBigInteger(double self, EnsoBigInteger that) {
|
||||
if (Double.isNaN(self)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self < BigIntegerOps.toDouble(that.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(double self, Object that) {
|
||||
Object doOther(double self, Object that) {
|
||||
return incomparableError(self, that);
|
||||
}
|
||||
|
||||
private DataflowError incomparableError(Object self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@ -19,24 +20,40 @@ public abstract class LessOrEqualNode extends Node {
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doDouble(double self, double that) {
|
||||
return self <= that;
|
||||
Object doDouble(double self, double that) {
|
||||
if (Double.isNaN(self) || Double.isNaN(that)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self <= that;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doLong(double self, long that) {
|
||||
return self <= (double) that;
|
||||
Object doLong(double self, long that) {
|
||||
if (Double.isNaN(self)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self <= (double) that;
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doBigInteger(double self, EnsoBigInteger that) {
|
||||
return self <= BigIntegerOps.toDouble(that.getValue());
|
||||
Object doBigInteger(double self, EnsoBigInteger that) {
|
||||
if (Double.isNaN(self)) {
|
||||
return incomparableError(self, that);
|
||||
} else {
|
||||
return self <= BigIntegerOps.toDouble(that.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(double self, Object that) {
|
||||
Object doOther(double self, Object that) {
|
||||
return incomparableError(self, that);
|
||||
}
|
||||
|
||||
private DataflowError incomparableError(Object self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ public abstract class GreaterNode extends Node {
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(long self, Object that) {
|
||||
Object doOther(long self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableValsErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableValsErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ public abstract class GreaterOrEqualNode extends Node {
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(long self, Object that) {
|
||||
Object doOther(long self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableValsErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableValsErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ public abstract class LessNode extends Node {
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(long self, Object that) {
|
||||
Object doOther(long self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableValsErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableValsErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ public abstract class LessOrEqualNode extends Node {
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(long self, Object that) {
|
||||
Object doOther(long self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
var incomparableValsErr = builtins.error().makeIncomparableValues(self, that);
|
||||
return DataflowError.withoutTrace(incomparableValsErr, this);
|
||||
}
|
||||
}
|
||||
|
@ -116,21 +116,13 @@ public abstract class LessThanNode extends Node {
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean lessLongBigInt(long self, EnsoBigInteger other) {
|
||||
if (BigIntegerOps.fitsInLong(other.getValue())) {
|
||||
return BigInteger.valueOf(self).compareTo(other.getValue()) < 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return BigInteger.valueOf(self).compareTo(other.getValue()) < 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean lessBigIntLong(EnsoBigInteger self, long other) {
|
||||
if (BigIntegerOps.fitsInLong(self.getValue())) {
|
||||
return self.getValue().compareTo(BigInteger.valueOf(other)) < 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return self.getValue().compareTo(BigInteger.valueOf(other)) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,29 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.AcceptsError;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
|
||||
/** Just a wrapper for {@code Array.sort_builtin} that delegates to {@code Vector.sort_builtin}. */
|
||||
@BuiltinMethod(type = "Array", name = "sort_builtin")
|
||||
public final class SortArrayNode extends Node {
|
||||
@Child private SortVectorNode sortVectorNode = SortVectorNode.build();
|
||||
|
||||
public Object execute(
|
||||
State state,
|
||||
@AcceptsError Object self,
|
||||
long ascending,
|
||||
Object comparators,
|
||||
Object compareFunctions,
|
||||
Object byFunc,
|
||||
Object onFunc,
|
||||
long problemBehavior) {
|
||||
return sortVectorNode.execute(
|
||||
state, self, ascending, comparators, compareFunctions, byFunc, onFunc, problemBehavior);
|
||||
}
|
||||
|
||||
public static SortArrayNode build() {
|
||||
return new SortArrayNode();
|
||||
}
|
||||
}
|
@ -0,0 +1,804 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.enso.interpreter.dsl.AcceptsError;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.callable.dispatch.CallOptimiserNode;
|
||||
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode;
|
||||
import org.enso.interpreter.node.expression.builtin.text.AnyToTextNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.Array;
|
||||
import org.enso.interpreter.runtime.data.ArrayRope;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.data.Vector;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.error.PanicException;
|
||||
import org.enso.interpreter.runtime.error.Warning;
|
||||
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
||||
import org.enso.interpreter.runtime.error.WithWarnings;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
|
||||
/**
|
||||
* Sorts a vector with elements that have only Default_Comparator, thus, only elements with a
|
||||
* builtin type, which is the most common scenario for sorting.
|
||||
*/
|
||||
@BuiltinMethod(type = "Vector", name = "sort_builtin", description = "Returns a sorted vector.")
|
||||
@GenerateUncached
|
||||
public abstract class SortVectorNode extends Node {
|
||||
|
||||
public static SortVectorNode build() {
|
||||
return SortVectorNodeGen.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts a vector with elements that have only Default_Comparator, thus, only builtin types.
|
||||
*
|
||||
* @param self Vector that has elements with only Default_Comparator, that are elements with
|
||||
* builtin types.
|
||||
* @param ascending -1 for descending, 1 for ascending
|
||||
* @param comparators Vector of comparators, with the same length of self. This is gather in the
|
||||
* Enso code, because doing that in this builtin would be difficult. If {@code onFunc}
|
||||
* parameter is not {@code Nothing}, comparators are gathered from the result of {@code
|
||||
* onFunc} projection.
|
||||
* @param compareFunctions Vector of `Comparator.compare` functions gathered from the comparators
|
||||
* @param byFunc If Nothing, then the default `by` function should be used. The default `by`
|
||||
* function is `Ordering.compare`.
|
||||
* @param onFunc If Nothing, then the default identity function should be used.
|
||||
* @param problemBehavior A long representation of `Problem_Behavior`. Ignore is 0, Report_warning
|
||||
* is 1, and Report_Error is 2.
|
||||
* @return A new, sorted vector.
|
||||
*/
|
||||
public abstract Object execute(
|
||||
State state,
|
||||
@AcceptsError Object self,
|
||||
long ascending,
|
||||
Object comparators,
|
||||
Object compareFunctions,
|
||||
Object byFunc,
|
||||
Object onFunc,
|
||||
long problemBehavior);
|
||||
|
||||
/**
|
||||
* Sorts primitive values, i.e., values with only Default_Comparator. We can optimize this case.
|
||||
* It is important that `byFunc` is Nothing, i.e., has the default value. In that case, we can
|
||||
* hard code the partial ordering for the primitive values. If `byFunc` is a custom user function,
|
||||
* it can redefine the default partial ordering of the primitive values, which requires
|
||||
* topological sort.
|
||||
*/
|
||||
@Specialization(
|
||||
guards = {
|
||||
"interop.hasArrayElements(self)",
|
||||
"areAllDefaultComparators(interop, hostValueToEnsoNode, comparators)",
|
||||
"interop.isNull(byFunc)",
|
||||
"interop.isNull(onFunc)"
|
||||
})
|
||||
Object sortPrimitives(
|
||||
State state,
|
||||
Object self,
|
||||
long ascending,
|
||||
Object comparators,
|
||||
Object compareFunctions,
|
||||
Object byFunc,
|
||||
Object onFunc,
|
||||
long problemBehavior,
|
||||
@Cached LessThanNode lessThanNode,
|
||||
@Cached EqualsNode equalsNode,
|
||||
@Cached HostValueToEnsoNode hostValueToEnsoNode,
|
||||
@Cached TypeOfNode typeOfNode,
|
||||
@Cached AnyToTextNode toTextNode,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop) {
|
||||
EnsoContext ctx = EnsoContext.get(this);
|
||||
Object[] elems;
|
||||
try {
|
||||
long size = interop.getArraySize(self);
|
||||
assert size < Integer.MAX_VALUE;
|
||||
elems = new Object[(int) size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (interop.isArrayElementReadable(self, i)) {
|
||||
elems[i] = hostValueToEnsoNode.execute(interop.readArrayElement(self, i));
|
||||
} else {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new PanicException(
|
||||
ctx.getBuiltins()
|
||||
.error()
|
||||
.makeUnsupportedArgumentsError(
|
||||
new Object[] {self},
|
||||
"Cannot read array element at index " + i + " of " + self),
|
||||
this);
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
|
||||
throw new IllegalStateException("Should not reach here", e);
|
||||
}
|
||||
var javaComparator =
|
||||
createDefaultComparator(
|
||||
lessThanNode, equalsNode, typeOfNode, toTextNode, ascending, problemBehavior, interop);
|
||||
try {
|
||||
return sortPrimitiveVector(elems, javaComparator);
|
||||
} catch (CompareException e) {
|
||||
return DataflowError.withoutTrace(
|
||||
incomparableValuesError(e.leftOperand, e.rightOperand), this);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private DefaultSortComparator createDefaultComparator(
|
||||
LessThanNode lessThanNode,
|
||||
EqualsNode equalsNode,
|
||||
TypeOfNode typeOfNode,
|
||||
AnyToTextNode toTextNode,
|
||||
long ascending,
|
||||
long problemBehaviorNum,
|
||||
InteropLibrary interop) {
|
||||
return new DefaultSortComparator(
|
||||
lessThanNode,
|
||||
equalsNode,
|
||||
typeOfNode,
|
||||
toTextNode,
|
||||
ascending > 0,
|
||||
ProblemBehavior.fromInt((int) problemBehaviorNum),
|
||||
interop);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Specialization(
|
||||
guards = {
|
||||
"interop.hasArrayElements(self)",
|
||||
})
|
||||
Object sortGeneric(
|
||||
State state,
|
||||
Object self,
|
||||
long ascending,
|
||||
Object comparatorsArray,
|
||||
Object compareFuncsArray,
|
||||
Object byFunc,
|
||||
Object onFunc,
|
||||
long problemBehaviorNum,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "5") WarningsLibrary warningsLib,
|
||||
@Cached LessThanNode lessThanNode,
|
||||
@Cached EqualsNode equalsNode,
|
||||
@Cached TypeOfNode typeOfNode,
|
||||
@Cached AnyToTextNode toTextNode,
|
||||
@Cached(value = "build()", uncached = "build()") HostValueToEnsoNode hostValueToEnsoNode,
|
||||
@Cached(value = "build()", uncached = "build()") CallOptimiserNode callNode) {
|
||||
var problemBehavior = ProblemBehavior.fromInt((int) problemBehaviorNum);
|
||||
// Split into groups
|
||||
List<Object> elems = readInteropArray(interop, hostValueToEnsoNode, warningsLib, self);
|
||||
List<Type> comparators =
|
||||
readInteropArray(interop, hostValueToEnsoNode, warningsLib, comparatorsArray);
|
||||
List<Function> compareFuncs =
|
||||
readInteropArray(interop, hostValueToEnsoNode, warningsLib, compareFuncsArray);
|
||||
List<Group> groups = splitByComparators(elems, comparators, compareFuncs);
|
||||
|
||||
// Prepare input for DefaultSortComparator and GenericSortComparator and sort the elements
|
||||
// within groups
|
||||
var ctx = EnsoContext.get(this);
|
||||
Atom less = ctx.getBuiltins().ordering().newLess();
|
||||
Atom equal = ctx.getBuiltins().ordering().newEqual();
|
||||
Atom greater = ctx.getBuiltins().ordering().newGreater();
|
||||
Set<String> gatheredWarnings = new HashSet<>();
|
||||
List<Object> resultVec = new ArrayList<>();
|
||||
try {
|
||||
for (var group : groups) {
|
||||
SortComparator javaComparator;
|
||||
if (interop.isNull(byFunc) && interop.isNull(onFunc) && isPrimitiveGroup(group)) {
|
||||
javaComparator =
|
||||
new DefaultSortComparator(
|
||||
lessThanNode,
|
||||
equalsNode,
|
||||
typeOfNode,
|
||||
toTextNode,
|
||||
ascending > 0,
|
||||
problemBehavior,
|
||||
interop);
|
||||
} else {
|
||||
Object compareFunc = interop.isNull(byFunc) ? group.compareFunc : byFunc;
|
||||
javaComparator =
|
||||
new GenericSortComparator(
|
||||
ascending > 0,
|
||||
compareFunc,
|
||||
onFunc,
|
||||
problemBehavior,
|
||||
group.comparator,
|
||||
callNode,
|
||||
toTextNode,
|
||||
state,
|
||||
less,
|
||||
equal,
|
||||
greater,
|
||||
interop);
|
||||
}
|
||||
group.elems.sort(javaComparator);
|
||||
if (javaComparator.hasWarnings()) {
|
||||
gatheredWarnings.addAll(javaComparator.getEncounteredWarnings());
|
||||
}
|
||||
resultVec.addAll(group.elems);
|
||||
}
|
||||
var sortedVector = Vector.fromArray(new Array(resultVec.toArray()));
|
||||
// Attach gathered warnings along with different comparators warning
|
||||
switch (problemBehavior) {
|
||||
case REPORT_ERROR -> {
|
||||
if (groups.size() > 1) {
|
||||
assert groups.get(0).elems.size() > 0 : "Groups should not be empty";
|
||||
assert groups.get(1).elems.size() > 0 : "Groups should not be empty";
|
||||
var firstIncomparableElem = groups.get(0).elems.get(0);
|
||||
var secondIncomparableElem = groups.get(1).elems.get(0);
|
||||
var err =
|
||||
ctx.getBuiltins()
|
||||
.error()
|
||||
.makeIncomparableValues(firstIncomparableElem, secondIncomparableElem);
|
||||
return DataflowError.withoutTrace(err, this);
|
||||
} else {
|
||||
// Just one comparator, different from Default_Comparator
|
||||
if (!gatheredWarnings.isEmpty()) {
|
||||
throw new UnsupportedOperationException("unimplemented");
|
||||
} else {
|
||||
return sortedVector;
|
||||
}
|
||||
}
|
||||
}
|
||||
case REPORT_WARNING -> {
|
||||
return attachDifferentComparatorsWarning(
|
||||
attachWarnings(sortedVector, gatheredWarnings), groups);
|
||||
}
|
||||
case IGNORE -> {
|
||||
return sortedVector;
|
||||
}
|
||||
default -> throw new IllegalStateException("unreachable");
|
||||
}
|
||||
} catch (CompareException e) {
|
||||
return DataflowError.withoutTrace(
|
||||
incomparableValuesError(e.leftOperand, e.rightOperand), this);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary(allowInlining = true)
|
||||
private Object sortPrimitiveVector(Object[] elems, DefaultSortComparator javaComparator)
|
||||
throws CompareException {
|
||||
Arrays.sort(elems, javaComparator);
|
||||
var sortedVector = Vector.fromArray(new Array(elems));
|
||||
|
||||
if (javaComparator.hasWarnings()) {
|
||||
return attachWarnings(sortedVector, javaComparator.getEncounteredWarnings());
|
||||
} else {
|
||||
return sortedVector;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Group> splitByComparators(
|
||||
List<Object> elements, List<Type> comparators, List<Function> compareFuncs) {
|
||||
assert elements.size() == comparators.size();
|
||||
assert elements.size() == compareFuncs.size();
|
||||
// Mapping of FQN of comparator to groups
|
||||
Map<String, Group> groupMap = new HashMap<>();
|
||||
for (int i = 0; i < elements.size(); i++) {
|
||||
Object elem = elements.get(i);
|
||||
Type comparator = comparators.get(i);
|
||||
Function compareFunc = compareFuncs.get(i);
|
||||
String qualifiedName = comparator.getQualifiedName().toString();
|
||||
|
||||
if (!groupMap.containsKey(qualifiedName)) {
|
||||
groupMap.put(qualifiedName, new Group(new ArrayList<>(), comparator, compareFunc));
|
||||
}
|
||||
var group = groupMap.get(qualifiedName);
|
||||
group.elems.add(elem);
|
||||
}
|
||||
|
||||
// Sort groups by the FQN of their comparator, with the default comparator
|
||||
// being the first one (the first group).
|
||||
String defCompFQN = getDefaultComparatorQualifiedName();
|
||||
return groupMap.entrySet().stream()
|
||||
.sorted(
|
||||
(entry1, entry2) -> {
|
||||
var fqn1 = entry1.getKey();
|
||||
var fqn2 = entry2.getKey();
|
||||
if (fqn1.equals(defCompFQN)) {
|
||||
return -1;
|
||||
} else if (fqn2.equals(defCompFQN)) {
|
||||
return 1;
|
||||
} else {
|
||||
return fqn1.compareTo(fqn2);
|
||||
}
|
||||
})
|
||||
.map(Entry::getValue)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Object attachWarnings(Object vector, Set<String> warnings) {
|
||||
var warnArray =
|
||||
warnings.stream()
|
||||
.map(Text::create)
|
||||
.map(text -> Warning.create(EnsoContext.get(this), text, this))
|
||||
.toArray(Warning[]::new);
|
||||
return WithWarnings.appendTo(vector, new ArrayRope<>(warnArray));
|
||||
}
|
||||
|
||||
private Object attachDifferentComparatorsWarning(Object vector, List<Group> groups) {
|
||||
var diffCompsMsg =
|
||||
groups.stream()
|
||||
.map(Group::comparator)
|
||||
.map(comparator -> comparator.getQualifiedName().toString())
|
||||
.collect(Collectors.joining(", "));
|
||||
var text = Text.create("Different comparators: [" + diffCompsMsg + "]");
|
||||
var warn = Warning.create(EnsoContext.get(this), text, this);
|
||||
return WithWarnings.appendTo(vector, new ArrayRope<>(warn));
|
||||
}
|
||||
|
||||
private String getDefaultComparatorQualifiedName() {
|
||||
return EnsoContext.get(this)
|
||||
.getBuiltins()
|
||||
.defaultComparator()
|
||||
.getType()
|
||||
.getQualifiedName()
|
||||
.toString();
|
||||
}
|
||||
|
||||
/** A group is "primitive" iff its comparator is the default comparator. */
|
||||
private boolean isPrimitiveGroup(Group group) {
|
||||
return group
|
||||
.comparator
|
||||
.getQualifiedName()
|
||||
.toString()
|
||||
.equals(getDefaultComparatorQualifiedName());
|
||||
}
|
||||
|
||||
private Object incomparableValuesError(Object left, Object right) {
|
||||
return EnsoContext.get(this).getBuiltins().error().makeIncomparableValues(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper slow-path method to conveniently gather elements from interop arrays into a java list
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<T> readInteropArray(
|
||||
InteropLibrary interop,
|
||||
HostValueToEnsoNode hostValueToEnsoNode,
|
||||
WarningsLibrary warningsLib,
|
||||
Object vector) {
|
||||
try {
|
||||
int size = (int) interop.getArraySize(vector);
|
||||
List<T> res = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
Object elem = hostValueToEnsoNode.execute(interop.readArrayElement(vector, i));
|
||||
if (warningsLib.hasWarnings(elem)) {
|
||||
elem = warningsLib.removeWarnings(elem);
|
||||
}
|
||||
res.add((T) elem);
|
||||
}
|
||||
return res;
|
||||
} catch (UnsupportedMessageException | InvalidArrayIndexException | ClassCastException e) {
|
||||
throw new IllegalStateException("Should not be reachable", e);
|
||||
}
|
||||
}
|
||||
|
||||
private int getBuiltinTypeCost(Object builtinType) {
|
||||
assert isBuiltinType(builtinType);
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
if (builtinType == builtins.number().getNumber()
|
||||
|| builtinType == builtins.number().getInteger()
|
||||
|| builtinType == builtins.number().getDecimal()) {
|
||||
return 1;
|
||||
} else if (builtinType == builtins.text()) {
|
||||
return 2;
|
||||
} else if (builtinType == builtins.bool().getType()) {
|
||||
return 3;
|
||||
} else if (builtinType == builtins.date()) {
|
||||
return 4;
|
||||
} else if (builtinType == builtins.dateTime()) {
|
||||
return 5;
|
||||
} else if (builtinType == builtins.duration()) {
|
||||
return 6;
|
||||
} else if (builtinType == builtins.vector()) {
|
||||
// vectors are incomparable, but we want to sort them before Nothings and NaNs.
|
||||
return 50;
|
||||
} else {
|
||||
// Type is not a builtin type
|
||||
throw new IllegalStateException("Should be a builtin type: " + builtinType);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBuiltinType(Object type) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
return builtins.number().getNumber() == type
|
||||
|| builtins.number().getDecimal() == type
|
||||
|| builtins.number().getInteger() == type
|
||||
|| builtins.nothing() == type
|
||||
|| builtins.text() == type
|
||||
|| builtins.bool().getType() == type
|
||||
|| builtins.date() == type
|
||||
|| builtins.dateTime() == type
|
||||
|| builtins.duration() == type
|
||||
|| builtins.vector() == type;
|
||||
}
|
||||
|
||||
private boolean isTrue(Object object) {
|
||||
return Boolean.TRUE.equals(object);
|
||||
}
|
||||
|
||||
/** Returns true iff the given array of comparators is all Default_Comparator */
|
||||
boolean areAllDefaultComparators(
|
||||
InteropLibrary interop, HostValueToEnsoNode hostValueToEnsoNode, Object comparators) {
|
||||
assert interop.hasArrayElements(comparators);
|
||||
var ctx = EnsoContext.get(this);
|
||||
try {
|
||||
int compSize = (int) interop.getArraySize(comparators);
|
||||
for (int i = 0; i < compSize; i++) {
|
||||
assert interop.isArrayElementReadable(comparators, i);
|
||||
Object comparator = hostValueToEnsoNode.execute(interop.readArrayElement(comparators, i));
|
||||
if (!isDefaultComparator(comparator, ctx)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
|
||||
throw new IllegalStateException("Should not be reachable", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isDefaultComparator(Object object, EnsoContext ctx) {
|
||||
return ctx.getBuiltins().defaultComparator().getType() == object;
|
||||
}
|
||||
|
||||
private boolean isNan(Object object) {
|
||||
return object instanceof Double dbl && dbl.isNaN();
|
||||
}
|
||||
|
||||
private enum ProblemBehavior {
|
||||
IGNORE,
|
||||
REPORT_WARNING,
|
||||
REPORT_ERROR;
|
||||
|
||||
static ProblemBehavior fromInt(int intValue) {
|
||||
return switch (intValue) {
|
||||
case 0 -> IGNORE;
|
||||
case 1 -> REPORT_WARNING;
|
||||
case 2 -> REPORT_ERROR;
|
||||
default -> throw new IllegalArgumentException(Integer.toString(intValue));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of elements grouped by comparator.
|
||||
*
|
||||
* @param elems Elements of the group.
|
||||
* @param comparator SortComparator for the elems, i.e., it should hold that {@code elems.each
|
||||
* it-> (Comparable.from it) == comparator}.
|
||||
* @param compareFunc `SortComparator.compare` function extracted from the comparator.
|
||||
*/
|
||||
private record Group(List<Object> elems, Type comparator, Function compareFunc) {}
|
||||
|
||||
/**
|
||||
* Convenient base class that implements java.util.Comparator, and gathers warnings about
|
||||
* incomparable values. The warnings are gathered as pure Strings in a hash set, so that they are
|
||||
* not duplicated.
|
||||
*/
|
||||
private abstract static class SortComparator implements java.util.Comparator<Object> {
|
||||
|
||||
private final Set<String> warnings = new HashSet<>();
|
||||
final AnyToTextNode toTextNode;
|
||||
final ProblemBehavior problemBehavior;
|
||||
final InteropLibrary interop;
|
||||
|
||||
protected SortComparator(
|
||||
AnyToTextNode toTextNode, ProblemBehavior problemBehavior, InteropLibrary interop) {
|
||||
this.toTextNode = toTextNode;
|
||||
this.problemBehavior = problemBehavior;
|
||||
this.interop = interop;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
protected void attachIncomparableValuesWarning(Object x, Object y) {
|
||||
var xStr = toTextNode.execute(x).toString();
|
||||
var yStr = toTextNode.execute(y).toString();
|
||||
String warnText = "Values " + xStr + " and " + yStr + " are incomparable";
|
||||
warnings.add(warnText);
|
||||
}
|
||||
|
||||
public Set<String> getEncounteredWarnings() {
|
||||
return warnings;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
public boolean hasWarnings() {
|
||||
return !warnings.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SortComparator for comparing all values that have Default_Comparator. These are either
|
||||
* primitive types, or the types that do not provide their own comparator.
|
||||
*
|
||||
* <p>Note that it is essential for this class that the {@code by} method parameter to {@code
|
||||
* Vector.sort} is set to the default value, which is {@code Ordering.compare}, because then, we
|
||||
* know that the partial ordering for primitive types was not redefined by the user (we handle
|
||||
* partial ordering for primitive types specifically, partial ordering for other types is not
|
||||
* implemented yet - that requires topological sorting).
|
||||
*/
|
||||
final class DefaultSortComparator extends SortComparator {
|
||||
|
||||
private final LessThanNode lessThanNode;
|
||||
private final EqualsNode equalsNode;
|
||||
private final TypeOfNode typeOfNode;
|
||||
private final boolean ascending;
|
||||
|
||||
private DefaultSortComparator(
|
||||
LessThanNode lessThanNode,
|
||||
EqualsNode equalsNode,
|
||||
TypeOfNode typeOfNode,
|
||||
AnyToTextNode toTextNode,
|
||||
boolean ascending,
|
||||
ProblemBehavior problemBehavior,
|
||||
InteropLibrary interop) {
|
||||
super(toTextNode, problemBehavior, interop);
|
||||
this.lessThanNode = lessThanNode;
|
||||
this.equalsNode = equalsNode;
|
||||
this.typeOfNode = typeOfNode;
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Object x, Object y) {
|
||||
return compareValuesWithDefaultComparator(x, y);
|
||||
}
|
||||
|
||||
int compareValuesWithDefaultComparator(Object x, Object y) {
|
||||
if (equalsNode.execute(x, y)) {
|
||||
return 0;
|
||||
} else {
|
||||
// Check if x < y
|
||||
Object xLessThanYRes = lessThanNode.execute(x, y);
|
||||
if (interop.isNull(xLessThanYRes)) {
|
||||
// x and y are incomparable - this can happen if x and y are different types
|
||||
return handleIncomparableValues(x, y);
|
||||
} else if (isTrue(xLessThanYRes)) {
|
||||
return ascending ? -1 : 1;
|
||||
} else {
|
||||
// Check if x > y
|
||||
Object yLessThanXRes = lessThanNode.execute(y, x);
|
||||
if (isTrue(yLessThanXRes)) {
|
||||
return ascending ? 1 : -1;
|
||||
} else {
|
||||
// yLessThanXRes is either Nothing or False
|
||||
return handleIncomparableValues(y, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int handleIncomparableValues(Object x, Object y) {
|
||||
switch (problemBehavior) {
|
||||
case REPORT_ERROR -> throw new CompareException(x, y);
|
||||
case REPORT_WARNING -> attachIncomparableValuesWarning(x, y);
|
||||
}
|
||||
if (isPrimitiveValue(x) || isPrimitiveValue(y)) {
|
||||
if (isPrimitiveValue(x) && isPrimitiveValue(y)) {
|
||||
return handleIncomparablePrimitives(x, y);
|
||||
} else if (isPrimitiveValue(x)) {
|
||||
// Primitive values are always before non-primitive values - Default_Comparator
|
||||
// group should be the first one.
|
||||
return ascending ? -1 : 1;
|
||||
} else if (isPrimitiveValue(y)) {
|
||||
return ascending ? 1 : -1;
|
||||
} else {
|
||||
throw new IllegalStateException("Should not be reachable");
|
||||
}
|
||||
} else {
|
||||
// Values other than primitives are compared just by their type's FQN.
|
||||
var xTypeName = getQualifiedTypeName(x);
|
||||
var yTypeName = getQualifiedTypeName(y);
|
||||
return xTypeName.compareTo(yTypeName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incomparable primitive values have either different builtin types, or are Nothing or NaN. All
|
||||
* these cases are handled specifically - we hardcode the order of these incomparable values.
|
||||
*/
|
||||
private int handleIncomparablePrimitives(Object x, Object y) {
|
||||
int xCost = getPrimitiveValueCost(x);
|
||||
int yCost = getPrimitiveValueCost(y);
|
||||
int res = Integer.compare(xCost, yCost);
|
||||
return ascending ? res : -res;
|
||||
}
|
||||
|
||||
private boolean isPrimitiveValue(Object object) {
|
||||
return isBuiltinType(typeOfNode.execute(object));
|
||||
}
|
||||
|
||||
private String getQualifiedTypeName(Object object) {
|
||||
var typeObj = typeOfNode.execute(object);
|
||||
return toTextNode.execute(typeObj).toString();
|
||||
}
|
||||
|
||||
private int getPrimitiveValueCost(Object object) {
|
||||
if (interop.isNull(object)) {
|
||||
return 200;
|
||||
} else if (isNan(object)) {
|
||||
return 100;
|
||||
} else {
|
||||
var type = typeOfNode.execute(object);
|
||||
return getBuiltinTypeCost(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator for any values. This comparator compares the values by calling back to Enso (by
|
||||
* {@link #compareFunc}), rather than using compare nodes (i.e. {@link LessThanNode}). directly,
|
||||
* as opposed to {@link DefaultSortComparator}.
|
||||
*/
|
||||
private final class GenericSortComparator extends SortComparator {
|
||||
|
||||
private final boolean ascending;
|
||||
/**
|
||||
* Either function from `by` parameter to the `Vector.sort` method, or the `compare` function
|
||||
* extracted from the comparator for the appropriate group.
|
||||
*/
|
||||
private final Function compareFunc;
|
||||
|
||||
private final Function onFunc;
|
||||
private final boolean hasCustomOnFunc;
|
||||
private final Type comparator;
|
||||
private final CallOptimiserNode callNode;
|
||||
private final State state;
|
||||
private final Atom less;
|
||||
private final Atom equal;
|
||||
private final Atom greater;
|
||||
|
||||
private GenericSortComparator(
|
||||
boolean ascending,
|
||||
Object compareFunc,
|
||||
Object onFunc,
|
||||
ProblemBehavior problemBehavior,
|
||||
Type comparator,
|
||||
CallOptimiserNode callNode,
|
||||
AnyToTextNode toTextNode,
|
||||
State state,
|
||||
Atom less,
|
||||
Atom equal,
|
||||
Atom greater,
|
||||
InteropLibrary interop) {
|
||||
super(toTextNode, problemBehavior, interop);
|
||||
assert compareFunc != null;
|
||||
assert comparator != null;
|
||||
this.comparator = comparator;
|
||||
this.state = state;
|
||||
this.ascending = ascending;
|
||||
this.compareFunc = checkAndConvertByFunc(compareFunc);
|
||||
if (interop.isNull(onFunc)) {
|
||||
this.hasCustomOnFunc = false;
|
||||
this.onFunc = null;
|
||||
} else {
|
||||
this.hasCustomOnFunc = true;
|
||||
this.onFunc = checkAndConvertOnFunc(onFunc);
|
||||
}
|
||||
this.callNode = callNode;
|
||||
this.less = less;
|
||||
this.equal = equal;
|
||||
this.greater = greater;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Object x, Object y) {
|
||||
Object xConverted;
|
||||
Object yConverted;
|
||||
if (hasCustomOnFunc) {
|
||||
// onFunc cannot have `self` argument, we assume it has just one argument.
|
||||
xConverted = callNode.executeDispatch(onFunc, null, state, new Object[] {x});
|
||||
yConverted = callNode.executeDispatch(onFunc, null, state, new Object[] {y});
|
||||
} else {
|
||||
xConverted = x;
|
||||
yConverted = y;
|
||||
}
|
||||
Object[] args;
|
||||
if (hasFunctionSelfArgument(compareFunc)) {
|
||||
args = new Object[] {comparator, xConverted, yConverted};
|
||||
} else {
|
||||
args = new Object[] {xConverted, yConverted};
|
||||
}
|
||||
Object res = callNode.executeDispatch(compareFunc, null, state, args);
|
||||
if (res == less) {
|
||||
return ascending ? -1 : 1;
|
||||
} else if (res == equal) {
|
||||
return 0;
|
||||
} else if (res == greater) {
|
||||
return ascending ? 1 : -1;
|
||||
} else {
|
||||
// res is either Nothing, or Incomparable_Values. Either way, it means that x and y are
|
||||
// incomparable.
|
||||
// This case is not supported yet, as it requires topological sorting.
|
||||
// We cannot detect if the result was actually returned from the default comparator (it
|
||||
// could have been transitively called), so we just bailout.
|
||||
throw new CompareException(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasFunctionSelfArgument(Function function) {
|
||||
if (function.getSchema().getArgumentsCount() > 0) {
|
||||
return function.getSchema().getArgumentInfos()[0].getName().equals("self");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks value given for {@code by} parameter and converts it to {@link Function}. Throw a
|
||||
* dataflow error otherwise.
|
||||
*/
|
||||
private Function checkAndConvertByFunc(Object byFuncObj) {
|
||||
return checkAndConvertFunction(
|
||||
byFuncObj, "Unsupported argument for `by`, expected a method with two arguments", 2, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the value given for {@code on} parameter and converts it to {@link Function}. Throws a
|
||||
* dataflow error otherwise.
|
||||
*/
|
||||
private Function checkAndConvertOnFunc(Object onFuncObj) {
|
||||
return checkAndConvertFunction(
|
||||
onFuncObj, "Unsupported argument for `on`, expected a method with one argument", 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param minArgCount Minimal count of arguments without a default value.
|
||||
* @param maxArgCount Maximal count of argument without a default value.
|
||||
*/
|
||||
private Function checkAndConvertFunction(
|
||||
Object funcObj, String errMsg, int minArgCount, int maxArgCount) {
|
||||
var err = new IllegalArgumentException(errMsg + ", got " + funcObj);
|
||||
if (funcObj instanceof Function func) {
|
||||
var argCount = getNumberOfNonDefaultArguments(func);
|
||||
if (minArgCount <= argCount && argCount <= maxArgCount) {
|
||||
return func;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getNumberOfNonDefaultArguments(Function function) {
|
||||
return (int)
|
||||
Arrays.stream(function.getSchema().getArgumentInfos())
|
||||
.filter(argInfo -> !argInfo.hasDefaultValue())
|
||||
.count();
|
||||
}
|
||||
|
||||
private static final class CompareException extends RuntimeException {
|
||||
|
||||
final Object leftOperand;
|
||||
final Object rightOperand;
|
||||
|
||||
private CompareException(Object leftOperand, Object rightOperand) {
|
||||
this.leftOperand = leftOperand;
|
||||
this.rightOperand = rightOperand;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package org.enso.interpreter.node.expression.builtin.text;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
@ -14,12 +15,8 @@ import org.enso.interpreter.runtime.callable.atom.StructsLibrary;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
|
||||
@BuiltinMethod(type = "Any", name = "to_text", description = "Generic text conversion.")
|
||||
@GenerateUncached
|
||||
public abstract class AnyToTextNode extends Node {
|
||||
private static final int DISPATCH_CACHE = 3;
|
||||
private @Child InteropLibrary displays =
|
||||
InteropLibrary.getFactory().createDispatched(DISPATCH_CACHE);
|
||||
private @Child InteropLibrary strings =
|
||||
InteropLibrary.getFactory().createDispatched(DISPATCH_CACHE);
|
||||
|
||||
public static AnyToTextNode build() {
|
||||
return AnyToTextNodeGen.create();
|
||||
@ -28,19 +25,22 @@ public abstract class AnyToTextNode extends Node {
|
||||
public abstract Text execute(Object self);
|
||||
|
||||
@Specialization
|
||||
Text doAtom(Atom at, @CachedLibrary(limit = "10") StructsLibrary structs) {
|
||||
Text doAtom(
|
||||
Atom at,
|
||||
@CachedLibrary(limit = "10") StructsLibrary structs,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop) {
|
||||
var fields = structs.getFields(at);
|
||||
if (fields.length == 0) {
|
||||
return consName(at.getConstructor());
|
||||
} else {
|
||||
return doComplexAtom(at, fields);
|
||||
return doComplexAtom(at, fields, interop);
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
Text doOther(Object object) {
|
||||
Text doOther(Object object, @CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
try {
|
||||
return Text.create(showObject(object));
|
||||
return Text.create(showObject(object, interop));
|
||||
} catch (UnsupportedMessageException e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
return Text.create(object.toString());
|
||||
@ -53,18 +53,18 @@ public abstract class AnyToTextNode extends Node {
|
||||
}
|
||||
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
private Text doComplexAtom(Atom atom, Object[] fields) {
|
||||
private Text doComplexAtom(Atom atom, Object[] fields, InteropLibrary interop) {
|
||||
Text res = Text.create("(", consName(atom.getConstructor()));
|
||||
res = Text.create(res, " ");
|
||||
try {
|
||||
res = Text.create(res, showObject(fields[0]));
|
||||
res = Text.create(res, showObject(fields[0], interop));
|
||||
} catch (UnsupportedMessageException e) {
|
||||
res = Text.create(res, fields[0].toString());
|
||||
}
|
||||
for (int i = 1; i < fields.length; i++) {
|
||||
res = Text.create(res, " ");
|
||||
try {
|
||||
res = Text.create(res, showObject(fields[i]));
|
||||
res = Text.create(res, showObject(fields[i], interop));
|
||||
} catch (UnsupportedMessageException e) {
|
||||
res = Text.create(res, fields[i].toString());
|
||||
}
|
||||
@ -74,7 +74,8 @@ public abstract class AnyToTextNode extends Node {
|
||||
}
|
||||
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
private String showObject(Object child) throws UnsupportedMessageException {
|
||||
private String showObject(Object child, InteropLibrary interop)
|
||||
throws UnsupportedMessageException {
|
||||
if (child == null) {
|
||||
// TODO [RW] This is a temporary workaround to make it possible to display errors related to
|
||||
// https://www.pivotaltracker.com/story/show/181652974
|
||||
@ -83,7 +84,7 @@ public abstract class AnyToTextNode extends Node {
|
||||
} else if (child instanceof Boolean) {
|
||||
return (boolean) child ? "True" : "False";
|
||||
} else {
|
||||
return strings.asString(displays.toDisplayString(child));
|
||||
return interop.asString(interop.toDisplayString(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,8 @@ public class DebuggingEnsoTest {
|
||||
@Test
|
||||
public void recursiveFactorialCall() {
|
||||
final Value facFn = createEnsoMethod("""
|
||||
from Standard.Base.Data.Ordering import all
|
||||
|
||||
fac : Number -> Number
|
||||
fac n =
|
||||
facacc : Number -> Number -> Number
|
||||
|
@ -116,6 +116,17 @@ public abstract class TestBase {
|
||||
return mainMethod.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given module and returns a method by the given name from the module.
|
||||
*
|
||||
* @param moduleSrc Source of the whole module
|
||||
* @return Reference to the method.
|
||||
*/
|
||||
protected static Value getMethodFromModule(Context ctx, String moduleSrc, String methodName) {
|
||||
Value module = ctx.eval(Source.create("enso", moduleSrc));
|
||||
return module.invokeMember(Module.EVAL_EXPRESSION, methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* An artificial RootNode. Used for tests of nodes that need to be adopted. Just create this root
|
||||
* node inside a context, all the other nodes, and insert them via {@link
|
||||
|
@ -301,10 +301,17 @@ class ValuesGenerator {
|
||||
collect.add(v(null, "", "42").type());
|
||||
collect.add(v(null, "", "6.7").type());
|
||||
collect.add(v(null, "", "40321 * 43202").type());
|
||||
collect.add(v(null, """
|
||||
collect.add(
|
||||
v(
|
||||
null,
|
||||
"""
|
||||
from Standard.Base.Data.Ordering import all
|
||||
|
||||
fac s n = if n <= 1 then s else
|
||||
@Tail_Call fac n*s n-1
|
||||
""", "fac 1 100").type());
|
||||
""",
|
||||
"fac 1 100")
|
||||
.type());
|
||||
collect.add(v(null, "", "123 * 10^40").type());
|
||||
collect.add(v(null, "", "123 * 10^40 + 0.0").type());
|
||||
collect.add(v(null, "", "123 * 10^40 + 1.0").type());
|
||||
|
@ -0,0 +1,100 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.enso.interpreter.test.ValuesGenerator.Language;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assume;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* This test ensures that any combination of values can be sorted - no attempt to sort should fail.
|
||||
*/
|
||||
@RunWith(Theories.class)
|
||||
public class VectorSortTest extends TestBase {
|
||||
private static Context context;
|
||||
private static Value sortFunc;
|
||||
private static Value equalsFunc;
|
||||
|
||||
@BeforeClass
|
||||
public static void initCtxAndNodes() {
|
||||
context = createDefaultContext();
|
||||
var code =
|
||||
"""
|
||||
from Standard.Base import all
|
||||
|
||||
sort val1 val2 = [val1, val2].sort
|
||||
equals val1 val2 = val1 == val2
|
||||
""";
|
||||
sortFunc = getMethodFromModule(context, code, "sort");
|
||||
equalsFunc = getMethodFromModule(context, code, "equals");
|
||||
|
||||
values = new ArrayList<>();
|
||||
var valuesGenerator =
|
||||
ValuesGenerator.create(context, Language.ENSO, Language.JAVA, Language.JAVASCRIPT);
|
||||
values.addAll(valuesGenerator.numbers());
|
||||
values.addAll(valuesGenerator.vectors());
|
||||
values.addAll(valuesGenerator.arrayLike());
|
||||
values.addAll(valuesGenerator.booleans());
|
||||
values.addAll(valuesGenerator.durations());
|
||||
values.addAll(valuesGenerator.maps());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void disposeCtx() {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@DataPoints public static List<Value> values;
|
||||
|
||||
@Theory
|
||||
public void testSortHandlesAllValues(Value value1, Value value2) {
|
||||
Assume.assumeFalse(isNan(value1) || isNan(value2));
|
||||
Value res = sortFunc.execute(value1, value2);
|
||||
assertTrue(res.hasArrayElements());
|
||||
assertEquals(2, res.getArraySize());
|
||||
List<Value> resArray = readPolyglotArray(res);
|
||||
// check that value1 is there unchanged on some index, and the same for value2
|
||||
assertTrue(
|
||||
"Sorted vector should contain the first value at any index",
|
||||
invokeEquals(value1, resArray.get(0)) || invokeEquals(value1, resArray.get(1)));
|
||||
assertTrue(
|
||||
"Sorted vector should contain the second value at any index",
|
||||
invokeEquals(value2, resArray.get(0)) || invokeEquals(value2, resArray.get(1)));
|
||||
}
|
||||
|
||||
private boolean isNan(Value value) {
|
||||
if (value.isNumber() && value.fitsInDouble()) {
|
||||
return Double.isNaN(value.asDouble());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Value> readPolyglotArray(Value array) {
|
||||
assertTrue(array.hasArrayElements());
|
||||
assertTrue(array.hasIterator());
|
||||
Value iterator = array.getIterator();
|
||||
assertTrue(iterator.isIterator());
|
||||
List<Value> res = new ArrayList<>();
|
||||
while (iterator.hasIteratorNextElement()) {
|
||||
res.add(iterator.getIteratorNextElement());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private boolean invokeEquals(Value val1, Value val2) {
|
||||
Value res = equalsFunc.execute(val1, val2);
|
||||
assertTrue("Result from Any.== should be boolean", res.isBoolean());
|
||||
return res.asBoolean();
|
||||
}
|
||||
}
|
@ -472,7 +472,7 @@ spec =
|
||||
c = Column.from_vector 'foo' [My.Data 1 2, My.Data 2 5, My.Data 3 4, My.Data 6 3, Nothing, My.Data 1 0]
|
||||
cmp a b = Ordering.compare (a.x-a.y).abs (b.x-b.y).abs
|
||||
r = c.sort by=cmp
|
||||
r.to_vector.should_equal [My.Data 1 2, My.Data 3 4, My.Data 1 0, My.Data 2 5, My.Data 6 3, Nothing]
|
||||
r.to_vector.should_equal [Nothing, My.Data 1 2, My.Data 3 4, My.Data 1 0, My.Data 2 5, My.Data 6 3]
|
||||
|
||||
d = Column.from_vector 'foo' [1,3,2,4,Nothing,5]
|
||||
cmp2 a b = Ordering.compare -a -b
|
||||
|
@ -52,10 +52,7 @@ spec =
|
||||
Test.group "Compare functionality with Vector" <|
|
||||
Test.specify "compare methods" <|
|
||||
vector_methods = Meta.meta Vector . methods . sort
|
||||
array_methods_all = Meta.meta Array . methods . sort
|
||||
|
||||
array_methods = array_methods_all.filter (name -> name != "sort_builtin")
|
||||
|
||||
array_methods = Meta.meta Array . methods . sort
|
||||
vector_methods . should_equal array_methods
|
||||
|
||||
Test.group "ArrayOverBuffer" <|
|
||||
|
@ -1,6 +1,6 @@
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Errors.Common.Arithmetic_Error
|
||||
import Standard.Base.Errors.Common.Type_Error
|
||||
import Standard.Base.Errors.Common.Incomparable_Values
|
||||
|
||||
from Standard.Base.Data.Numbers import Number_Parse_Error
|
||||
|
||||
@ -297,21 +297,21 @@ spec =
|
||||
(1 < 0.99).should_be_false
|
||||
(3 < hundred_factorial).should_be_true
|
||||
(3 < very_negative).should_be_false
|
||||
(3 < Nothing).should_fail_with Type_Error
|
||||
(3 < Nothing).should_fail_with Incomparable_Values
|
||||
(1.01 < 0.99).should_be_false
|
||||
(1.01 < 1.02).should_be_true
|
||||
(1.01 < 1).should_be_false
|
||||
(1.01 < 2).should_be_true
|
||||
(3.14 < hundred_factorial).should_be_true
|
||||
(3.14 < very_negative).should_be_false
|
||||
(1.5 < Nothing).should_fail_with Type_Error
|
||||
(1.5 < Nothing).should_fail_with Incomparable_Values
|
||||
(hundred_factorial < 1).should_be_false
|
||||
(hundred_factorial < 1.5).should_be_false
|
||||
(very_negative < 1).should_be_true
|
||||
(very_negative < 1.5).should_be_true
|
||||
(hundred_factorial < very_negative).should_be_false
|
||||
(very_negative < hundred_factorial).should_be_true
|
||||
(very_negative < Nothing).should_fail_with Type_Error
|
||||
(very_negative < Nothing).should_fail_with Incomparable_Values
|
||||
|
||||
Test.specify "should support less than or equal to operator" <|
|
||||
(1 <= 2).should_be_true
|
||||
@ -321,21 +321,21 @@ spec =
|
||||
(1 <= 0.99).should_be_false
|
||||
(3 <= hundred_factorial).should_be_true
|
||||
(3 <= very_negative).should_be_false
|
||||
(3 <= Nothing).should_fail_with Type_Error
|
||||
(3 <= Nothing).should_fail_with Incomparable_Values
|
||||
(1.01 <= 0.99).should_be_false
|
||||
(1.01 <= 1.02).should_be_true
|
||||
(1.01 <= 1).should_be_false
|
||||
(1.01 <= 2).should_be_true
|
||||
(3.14 <= hundred_factorial).should_be_true
|
||||
(3.14 <= very_negative).should_be_false
|
||||
(1.5 <= Nothing).should_fail_with Type_Error
|
||||
(1.5 <= Nothing).should_fail_with Incomparable_Values
|
||||
(hundred_factorial <= 1).should_be_false
|
||||
(hundred_factorial <= 1.5).should_be_false
|
||||
(very_negative <= 1).should_be_true
|
||||
(very_negative <= 1.5).should_be_true
|
||||
(hundred_factorial <= very_negative).should_be_false
|
||||
(very_negative <= hundred_factorial).should_be_true
|
||||
(very_negative <= Nothing).should_fail_with Type_Error
|
||||
(very_negative <= Nothing).should_fail_with Incomparable_Values
|
||||
|
||||
Test.specify "should support greater than operator" <|
|
||||
(1 > 2).should_be_false
|
||||
@ -345,21 +345,21 @@ spec =
|
||||
(1 > 0.99).should_be_true
|
||||
(3 > hundred_factorial).should_be_false
|
||||
(3 > very_negative).should_be_true
|
||||
(3 > Nothing).should_fail_with Type_Error
|
||||
(3 > Nothing).should_fail_with Incomparable_Values
|
||||
(1.01 > 0.99).should_be_true
|
||||
(1.01 > 1.02).should_be_false
|
||||
(1.01 > 1).should_be_true
|
||||
(1.01 > 2).should_be_false
|
||||
(3.14 > hundred_factorial).should_be_false
|
||||
(3.14 > very_negative).should_be_true
|
||||
(1.5 > Nothing).should_fail_with Type_Error
|
||||
(1.5 > Nothing).should_fail_with Incomparable_Values
|
||||
(hundred_factorial > 1).should_be_true
|
||||
(hundred_factorial > 1.5).should_be_true
|
||||
(very_negative > 1).should_be_false
|
||||
(very_negative > 1.5).should_be_false
|
||||
(hundred_factorial > very_negative).should_be_true
|
||||
(very_negative > hundred_factorial).should_be_false
|
||||
(very_negative > Nothing).should_fail_with Type_Error
|
||||
(very_negative > Nothing).should_fail_with Incomparable_Values
|
||||
|
||||
Test.specify "should support greater than or equal to operator" <|
|
||||
(1 >= 2).should_be_false
|
||||
@ -369,21 +369,21 @@ spec =
|
||||
(1 >= 0.99).should_be_true
|
||||
(3 >= hundred_factorial).should_be_false
|
||||
(3 >= very_negative).should_be_true
|
||||
(3 >= Nothing).should_fail_with Type_Error
|
||||
(3 >= Nothing).should_fail_with Incomparable_Values
|
||||
(1.01 >= 0.99).should_be_true
|
||||
(1.01 >= 1.02).should_be_false
|
||||
(1.01 >= 1).should_be_true
|
||||
(1.01 >= 2).should_be_false
|
||||
(3.14 >= hundred_factorial).should_be_false
|
||||
(3.14 >= very_negative).should_be_true
|
||||
(1.5 >= Nothing).should_fail_with Type_Error
|
||||
(1.5 >= Nothing).should_fail_with Incomparable_Values
|
||||
(hundred_factorial >= 1).should_be_true
|
||||
(hundred_factorial >= 1.5).should_be_true
|
||||
(very_negative >= 1).should_be_false
|
||||
(very_negative >= 1.5).should_be_false
|
||||
(hundred_factorial >= very_negative).should_be_true
|
||||
(very_negative >= hundred_factorial).should_be_false
|
||||
(very_negative >= Nothing).should_fail_with Type_Error
|
||||
(very_negative >= Nothing).should_fail_with Incomparable_Values
|
||||
|
||||
Test.specify "should be ordered by the default comparator" <|
|
||||
Ordering.compare 1 2 . should_equal Ordering.Less
|
||||
@ -393,21 +393,21 @@ spec =
|
||||
Ordering.compare 1 0.99 . should_equal Ordering.Greater
|
||||
Ordering.compare 3 hundred_factorial . should_equal Ordering.Less
|
||||
Ordering.compare 3 very_negative . should_equal Ordering.Greater
|
||||
Ordering.compare 3 Nothing . should_fail_with Type_Error
|
||||
Ordering.compare 3 Nothing . should_fail_with Incomparable_Values
|
||||
Ordering.compare 1.01 0.99 . should_equal Ordering.Greater
|
||||
Ordering.compare 1.01 1.02 . should_equal Ordering.Less
|
||||
Ordering.compare 1.01 1 . should_equal Ordering.Greater
|
||||
Ordering.compare 1.01 2 . should_equal Ordering.Less
|
||||
Ordering.compare 3.14 hundred_factorial . should_equal Ordering.Less
|
||||
Ordering.compare 3.14 very_negative . should_equal Ordering.Greater
|
||||
Ordering.compare 1.5 Nothing . should_fail_with Type_Error
|
||||
Ordering.compare 1.5 Nothing . should_fail_with Incomparable_Values
|
||||
Ordering.compare hundred_factorial 1 . should_equal Ordering.Greater
|
||||
Ordering.compare hundred_factorial 1.5 . should_equal Ordering.Greater
|
||||
Ordering.compare very_negative 1 . should_equal Ordering.Less
|
||||
Ordering.compare very_negative 1.5 . should_equal Ordering.Less
|
||||
Ordering.compare hundred_factorial very_negative . should_equal Ordering.Greater
|
||||
Ordering.compare very_negative hundred_factorial . should_equal Ordering.Less
|
||||
Ordering.compare very_negative Nothing . should_fail_with Type_Error
|
||||
Ordering.compare very_negative Nothing . should_fail_with Incomparable_Values
|
||||
|
||||
Test.specify "should expose exponentiation operations" <|
|
||||
(3.14 ^ 2.71).should_equal 22.216689546 epsilon=eps
|
||||
@ -467,7 +467,7 @@ spec =
|
||||
Number.negative_infinity . should_equal (-Number.positive_infinity)
|
||||
Number.negative_infinity . equals (-Number.positive_infinity) . should_be_true
|
||||
|
||||
Number.nan . equals Number.nan . should_be_false
|
||||
Number.nan . equals 0 . should_be_false
|
||||
Number.nan . equals Number.nan . should_fail_with Incomparable_Values
|
||||
Number.nan . equals 0 . should_fail_with Incomparable_Values
|
||||
|
||||
main = Test_Suite.run_main spec
|
||||
|
@ -2,7 +2,7 @@ from Standard.Base import all
|
||||
import Standard.Base.Errors.Common.Incomparable_Values
|
||||
import Standard.Base.Errors.Common.Type_Error
|
||||
|
||||
from Standard.Test import Test, Test_Suite
|
||||
from Standard.Test import Test, Test_Suite, Problems
|
||||
import Standard.Test.Extensions
|
||||
|
||||
# === Test Resources ===
|
||||
@ -16,7 +16,18 @@ type Ord_Comparator
|
||||
|
||||
Comparable.from (_:Ord) = Ord_Comparator
|
||||
|
||||
## Unordered pair
|
||||
|
||||
type My_Type
|
||||
Value val
|
||||
|
||||
type My_Type_Comparator
|
||||
compare x y = (Comparable.from x.val).compare x.val y.val
|
||||
hash x = (Comparable.from x.val).hash x.val
|
||||
|
||||
Comparable.from (_:My_Type) = My_Type_Comparator
|
||||
|
||||
|
||||
## Unordered pair - its `compare` method returns either `Nothing` or `Ordering.Equal`.
|
||||
type UPair
|
||||
Value x y
|
||||
|
||||
@ -36,9 +47,33 @@ Comparable.from (_ : UPair) = UPair_Comparator
|
||||
type Parent
|
||||
Value child
|
||||
|
||||
# === The Tests ===
|
||||
# Just a type without custom comparator
|
||||
type No_Comp_Type
|
||||
Value val
|
||||
|
||||
|
||||
## Expects that `result` contains incomparable values warning.
|
||||
The values within the warning message can be switched - the order
|
||||
does not matter. Iterates through all the warnings of result.
|
||||
expect_incomparable_warn : Any -> Any -> Any -> Nothing
|
||||
expect_incomparable_warn left_val right_val result =
|
||||
# Incomparable values warning wraps Text values in simple quotes
|
||||
left_val_text = left_val.pretty
|
||||
right_val_text = right_val.pretty
|
||||
expected_warn_msg_left = "Values " + left_val_text + " and " + right_val_text + " are incomparable"
|
||||
expected_warn_msg_right = "Values " + right_val_text + " and " + left_val_text + " are incomparable"
|
||||
has_expected_warning = Warning.get_all result . map (_.value) . any (it-> it == expected_warn_msg_left || it == expected_warn_msg_right)
|
||||
has_expected_warning . should_be_true
|
||||
|
||||
expect_no_warns : Any -> Nothing
|
||||
expect_no_warns result =
|
||||
Warning.get_all result . length . should_equal 0
|
||||
|
||||
|
||||
# === The Tests ===
|
||||
spec =
|
||||
topo_sort_pending = "Waiting for implementation of topological sort (https://github.com/enso-org/enso/issues/5834)"
|
||||
|
||||
Test.group "Default comparator" <|
|
||||
Test.specify "should support custom comparator" <|
|
||||
Ordering.compare (Ord.Value 1) (Ord.Value 2) . should_equal Ordering.Less
|
||||
@ -50,9 +85,9 @@ spec =
|
||||
((Parent.Value (Ord.Value 1)) == (Parent.Value (Ord.Value 1))) . should_be_true
|
||||
((Parent.Value (Ord.Value 1)) == (Parent.Value (Ord.Value 22))) . should_be_false
|
||||
|
||||
Test.specify "should throw Type_Error when comparing different types" <|
|
||||
Ordering.compare (UPair.Value 1 2) (Ord.Value 2) . should_fail_with Type_Error
|
||||
Ordering.compare 1 Nothing . should_fail_with Type_Error
|
||||
Test.specify "should throw Incomparable_Values when comparing different types" <|
|
||||
Ordering.compare (UPair.Value 1 2) (Ord.Value 2) . should_fail_with Incomparable_Values
|
||||
Ordering.compare 1 Nothing . should_fail_with Incomparable_Values
|
||||
|
||||
Test.group "Ordering" <|
|
||||
Test.specify "should allow conversion to sign representation" <|
|
||||
@ -93,10 +128,106 @@ spec =
|
||||
Ordering.compare 42.5 67.9 . should_equal Ordering.Less
|
||||
Meta.is_same_object (Comparable.from Number.nan) (Comparable.from 42.0) . should_be_true
|
||||
|
||||
Test.specify "should fail with Type_Error for wrong type of that" <|
|
||||
Ordering.compare Ordering.Less 1 . should_fail_with Type_Error
|
||||
Ordering.compare Ordering.Less Nothing . should_fail_with Type_Error
|
||||
Ordering.compare Ordering.Less "Hello" . should_fail_with Type_Error
|
||||
Test.specify "should fail with Incomparable_Values for wrong type of that" <|
|
||||
Ordering.compare Ordering.Less 1 . should_fail_with Incomparable_Values
|
||||
Ordering.compare Ordering.Less Nothing . should_fail_with Incomparable_Values
|
||||
Ordering.compare Ordering.Less "Hello" . should_fail_with Incomparable_Values
|
||||
|
||||
Test.group "Sorting with the default comparator" <|
|
||||
|
||||
Test.specify "should be able to sort primitive types" <|
|
||||
[3, 2, 1, Nothing].sort . should_equal [1, 2, 3, Nothing]
|
||||
[Nothing, Number.nan].sort . at 0 . is_nan . should_be_true
|
||||
[Nothing, Number.nan].sort . at 1 . is_nothing . should_be_true
|
||||
[3, 2.5].sort . should_equal [2.5, 3]
|
||||
["hello", 3].sort . should_equal [3, "hello"]
|
||||
["hello", "ahoj", 3].sort . should_equal [3, "ahoj", "hello"]
|
||||
["hello", "ahoj", 3, 2].sort . should_equal [2, 3, "ahoj", "hello"]
|
||||
["hello", "ahoj", Number.nan, 3].sort . take 3 . should_equal [3, "ahoj", "hello"]
|
||||
["hello", "ahoj", Number.nan, 3].sort . at 3 . is_nan . should_be_true
|
||||
[100, Date.new 2020, 50].sort . should_equal [50, 100, Date.new 2020]
|
||||
[100, Nothing, Date.new 2020, 50].sort . should_equal [50, 100, Date.new 2020, Nothing]
|
||||
[3, 2, True, False].sort . should_equal [2, 3, False, True]
|
||||
[3, True, 2, False].sort . should_equal [2, 3, False, True]
|
||||
[Nothing, False].sort . should_equal [False, Nothing]
|
||||
|
||||
Test.specify "should be able to sort any single-element vector without any warnings" <|
|
||||
[Nothing].sort . should_equal [Nothing]
|
||||
expect_no_warns [Nothing].sort
|
||||
[[Nothing]].sort . should_equal [[Nothing]]
|
||||
expect_no_warns [[Nothing]].sort
|
||||
[[1]].sort . should_equal [[1]]
|
||||
expect_no_warns [[1]].sort
|
||||
|
||||
Test.specify "should produce warnings when sorting nested vectors" <|
|
||||
[[1], [2]].sort . should_equal [[1], [2]]
|
||||
[[2], [1]].sort . should_equal [[2], [1]]
|
||||
|
||||
Test.specify "should be able to sort primitive values in atoms" pending=topo_sort_pending <|
|
||||
[Ord.Value Nothing, Ord.Value 20, Ord.Value 10].sort . should_equal [Ord.Value 10, Ord.Value 20, Ord.Value Nothing]
|
||||
|
||||
Test.specify "should produce warnings when sorting primitive values in atoms" pending=topo_sort_pending <|
|
||||
expect_incomparable_warn (Ord.Value 1) (Ord.Value Nothing) [Ord.Value 1, Ord.Value Nothing].sort
|
||||
|
||||
Test.specify "should fail to sort custom incomparable values until topological sorting is implemented" <|
|
||||
[(UPair.Value 1 2), (UPair.Value 3 4)].sort . should_fail_with Incomparable_Values
|
||||
|
||||
Test.specify "should attach warning when trying to sort incomparable values" <|
|
||||
expect_incomparable_warn Nothing Number.nan <| [Nothing, Number.nan].sort on_incomparable=Problem_Behavior.Report_Warning
|
||||
expect_incomparable_warn 1 "hello" <| [1, "hello"].sort on_incomparable=Problem_Behavior.Report_Warning
|
||||
|
||||
Test.specify "should respect previous warnings on a vector" <|
|
||||
Problems.expect_warning "my_warn" <| (Warning.attach "my_warn" [3, 2]) . sort
|
||||
Problems.expect_warning "my_warn" <| (Warning.attach "my_warn" [3, Number.nan]) . sort
|
||||
expect_incomparable_warn 3 Number.nan <| (Warning.attach "my_warn" [3, Number.nan]) . sort on_incomparable=Problem_Behavior.Report_Warning
|
||||
|
||||
Test.specify "should respect previous warnings on vectors" pending="https://github.com/enso-org/enso/issues/6070" <|
|
||||
Problems.expect_warning "my_warn" <| [3, Warning.attach "my_warn" 2].sort
|
||||
expect_incomparable_warn 1 Number.nan [1, Warning.attach "my_warn" Number.nan].sort
|
||||
Problems.expect_warning "my_warn" <| [1, Warning.attach "my_warn" Number.nan].sort
|
||||
|
||||
Test.specify "should not fail when sorting incomparable types without custom comparator" <|
|
||||
# Parent, and No_Comp_Type do not have custom comparators
|
||||
[No_Comp_Type.Value 42, "hello"].sort . should_equal ["hello", No_Comp_Type.Value 42]
|
||||
[Parent.Value 42, No_Comp_Type.Value 42].sort . should_equal [No_Comp_Type.Value 42, Parent.Value 42]
|
||||
[No_Comp_Type.Value 42, Parent.Value 42].sort . should_equal [No_Comp_Type.Value 42, Parent.Value 42]
|
||||
|
||||
Test.group "Sorting with multiple comparators" <|
|
||||
Test.specify "should sort primitive values with the default comparator as the first group" <|
|
||||
[Ord.Value 4, Ord.Value 3, 20, 10].sort . should_equal [10, 20, Ord.Value 3, Ord.Value 4]
|
||||
[Ord.Value 4, 20, Ord.Value 3, 10].sort . should_equal [10, 20, Ord.Value 3, Ord.Value 4]
|
||||
[20, Ord.Value 4, Ord.Value 3, 10].sort . should_equal [10, 20, Ord.Value 3, Ord.Value 4]
|
||||
[Ord.Value 4, 20, Ord.Value 3, 10].sort . should_equal [10, 20, Ord.Value 3, Ord.Value 4]
|
||||
[Nothing, Ord.Value 4, 20, Ord.Value 3, 10].sort . should_equal [10, 20, Nothing, Ord.Value 3, Ord.Value 4]
|
||||
[Ord.Value 4, 20, Ord.Value 3, Nothing, 10].sort . should_equal [10, 20, Nothing, Ord.Value 3, Ord.Value 4]
|
||||
|
||||
Test.specify "should produce warning when sorting types with different comparators" <|
|
||||
[Ord.Value 1, 1].sort . should_equal [1, Ord.Value 1]
|
||||
sorted = [Ord.Value 1, 1].sort on_incomparable=Problem_Behavior.Report_Warning
|
||||
Warning.get_all sorted . at 0 . value . starts_with "Different comparators" . should_be_true
|
||||
|
||||
Test.specify "should merge groups of values with custom comparators based on the comparators FQN" <|
|
||||
[Ord.Value 1, My_Type.Value 1].sort . should_equal [My_Type.Value 1, Ord.Value 1]
|
||||
[My_Type.Value 1, Ord.Value 1].sort . should_equal [My_Type.Value 1, Ord.Value 1]
|
||||
sorted = [Ord.Value 1, My_Type.Value 1].sort on_incomparable=Problem_Behavior.Report_Warning
|
||||
Warning.get_all sorted . at 0 . value . starts_with "Different comparators" . should_be_true
|
||||
|
||||
Test.specify "should be stable when sorting values with different comparators" <|
|
||||
[Ord.Value 1, 20, My_Type.Value 1, 10].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1]
|
||||
[20, Ord.Value 1, My_Type.Value 1, 10].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1]
|
||||
[20, My_Type.Value 1, Ord.Value 1, 10].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1]
|
||||
[20, 10, My_Type.Value 1, Ord.Value 1].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1]
|
||||
[My_Type.Value 1, Ord.Value 1, 20, 10].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1]
|
||||
[Ord.Value 1, 20, 10, My_Type.Value 1].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1]
|
||||
|
||||
Test.specify "should be able to sort even unordered values" pending=topo_sort_pending <|
|
||||
[Ord.Value 2, UPair.Value "a" "b", Ord.Value 1, UPair.Value "c" "d"].sort . should_equal [Ord.Value 2, Ord.Value 1, UPair.Value "a" "b", UPair.Value "c" "d"]
|
||||
[Ord.Value 2, UPair.Value "X" "Y", Ord.Value 1, UPair.Value "c" "d"].sort . should_equal [Ord.Value 2, Ord.Value 1, UPair.Value "X" "Y", UPair.Value "c" "d"]
|
||||
|
||||
Test.specify "should produce warning when sorting unordered values" pending=topo_sort_pending <|
|
||||
expect_incomparable_warn (UPair.Value 1 2) (UPair.Value 3 4) [UPair.Value 1 2, UPair.Value 3 4].sort
|
||||
|
||||
|
||||
|
||||
|
||||
main = Test_Suite.run_main spec
|
||||
|
@ -557,11 +557,8 @@ type_spec name alter = Test.group name <|
|
||||
alter [Time_Of_Day.new 12, Time_Of_Day.new 10 30] . sort . should_equal [Time_Of_Day.new 10 30, Time_Of_Day.new 12]
|
||||
alter [Date_Time.new 2000 12 30 12 30, Date_Time.new 2000 12 30 12 00] . sort . should_equal [Date_Time.new 2000 12 30 12 00, Date_Time.new 2000 12 30 12 30]
|
||||
|
||||
alter ["aa", 2] . sort . should_fail_with Incomparable_Values
|
||||
alter [2, Date.new 1999] . sort . should_fail_with Incomparable_Values
|
||||
alter [Date_Time.new 1999 1 1 12 30, Date.new 1999] . sort . should_fail_with Incomparable_Values
|
||||
alter [Date_Time.new 1999 1 1 12 30, Time_Of_Day.new 12 30] . sort . should_fail_with Incomparable_Values
|
||||
Test.expect_panic_with ([3,2,1].to_array.sort_builtin 42) Type_Error
|
||||
alter ["aa", 2] . sort . should_equal [2, "aa"]
|
||||
alter [2, Date.new 1999] . sort . should_equal [2, Date.new 1999]
|
||||
|
||||
Test.specify "should leave the original vector unchanged" <|
|
||||
non_empty_vec = alter [2, 4, 2, 3, 2, 3]
|
||||
@ -574,6 +571,10 @@ type_spec name alter = Test.group name <|
|
||||
small_expected = [T.Value -20 0, T.Value -1 1, T.Value -1 10, T.Value 1 8, T.Value 1 3, T.Value 4 0]
|
||||
small_vec.sort . should_equal small_expected
|
||||
|
||||
Test.specify "should fail the sort if Report_Error problem_behavior specified" <|
|
||||
alter [T.Value 1 8, Nothing] . sort on_incomparable=Problem_Behavior.Report_Error . should_fail_with Incomparable_Values
|
||||
alter [Nothing, Number.nan] . sort on_incomparable=Problem_Behavior.Report_Error . should_fail_with Incomparable_Values
|
||||
|
||||
Test.specify "should be able to use a custom element projection" <|
|
||||
small_vec = alter [T.Value 1 8, T.Value 1 3, T.Value -20 0, T.Value -1 1, T.Value -1 10, T.Value 4 0]
|
||||
small_expected = [T.Value -20 0, T.Value 4 0, T.Value -1 1, T.Value 1 3, T.Value 1 8, T.Value -1 10]
|
||||
|
@ -5,9 +5,13 @@ type Any
|
||||
catch_primitive handler = @Builtin_Method "Any.catch_primitive"
|
||||
to_text self = @Builtin_Method "Any.to_text"
|
||||
to_display_text self = @Builtin_Method "Any.to_display_text"
|
||||
# Access EqualsNode directly
|
||||
== self other = Comparable.equals_builtin self other
|
||||
is_error self = False
|
||||
== self other = Comparable.equals_builtin self other
|
||||
!= self other = (self == other).not
|
||||
< self other = Comparable.less_than_builtin self other
|
||||
<= self other = Comparable.less_than_builtin self other || Comparable.equals_builtin self other
|
||||
> self other = Comparable.less_than_builtin other self
|
||||
>= self other = Comparable.less_than_builtin other self || Comparable.equals_builtin other self
|
||||
|
||||
@Builtin_Type
|
||||
type Comparable
|
||||
|
@ -588,16 +588,28 @@ def compare_runs(bench_run_id_1: str, bench_run_id_2: str, cache: Cache, tmp_dir
|
||||
bench_run_2 = _parse_bench_run_from_json(res_2)
|
||||
bench_report_1 = get_bench_report(bench_run_1, cache, tmp_dir)
|
||||
bench_report_2 = get_bench_report(bench_run_2, cache, tmp_dir)
|
||||
bench_labels: List[str] = list(bench_report_1.label_score_dict.keys())
|
||||
# Check that the runs have the same labels, and get their intersection
|
||||
bench_labels_1 = set(bench_report_1.label_score_dict.keys())
|
||||
bench_labels_2 = set(bench_report_2.label_score_dict.keys())
|
||||
if bench_labels_1 != bench_labels_2:
|
||||
logging.warning(
|
||||
f"Benchmark labels are not the same in both runs. This means that "
|
||||
f"there will be some missing numbers in one of the runs. "
|
||||
f"The set difference is {bench_labels_1.difference(bench_labels_2)}")
|
||||
all_labels: List[str] = sorted(
|
||||
list(bench_labels_1.intersection(bench_labels_2)))
|
||||
bench_report_2.label_score_dict.keys()
|
||||
|
||||
df = pd.DataFrame(columns=["bench_label", "score-run-1", "score-run-2"])
|
||||
for bench_label in bench_labels:
|
||||
for bench_label in all_labels:
|
||||
df = pd.concat([df, pd.DataFrame([{
|
||||
"bench_label": bench_label,
|
||||
"score-run-1": bench_report_1.label_score_dict[bench_label],
|
||||
"score-run-2": bench_report_2.label_score_dict[bench_label],
|
||||
}])], ignore_index=True)
|
||||
df["score-diff"] = np.diff(df[["score-run-1", "score-run-2"]], axis=1)
|
||||
df["score-diff-perc"] = df.apply(lambda row: perc_str(percent_change(row["score-run-1"], row["score-run-2"])),
|
||||
df["score-diff-perc"] = df.apply(lambda row: perc_str(
|
||||
percent_change(row["score-run-1"], row["score-run-2"])),
|
||||
axis=1)
|
||||
print("================================")
|
||||
print(df.to_string(index=False, header=True, justify="center", float_format="%.5f"))
|
||||
|
Loading…
Reference in New Issue
Block a user