mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 13:15:52 +03:00
Add Comparator conversion for all types (#4067)
Add `Comparator` type class emulation for all types. Migrate all the types in stdlib to this new `Comparator` API. The main documentation is in `Ordering.enso`. Fixes these pivotals: - https://www.pivotaltracker.com/story/show/183945328 - https://www.pivotaltracker.com/story/show/183958734 - https://www.pivotaltracker.com/story/show/184380208 # Important Notes - The new Comparator API forces users to specify both `equals` and `hash` methods on their custom comparators. - All the `compare_to` overrides were replaced by definition of a custom _ordered_ comparator. - All the call sites of `x.compare_to y` method were replaced with `Ordering.compare x y`. - `Ordering.compare` is essentially a shortcut for `Comparable.from x . compare x y`. - The default comparator for `Any` is `Default_Unordered_Comparator`, which just forwards to the builtin `EqualsNode` and `HashCodeNode` nodes. - For `x`, one can get its hash with `Comparable.from x . hash x`. - This makes `hash` as _hidden_ as possible. There are no other public methods to get a hash code of an object. - Comparing `x` and `y` can be done either by `Ordering.compare x y` or `Comparable.from x . compare x y` instead of `x.compare_to y`.
This commit is contained in:
parent
4f70bcc5ff
commit
1f8511dab2
@ -556,6 +556,7 @@
|
||||
- [Optimize Atom storage layouts][3862]
|
||||
- [Make instance methods callable like statics for builtin types][4077]
|
||||
- [Convert large longs to doubles, safely, for host calls][4099]
|
||||
- [Consistent ordering with comparators](4067)
|
||||
- [Profile engine startup][4110]
|
||||
- [Report type of polyglot values][4111]
|
||||
- [Engine can now recover from serialization failures][5591]
|
||||
@ -649,6 +650,7 @@
|
||||
[4056]: https://github.com/enso-org/enso/pull/4056
|
||||
[4077]: https://github.com/enso-org/enso/pull/4077
|
||||
[4099]: https://github.com/enso-org/enso/pull/4099
|
||||
[4067]: https://github.com/enso-org/enso/pull/4067
|
||||
[4110]: https://github.com/enso-org/enso/pull/4110
|
||||
[4111]: https://github.com/enso-org/enso/pull/4111
|
||||
[5591]: https://github.com/enso-org/enso/pull/5591
|
||||
|
@ -1,12 +1,16 @@
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Pair.Pair
|
||||
import project.Data.Range.Extensions
|
||||
import project.Data.Text.Text
|
||||
import project.Error.Error
|
||||
import project.Error.Incomparable_Values.Incomparable_Values
|
||||
import project.Error.Common.No_Such_Conversion
|
||||
import project.Error.Common.Type_Error
|
||||
import project.Nothing.Nothing
|
||||
import project.Meta
|
||||
import project.Panic.Panic
|
||||
|
||||
from project.Data.Boolean import Boolean, True, False
|
||||
from project.Data.Ordering import all
|
||||
|
||||
## Any is the universal top-type, with all other types being subsumed by it.
|
||||
|
||||
@ -101,7 +105,21 @@ type Any
|
||||
a = 7 * 21
|
||||
a == 147
|
||||
== : Any -> Boolean
|
||||
== self that = @Builtin_Method "Any.=="
|
||||
== self that =
|
||||
# If there is No_Such_Conversion, then `self` and `that` are probably
|
||||
# host or polyglot values, so we just compare them with the default comparator.
|
||||
eq_self = Panic.catch No_Such_Conversion (Comparable.from self) _-> Default_Unordered_Comparator
|
||||
eq_that = Panic.catch No_Such_Conversion (Comparable.from that) _-> Default_Unordered_Comparator
|
||||
if Meta.is_same_object eq_self Incomparable then False else
|
||||
similar_type = Meta.is_same_object eq_self eq_that
|
||||
if similar_type.not then False else
|
||||
case eq_self.is_ordered of
|
||||
True ->
|
||||
# Comparable.equals_builtin is a hack how to directly access EqualsNode from the
|
||||
# engine, so that we don't end up in an infinite recursion here (which would happen
|
||||
# if we would compare with `eq_self == eq_that`).
|
||||
Comparable.equals_builtin (eq_self.compare self that) Ordering.Equal
|
||||
False -> eq_self.equals self that
|
||||
|
||||
## ALIAS Inequality
|
||||
|
||||
@ -133,12 +151,8 @@ type Any
|
||||
Arguments:
|
||||
- that: The value to compare `self` against.
|
||||
|
||||
To have `>` defined, a type must define `compare_to`, returning an Ordering.
|
||||
|
||||
! Implementing Greater Than
|
||||
Many types can admit a definition of greater than that is more efficient
|
||||
than the generic one given here. When implementing this for your own types
|
||||
please ensure that it is semantically equivalent to using `.compare_to`.
|
||||
To have `>` properly defined, a type must have an associated ordered comparator.
|
||||
See `Ordering.enso` for information how comparators work.
|
||||
|
||||
> Example
|
||||
Checking if the variable `a` is greater than `147`.
|
||||
@ -148,8 +162,12 @@ type Any
|
||||
example_greater =
|
||||
a = 7 * 28
|
||||
a > 147
|
||||
> : Any -> Boolean
|
||||
> self that = self.compare_to that == Ordering.Greater
|
||||
> : Any -> Boolean ! Incomparable_Values
|
||||
> self that =
|
||||
assert_ordered_comparators self that <|
|
||||
case (Comparable.from self).compare self that of
|
||||
Ordering.Greater -> True
|
||||
_ -> False
|
||||
|
||||
## ALIAS Greater Than or Equal
|
||||
|
||||
@ -174,10 +192,13 @@ type Any
|
||||
example_greater_eq =
|
||||
a = 6 * 21
|
||||
a >= 147
|
||||
>= : Any -> Boolean
|
||||
>= : Any -> Boolean ! Incomparable_Values
|
||||
>= self that =
|
||||
ordering = self.compare_to that
|
||||
(ordering == Ordering.Greater) || (ordering == Ordering.Equal)
|
||||
assert_ordered_comparators self that <|
|
||||
case (Comparable.from self).compare self that of
|
||||
Ordering.Less -> False
|
||||
Ordering.Equal -> True
|
||||
Ordering.Greater -> True
|
||||
|
||||
## ALIAS Less Than
|
||||
|
||||
@ -186,12 +207,8 @@ type Any
|
||||
Arguments:
|
||||
- that: The value to compare `self` against.
|
||||
|
||||
To have `<` defined, a type must define `compare_to`, returning an Ordering.
|
||||
|
||||
! Implementing Less Than
|
||||
Many types can admit a definition of less than that is more efficient than
|
||||
the generic one given here. When implementing this for your own types
|
||||
please ensure that it is semantically equivalent to using `.compare_to`.
|
||||
To have `<` properly defined, a type must have an associated ordered comparator.
|
||||
See `Ordering.enso` for information how comparators work.
|
||||
|
||||
> Example
|
||||
Checking if the variable `a` is less than `147`.
|
||||
@ -201,8 +218,12 @@ type Any
|
||||
example_less =
|
||||
a = 7 * 21
|
||||
a < 147
|
||||
< : Any -> Boolean
|
||||
< self that = self.compare_to that == Ordering.Less
|
||||
< : Any -> Boolean ! Incomparable_Values
|
||||
< self that =
|
||||
assert_ordered_comparators self that <|
|
||||
case (Comparable.from self).compare self that of
|
||||
Ordering.Less -> True
|
||||
_ -> False
|
||||
|
||||
## ALIAS Less Than or Equal
|
||||
|
||||
@ -227,10 +248,13 @@ type Any
|
||||
example_less_eq =
|
||||
a = 7 * 21
|
||||
a < 147
|
||||
<= : Any -> Boolean
|
||||
<= : Any -> Boolean ! Incomparable_Values
|
||||
<= self that =
|
||||
ordering = self.compare_to that
|
||||
(ordering == Ordering.Less) || (ordering == Ordering.Equal)
|
||||
assert_ordered_comparators self that <|
|
||||
case (Comparable.from self).compare self that of
|
||||
Ordering.Less -> True
|
||||
Ordering.Equal -> True
|
||||
Ordering.Greater -> False
|
||||
|
||||
## Checks if the type is an instance of `Nothing`.
|
||||
|
||||
@ -396,3 +420,18 @@ type Any
|
||||
(+1 >> *2) 2
|
||||
>> : (Any -> Any) -> (Any -> Any) -> Any -> Any
|
||||
>> self ~that = x -> that (self x)
|
||||
|
||||
|
||||
## PRIVATE
|
||||
Checks if both comparators of the given objects are both of same type and ordered.
|
||||
If they are not of same type, a `Type_Error` is thrown.
|
||||
If the comparators are either `Incomparable`, or unordered, `False` is returned.
|
||||
assert_ordered_comparators : Any -> (Any -> Any) -> Any ! (Type_Error | Incomparable_Values)
|
||||
assert_ordered_comparators this that ~action =
|
||||
comp_this = Comparable.from this
|
||||
comp_that = Comparable.from that
|
||||
if (Meta.is_same_object comp_this comp_that).not then Error.throw (Type_Error.Error (Meta.type_of comp_this) comp_that "comp_that") else
|
||||
if Meta.is_same_object comp_this Incomparable || comp_this.is_ordered.not then Error.throw Incomparable_Values else
|
||||
action
|
||||
|
||||
|
||||
|
@ -146,7 +146,7 @@ type Array
|
||||
|
||||
[3,2,1].to_array.sort
|
||||
sort : (Any -> Any -> Ordering) -> Array
|
||||
sort self comparator=(_.compare_to _) =
|
||||
sort self comparator=(Ordering.compare _ _) =
|
||||
self.sort_builtin comparator
|
||||
|
||||
## Identity.
|
||||
|
@ -1,5 +1,7 @@
|
||||
import project.Any.Any
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Ordering.Default_Ordered_Comparator
|
||||
import project.Nothing.Nothing
|
||||
|
||||
from project.Data.Boolean.Boolean import True, False
|
||||
@ -55,19 +57,6 @@ type Boolean
|
||||
not : Boolean
|
||||
not self = @Builtin_Method "Boolean.not"
|
||||
|
||||
## Compares the two operands to determine the ordering of this with
|
||||
respect to that.
|
||||
|
||||
Arguments:
|
||||
- that: The operand to order this with respect to.
|
||||
|
||||
> Example
|
||||
Computing the ordering of True and False
|
||||
|
||||
True.compare_to False
|
||||
compare_to : Boolean -> Ordering
|
||||
compare_to self that = @Builtin_Method "Boolean.compare_to"
|
||||
|
||||
## The if-then-else control flow operator that executes one of two branches
|
||||
based on a conditional.
|
||||
|
||||
@ -100,3 +89,6 @@ type Boolean
|
||||
if (27 % 3) == 0 then IO.println "Fizz"
|
||||
if_then : Any -> Any | Nothing
|
||||
if_then self ~on_true = @Builtin_Method "Boolean.if_then"
|
||||
|
||||
|
||||
Comparable.from (_:Boolean) = Default_Ordered_Comparator
|
||||
|
@ -22,7 +22,8 @@ import project.Warning.Warning
|
||||
from project.Metadata.Widget import Single_Choice
|
||||
from project.Metadata.Choice import Option
|
||||
import project.Metadata.Display
|
||||
|
||||
# We need to import conversion methods from Ordering, therefore, we import all
|
||||
from project.Data.Ordering import all
|
||||
from project.Data.Boolean import Boolean, True, False
|
||||
|
||||
## Methods for serializing from and to JSON.
|
||||
@ -174,19 +175,6 @@ type JS_Object
|
||||
to_json : Text
|
||||
to_json self = self.to_text
|
||||
|
||||
## Checks if this JS_Object is equal to another JS_Object.
|
||||
|
||||
Arguments:
|
||||
- that: The map to compare `self` to.
|
||||
== : JS_Object -> Boolean
|
||||
== self that = case that of
|
||||
_ : JS_Object ->
|
||||
self_keys = self.field_names
|
||||
that_keys = that.field_names
|
||||
self_keys.length == that_keys.length && self_keys.all key->
|
||||
(self.get key == that.at key).catch No_Such_Key.Error _->False
|
||||
_ -> False
|
||||
|
||||
## UNSTABLE
|
||||
|
||||
Transform the vector into text for displaying as part of its default
|
||||
@ -195,6 +183,29 @@ type JS_Object
|
||||
to_default_visualization_data self =
|
||||
render self
|
||||
|
||||
## PRIVATE
|
||||
type JS_Object_Comparator
|
||||
is_ordered : Boolean
|
||||
is_ordered = False
|
||||
|
||||
equals : JS_Object -> JS_Object -> Boolean
|
||||
equals obj1 obj2 =
|
||||
obj1_keys = obj1.field_names
|
||||
obj2_keys = obj2.field_names
|
||||
obj1_keys.length == obj2_keys.length && obj1_keys.all key->
|
||||
(obj1.get key == obj2.at key).catch No_Such_Key.Error _->False
|
||||
|
||||
hash : JS_Object -> Integer
|
||||
hash obj =
|
||||
values_hashes = obj.field_names.map field_name->
|
||||
val = obj.get field_name
|
||||
Comparable.from val . hash val
|
||||
# Return sum, as we don't care about ordering of field names
|
||||
values_hashes.fold 0 (+)
|
||||
|
||||
Comparable.from (_:JS_Object) = JS_Object_Comparator
|
||||
|
||||
|
||||
## PRIVATE
|
||||
Render the JS_Object to Text with truncated depth.
|
||||
render object depth=0 max_depth=5 max_length=100 = case object of
|
||||
|
@ -1,4 +1,6 @@
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Ordering.Default_Ordered_Comparator
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Locale.Locale
|
||||
import project.Error.Common.Arithmetic_Error
|
||||
@ -529,19 +531,6 @@ type Decimal
|
||||
ceil : Integer
|
||||
ceil self = @Builtin_Method "Decimal.ceil"
|
||||
|
||||
## Compares the two operands to determine the ordering of this with
|
||||
respect to that.
|
||||
|
||||
Arguments:
|
||||
- that: The operand to order this with respect to.
|
||||
|
||||
> Example
|
||||
Computing the ordering of 1.732 and 4 (Less).
|
||||
|
||||
1.732.compare_to 4
|
||||
compare_to : Number -> Ordering
|
||||
compare_to self that = @Builtin_Method "Decimal.compare_to"
|
||||
|
||||
## Computes the nearest integer below this decimal.
|
||||
|
||||
This method provides a means of converting a Decimal to an Integer.
|
||||
@ -768,19 +757,6 @@ type Integer
|
||||
ceil : Integer
|
||||
ceil self = @Builtin_Method "Integer.ceil"
|
||||
|
||||
## Compares the two operands to determine the ordering of this with
|
||||
respect to that.
|
||||
|
||||
Arguments:
|
||||
- that: The operand to order this with respect to.
|
||||
|
||||
> Example
|
||||
Computing the ordering of 1 and 4 (Less).
|
||||
|
||||
1.compare_to 4
|
||||
compare_to : Number -> Ordering
|
||||
compare_to self that = @Builtin_Method "Integer.compare_to"
|
||||
|
||||
## Computes the integer division of this by that.
|
||||
|
||||
Arguments:
|
||||
@ -964,6 +940,8 @@ type Integer
|
||||
|
||||
parse_builtin text radix = @Builtin_Method "Integer.parse"
|
||||
|
||||
Comparable.from (_:Number) = Default_Ordered_Comparator
|
||||
|
||||
## UNSTABLE
|
||||
|
||||
A syntax error when parsing a double.
|
||||
|
@ -1,14 +1,182 @@
|
||||
import project.Any.Any
|
||||
import project.Data.Numbers.Decimal
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Numbers.Number
|
||||
import project.Error.Common.Type_Error
|
||||
import project.Error.Error
|
||||
import project.Error.Incomparable_Values.Incomparable_Values
|
||||
import project.Error.Unimplemented.Unimplemented
|
||||
import project.Nothing
|
||||
import project.Meta
|
||||
import project.Meta.Atom
|
||||
from project.Data.Boolean import all
|
||||
|
||||
## Provides custom ordering, equality check and hash code for types that need it.
|
||||
|
||||
The Enso runtime system offers default implementation of _equality_
|
||||
as well as capability to _compute hash code_ (for use in `Map`) automatically.
|
||||
The default implementation is sufficient for most of the programming activities.
|
||||
Especially when defining new type and its constructors, they get sensible
|
||||
implementation of both functions.
|
||||
|
||||
Should there be a need to redefine the default implementation, here is a way:
|
||||
Define conversion function `Comparable.from` for your `type` and return pointer to
|
||||
another `type` that satisfies either of the following two definitions:
|
||||
|
||||
```
|
||||
type Ordered_Comparator T
|
||||
is_ordered = True
|
||||
compare : T -> T -> Ordering
|
||||
hash : T -> Integer
|
||||
|
||||
type Unordered_Comparator T
|
||||
is_ordered = False
|
||||
equals : T -> T -> Boolean
|
||||
hash : T -> Integer
|
||||
```
|
||||
|
||||
Or `Incomparable` in case that the type `T` should not be compared at all.
|
||||
|
||||
Note that there has to be `is_ordered` method defined which returns a Boolean
|
||||
indicating that the comparator is ordered. This is currently needed as there is
|
||||
no way to define interfaces in Enso.
|
||||
|
||||
An _unordered comparator_ has to implement both `equals` and `hash` to define
|
||||
a _total_ custom equality. By _total_, we mean that every instance of the type
|
||||
has to be either equal or not equal, which is represented by the type signature
|
||||
of `equals` - just `Boolean` is returned without any errors thrown.
|
||||
|
||||
An _ordered comparator_ has `compare` method instead of `equals` method, that is
|
||||
expected to return `Ordering` signaling that an instance of the type is either
|
||||
less than, equal to, or greater than the other instance. This relation is also
|
||||
_total_, meaning that all the instances of the type are comparable.
|
||||
|
||||
The runtime expects the following semantics for all the comparators:
|
||||
- Hash consistency:
|
||||
- If x == y then hash(x) == hash(y)
|
||||
- If hash(x) != hash(y) then x != y
|
||||
- Consistency: if x == y then x == y for all the subsequent invocations.
|
||||
- Symmetry: if x == y then y == x
|
||||
- Reflexivity: x == x
|
||||
- Transitivity: if x < y and y < z then x < z
|
||||
- Antisymmetry (?): if x > y then y < x
|
||||
|
||||
Users are responsible for the compliance to the aforementioned semantics.
|
||||
Should the semantics be violated, an unexpected behavior may be encountered, e.g.,
|
||||
`Array.sort` may return unexpected results or fail with `Incomparable_Values`.
|
||||
|
||||
|
||||
> Example
|
||||
Comparator for an unordered Pair `UPair`. In this example, we can see an
|
||||
implementation of the `hash` method that delegates to hash methods of
|
||||
fields.
|
||||
|
||||
```
|
||||
type UPair
|
||||
Value x y
|
||||
|
||||
type UPair_Comparator
|
||||
is_ordered = False
|
||||
|
||||
equals pair1 pair2 =
|
||||
if pair1.x == pair2.x && pair1.y == pair2.y then True else
|
||||
if pair1.x == pair2.y && pair1.y == pair2.x then True else
|
||||
False
|
||||
|
||||
hash upair =
|
||||
x_comp = Comparable.from upair.x
|
||||
y_comp = Comparable.from upair.y
|
||||
(x_comp.hash upair.x) + (y_comp.hash upair.y)
|
||||
|
||||
Comparable.from (_ : UPair) = UPair_Comparator
|
||||
```
|
||||
|
||||
> Example
|
||||
Representation of _rational numbers_ as a pair of integers needs a
|
||||
special equality. Here is a way to define it:
|
||||
|
||||
```
|
||||
type Rational
|
||||
Fraction (numerator:Integer) (denominator:Integer)
|
||||
|
||||
Comparable.from (_:Rational) = Rational_Ordering
|
||||
|
||||
type Rational_Ordering
|
||||
is_ordered = True
|
||||
compare self r1 r2 =
|
||||
v1 = r1.numerator * r2.denominator
|
||||
v2 = r2.numerator * r1.denominator
|
||||
if v1 < v2 then Ordering.Less else
|
||||
if v1 > v2 then Ordering.Greater else
|
||||
Ordering.Equal
|
||||
hash self r1 = 42 # or something better
|
||||
```
|
||||
|
||||
By defining the `Rational_Ordering` and making it available via
|
||||
`Comparable.from (_:Rational)` method, all parts of the Enso system will use
|
||||
the custom comparator whenever equality or hash code is needed.
|
||||
|
||||
The equality check of two objects:
|
||||
- verifies both objects share the same type of `comparator`
|
||||
- consults their `compare`, or `equals` method, based on whether the comparator is
|
||||
ordered or unordered.
|
||||
- if the result is `Ordering.Equal` the system also checks that both instances have the same `hash`
|
||||
- the `hash` code check may be done only from time to time to speed things up
|
||||
@Builtin_Type
|
||||
type Comparable
|
||||
## PRIVATE
|
||||
Called as a callback from internal engine code for an atom with a custom
|
||||
comparator. It is assumed that the given atom has a custom comparator, that is
|
||||
a comparator different than `Default_Unordered_Comparator`.
|
||||
hash_callback : Atom -> Integer
|
||||
hash_callback atom = (Comparable.from atom).hash atom
|
||||
|
||||
## PRIVATE
|
||||
A custom comparator is any comparator that is different than the
|
||||
default ones.
|
||||
has_custom_comparator : Atom -> Boolean
|
||||
has_custom_comparator atom =
|
||||
comp = Comparable.from atom
|
||||
(comp.is_a Default_Unordered_Comparator).not && (comp.is_a Default_Ordered_Comparator).not
|
||||
|
||||
## Singleton denoting that values of certain type are not comparable.
|
||||
type Incomparable
|
||||
Singleton
|
||||
|
||||
## Default implementation of unordered comparator.
|
||||
@Builtin_Type
|
||||
type Default_Unordered_Comparator
|
||||
is_ordered = False
|
||||
|
||||
equals : Any -> Any -> Boolean
|
||||
equals x y = Comparable.equals_builtin x y
|
||||
|
||||
hash : Any -> Integer
|
||||
hash object = Comparable.hash_builtin object
|
||||
|
||||
|
||||
## Default implementation of an ordered _comparator_. Handles only primitive types,
|
||||
does not handle atoms, or vectors. Any type that requires an ordering, must
|
||||
define its own ordered comparator.
|
||||
@Builtin_Type
|
||||
type Default_Ordered_Comparator
|
||||
is_ordered = True
|
||||
|
||||
## Handles only primitive types, not atoms or vectors.
|
||||
compare : Any -> Any -> Ordering
|
||||
compare x y =
|
||||
if Comparable.less_than_builtin x y then Ordering.Less else
|
||||
if Comparable.equals_builtin x y then Ordering.Equal else
|
||||
if Comparable.less_than_builtin y x then Ordering.Greater
|
||||
|
||||
hash : Number -> Integer
|
||||
hash x = Comparable.hash_builtin x
|
||||
|
||||
|
||||
Comparable.from (_:Any) = Default_Unordered_Comparator
|
||||
|
||||
|
||||
## Types representing the ordering of values.
|
||||
|
||||
These are intended to be returned from the `compare_to` function, that has a
|
||||
type as follows for a type `A`: `A.compare_to : A -> Ordering`.
|
||||
|
||||
The result should be returned in terms of how `self` orders in comparison to
|
||||
`that`. So, if `self` is greater than `that`, you should return `Greater.`
|
||||
@Builtin_Type
|
||||
type Ordering
|
||||
## A representation that the first value orders as less than the second.
|
||||
@ -20,6 +188,14 @@ type Ordering
|
||||
## A representation that the first value orders as greater than the second.
|
||||
Greater
|
||||
|
||||
## Compares to values and returns an Ordering
|
||||
compare : Any -> Any -> Ordering ! (Incomparable_Values | Type_Error)
|
||||
compare x y =
|
||||
if x < y then Ordering.Less else
|
||||
if x == y then Ordering.Equal else
|
||||
if x > y then Ordering.Greater else
|
||||
Error.throw Incomparable_Values
|
||||
|
||||
## Converts the ordering to the signed notion of ordering based on integers.
|
||||
|
||||
> Example
|
||||
@ -42,11 +218,6 @@ type Ordering
|
||||
Ordering.Equal -> other
|
||||
Ordering.Greater -> Ordering.Greater
|
||||
|
||||
compare_to : Ordering -> Ordering
|
||||
compare_to self that = case that of
|
||||
_ : Ordering -> self.to_sign.compare_to that.to_sign
|
||||
_ -> Error.throw (Type_Error.Error Ordering that "that")
|
||||
|
||||
## Converts a sign-based representation of ordering to Enso's native ordering.
|
||||
|
||||
Arguments:
|
||||
@ -59,3 +230,10 @@ type Ordering
|
||||
from_sign : Integer -> Ordering
|
||||
from_sign sign = if sign == 0 then Ordering.Equal else
|
||||
if sign > 0 then Ordering.Greater else Ordering.Less
|
||||
|
||||
type Ordering_Comparator
|
||||
is_ordered = True
|
||||
compare x y = (Comparable.from x.to_sign).compare x.to_sign y.to_sign
|
||||
hash x = x.to_sign
|
||||
|
||||
Comparable.from (_:Ordering) = Ordering_Comparator
|
||||
|
@ -1,6 +1,7 @@
|
||||
import project.Any.Any
|
||||
import project.Data.Ordering.Natural_Order
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Text.Case_Sensitivity.Case_Sensitivity
|
||||
import project.Data.Text.Text_Ordering.Text_Ordering
|
||||
import project.Error.Incomparable_Values.Incomparable_Values
|
||||
@ -11,18 +12,19 @@ from project.Data.Boolean import True, False
|
||||
polyglot java import org.enso.base.ObjectComparator
|
||||
|
||||
## ADVANCED
|
||||
Creates a Java Comparator object which can call Enso compare_to
|
||||
Creates a Java Comparator object which can call back to Enso for comparison
|
||||
of non-primitive types.
|
||||
|
||||
Arguments:
|
||||
- custom_comparator:
|
||||
If `Nothing` will get a singleton instance for `.compare_to`.
|
||||
Otherwise can support a custom fallback comparator.
|
||||
If `Nothing` will get an ordered comparator from each element.
|
||||
Otherwise can support a custom fallback compare function.
|
||||
new : Nothing | (Any -> Any -> Ordering) -> ObjectComparator
|
||||
new custom_comparator=Nothing =
|
||||
comparator_to_java cmp x y = Incomparable_Values.handle_errors (cmp x y . to_sign)
|
||||
|
||||
case custom_comparator of
|
||||
Nothing -> ObjectComparator.getInstance (comparator_to_java .compare_to)
|
||||
Nothing -> ObjectComparator.getInstance (comparator_to_java Ordering.compare)
|
||||
_ -> ObjectComparator.new (comparator_to_java custom_comparator)
|
||||
|
||||
## ADVANCED
|
||||
|
@ -23,8 +23,8 @@ polyglot java import com.ibm.icu.text.BreakIterator
|
||||
compare : Text -> Text -> Case_Sensitivity -> Ordering
|
||||
compare text1 text2 case_sensitivity=Case_Sensitivity.Sensitive =
|
||||
compare_text = case case_sensitivity of
|
||||
Case_Sensitivity.Default -> _.compare_to _
|
||||
Case_Sensitivity.Sensitive -> _.compare_to _
|
||||
Case_Sensitivity.Default -> Ordering.compare _ _
|
||||
Case_Sensitivity.Sensitive -> Ordering.compare _ _
|
||||
Case_Sensitivity.Insensitive locale -> a -> b -> a.compare_to_ignore_case b locale
|
||||
|
||||
iter1 = BreakIterator.getCharacterInstance
|
||||
@ -60,7 +60,7 @@ compare text1 text2 case_sensitivity=Case_Sensitivity.Sensitive =
|
||||
[substring, decimal, pair.first, next_index]
|
||||
|
||||
|
||||
## Loop to computer the ordering of text1 and text2.
|
||||
## Loop to compute the ordering of text1 and text2.
|
||||
Ordering: Nothing < Number < Text
|
||||
prev1 - index to start of current character in text1.
|
||||
next1 - index to start of next character (or -1 if finished) in text1.
|
||||
@ -98,7 +98,7 @@ compare text1 text2 case_sensitivity=Case_Sensitivity.Sensitive =
|
||||
num_text2 = parsed2.at 0
|
||||
value2 = parsed2.at 1
|
||||
|
||||
value_comparison = value1.compare_to value2
|
||||
value_comparison = Ordering.compare value1 value2
|
||||
if value_comparison != Ordering.Equal then value_comparison else
|
||||
text_comparison = compare_text num_text1 num_text2
|
||||
if text_comparison != Ordering.Equal then text_comparison else
|
||||
|
@ -9,7 +9,7 @@ from project.Data.Boolean import True, False
|
||||
Arguments:
|
||||
- vector1: The first vector to compare.
|
||||
- vector2: The second vector to compare.
|
||||
- element_comparator (optional): A custom comparator defining the order
|
||||
- element_comparator (optional): A custom compare function defining the order
|
||||
between particular elements. Uses the default ordering by default.
|
||||
|
||||
Returns whether `vector1` is less, equal or greater than `vector2` according
|
||||
@ -30,7 +30,7 @@ from project.Data.Boolean import True, False
|
||||
Vector_Lexicographic_Order.compare [] [1] == Ordering.Less
|
||||
Vector_Lexicographic_Order.compare [1] [1] == Ordering.Equal
|
||||
compare : Vector -> Vector -> (Any -> Any -> Ordering) -> Ordering
|
||||
compare vector1 vector2 (element_comparator = _.compare_to _) =
|
||||
compare vector1 vector2 (element_comparator = Ordering.compare _ _) =
|
||||
is_index_contained_in_both ix = ix<vector1.length && ix<vector2.length
|
||||
go ix = case is_index_contained_in_both ix of
|
||||
True ->
|
||||
@ -48,5 +48,5 @@ compare vector1 vector2 (element_comparator = _.compare_to _) =
|
||||
## At least one of the vectors ran out of elements. In that case,
|
||||
the longer Vector is the greater one and if both have the same
|
||||
length that means they must have been equal.
|
||||
vector1.length . compare_to vector2.length
|
||||
Ordering.compare vector1.length vector2.length
|
||||
go 0
|
||||
|
@ -5,6 +5,7 @@ import project.Data.Numbers.Integer
|
||||
import project.Data.Numbers.Number
|
||||
import project.Data.Ordering.Comparator
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Range.Extensions
|
||||
import project.Data.Vector.Vector
|
||||
import project.Error.Error
|
||||
@ -341,12 +342,13 @@ compute_fold counter current value =
|
||||
counter.increment
|
||||
|
||||
if counter.comparatorError.not then
|
||||
value_comparator = Comparable.from value
|
||||
if counter.minimum.is_nothing then counter.setMinimum value else
|
||||
ordering = Incomparable_Values.handle_errors <| value.compare_to counter.minimum
|
||||
ordering = Incomparable_Values.handle_errors <| value_comparator.compare value counter.minimum
|
||||
if ordering.is_error then counter.failComparator else
|
||||
if ordering == Ordering.Less then counter.setMinimum value
|
||||
if counter.maximum.is_nothing then counter.setMaximum value else
|
||||
ordering = Incomparable_Values.handle_errors <| value.compare_to counter.maximum
|
||||
ordering = Incomparable_Values.handle_errors <| value_comparator.compare value counter.maximum
|
||||
if ordering.is_error then counter.failComparator else
|
||||
if ordering == Ordering.Greater then counter.setMaximum value
|
||||
|
||||
|
@ -5,6 +5,7 @@ import project.Error.Common.Type_Error
|
||||
import project.Error.Error
|
||||
import project.Meta
|
||||
|
||||
from project.Data.Ordering import all
|
||||
from project.Data.Boolean import Boolean, True, False
|
||||
|
||||
polyglot java import org.enso.base.Text_Utils
|
||||
@ -33,23 +34,6 @@ type Text
|
||||
+ : Text -> Text
|
||||
+ self that = @Builtin_Method "Text.+"
|
||||
|
||||
## Compare two texts to discover their ordering.
|
||||
|
||||
Arguments:
|
||||
- that: The text to order `self` with respect to.
|
||||
|
||||
> Example
|
||||
Checking how "a" orders in relation to "b".
|
||||
|
||||
"a".compare_to "b"
|
||||
compare_to : Text -> Ordering
|
||||
compare_to self that = case that of
|
||||
_ : Text ->
|
||||
is_normalized = self.is_normalized && that.is_normalized
|
||||
comparison_result = Text_Utils.compare self that is_normalized
|
||||
Ordering.from_sign comparison_result
|
||||
_ -> Error.throw (Type_Error.Error Text that "that")
|
||||
|
||||
## Checks whether `self` is equal to `that`, ignoring the case of the texts.
|
||||
|
||||
Arguments:
|
||||
@ -134,3 +118,5 @@ type Text
|
||||
Conversion to Text that overrides the default `to_text` behavior.
|
||||
to_text : Text
|
||||
to_text self = self
|
||||
|
||||
Comparable.from (_:Text) = Default_Ordered_Comparator
|
||||
|
@ -3,6 +3,8 @@ import project.Data.Json.JS_Object
|
||||
import project.Data.Locale.Locale
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Ordering.Default_Ordered_Comparator
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Time.Date_Period.Date_Period
|
||||
import project.Data.Time.Date_Time.Date_Time
|
||||
@ -589,22 +591,6 @@ type Date
|
||||
format : Text -> Text
|
||||
format self pattern = Time_Utils.local_date_format self pattern
|
||||
|
||||
## Compares `self` to `that` to produce an ordering.
|
||||
|
||||
Arguments:
|
||||
- that: The other `Date` to compare against.
|
||||
|
||||
> Example
|
||||
Compare two dates for their ordering.
|
||||
|
||||
(Date.new 2000).compare_to (Date.new 2001)
|
||||
compare_to : Date -> Ordering
|
||||
compare_to self that = case that of
|
||||
_ : Date ->
|
||||
sign = Time_Utils.compare_to_localdate self that
|
||||
Ordering.from_sign sign
|
||||
_ -> Error.throw (Type_Error.Error Date that "that")
|
||||
|
||||
|
||||
## PRIVATE
|
||||
week_days_between start end =
|
||||
@ -641,3 +627,6 @@ is_weekend date =
|
||||
## PRIVATE
|
||||
fits_in_range start end date =
|
||||
(start <= date) && (date < end)
|
||||
|
||||
|
||||
Comparable.from (_:Date) = Default_Ordered_Comparator
|
||||
|
@ -3,6 +3,8 @@ import project.Data.Json.JS_Object
|
||||
import project.Data.Locale.Locale
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Ordering.Default_Ordered_Comparator
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Time.Date.Date
|
||||
import project.Data.Time.Date_Period.Date_Period
|
||||
@ -686,18 +688,5 @@ type Date_Time
|
||||
format : Text -> Text
|
||||
format self pattern = @Builtin_Method "Date_Time.format"
|
||||
|
||||
## Compares `self` to `that` to produce an ordering.
|
||||
|
||||
Arguments:
|
||||
- that: The other `Date_Time` to compare against.
|
||||
|
||||
> Example
|
||||
Compare two times for their ordering.
|
||||
|
||||
(Date_Time.new 2000).compare_to (Date_Time.new 2001)
|
||||
compare_to : Date_Time -> Ordering
|
||||
compare_to self that = case that of
|
||||
_ : Date_Time ->
|
||||
sign = Time_Utils.compare_to_zoneddatetime self that
|
||||
Ordering.from_sign sign
|
||||
_ -> Error.throw (Type_Error.Error Date_Time that "that")
|
||||
Comparable.from (_:Date_Time) = Default_Ordered_Comparator
|
||||
|
@ -2,7 +2,8 @@ import project.Any.Any
|
||||
import project.Data.Json.JS_Object
|
||||
import project.Data.Numbers.Decimal
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Ordering.Default_Ordered_Comparator
|
||||
import project.Data.Pair.Pair
|
||||
import project.Data.Time.Date_Time.Date_Time
|
||||
import project.Data.Time.Period.Period
|
||||
@ -167,25 +168,6 @@ type Duration
|
||||
Panic.catch ArithmeticException (self.minus_builtin that) err->
|
||||
Error.throw (Time_Error.Error err.payload.getMessage)
|
||||
|
||||
## Compares `self` to `that` to produce an ordering.
|
||||
|
||||
Arguments:
|
||||
- that: The other `Duration` to compare against.
|
||||
|
||||
> Example
|
||||
Compare two durations for their ordering.
|
||||
|
||||
import Standard.Base.Data.Time.Duration
|
||||
|
||||
example_compare_to =
|
||||
duration_1 = (Duration.new hour=1)
|
||||
duration_2 = (Duration.new minutes=60) + (Duration.new minutes=5)
|
||||
duration_1.compare_to duration_2
|
||||
compare_to : Duration -> Ordering
|
||||
compare_to self that = case that of
|
||||
_ : Duration -> Ordering.from_sign (self.compare_to_builtin that)
|
||||
_ -> Error.throw (Type_Error.Error Duration that "that")
|
||||
|
||||
## Get the portion of the duration expressed in nanoseconds.
|
||||
|
||||
> Example
|
||||
@ -312,3 +294,5 @@ type Duration
|
||||
example_is_empty = Duration.zero.is_empty
|
||||
is_empty : Boolean
|
||||
is_empty self = self.to_vector . all (==0)
|
||||
|
||||
Comparable.from (_:Duration) = Default_Ordered_Comparator
|
||||
|
@ -2,6 +2,8 @@ import project.Any.Any
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Time.Date.Date
|
||||
import project.Data.Time.Duration.Duration
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Ordering.Incomparable
|
||||
import project.Data.Text.Text
|
||||
import project.Error.Error
|
||||
import project.Error.Illegal_Argument.Illegal_Argument
|
||||
@ -128,11 +130,3 @@ type Period
|
||||
case err of
|
||||
DateTimeException -> Error.throw Time_Error.Error "Period subtraction failed"
|
||||
ArithmeticException -> Error.throw Illegal_Argument.Error "Arithmetic error"
|
||||
|
||||
## Just throws `Incomparable_Values`, because periods cannot be
|
||||
compared without additional context.
|
||||
|
||||
To compare two Periods, use something like:
|
||||
`(start_date + period1) .compare_to (start_date + period2)`
|
||||
compare_to : Period -> Nothing ! Incomparable_Values
|
||||
compare_to self _ = Error.throw Incomparable_Values
|
||||
|
@ -2,7 +2,8 @@ import project.Any.Any
|
||||
import project.Data.Json.JS_Object
|
||||
import project.Data.Locale.Locale
|
||||
import project.Data.Numbers.Integer
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Ordering.Default_Ordered_Comparator
|
||||
import project.Data.Text.Text
|
||||
import project.Data.Time.Date.Date
|
||||
import project.Data.Time.Date_Time.Date_Time
|
||||
@ -357,23 +358,4 @@ type Time_Of_Day
|
||||
format : Text -> Text
|
||||
format self pattern = @Builtin_Method "Time_Of_Day.format"
|
||||
|
||||
## Compares `self` to `that` to produce an ordering.
|
||||
|
||||
Arguments:
|
||||
- that: The other `Time_Of_Day` to compare against.
|
||||
|
||||
> Example
|
||||
Compare two times for their ordering.
|
||||
|
||||
from Standard.Base import Time_Of_Day
|
||||
|
||||
example_compare_to =
|
||||
time_1 = Time_Of_Day.new hour=2 minute=30
|
||||
time_2 = Time_Of_Day.new minute=50
|
||||
time_1.compare_to time_2
|
||||
compare_to : Time_Of_Day -> Ordering
|
||||
compare_to self that = case that of
|
||||
_ : Time_Of_Day ->
|
||||
sign = Time_Utils.compare_to_localtime self that
|
||||
Ordering.from_sign sign
|
||||
_ -> Error.throw (Type_Error.Error Time_Of_Day that "that")
|
||||
Comparable.from (_:Time_Of_Day) = Default_Ordered_Comparator
|
||||
|
@ -845,7 +845,8 @@ type Vector a
|
||||
elements, returning an Ordering to compare them.
|
||||
|
||||
By default, elements are sorted in ascending order, using the comparator
|
||||
`compare_to`. A custom comparator may be passed to the sort function.
|
||||
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.
|
||||
@ -877,7 +878,7 @@ type Vector a
|
||||
|
||||
[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 = (_.compare_to _)) =
|
||||
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
|
||||
|
@ -76,6 +76,9 @@ import project.Data.Maybe.Maybe
|
||||
import project.Data.Noise
|
||||
import project.Data.Ordering.Natural_Order
|
||||
import project.Data.Ordering.Ordering
|
||||
import project.Data.Ordering.Comparable
|
||||
import project.Data.Ordering.Incomparable
|
||||
import project.Data.Ordering.Default_Ordered_Comparator
|
||||
import project.Data.Ordering.Sort_Direction.Sort_Direction
|
||||
import project.Data.Pair.Pair
|
||||
import project.Data.Range.Range
|
||||
@ -127,6 +130,9 @@ export project.Data.Locale.Locale
|
||||
export project.Data.Maybe.Maybe
|
||||
export project.Data.Ordering.Natural_Order
|
||||
export project.Data.Ordering.Ordering
|
||||
export project.Data.Ordering.Comparable
|
||||
export project.Data.Ordering.Incomparable
|
||||
export project.Data.Ordering.Default_Ordered_Comparator
|
||||
export project.Data.Ordering.Sort_Direction.Sort_Direction
|
||||
export project.Data.Pair.Pair
|
||||
export project.Data.Range.Range
|
||||
|
@ -289,7 +289,7 @@ type Table
|
||||
|
||||
## Returns a new table with the columns sorted by name according to the
|
||||
specified sort method. By default, sorting will be according to
|
||||
case-sensitive ascending order based on the `Text.compare_to` operator.
|
||||
case-sensitive ascending order based on the normalized Unicode ordering.
|
||||
|
||||
Arguments:
|
||||
- order: Whether sorting should be in ascending or descending order.
|
||||
|
@ -1172,7 +1172,7 @@ type Column
|
||||
this rule, ignoring the ascending / descending setting.
|
||||
- by: function taking two items in this column and returning an
|
||||
ordering. If specified, it is used instead of the natural
|
||||
(`.compare_to`) ordering.
|
||||
ordering of the values.
|
||||
|
||||
> Example
|
||||
Sorting a column in ascending order.
|
||||
@ -1191,14 +1191,14 @@ type Column
|
||||
Examples.integer_column.sort Sort_Direction.Descending missing_last=False
|
||||
|
||||
> Example
|
||||
Sorting `column` in ascending order, using a custom comparator
|
||||
Sorting `column` in ascending order, using a custom comparison
|
||||
function.
|
||||
|
||||
import Standard.Examples
|
||||
|
||||
example_sort =
|
||||
my_comparator a b = a.abs.compare_to b.abs
|
||||
Examples.decimal_column.sort by=my_comparator
|
||||
my_compare a b = Ordering.compare a.abs b.abs
|
||||
Examples.decimal_column.sort by=my_compare
|
||||
sort : Sort_Direction -> Boolean -> (Any -> Any -> Ordering) | Nothing -> Column
|
||||
sort self order=Sort_Direction.Ascending missing_last=True by=Nothing =
|
||||
order_bool = case order of
|
||||
|
@ -402,8 +402,7 @@ type Table
|
||||
|
||||
## Returns a new table with the columns sorted by name according to the
|
||||
specified sort method. By default, sorting will be according to
|
||||
case-sensitive ascending order based on the `compare_to` operator for
|
||||
`Text`.
|
||||
case-sensitive ascending order based on the normalized Unicode ordering.
|
||||
|
||||
Arguments:
|
||||
- order: Whether sorting should be in ascending or descending order.
|
||||
|
@ -22,8 +22,12 @@ type Bits
|
||||
Bits.Bits_32 -> 32
|
||||
Bits.Bits_64 -> 64
|
||||
|
||||
## PRIVATE
|
||||
compare_to self other = self.to_bits . compare_to other.to_bits
|
||||
type Bits_Comparator
|
||||
is_ordered = True
|
||||
compare x y = Comparable.from x.to_bits . compare x.to_bits y.to_bits
|
||||
hash x = Comparable.from x.to_bits . hash x.to_bits
|
||||
|
||||
Comparable.from (_:Bits) = Bits_Comparator
|
||||
|
||||
## Represents the different possible types of values within RDBMS columns.
|
||||
type Value_Type
|
||||
|
@ -373,7 +373,7 @@ sort_columns internal_columns order text_ordering =
|
||||
col -> col.name.to_case_insensitive_key locale=locale
|
||||
comparator = case text_ordering.sort_digits_as_numbers of
|
||||
True -> Natural_Order.compare
|
||||
False -> .compare_to
|
||||
False -> Ordering.compare
|
||||
internal_columns.sort order=order on=mapper by=comparator
|
||||
|
||||
## PRIVATE
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.enso.interpreter.epb.runtime;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Cached.Exclusive;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
import com.oracle.truffle.api.interop.ExceptionType;
|
||||
|
@ -0,0 +1,104 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConversionMethodTests extends TestBase {
|
||||
private static Context ctx;
|
||||
|
||||
@BeforeClass
|
||||
public static void initCtx() {
|
||||
ctx = createDefaultContext();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void disposeCtx() {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleConversion() {
|
||||
String src = """
|
||||
type Foo
|
||||
Mk_Foo foo
|
||||
type Bar
|
||||
Mk_Bar bar
|
||||
type Baz
|
||||
Mk_Baz baz
|
||||
|
||||
Foo.from (that:Bar) = Foo.Mk_Foo that.bar
|
||||
Foo.from (that:Baz) = Foo.Mk_Foo that.baz
|
||||
|
||||
main = (Foo.from (Baz.Mk_Baz 10)).foo + (Foo.from (Bar.Mk_Bar 20)).foo
|
||||
""";
|
||||
Value res = evalModule(ctx, src);
|
||||
assertEquals(30, res.asInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispatchOnHostMap() {
|
||||
String src = """
|
||||
polyglot java import java.util.Map as Java_Map
|
||||
import Standard.Base.Data.Map.Map
|
||||
|
||||
type Foo
|
||||
Mk_Foo data
|
||||
|
||||
Foo.from (that:Map) = Foo.Mk_Foo that
|
||||
|
||||
main =
|
||||
jmap = Java_Map.of "A" 1 "B" 2 "C" 3
|
||||
Foo.from jmap . data . size
|
||||
""";
|
||||
Value res = evalModule(ctx, src);
|
||||
assertEquals(3, res.asInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispatchOnJSMap() {
|
||||
String src = """
|
||||
import Standard.Base.Data.Map.Map
|
||||
|
||||
foreign js js_map = '''
|
||||
let m = new Map()
|
||||
m.set("A", 1)
|
||||
m.set("B", 2)
|
||||
return m
|
||||
|
||||
type Foo
|
||||
Mk_Foo data
|
||||
|
||||
Foo.from (that:Map) = Foo.Mk_Foo that
|
||||
|
||||
main =
|
||||
Foo.from js_map . data . size
|
||||
""";
|
||||
Value res = evalModule(ctx, src);
|
||||
assertEquals(2, res.asInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispatchOnJSDateTime() {
|
||||
String src = """
|
||||
import Standard.Base.Data.Time.Date_Time.Date_Time
|
||||
|
||||
foreign js js_date year month day hour minute second nanosecond = '''
|
||||
return new Date(year, month - 1, day, hour, minute, second, nanosecond / 1000000);
|
||||
|
||||
type Foo
|
||||
Mk_Foo data
|
||||
|
||||
Foo.from (that:Date_Time) = Foo.Mk_Foo that
|
||||
|
||||
main =
|
||||
Foo.from (js_date 2023 2 7 23 59 0 10) . data . day
|
||||
""";
|
||||
Value res = evalModule(ctx, src);
|
||||
assertEquals(7, res.asInt());
|
||||
}
|
||||
}
|
@ -44,6 +44,8 @@ public class CurriedFunctionBenchmarks {
|
||||
|
||||
var benchmarkName = params.getBenchmark().replaceFirst(".*\\.", "");
|
||||
var code = """
|
||||
import Standard.Base.Any.Any
|
||||
|
||||
avg fn len =
|
||||
sum acc i = if i == len then acc else
|
||||
sum (acc + fn i) i+1
|
||||
|
@ -71,6 +71,7 @@ public class EqualsBenchmarks {
|
||||
var benchmarkName = params.getBenchmark().replaceFirst(".*\\.", "");
|
||||
var codeBuilder = new StringBuilder("""
|
||||
import Standard.Base.Data.Range.Extensions
|
||||
import Standard.Base.Any.Any
|
||||
|
||||
type Node
|
||||
C1 f1
|
||||
|
@ -48,6 +48,7 @@ public class VectorBenchmarks {
|
||||
var code = """
|
||||
import Standard.Base.Data.Vector.Vector
|
||||
import Standard.Base.Data.Array_Proxy.Array_Proxy
|
||||
import Standard.Base.Any.Any
|
||||
|
||||
avg arr =
|
||||
sum acc i = if i == arr.length then acc else
|
||||
|
@ -7,6 +7,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
||||
|
||||
val millionElementList = eval(
|
||||
s"""import Standard.Base.Data.List.List
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| generator fn acc i end = if i == end then acc else @Tail_Call generator fn (fn acc i) i+1 end
|
||||
@ -16,6 +17,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
||||
|
||||
val generateListCode =
|
||||
"""import Standard.Base.Data.List.List
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main = length ->
|
||||
| generator = acc -> i -> if i == 0 then acc else @Tail_Call generator (List.Cons i acc) (i - 1)
|
||||
@ -27,6 +29,7 @@ class AtomFixtures extends DefaultInterpreterRunner {
|
||||
|
||||
val generateListQualifiedCode =
|
||||
"""import Standard.Base.Data.List.List
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main = length ->
|
||||
| generator = acc -> i -> if i == 0 then acc else @Tail_Call generator (List.Cons i acc) (i - 1)
|
||||
|
@ -8,6 +8,7 @@ class CallableFixtures extends DefaultInterpreterRunner {
|
||||
val sumTCOfromCallCode =
|
||||
"""
|
||||
|from Standard.Base.Data.Numbers import all
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|type Foo
|
||||
|
|
||||
@ -22,7 +23,8 @@ class CallableFixtures extends DefaultInterpreterRunner {
|
||||
|
||||
|
||||
val sumTCOmethodCallCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|summator = acc -> current ->
|
||||
| if current == 0 then acc else @Tail_Call summator (acc + current) (current - 1)
|
||||
|
|
||||
@ -33,7 +35,8 @@ class CallableFixtures extends DefaultInterpreterRunner {
|
||||
val sumTCOmethodCall = getMain(sumTCOmethodCallCode)
|
||||
|
||||
val sumTCOmethodCallWithNamedArgumentsCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|summator = acc -> current ->
|
||||
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
|
||||
|
|
||||
@ -45,7 +48,8 @@ class CallableFixtures extends DefaultInterpreterRunner {
|
||||
getMain(sumTCOmethodCallWithNamedArgumentsCode)
|
||||
|
||||
val sumTCOmethodCallWithDefaultedArgumentsCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|summator = (acc = 0) -> current ->
|
||||
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
|
||||
|
|
||||
|
@ -6,7 +6,8 @@ class NamedDefaultedArgumentFixtures extends DefaultInterpreterRunner {
|
||||
val hundredMillion: Long = 100000000
|
||||
|
||||
val sumTCOWithNamedArgumentsCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|main = sumTo ->
|
||||
| summator = acc -> current ->
|
||||
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
|
||||
@ -17,7 +18,8 @@ class NamedDefaultedArgumentFixtures extends DefaultInterpreterRunner {
|
||||
val sumTCOWithNamedArguments = getMain(sumTCOWithNamedArgumentsCode)
|
||||
|
||||
val sumTCOWithDefaultedArgumentsCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|main = sumTo ->
|
||||
| summator = (acc = 0) -> current ->
|
||||
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
|
||||
|
@ -9,7 +9,8 @@ class RecursionFixtures extends DefaultInterpreterRunner {
|
||||
val hundred: Long = 100
|
||||
|
||||
val sumTCOCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|main = sumTo ->
|
||||
| summator = acc -> current ->
|
||||
| if current == 0 then acc else @Tail_Call summator acc+current current-1
|
||||
@ -20,7 +21,8 @@ class RecursionFixtures extends DefaultInterpreterRunner {
|
||||
val sumTCO = getMain(sumTCOCode)
|
||||
|
||||
val sumTCOFoldLikeCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|main = sumTo ->
|
||||
| summator = acc -> i -> f ->
|
||||
| if i == 0 then acc else @Tail_Call summator (f acc i) (i - 1) f
|
||||
@ -30,7 +32,8 @@ class RecursionFixtures extends DefaultInterpreterRunner {
|
||||
val sumTCOFoldLike = getMain(sumTCOFoldLikeCode)
|
||||
|
||||
val sumRecursiveCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|main = sumTo ->
|
||||
| summator = i -> if i == 0 then 0 else i + summator (i - 1)
|
||||
| res = summator sumTo
|
||||
@ -39,7 +42,8 @@ class RecursionFixtures extends DefaultInterpreterRunner {
|
||||
val sumRecursive = getMain(sumRecursiveCode)
|
||||
|
||||
val oversaturatedRecursiveCallTCOCode =
|
||||
"""
|
||||
"""import Standard.Base.Any.Any
|
||||
|
|
||||
|main = sumTo ->
|
||||
| summator = acc -> i -> f ->
|
||||
| if i == 0 then acc else @Tail_Call summator (f acc i) (i - 1) f
|
||||
|
@ -63,6 +63,13 @@ public abstract class InvokeConversionNode extends BaseNode {
|
||||
this.invokeFunctionNode.setTailStatus(tailStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self A target of the conversion. Should be a {@link Type} on which the {@code from}
|
||||
* method is defined. If it is not a {@link Type}, "Invalid conversion target" panic is
|
||||
* thrown.
|
||||
* @param that Source of the conversion. Can be arbitrary object, including polyglot values.
|
||||
* @param arguments Additional arguments passed to the conversion function.
|
||||
*/
|
||||
public abstract Object execute(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
@ -196,6 +203,131 @@ public abstract class InvokeConversionNode extends BaseNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {
|
||||
"!typesLib.hasType(that)",
|
||||
"!typesLib.hasSpecialDispatch(that)",
|
||||
"!interop.isTime(that)",
|
||||
"interop.isDate(that)",
|
||||
})
|
||||
Object doConvertDate(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
UnresolvedConversion conversion,
|
||||
Object self,
|
||||
Object that,
|
||||
Object[] arguments,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "10") TypesLibrary typesLib,
|
||||
@Cached ConversionResolverNode conversionResolverNode) {
|
||||
Function function =
|
||||
conversionResolverNode.expectNonNull(
|
||||
that, extractConstructor(self), EnsoContext.get(this).getBuiltins().date(), conversion);
|
||||
return invokeFunctionNode.execute(function, frame, state, arguments);
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {
|
||||
"!typesLib.hasType(that)",
|
||||
"!typesLib.hasSpecialDispatch(that)",
|
||||
"interop.isTime(that)",
|
||||
"!interop.isDate(that)",
|
||||
})
|
||||
Object doConvertTime(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
UnresolvedConversion conversion,
|
||||
Object self,
|
||||
Object that,
|
||||
Object[] arguments,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "10") TypesLibrary typesLib,
|
||||
@Cached ConversionResolverNode conversionResolverNode) {
|
||||
Function function =
|
||||
conversionResolverNode.expectNonNull(
|
||||
that,
|
||||
extractConstructor(self),
|
||||
EnsoContext.get(this).getBuiltins().timeOfDay(),
|
||||
conversion);
|
||||
return invokeFunctionNode.execute(function, frame, state, arguments);
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {
|
||||
"!typesLib.hasType(that)",
|
||||
"!typesLib.hasSpecialDispatch(that)",
|
||||
"interop.isTime(that)",
|
||||
"interop.isDate(that)",
|
||||
})
|
||||
Object doConvertDateTime(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
UnresolvedConversion conversion,
|
||||
Object self,
|
||||
Object that,
|
||||
Object[] arguments,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "10") TypesLibrary typesLib,
|
||||
@Cached ConversionResolverNode conversionResolverNode) {
|
||||
Function function =
|
||||
conversionResolverNode.expectNonNull(
|
||||
that,
|
||||
extractConstructor(self),
|
||||
EnsoContext.get(this).getBuiltins().dateTime(),
|
||||
conversion);
|
||||
return invokeFunctionNode.execute(function, frame, state, arguments);
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {
|
||||
"!typesLib.hasType(that)",
|
||||
"!typesLib.hasSpecialDispatch(that)",
|
||||
"interop.isDuration(that)",
|
||||
})
|
||||
Object doConvertDuration(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
UnresolvedConversion conversion,
|
||||
Object self,
|
||||
Object that,
|
||||
Object[] arguments,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "10") TypesLibrary typesLib,
|
||||
@Cached ConversionResolverNode conversionResolverNode) {
|
||||
Function function =
|
||||
conversionResolverNode.expectNonNull(
|
||||
that,
|
||||
extractConstructor(self),
|
||||
EnsoContext.get(this).getBuiltins().duration(),
|
||||
conversion);
|
||||
return invokeFunctionNode.execute(function, frame, state, arguments);
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {
|
||||
"!typesLib.hasType(thatMap)",
|
||||
"!typesLib.hasSpecialDispatch(thatMap)",
|
||||
"interop.hasHashEntries(thatMap)",
|
||||
})
|
||||
Object doConvertMap(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
UnresolvedConversion conversion,
|
||||
Object self,
|
||||
Object thatMap,
|
||||
Object[] arguments,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "10") TypesLibrary typesLib,
|
||||
@Cached ConversionResolverNode conversionResolverNode) {
|
||||
Function function =
|
||||
conversionResolverNode.expectNonNull(
|
||||
thatMap,
|
||||
extractConstructor(self),
|
||||
EnsoContext.get(this).getBuiltins().map(),
|
||||
conversion);
|
||||
return invokeFunctionNode.execute(function, frame, state, arguments);
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {
|
||||
"!methods.hasType(that)",
|
||||
|
@ -29,7 +29,7 @@ public abstract class IndirectInvokeFunctionNode extends Node {
|
||||
* Executes the {@link IndirectInvokeFunctionNode} to apply the function to given arguments.
|
||||
*
|
||||
* @param callable the function to call
|
||||
* @param callerFrame the caller frame to pass to the function
|
||||
* @param callerFrame the caller frame to pass to the function, may be null.
|
||||
* @param state the state to pass to the function
|
||||
* @param arguments the arguments being passed to {@code function}
|
||||
* @param schema the names and ordering of arguments for this call site
|
||||
|
@ -1,38 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.bool;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.ordering.Ordering;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
|
||||
@BuiltinMethod(type = "Boolean", name = "compare_to", description = "Comparison for Booleans.")
|
||||
public abstract class CompareToNode extends Node {
|
||||
static CompareToNode build() {
|
||||
return CompareToNodeGen.create();
|
||||
}
|
||||
|
||||
abstract Object execute(Boolean self, Object that);
|
||||
|
||||
@Specialization
|
||||
Atom doBoolean(Boolean self, Boolean that) {
|
||||
Ordering ordering = EnsoContext.get(this).getBuiltins().ordering();
|
||||
if (self == that) {
|
||||
return ordering.newEqual();
|
||||
} else if (self) {
|
||||
return ordering.newGreater();
|
||||
} else {
|
||||
return ordering.newLess();
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(Boolean self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.bool().getType(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ public abstract class PrintlnNode extends Node {
|
||||
try {
|
||||
print(ctx.getOut(), strings.asString(message));
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException("Impossible. self is guaranteed to be a string");
|
||||
throw new IllegalStateException("Impossible. `message` is guaranteed to be a string");
|
||||
}
|
||||
return ctx.getNothing();
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.enso.interpreter.node.expression.builtin.meta;
|
||||
|
||||
import com.ibm.icu.text.Normalizer;
|
||||
import com.ibm.icu.text.Normalizer2;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
@ -19,45 +18,59 @@ import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.profiles.ConditionProfile;
|
||||
import com.oracle.truffle.api.profiles.LoopConditionProfile;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.enso.interpreter.dsl.AcceptsError;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.callable.ExecuteCallNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNodeGen.InvokeEqualsNodeGen;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
|
||||
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||
import org.enso.interpreter.node.expression.builtin.ordering.HasCustomComparatorNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.Module;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.atom.StructsLibrary;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.EnsoFile;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
||||
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
import org.enso.polyglot.MethodNames;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Any",
|
||||
name = "==",
|
||||
description = "Implementation of Any.=="
|
||||
type = "Comparable",
|
||||
name = "equals_builtin",
|
||||
description = """
|
||||
Compares self with other object and returns True iff `self` is exactly the same as
|
||||
the other object, including all its transitively accessible properties or fields.
|
||||
Can handle arbitrary objects, including all foreign objects.
|
||||
|
||||
Does not throw exceptions.
|
||||
|
||||
Note that this is different than `Meta.is_same_object`, which checks whether two
|
||||
references point to the same object on the heap.
|
||||
"""
|
||||
)
|
||||
@GenerateUncached
|
||||
public abstract class EqualsAnyNode extends Node {
|
||||
public abstract class EqualsNode extends Node {
|
||||
|
||||
protected static String EQUALS_MEMBER_NAME = MethodNames.Function.EQUALS;
|
||||
|
||||
public static EqualsAnyNode build() {
|
||||
return EqualsAnyNodeGen.create();
|
||||
public static EqualsNode build() {
|
||||
return EqualsNodeGen.create();
|
||||
}
|
||||
|
||||
public abstract boolean execute(@AcceptsError Object self, @AcceptsError Object right);
|
||||
public abstract boolean execute(@AcceptsError Object left, @AcceptsError Object right);
|
||||
|
||||
/**
|
||||
* Primitive values
|
||||
@ -69,6 +82,11 @@ public abstract class EqualsAnyNode extends Node {
|
||||
return self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsBytes(byte self, byte other) {
|
||||
return self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsLong(long self, long other) {
|
||||
return self == other;
|
||||
@ -133,17 +151,55 @@ public abstract class EqualsAnyNode extends Node {
|
||||
|
||||
@Specialization
|
||||
boolean equalsUnresolvedSymbols(UnresolvedSymbol self, UnresolvedSymbol otherSymbol,
|
||||
@Cached EqualsAnyNode equalsNode) {
|
||||
@Cached EqualsNode equalsNode) {
|
||||
return self.getName().equals(otherSymbol.getName())
|
||||
&& equalsNode.execute(self.getScope(), otherSymbol.getScope());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsUnresolvedConversion(UnresolvedConversion selfConversion, UnresolvedConversion otherConversion,
|
||||
@Cached EqualsAnyNode equalsNode) {
|
||||
@Cached EqualsNode equalsNode) {
|
||||
return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsModuleScopes(ModuleScope selfModuleScope, ModuleScope otherModuleScope,
|
||||
@Cached EqualsNode equalsNode) {
|
||||
return equalsNode.execute(selfModuleScope.getModule(), otherModuleScope.getModule());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean equalsModules(Module selfModule, Module otherModule,
|
||||
@Cached EqualsNode equalsNode) {
|
||||
return equalsNode.execute(selfModule.getName().toString(), otherModule.getName().toString());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsFiles(EnsoFile selfFile, EnsoFile otherFile,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
return equalsStrings(selfFile.getPath(), otherFile.getPath(), interop, interop);
|
||||
}
|
||||
|
||||
/**
|
||||
* There is no specialization for {@link TypesLibrary#hasType(Object)}, because also
|
||||
* primitive values would fall into that specialization and it would be too complicated
|
||||
* to make that specialization disjunctive. So we rather specialize directly for
|
||||
* {@link Type types}.
|
||||
*/
|
||||
@Specialization(guards = {
|
||||
"typesLib.hasType(selfType)",
|
||||
"typesLib.hasType(otherType)"
|
||||
})
|
||||
boolean equalsTypes(Type selfType, Type otherType,
|
||||
@Cached EqualsNode equalsNode,
|
||||
@CachedLibrary(limit = "5") TypesLibrary typesLib) {
|
||||
return equalsNode.execute(
|
||||
selfType.getQualifiedName().toString(),
|
||||
otherType.getQualifiedName().toString()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If one of the objects has warnings attached, just treat it as an object without any
|
||||
* warnings.
|
||||
@ -154,7 +210,7 @@ public abstract class EqualsAnyNode extends Node {
|
||||
boolean equalsWithWarnings(Object selfWithWarnings, Object otherWithWarnings,
|
||||
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
|
||||
@CachedLibrary("otherWithWarnings") WarningsLibrary otherWarnLib,
|
||||
@Cached EqualsAnyNode equalsNode
|
||||
@Cached EqualsNode equalsNode
|
||||
) {
|
||||
try {
|
||||
Object self =
|
||||
@ -193,23 +249,6 @@ public abstract class EqualsAnyNode extends Node {
|
||||
return selfInterop.isNull(selfNull) && otherInterop.isNull(otherNull);
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"isHostObject(selfHostObject)",
|
||||
"isHostObject(otherHostObject)",
|
||||
})
|
||||
boolean equalsHostObjects(
|
||||
Object selfHostObject, Object otherHostObject,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop
|
||||
) {
|
||||
try {
|
||||
return interop.asBoolean(
|
||||
interop.invokeMember(selfHostObject, "equals", otherHostObject)
|
||||
);
|
||||
} catch (UnsupportedMessageException | ArityException | UnknownIdentifierException |
|
||||
UnsupportedTypeException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isBoolean(selfBoolean)",
|
||||
@ -271,7 +310,8 @@ public abstract class EqualsAnyNode extends Node {
|
||||
otherInterop.asTime(otherZonedDateTime),
|
||||
otherInterop.asTimeZone(otherZonedDateTime)
|
||||
);
|
||||
return self.isEqual(other);
|
||||
// We cannot use self.isEqual(other), because that does not include timezone.
|
||||
return self.compareTo(other) == 0;
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
@ -388,12 +428,16 @@ public abstract class EqualsAnyNode extends Node {
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.hasArrayElements(selfArray)",
|
||||
"otherInterop.hasArrayElements(otherArray)"
|
||||
"otherInterop.hasArrayElements(otherArray)",
|
||||
"!selfInterop.hasHashEntries(selfArray)",
|
||||
"!otherInterop.hasHashEntries(otherArray)",
|
||||
}, limit = "3")
|
||||
boolean equalsArrays(Object selfArray, Object otherArray,
|
||||
@CachedLibrary("selfArray") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherArray") InteropLibrary otherInterop,
|
||||
@Cached EqualsAnyNode equalsNode
|
||||
@Cached EqualsNode equalsNode,
|
||||
@Cached HasCustomComparatorNode hasCustomComparatorNode,
|
||||
@Cached InvokeAnyEqualsNode invokeAnyEqualsNode
|
||||
) {
|
||||
try {
|
||||
long selfSize = selfInterop.getArraySize(selfArray);
|
||||
@ -403,7 +447,15 @@ public abstract class EqualsAnyNode extends Node {
|
||||
for (long i = 0; i < selfSize; i++) {
|
||||
Object selfElem = selfInterop.readArrayElement(selfArray, i);
|
||||
Object otherElem = otherInterop.readArrayElement(otherArray, i);
|
||||
if (!equalsNode.execute(selfElem, otherElem)) {
|
||||
boolean elemsAreEqual;
|
||||
if (selfElem instanceof Atom selfAtomElem
|
||||
&& otherElem instanceof Atom otherAtomElem
|
||||
&& hasCustomComparatorNode.execute(selfAtomElem)) {
|
||||
elemsAreEqual = invokeAnyEqualsNode.execute(selfAtomElem, otherAtomElem);
|
||||
} else {
|
||||
elemsAreEqual = equalsNode.execute(selfElem, otherElem);
|
||||
}
|
||||
if (!elemsAreEqual) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -415,13 +467,15 @@ public abstract class EqualsAnyNode extends Node {
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.hasHashEntries(selfHashMap)",
|
||||
"otherInterop.hasHashEntries(otherHashMap)"
|
||||
"otherInterop.hasHashEntries(otherHashMap)",
|
||||
"!selfInterop.hasArrayElements(selfHashMap)",
|
||||
"!otherInterop.hasArrayElements(otherHashMap)"
|
||||
}, limit = "3")
|
||||
boolean equalsHashMaps(Object selfHashMap, Object otherHashMap,
|
||||
@CachedLibrary("selfHashMap") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherHashMap") InteropLibrary otherInterop,
|
||||
@CachedLibrary(limit = "5") InteropLibrary entriesInterop,
|
||||
@Cached EqualsAnyNode equalsNode) {
|
||||
@Cached EqualsNode equalsNode) {
|
||||
try {
|
||||
int selfHashSize = (int) selfInterop.getHashSize(selfHashMap);
|
||||
int otherHashSize = (int) otherInterop.getHashSize(otherHashMap);
|
||||
@ -450,6 +504,68 @@ public abstract class EqualsAnyNode extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"!isAtom(selfObject)",
|
||||
"!isAtom(otherObject)",
|
||||
"!isHostObject(selfObject)",
|
||||
"!isHostObject(otherObject)",
|
||||
"interop.hasMembers(selfObject)",
|
||||
"interop.hasMembers(otherObject)",
|
||||
"!interop.isDate(selfObject)",
|
||||
"!interop.isDate(otherObject)",
|
||||
"!interop.isTime(selfObject)",
|
||||
"!interop.isTime(otherObject)",
|
||||
// Objects with types are handled in `equalsTypes` specialization, so we have to
|
||||
// negate the guards of that specialization here - to make the specializations
|
||||
// disjunctive.
|
||||
"!typesLib.hasType(selfObject)",
|
||||
"!typesLib.hasType(otherObject)",
|
||||
})
|
||||
boolean equalsInteropObjectWithMembers(Object selfObject, Object otherObject,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "5") TypesLibrary typesLib,
|
||||
@Cached EqualsNode equalsNode) {
|
||||
try {
|
||||
Object selfMembers = interop.getMembers(selfObject);
|
||||
Object otherMembers = interop.getMembers(otherObject);
|
||||
assert interop.getArraySize(selfMembers) < Integer.MAX_VALUE : "Long array sizes not supported";
|
||||
int membersSize = (int) interop.getArraySize(selfMembers);
|
||||
if (interop.getArraySize(otherMembers) != membersSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check member names
|
||||
String[] memberNames = new String[membersSize];
|
||||
for (int i = 0; i < membersSize; i++) {
|
||||
String selfMemberName = interop.asString(interop.readArrayElement(selfMembers, i));
|
||||
String otherMemberName = interop.asString(interop.readArrayElement(otherMembers, i));
|
||||
if (!equalsNode.execute(selfMemberName, otherMemberName)) {
|
||||
return false;
|
||||
}
|
||||
memberNames[i] = selfMemberName;
|
||||
}
|
||||
|
||||
// Check member values
|
||||
for (int i = 0; i < membersSize; i++) {
|
||||
if (interop.isMemberReadable(selfObject, memberNames[i]) &&
|
||||
interop.isMemberReadable(otherObject, memberNames[i])) {
|
||||
Object selfMember = interop.readMember(selfObject, memberNames[i]);
|
||||
Object otherMember = interop.readMember(otherObject, memberNames[i]);
|
||||
if (!equalsNode.execute(selfMember, otherMember)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (UnsupportedMessageException | InvalidArrayIndexException | UnknownIdentifierException e) {
|
||||
throw new IllegalStateException(
|
||||
String.format("One of the interop objects has probably wrongly specified interop API "
|
||||
+ "for members. selfObject = %s ; otherObject = %s", selfObject, otherObject),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Equals for Atoms and AtomConstructors */
|
||||
|
||||
@Specialization
|
||||
@ -458,13 +574,13 @@ public abstract class EqualsAnyNode extends Node {
|
||||
}
|
||||
|
||||
/**
|
||||
* How many {@link EqualsAnyNode} should be created for fields in specialization for atoms.
|
||||
* How many {@link EqualsNode} should be created for fields in specialization for atoms.
|
||||
*/
|
||||
static final int equalsNodeCountForFields = 10;
|
||||
|
||||
static EqualsAnyNode[] createEqualsNodes(int size) {
|
||||
EqualsAnyNode[] nodes = new EqualsAnyNode[size];
|
||||
Arrays.fill(nodes, EqualsAnyNode.build());
|
||||
static EqualsNode[] createEqualsNodes(int size) {
|
||||
EqualsNode[] nodes = new EqualsNode[size];
|
||||
Arrays.fill(nodes, EqualsNode.build());
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@ -473,17 +589,14 @@ public abstract class EqualsAnyNode extends Node {
|
||||
Atom self,
|
||||
Atom other,
|
||||
@Cached LoopConditionProfile loopProfile,
|
||||
@Cached(value = "createEqualsNodes(equalsNodeCountForFields)", allowUncached = true) EqualsAnyNode[] fieldEqualsNodes,
|
||||
@Cached InvokeEqualsNode atomInvokeEqualsNode,
|
||||
@Cached(value = "createEqualsNodes(equalsNodeCountForFields)", allowUncached = true) EqualsNode[] fieldEqualsNodes,
|
||||
@Cached ConditionProfile enoughEqualNodesForFieldsProfile,
|
||||
@Cached ConditionProfile constructorsNotEqualProfile,
|
||||
@CachedLibrary(limit = "3") StructsLibrary selfStructs,
|
||||
@CachedLibrary(limit = "3") StructsLibrary otherStructs
|
||||
@CachedLibrary(limit = "3") StructsLibrary otherStructs,
|
||||
@Cached HasCustomComparatorNode hasCustomComparatorNode,
|
||||
@Cached InvokeAnyEqualsNode invokeAnyEqualsNode
|
||||
) {
|
||||
if (atomOverridesEquals(self)) {
|
||||
return atomInvokeEqualsNode.execute(self, other);
|
||||
}
|
||||
|
||||
if (constructorsNotEqualProfile.profile(
|
||||
self.getConstructor() != other.getConstructor()
|
||||
)) {
|
||||
@ -497,10 +610,19 @@ public abstract class EqualsAnyNode extends Node {
|
||||
if (enoughEqualNodesForFieldsProfile.profile(fieldsSize <= equalsNodeCountForFields)) {
|
||||
loopProfile.profileCounted(fieldsSize);
|
||||
for (int i = 0; loopProfile.inject(i < fieldsSize); i++) {
|
||||
if (!fieldEqualsNodes[i].execute(
|
||||
selfFields[i],
|
||||
otherFields[i]
|
||||
)) {
|
||||
boolean fieldsAreEqual;
|
||||
// We don't check whether `other` has the same type of comparator, that is checked in
|
||||
// `Any.==` that we invoke here anyway.
|
||||
if (selfFields[i] instanceof Atom selfAtomField
|
||||
&& otherFields[i] instanceof Atom otherAtomField
|
||||
&& hasCustomComparatorNode.execute(selfAtomField)) {
|
||||
// If selfFields[i] has a custom comparator, we delegate to `Any.==` that deals with
|
||||
// custom comparators. EqualsNode cannot deal with custom comparators.
|
||||
fieldsAreEqual = invokeAnyEqualsNode.execute(selfAtomField, otherAtomField);
|
||||
} else {
|
||||
fieldsAreEqual = fieldEqualsNodes[i].execute(selfFields[i], otherFields[i]);
|
||||
}
|
||||
if (!fieldsAreEqual) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -517,10 +639,10 @@ public abstract class EqualsAnyNode extends Node {
|
||||
boolean areFieldsSame;
|
||||
if (selfFields[i] instanceof Atom selfFieldAtom
|
||||
&& otherFields[i] instanceof Atom otherFieldAtom
|
||||
&& atomOverridesEquals(selfFieldAtom)) {
|
||||
areFieldsSame = InvokeEqualsNode.getUncached().execute(selfFieldAtom, otherFieldAtom);
|
||||
&& HasCustomComparatorNode.getUncached().execute(selfFieldAtom)) {
|
||||
areFieldsSame = InvokeAnyEqualsNode.getUncached().execute(selfFieldAtom, otherFieldAtom);
|
||||
} else {
|
||||
areFieldsSame = EqualsAnyNodeGen.getUncached().execute(selfFields[i], otherFields[i]);
|
||||
areFieldsSame = EqualsNodeGen.getUncached().execute(selfFields[i], otherFields[i]);
|
||||
}
|
||||
if (!areFieldsSame) {
|
||||
return false;
|
||||
@ -529,102 +651,119 @@ public abstract class EqualsAnyNode extends Node {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper node for invoking `==` method on atoms, that override this method.
|
||||
*/
|
||||
@GenerateUncached
|
||||
static abstract class InvokeEqualsNode extends Node {
|
||||
static InvokeEqualsNode getUncached() {
|
||||
return InvokeEqualsNodeGen.getUncached();
|
||||
}
|
||||
|
||||
static InvokeEqualsNode build() {
|
||||
return InvokeEqualsNodeGen.create();
|
||||
}
|
||||
|
||||
abstract boolean execute(Atom selfAtom, Atom otherAtom);
|
||||
|
||||
@Specialization(guards = "cachedSelfAtomCtor == selfAtom.getConstructor()")
|
||||
boolean invokeEqualsCachedAtomCtor(Atom selfAtom, Atom otherAtom,
|
||||
@Cached("selfAtom.getConstructor()") AtomConstructor cachedSelfAtomCtor,
|
||||
@Cached("getEqualsMethod(cachedSelfAtomCtor)") Function equalsMethod,
|
||||
@Cached ExecuteCallNode executeCallNode,
|
||||
@CachedLibrary(limit = "3") InteropLibrary interop) {
|
||||
assert atomOverridesEquals(selfAtom);
|
||||
Object ret = executeCallNode.executeCall(
|
||||
equalsMethod,
|
||||
null,
|
||||
State.create(EnsoContext.get(this)),
|
||||
new Object[]{selfAtom, otherAtom}
|
||||
@Specialization(guards = {
|
||||
"isHostObject(selfHostObject)",
|
||||
"isHostObject(otherHostObject)"
|
||||
})
|
||||
boolean equalsHostObjects(
|
||||
Object selfHostObject, Object otherHostObject,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop
|
||||
) {
|
||||
try {
|
||||
return interop.asBoolean(
|
||||
interop.invokeMember(selfHostObject, "equals", otherHostObject)
|
||||
);
|
||||
try {
|
||||
return interop.asBoolean(ret);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Specialization(replaces = "invokeEqualsCachedAtomCtor")
|
||||
boolean invokeEqualsUncached(Atom selfAtom, Atom otherAtom,
|
||||
@Cached ExecuteCallNode executeCallNode) {
|
||||
Function equalsMethod = getEqualsMethod(selfAtom.getConstructor());
|
||||
Object ret = executeCallNode.executeCall(
|
||||
equalsMethod,
|
||||
null,
|
||||
State.create(EnsoContext.get(this)),
|
||||
new Object[]{selfAtom, otherAtom}
|
||||
);
|
||||
assert InteropLibrary.getUncached().isBoolean(ret);
|
||||
try {
|
||||
return InteropLibrary.getUncached().asBoolean(ret);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
static Function getEqualsMethod(AtomConstructor atomConstructor) {
|
||||
Type atomType = atomConstructor.getType();
|
||||
Function equalsFunction = atomConstructor
|
||||
.getDefinitionScope()
|
||||
.getMethods()
|
||||
.get(atomType)
|
||||
.get("==");
|
||||
assert equalsFunction != null;
|
||||
return equalsFunction;
|
||||
} catch (UnsupportedMessageException | ArityException | UnknownIdentifierException |
|
||||
UnsupportedTypeException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given atom overrides `==` operator.
|
||||
*/
|
||||
@TruffleBoundary
|
||||
private static boolean atomOverridesEquals(Atom atom) {
|
||||
var atomType = atom.getConstructor().getType();
|
||||
Map<String, Function> methodsOnType = atom
|
||||
.getConstructor()
|
||||
.getDefinitionScope()
|
||||
.getMethods()
|
||||
.get(atomType);
|
||||
if (methodsOnType != null) {
|
||||
return methodsOnType.containsKey("==");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// HostFunction is identified by a qualified name, it is not a lambda.
|
||||
// It has well-defined equality based on the qualified name.
|
||||
@Specialization(guards = {
|
||||
"isHostFunction(selfHostFunc)",
|
||||
"isHostFunction(otherHostFunc)"
|
||||
})
|
||||
boolean equalsHostFunctions(Object selfHostFunc, Object otherHostFunc,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop,
|
||||
@Cached EqualsNode equalsNode) {
|
||||
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc);
|
||||
Object otherFuncStrRepr = interop.toDisplayString(otherHostFunc);
|
||||
return equalsNode.execute(selfFuncStrRepr, otherFuncStrRepr);
|
||||
}
|
||||
|
||||
@Fallback
|
||||
@TruffleBoundary
|
||||
boolean equalsGeneric(Object left, Object right,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "5") TypesLibrary typesLib) {
|
||||
return left == right
|
||||
|| interop.isIdentical(left, right, interop)
|
||||
|| left.equals(right);
|
||||
|| left.equals(right)
|
||||
|| (isNullOrNothing(left, typesLib, interop) && isNullOrNothing(right, typesLib, interop));
|
||||
}
|
||||
|
||||
private boolean isNullOrNothing(Object object, TypesLibrary typesLib, InteropLibrary interop) {
|
||||
if (typesLib.hasType(object)) {
|
||||
return typesLib.getType(object) == EnsoContext.get(this).getNothing();
|
||||
} else if (interop.isNull(object)) {
|
||||
return true;
|
||||
} else {
|
||||
return object == null;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isAtom(Object object) {
|
||||
return object instanceof Atom;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
boolean isHostObject(Object object) {
|
||||
return EnsoContext.get(this).getEnvironment().isHostObject(object);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
boolean isHostFunction(Object object) {
|
||||
return EnsoContext.get(this).getEnvironment().isHostFunction(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper node for invoking `Any.==` method.
|
||||
*/
|
||||
@GenerateUncached
|
||||
static abstract class InvokeAnyEqualsNode extends Node {
|
||||
static InvokeAnyEqualsNode getUncached() {
|
||||
return EqualsNodeGen.InvokeAnyEqualsNodeGen.getUncached();
|
||||
}
|
||||
|
||||
abstract boolean execute(Atom selfAtom, Atom otherAtom);
|
||||
|
||||
@Specialization
|
||||
boolean invokeEqualsCachedAtomCtor(Atom selfAtom, Atom thatAtom,
|
||||
@Cached(value = "getAnyEqualsMethod()", allowUncached = true) Function anyEqualsFunc,
|
||||
@Cached(value = "buildInvokeFuncNodeForAnyEquals()", allowUncached = true) InvokeFunctionNode invokeAnyEqualsNode,
|
||||
@CachedLibrary(limit = "3") InteropLibrary interop) {
|
||||
// TODO: Shouldn't Comparable type be the very first argument? (synthetic self)?
|
||||
Object ret = invokeAnyEqualsNode.execute(
|
||||
anyEqualsFunc,
|
||||
null,
|
||||
State.create(EnsoContext.get(this)),
|
||||
new Object[]{selfAtom, thatAtom}
|
||||
);
|
||||
try {
|
||||
return interop.asBoolean(ret);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException("Return value from Any== should be Boolean", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
Function getAnyEqualsMethod() {
|
||||
var anyType = EnsoContext.get(this).getBuiltins().any();
|
||||
Function anyEqualsFunc =
|
||||
anyType.getDefinitionScope().getMethods().get(anyType).get("==");
|
||||
assert anyEqualsFunc != null : "Any.== method must exist";
|
||||
return anyEqualsFunc;
|
||||
}
|
||||
|
||||
InvokeFunctionNode buildInvokeFuncNodeForAnyEquals() {
|
||||
return InvokeFunctionNode.build(
|
||||
new CallArgumentInfo[]{new CallArgumentInfo("self"), new CallArgumentInfo("that")},
|
||||
DefaultsExecutionMode.EXECUTE,
|
||||
ArgumentsExecutionMode.EXECUTE
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package org.enso.interpreter.node.expression.builtin.meta;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.ibm.icu.text.Normalizer2;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
@ -23,14 +24,24 @@ import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.enso.interpreter.dsl.AcceptsError;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||
import org.enso.interpreter.node.expression.builtin.ordering.HasCustomComparatorNode;
|
||||
import org.enso.interpreter.node.expression.builtin.ordering.HashCallbackNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.Module;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.atom.StructsLibrary;
|
||||
import org.enso.interpreter.runtime.data.EnsoFile;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
||||
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
|
||||
/**
|
||||
* Implements {@code hash_code} functionality.
|
||||
@ -40,7 +51,7 @@ import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
* <h3>Hashing contract:</h3>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Whenever two objects are equal ({@code EqualsAnyNode} returns {@code true}), their hashcode
|
||||
* <li>Whenever two objects are equal ({@code EqualsNode} returns {@code true}), their hashcode
|
||||
* should equal. More formally: {@code For all objects o1, o2: if o1 == o2 then hash(o1) ==
|
||||
* hash(o2)}
|
||||
* <li>Whenever two hash codes are different, their associated objects are different: {@code For all objects
|
||||
@ -48,13 +59,20 @@ import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
* </ul>
|
||||
*/
|
||||
@GenerateUncached
|
||||
public abstract class HashCodeAnyNode extends Node {
|
||||
@BuiltinMethod(
|
||||
type = "Comparable",
|
||||
name = "hash_builtin",
|
||||
description = """
|
||||
Returns hash code of this atom. Use only for overriding default Comparator.
|
||||
"""
|
||||
)
|
||||
public abstract class HashCodeNode extends Node {
|
||||
|
||||
public static HashCodeAnyNode build() {
|
||||
return HashCodeAnyNodeGen.create();
|
||||
public static HashCodeNode build() {
|
||||
return HashCodeNodeGen.create();
|
||||
}
|
||||
|
||||
public abstract long execute(@AcceptsError Object self);
|
||||
public abstract long execute(@AcceptsError Object object);
|
||||
|
||||
/** Specializations for primitive values * */
|
||||
@Specialization
|
||||
@ -69,12 +87,13 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
|
||||
@Specialization
|
||||
long hashCodeForLong(long l) {
|
||||
return Long.hashCode(l);
|
||||
// By casting long to double, we lose some precision on purpose
|
||||
return hashCodeForDouble((double) l);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
long hashCodeForInt(int i) {
|
||||
return i;
|
||||
return hashCodeForLong(i);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@ -83,20 +102,20 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
long hashCodeForDouble(double d) {
|
||||
if (d % 1.0 != 0.0) {
|
||||
if (d % 1.0 != 0 || BigIntegerOps.fitsInLong(d)) {
|
||||
return Double.hashCode(d);
|
||||
} else {
|
||||
if (BigIntegerOps.fitsInLong(d)) {
|
||||
return hashCodeForLong(Double.valueOf(d).longValue());
|
||||
} else {
|
||||
try {
|
||||
return BigDecimal.valueOf(d).toBigIntegerExact().hashCode();
|
||||
} catch (ArithmeticException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return bigDoubleHash(d);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private static long bigDoubleHash(double d) {
|
||||
try {
|
||||
return BigDecimal.valueOf(d).toBigIntegerExact().hashCode();
|
||||
} catch (ArithmeticException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,12 +130,63 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
return System.identityHashCode(atomConstructor);
|
||||
}
|
||||
|
||||
/** How many {@link HashCodeAnyNode} nodes should be created for fields in atoms. */
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
long hashCodeForUnresolvedSymbol(UnresolvedSymbol unresolvedSymbol,
|
||||
@Cached HashCodeNode hashCodeNode) {
|
||||
long nameHash = hashCodeNode.execute(unresolvedSymbol.getName());
|
||||
long scopeHash = hashCodeNode.execute(unresolvedSymbol.getScope());
|
||||
return Objects.hashCode(nameHash, scopeHash);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
long hashCodeForUnresolvedConversion(UnresolvedConversion unresolvedConversion,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
return hashCodeForModuleScope(unresolvedConversion.getScope(), interop);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
long hashCodeForModuleScope(ModuleScope moduleScope,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
return hashCodeForModule(moduleScope.getModule(), interop);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
long hashCodeForModule(Module module,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
return hashCodeForString(module.toString(), interop);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
long hashCodeForFile(EnsoFile file,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
return hashCodeForString(file.getPath(), interop);
|
||||
}
|
||||
|
||||
/**
|
||||
* There is no specialization for {@link TypesLibrary#hasType(Object)}, because also
|
||||
* primitive values would fall into that specialization and it would be too complicated
|
||||
* to make that specialization disjunctive. So we rather specialize directly for
|
||||
* {@link Type}.
|
||||
*/
|
||||
@Specialization
|
||||
long hashCodeForType(Type type,
|
||||
@Cached HashCodeNode hashCodeNode) {
|
||||
if (EnsoContext.get(this).getNothing() == type) {
|
||||
// Nothing should be equal to `null`
|
||||
return 0;
|
||||
} else {
|
||||
return hashCodeNode.execute(type.getQualifiedName().toString());
|
||||
}
|
||||
}
|
||||
|
||||
/** How many {@link HashCodeNode} nodes should be created for fields in atoms. */
|
||||
static final int hashCodeNodeCountForFields = 10;
|
||||
|
||||
static HashCodeAnyNode[] createHashCodeNodes(int size) {
|
||||
HashCodeAnyNode[] nodes = new HashCodeAnyNode[size];
|
||||
Arrays.fill(nodes, HashCodeAnyNode.build());
|
||||
static HashCodeNode[] createHashCodeNodes(int size) {
|
||||
HashCodeNode[] nodes = new HashCodeNode[size];
|
||||
Arrays.fill(nodes, HashCodeNode.build());
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@ -124,16 +194,17 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
long hashCodeForAtom(
|
||||
Atom atom,
|
||||
@Cached(value = "createHashCodeNodes(hashCodeNodeCountForFields)", allowUncached = true)
|
||||
HashCodeAnyNode[] fieldHashCodeNodes,
|
||||
HashCodeNode[] fieldHashCodeNodes,
|
||||
@Cached ConditionProfile isHashCodeCached,
|
||||
@Cached ConditionProfile enoughHashCodeNodesForFields,
|
||||
@Cached LoopConditionProfile loopProfile,
|
||||
@CachedLibrary(limit = "10") StructsLibrary structs) {
|
||||
@CachedLibrary(limit = "10") StructsLibrary structs,
|
||||
@Cached HasCustomComparatorNode hasCustomComparatorNode,
|
||||
@Cached HashCallbackNode hashCallbackNode) {
|
||||
if (isHashCodeCached.profile(atom.getHashCode() != null)) {
|
||||
return atom.getHashCode();
|
||||
}
|
||||
// TODO[PM]: If atom overrides hash_code, call that method (Will be done in a follow-up PR for
|
||||
// https://www.pivotaltracker.com/story/show/183945328)
|
||||
|
||||
Object[] fields = structs.getFields(atom);
|
||||
int fieldsCount = fields.length;
|
||||
|
||||
@ -142,7 +213,11 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
if (enoughHashCodeNodesForFields.profile(fieldsCount <= hashCodeNodeCountForFields)) {
|
||||
loopProfile.profileCounted(fieldsCount);
|
||||
for (int i = 0; loopProfile.inject(i < fieldsCount); i++) {
|
||||
hashes[i] = (int) fieldHashCodeNodes[i].execute(fields[i]);
|
||||
if (fields[i] instanceof Atom atomField && hasCustomComparatorNode.execute(atomField)) {
|
||||
hashes[i] = (int) hashCallbackNode.execute(atomField);
|
||||
} else {
|
||||
hashes[i] = (int) fieldHashCodeNodes[i].execute(fields[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hashCodeForAtomFieldsUncached(fields, hashes);
|
||||
@ -159,7 +234,12 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
@TruffleBoundary
|
||||
private void hashCodeForAtomFieldsUncached(Object[] fields, int[] fieldHashes) {
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
fieldHashes[i] = (int) HashCodeAnyNodeGen.getUncached().execute(fields[i]);
|
||||
if (fields[i] instanceof Atom atomField
|
||||
&& HasCustomComparatorNode.getUncached().execute(atomField)) {
|
||||
fieldHashes[i] = (int) HashCallbackNode.getUncached().execute(atomField);
|
||||
} else {
|
||||
fieldHashes[i] = (int) HashCodeNodeGen.getUncached().execute(fields[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +249,7 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
long hashCodeForWarning(
|
||||
Object selfWithWarning,
|
||||
@CachedLibrary("selfWithWarning") WarningsLibrary warnLib,
|
||||
@Cached HashCodeAnyNode hashCodeNode) {
|
||||
@Cached HashCodeNode hashCodeNode) {
|
||||
try {
|
||||
return hashCodeNode.execute(warnLib.removeWarnings(selfWithWarning));
|
||||
} catch (UnsupportedMessageException e) {
|
||||
@ -318,20 +398,30 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {"interop.hasArrayElements(selfArray)"},
|
||||
guards = {
|
||||
"interop.hasArrayElements(selfArray)",
|
||||
"!interop.hasHashEntries(selfArray)"
|
||||
},
|
||||
limit = "3")
|
||||
long hashCodeForArray(
|
||||
Object selfArray,
|
||||
@CachedLibrary("selfArray") InteropLibrary interop,
|
||||
@Cached HashCodeAnyNode hashCodeNode,
|
||||
@Cached("createCountingProfile()") LoopConditionProfile loopProfile) {
|
||||
@Cached HashCodeNode hashCodeNode,
|
||||
@Cached("createCountingProfile()") LoopConditionProfile loopProfile,
|
||||
@Cached HashCallbackNode hashCallbackNode,
|
||||
@Cached HasCustomComparatorNode hasCustomComparatorNode) {
|
||||
try {
|
||||
long arraySize = interop.getArraySize(selfArray);
|
||||
loopProfile.profileCounted(arraySize);
|
||||
int[] elemHashCodes = new int[(int) arraySize];
|
||||
for (int i = 0; loopProfile.inject(i < arraySize); i++) {
|
||||
if (interop.isArrayElementReadable(selfArray, i)) {
|
||||
elemHashCodes[i] = (int) hashCodeNode.execute(interop.readArrayElement(selfArray, i));
|
||||
Object elem = interop.readArrayElement(selfArray, i);
|
||||
if (elem instanceof Atom atomElem && hasCustomComparatorNode.execute(atomElem)) {
|
||||
elemHashCodes[i] = (int) hashCallbackNode.execute(atomElem);
|
||||
} else {
|
||||
elemHashCodes[i] = (int) hashCodeNode.execute(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Arrays.hashCode(elemHashCodes);
|
||||
@ -348,7 +438,7 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
long hashCodeForMap(
|
||||
Object selfMap,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop,
|
||||
@Cached HashCodeAnyNode hashCodeNode) {
|
||||
@Cached HashCodeNode hashCodeNode) {
|
||||
int mapSize;
|
||||
long keysHashCode = 0;
|
||||
long valuesHashCode = 0;
|
||||
@ -369,6 +459,47 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
return Arrays.hashCode(new long[] {keysHashCode, valuesHashCode, mapSize});
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"!isAtom(objectWithMembers)",
|
||||
"!isHostObject(objectWithMembers)",
|
||||
"interop.hasMembers(objectWithMembers)",
|
||||
"!interop.hasArrayElements(objectWithMembers)",
|
||||
"!interop.isTime(objectWithMembers)",
|
||||
"!interop.isDate(objectWithMembers)",
|
||||
"!interop.isTimeZone(objectWithMembers)",
|
||||
"!typesLib.hasType(objectWithMembers)",
|
||||
})
|
||||
long hashCodeForInteropObjectWithMembers(Object objectWithMembers,
|
||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@CachedLibrary(limit = "5") TypesLibrary typesLib,
|
||||
@Cached HashCodeNode hashCodeNode) {
|
||||
try {
|
||||
Object members = interop.getMembers(objectWithMembers);
|
||||
assert interop.getArraySize(members) < Integer.MAX_VALUE : "long array size not supported";
|
||||
int size = (int) interop.getArraySize(members);
|
||||
// Final hash code will be put together from member names and member values.
|
||||
long[] hashCodes = new long[size * 2];
|
||||
int hashCodesIdx = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
String memberName = interop.asString(interop.readArrayElement(members, i));
|
||||
hashCodes[hashCodesIdx++] = hashCodeNode.execute(memberName);
|
||||
if (interop.isMemberReadable(objectWithMembers, memberName)) {
|
||||
Object member = interop.readMember(objectWithMembers, memberName);
|
||||
hashCodes[hashCodesIdx++] = hashCodeNode.execute(member);
|
||||
} else {
|
||||
hashCodes[hashCodesIdx++] = 0;
|
||||
}
|
||||
}
|
||||
return Arrays.hashCode(hashCodes);
|
||||
} catch (UnsupportedMessageException | InvalidArrayIndexException | UnknownIdentifierException e) {
|
||||
throw new IllegalStateException(
|
||||
String.format("An interop object (%s) has probably wrongly specified interop API"
|
||||
+ " for members.", objectWithMembers),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {"interop.isNull(selfNull)"},
|
||||
limit = "3")
|
||||
@ -391,8 +522,29 @@ public abstract class HashCodeAnyNode extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every host function has a unique fully qualified name, it is not a lambda.
|
||||
* We get the hashcode from the qualified name.
|
||||
*/
|
||||
@TruffleBoundary
|
||||
@Specialization(guards = "isHostFunction(hostFunction)")
|
||||
long hashCodeForHostFunction(Object hostFunction,
|
||||
@CachedLibrary(limit = "3") InteropLibrary interop,
|
||||
@Cached HashCodeNode hashCodeNode) {
|
||||
return hashCodeNode.execute(interop.toDisplayString(hostFunction));
|
||||
}
|
||||
|
||||
static boolean isAtom(Object object) {
|
||||
return object instanceof Atom;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
boolean isHostObject(Object object) {
|
||||
return EnsoContext.get(this).getEnvironment().isHostObject(object);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
boolean isHostFunction(Object object) {
|
||||
return EnsoContext.get(this).getEnvironment().isHostFunction(object);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.number.bigInteger;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
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.node.expression.builtin.ordering.Ordering;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Big_Integer",
|
||||
name = "compare_to",
|
||||
description = "Comparison for big integers.")
|
||||
public abstract class CompareToNode extends Node {
|
||||
|
||||
static CompareToNode build() {
|
||||
return CompareToNodeGen.create();
|
||||
}
|
||||
|
||||
abstract Object execute(EnsoBigInteger self, Object that);
|
||||
|
||||
@Specialization
|
||||
Atom doLong(EnsoBigInteger self, long that) {
|
||||
return getOrdering().fromJava(BigIntegerOps.compareTo(self.getValue(), that));
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Atom doBigInt(EnsoBigInteger self, EnsoBigInteger that) {
|
||||
return getOrdering().fromJava(BigIntegerOps.compareTo(self.getValue(), that.getValue()));
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Atom doDecimal(EnsoBigInteger self, double that) {
|
||||
return getOrdering().fromJava(BigIntegerOps.compareTo(self.getValue(), that));
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError 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);
|
||||
}
|
||||
|
||||
Ordering getOrdering() {
|
||||
return EnsoContext.get(this).getBuiltins().ordering();
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.number.decimal;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
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.node.expression.builtin.ordering.Ordering;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@BuiltinMethod(type = "Decimal", name = "compare_to", description = "Comparison for decimals.")
|
||||
public abstract class CompareToNode extends Node {
|
||||
|
||||
static CompareToNode build() {
|
||||
return CompareToNodeGen.create();
|
||||
}
|
||||
|
||||
abstract Object execute(double self, Object that);
|
||||
|
||||
@Specialization
|
||||
Atom doLong(double self, long that) {
|
||||
if (self == that) {
|
||||
return getOrdering().newEqual();
|
||||
} else if (self > that) {
|
||||
return getOrdering().newGreater();
|
||||
} else {
|
||||
return getOrdering().newLess();
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Atom doBigInt(double self, EnsoBigInteger that) {
|
||||
return getOrdering().fromJava(BigIntegerOps.compareTo(self, that.getValue()));
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Atom doDecimal(double self, double that) {
|
||||
if (self == that) {
|
||||
return getOrdering().newEqual();
|
||||
} else if (self > that) {
|
||||
return getOrdering().newGreater();
|
||||
} else {
|
||||
return getOrdering().newLess();
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError doOther(double self, Object that) {
|
||||
var builtins = EnsoContext.get(this).getBuiltins();
|
||||
var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
}
|
||||
|
||||
Ordering getOrdering() {
|
||||
return EnsoContext.get(this).getBuiltins().ordering();
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.number.smallInteger;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
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.node.expression.builtin.ordering.Ordering;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Small_Integer",
|
||||
name = "compare_to",
|
||||
description = "Comparison for small integers.")
|
||||
public abstract class CompareToNode extends Node {
|
||||
|
||||
static CompareToNode build() {
|
||||
return CompareToNodeGen.create();
|
||||
}
|
||||
|
||||
abstract Object execute(long self, Object that);
|
||||
|
||||
@Specialization
|
||||
Atom doLong(long self, long that) {
|
||||
if (self == that) {
|
||||
return getOrdering().newEqual();
|
||||
} else if (self > that) {
|
||||
return getOrdering().newGreater();
|
||||
} else {
|
||||
return getOrdering().newLess();
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Atom doBigInt(long self, EnsoBigInteger that) {
|
||||
return getOrdering().fromJava(BigIntegerOps.compareTo(self, that.getValue()));
|
||||
}
|
||||
|
||||
@Specialization
|
||||
Atom doDecimal(long self, double that) {
|
||||
if (self == that) {
|
||||
return getOrdering().newEqual();
|
||||
} else if (self > that) {
|
||||
return getOrdering().newGreater();
|
||||
} else {
|
||||
return getOrdering().newLess();
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
DataflowError 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);
|
||||
}
|
||||
|
||||
Ordering getOrdering() {
|
||||
return EnsoContext.get(this).getBuiltins().ordering();
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
import org.enso.interpreter.dsl.BuiltinType;
|
||||
import org.enso.interpreter.node.expression.builtin.Builtin;
|
||||
|
||||
/** A hidden builtin. Only conversions with target type of Comparable are visible. */
|
||||
@BuiltinType
|
||||
public class Comparable extends Builtin {}
|
@ -0,0 +1,7 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
import org.enso.interpreter.dsl.BuiltinType;
|
||||
import org.enso.interpreter.node.expression.builtin.Builtin;
|
||||
|
||||
@BuiltinType
|
||||
public class DefaultOrderedComparator extends Builtin {}
|
@ -0,0 +1,7 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
import org.enso.interpreter.dsl.BuiltinType;
|
||||
import org.enso.interpreter.node.expression.builtin.Builtin;
|
||||
|
||||
@BuiltinType
|
||||
public class DefaultUnorderedComparator extends Builtin {}
|
@ -0,0 +1,86 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
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.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
|
||||
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
|
||||
/**
|
||||
* Helper node for invocation of {@code Comparable.has_custom_comparator atom}. Note that emulating
|
||||
* the semantics of that function in Java code would be too complicated, so we rather implemented it
|
||||
* in Enso and just call it from this node.
|
||||
*/
|
||||
@GenerateUncached
|
||||
public abstract class HasCustomComparatorNode extends Node {
|
||||
|
||||
public static HasCustomComparatorNode getUncached() {
|
||||
return HasCustomComparatorNodeGen.getUncached();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given atom has a custom comparator, that is a comparator that is different
|
||||
* than the default (internal) ones. The default comparators are {@code
|
||||
* Default_Unordered_Comparator} and {@code Default_Ordered_Comparator}.
|
||||
*
|
||||
* @param atom Atom for which we check whether it has custom comparator
|
||||
* @return true iff the given atom has a custom comparator
|
||||
*/
|
||||
public abstract boolean execute(Atom atom);
|
||||
|
||||
@Specialization
|
||||
boolean hasCustomComparatorCached(
|
||||
Atom atom,
|
||||
@Cached(value = "getHasCustomComparatorFunction()", allowUncached = true)
|
||||
Function hasCustomComparatorFunc,
|
||||
@Cached(value = "buildInvokeNodeWithAtomArgument()", allowUncached = true)
|
||||
InvokeFunctionNode hasCustomComparatorInvokeNode,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
var ctx = EnsoContext.get(this);
|
||||
var comparableType = ctx.getBuiltins().comparable().getType();
|
||||
Object res =
|
||||
hasCustomComparatorInvokeNode.execute(
|
||||
hasCustomComparatorFunc, null, State.create(ctx), new Object[] {comparableType, atom});
|
||||
assert interop.isBoolean(res);
|
||||
try {
|
||||
return interop.asBoolean(res);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(
|
||||
"Return type from Comparable.has_custom_comparator should be Boolean", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an {@link InvokeFunctionNode} for a method with just one argument named {@code atom}.
|
||||
*/
|
||||
static InvokeFunctionNode buildInvokeNodeWithAtomArgument() {
|
||||
return InvokeFunctionNode.build(
|
||||
new CallArgumentInfo[] {new CallArgumentInfo("self"), new CallArgumentInfo("atom")},
|
||||
DefaultsExecutionMode.EXECUTE,
|
||||
ArgumentsExecutionMode.EXECUTE);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
Function getHasCustomComparatorFunction() {
|
||||
var comparableType = EnsoContext.get(this).getBuiltins().comparable().getType();
|
||||
Function hasCustomComparatorFunc =
|
||||
comparableType
|
||||
.getDefinitionScope()
|
||||
.getMethods()
|
||||
.get(comparableType)
|
||||
.get("has_custom_comparator");
|
||||
assert hasCustomComparatorFunc != null : "Comparable.has_custom_comparator function must exist";
|
||||
return hasCustomComparatorFunc;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
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.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode;
|
||||
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
|
||||
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
|
||||
/**
|
||||
* Helper node for invocation of {@code Comparable.hash_callback atom}. Note that emulating the
|
||||
* semantics of that function in Java code would be too complicated, so we rather implemented it in
|
||||
* Enso and just call it from this node.
|
||||
*/
|
||||
@GenerateUncached
|
||||
public abstract class HashCallbackNode extends Node {
|
||||
|
||||
public static HashCallbackNode getUncached() {
|
||||
return HashCallbackNodeGen.getUncached();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches to the appropriate comparator for the given atom and calls {@code hash} method on
|
||||
* it. Returns the value from that method.
|
||||
*
|
||||
* <p>Note that the given atom should have a custom comparator, otherwise it could be handled by
|
||||
* {@link org.enso.interpreter.node.expression.builtin.meta.HashCodeNode}.
|
||||
*
|
||||
* @param atom Atom, preferably with a custom comparator, for which we get the custom comparator
|
||||
* and call {@code hash} method on the comparator.
|
||||
* @return Hash code for the atom, as returned by the custom comparator.
|
||||
*/
|
||||
public abstract long execute(Atom atom);
|
||||
|
||||
@Specialization
|
||||
long hashCallbackCached(
|
||||
Atom atom,
|
||||
@Cached(value = "getHashCallbackFunction()", allowUncached = true) Function hashCallbackFunc,
|
||||
@Cached(value = "buildInvokeNodeWithAtomArgument()", allowUncached = true)
|
||||
InvokeFunctionNode hashCallbackInvokeNode,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
var ctx = EnsoContext.get(this);
|
||||
var comparableType = ctx.getBuiltins().comparable().getType();
|
||||
Object res =
|
||||
hashCallbackInvokeNode.execute(
|
||||
hashCallbackFunc, null, State.create(ctx), new Object[] {comparableType, atom});
|
||||
try {
|
||||
return interop.asLong(res);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(
|
||||
"Return type from Comparable.hash_callback should be Long", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an {@link InvokeFunctionNode} for a method with just one argument named {@code atom}.
|
||||
*/
|
||||
static InvokeFunctionNode buildInvokeNodeWithAtomArgument() {
|
||||
return InvokeFunctionNode.build(
|
||||
new CallArgumentInfo[] {new CallArgumentInfo("self"), new CallArgumentInfo("atom")},
|
||||
DefaultsExecutionMode.EXECUTE,
|
||||
ArgumentsExecutionMode.EXECUTE);
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
Function getHashCallbackFunction() {
|
||||
var comparableType = EnsoContext.get(this).getBuiltins().comparable().getType();
|
||||
Function hashCallback =
|
||||
comparableType.getDefinitionScope().getMethods().get(comparableType).get("hash_callback");
|
||||
assert hashCallback != null : "Comparable.hash_callback function must exist";
|
||||
return hashCallback;
|
||||
}
|
||||
}
|
@ -0,0 +1,305 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
import com.ibm.icu.text.Normalizer;
|
||||
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.GenerateUncached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import org.enso.interpreter.dsl.AcceptsError;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Comparable",
|
||||
name = "less_than_builtin",
|
||||
description = """
|
||||
Returns true if self is less than `other`. Or throw an error if the values are
|
||||
not comparable.
|
||||
"""
|
||||
)
|
||||
@GenerateUncached
|
||||
public abstract class LessThanNode extends Node {
|
||||
|
||||
public static LessThanNode build() {
|
||||
return LessThanNodeGen.create();
|
||||
}
|
||||
|
||||
public abstract Object execute(@AcceptsError Object left, @AcceptsError Object other);
|
||||
|
||||
@Specialization
|
||||
boolean lessIntegers(int i, int j) {
|
||||
return i < j;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean lessBools(boolean b1, boolean b2) {
|
||||
return !b1 && b2;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean lessLongs(long l1, long l2) {
|
||||
return l1 < l2;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean lessDoubles(double self, double other) {
|
||||
return self < other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean lessLongDouble(long self, double other) {
|
||||
return (double) self < other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean lessDoubleLong(double self, long other) {
|
||||
return self < (double) other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean lessIntLong(int self, long other) {
|
||||
return (long) self < other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean lessLongInt(long self, int other) {
|
||||
return self < (long) other;
|
||||
}
|
||||
|
||||
|
||||
@Specialization
|
||||
boolean lessIntDouble(int self, double other) {
|
||||
return (double) self < other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean lessDoubleInt(double self, int other) {
|
||||
return self < (double) other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean lessBigInt(EnsoBigInteger self, EnsoBigInteger other) {
|
||||
return self.getValue().compareTo(other.getValue()) < 0;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean lessBitIntDouble(EnsoBigInteger self, double other) {
|
||||
return self.doubleValue() < other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean lessDoubleBigInt(double self, EnsoBigInteger other) {
|
||||
return self < other.doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* If one of the objects has warnings attached, just treat it as an object without any
|
||||
* warnings.
|
||||
*/
|
||||
@Specialization(guards = {
|
||||
"selfWarnLib.hasWarnings(selfWithWarnings) || otherWarnLib.hasWarnings(otherWithWarnings)"
|
||||
}, limit = "3")
|
||||
Object lessWithWarnings(Object selfWithWarnings, Object otherWithWarnings,
|
||||
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
|
||||
@CachedLibrary("otherWithWarnings") WarningsLibrary otherWarnLib,
|
||||
@Cached LessThanNode lessThanNode
|
||||
) {
|
||||
try {
|
||||
Object self =
|
||||
selfWarnLib.hasWarnings(selfWithWarnings) ? selfWarnLib.removeWarnings(selfWithWarnings)
|
||||
: selfWithWarnings;
|
||||
Object other =
|
||||
otherWarnLib.hasWarnings(otherWithWarnings) ? otherWarnLib.removeWarnings(otherWithWarnings)
|
||||
: otherWithWarnings;
|
||||
return lessThanNode.execute(self, other);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(limit = "3")
|
||||
boolean lessTexts(Text selfText, Text otherText,
|
||||
@CachedLibrary("selfText") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherText") InteropLibrary otherInterop) {
|
||||
if (selfText.is_normalized() && otherText.is_normalized()) {
|
||||
return selfText.toString().compareTo(otherText.toString()) < 0;
|
||||
} else {
|
||||
return lessInteropStrings(selfText, otherText, selfInterop, otherInterop);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {
|
||||
"selfInterop.isString(selfStr)",
|
||||
"otherInterop.isString(otherStr)"
|
||||
},
|
||||
limit = "5"
|
||||
)
|
||||
@TruffleBoundary
|
||||
boolean lessInteropStrings(Object selfStr, Object otherStr,
|
||||
@CachedLibrary("selfStr") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherStr") InteropLibrary otherInterop) {
|
||||
String selfJavaString;
|
||||
String otherJavaString;
|
||||
try {
|
||||
selfJavaString = selfInterop.asString(selfStr);
|
||||
otherJavaString = otherInterop.asString(otherStr);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return Normalizer.compare(
|
||||
selfJavaString,
|
||||
otherJavaString,
|
||||
Normalizer.FOLD_CASE_DEFAULT
|
||||
) < 0;
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isBoolean(selfBoolean)",
|
||||
"otherInterop.isBoolean(otherBoolean)"
|
||||
}, limit = "3")
|
||||
boolean lessInteropBoolean(
|
||||
Object selfBoolean,
|
||||
Object otherBoolean,
|
||||
@CachedLibrary("selfBoolean") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherBoolean") InteropLibrary otherInterop
|
||||
) {
|
||||
try {
|
||||
return !selfInterop.asBoolean(selfBoolean) && otherInterop.asBoolean(otherBoolean);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isDate(selfZonedDateTime)",
|
||||
"selfInterop.isTime(selfZonedDateTime)",
|
||||
"selfInterop.isTimeZone(selfZonedDateTime)",
|
||||
"otherInterop.isDate(otherZonedDateTime)",
|
||||
"otherInterop.isTime(otherZonedDateTime)",
|
||||
"otherInterop.isTimeZone(otherZonedDateTime)"
|
||||
}, limit = "3")
|
||||
boolean lessInteropZonedDateTimes(Object selfZonedDateTime, Object otherZonedDateTime,
|
||||
@CachedLibrary("selfZonedDateTime") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherZonedDateTime") InteropLibrary otherInterop) {
|
||||
try {
|
||||
var self = ZonedDateTime.of(
|
||||
selfInterop.asDate(selfZonedDateTime),
|
||||
selfInterop.asTime(selfZonedDateTime),
|
||||
selfInterop.asTimeZone(selfZonedDateTime)
|
||||
);
|
||||
var other = ZonedDateTime.of(
|
||||
otherInterop.asDate(otherZonedDateTime),
|
||||
otherInterop.asTime(otherZonedDateTime),
|
||||
otherInterop.asTimeZone(otherZonedDateTime)
|
||||
);
|
||||
return self.compareTo(other) < 0;
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isDate(selfDateTime)",
|
||||
"selfInterop.isTime(selfDateTime)",
|
||||
"!selfInterop.isTimeZone(selfDateTime)",
|
||||
"otherInterop.isDate(otherDateTime)",
|
||||
"otherInterop.isTime(otherDateTime)",
|
||||
"!otherInterop.isTimeZone(otherDateTime)"
|
||||
}, limit = "3")
|
||||
boolean lessInteropDateTimes(Object selfDateTime, Object otherDateTime,
|
||||
@CachedLibrary("selfDateTime") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherDateTime") InteropLibrary otherInterop) {
|
||||
try {
|
||||
var self = LocalDateTime.of(
|
||||
selfInterop.asDate(selfDateTime),
|
||||
selfInterop.asTime(selfDateTime)
|
||||
);
|
||||
var other = LocalDateTime.of(
|
||||
otherInterop.asDate(otherDateTime),
|
||||
otherInterop.asTime(otherDateTime)
|
||||
);
|
||||
return self.isBefore(other);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isDate(selfDate)",
|
||||
"!selfInterop.isTime(selfDate)",
|
||||
"!selfInterop.isTimeZone(selfDate)",
|
||||
"otherInterop.isDate(otherDate)",
|
||||
"!otherInterop.isTime(otherDate)",
|
||||
"!otherInterop.isTimeZone(otherDate)"
|
||||
}, limit = "3")
|
||||
boolean lessInteropDates(Object selfDate, Object otherDate,
|
||||
@CachedLibrary("selfDate") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherDate") InteropLibrary otherInterop) {
|
||||
try {
|
||||
return selfInterop.asDate(selfDate).isBefore(
|
||||
otherInterop.asDate(otherDate)
|
||||
);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"!selfInterop.isDate(selfTime)",
|
||||
"selfInterop.isTime(selfTime)",
|
||||
"!selfInterop.isTimeZone(selfTime)",
|
||||
"!otherInterop.isDate(otherTime)",
|
||||
"otherInterop.isTime(otherTime)",
|
||||
"!otherInterop.isTimeZone(otherTime)"
|
||||
}, limit = "3")
|
||||
boolean lessInteropTimes(Object selfTime, Object otherTime,
|
||||
@CachedLibrary("selfTime") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherTime") InteropLibrary otherInterop) {
|
||||
try {
|
||||
return selfInterop.asTime(selfTime).isBefore(
|
||||
otherInterop.asTime(otherTime)
|
||||
);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isDuration(selfDuration)",
|
||||
"otherInterop.isDuration(otherDuration)"
|
||||
}, limit = "3")
|
||||
boolean lessInteropDuration(Object selfDuration, Object otherDuration,
|
||||
@CachedLibrary("selfDuration") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherDuration") InteropLibrary otherInterop) {
|
||||
try {
|
||||
Duration selfDur = selfInterop.asDuration(selfDuration);
|
||||
Duration otherDur = otherInterop.asDuration(otherDuration);
|
||||
return selfDur.compareTo(otherDur) < 0;
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
Object fallback(Object left, Object right) {
|
||||
var typeError = EnsoContext.get(this).getBuiltins().error().makeTypeError(left, right, "right");
|
||||
return DataflowError.withoutTrace(typeError, this);
|
||||
}
|
||||
}
|
@ -29,6 +29,9 @@ import org.enso.interpreter.node.expression.builtin.meta.ProjectDescription;
|
||||
import org.enso.interpreter.node.expression.builtin.mutable.Array;
|
||||
import org.enso.interpreter.node.expression.builtin.mutable.Ref;
|
||||
import org.enso.interpreter.node.expression.builtin.immutable.Vector;
|
||||
import org.enso.interpreter.node.expression.builtin.ordering.Comparable;
|
||||
import org.enso.interpreter.node.expression.builtin.ordering.DefaultOrderedComparator;
|
||||
import org.enso.interpreter.node.expression.builtin.ordering.DefaultUnorderedComparator;
|
||||
import org.enso.interpreter.node.expression.builtin.ordering.Ordering;
|
||||
import org.enso.interpreter.node.expression.builtin.resource.ManagedResource;
|
||||
import org.enso.interpreter.node.expression.builtin.text.Text;
|
||||
@ -82,6 +85,9 @@ public class Builtins {
|
||||
private final Number number;
|
||||
private final Boolean bool;
|
||||
private final Ordering ordering;
|
||||
private final Comparable comparable;
|
||||
private final DefaultOrderedComparator defaultOrderedComparator;
|
||||
private final DefaultUnorderedComparator defaultUnorderedComparator;
|
||||
private final System system;
|
||||
private final Special special;
|
||||
|
||||
@ -128,6 +134,9 @@ public class Builtins {
|
||||
|
||||
error = new Error(this, context);
|
||||
ordering = getBuiltinType(Ordering.class);
|
||||
comparable = getBuiltinType(Comparable.class);
|
||||
defaultUnorderedComparator = getBuiltinType(DefaultUnorderedComparator.class);
|
||||
defaultOrderedComparator = getBuiltinType(DefaultOrderedComparator.class);
|
||||
system = new System(this);
|
||||
number = new Number(this);
|
||||
bool = this.getBuiltinType(Boolean.class);
|
||||
@ -585,6 +594,18 @@ public class Builtins {
|
||||
return ordering;
|
||||
}
|
||||
|
||||
public Comparable comparable() {
|
||||
return comparable;
|
||||
}
|
||||
|
||||
public DefaultOrderedComparator defaultOrderedComparator() {
|
||||
return defaultOrderedComparator;
|
||||
}
|
||||
|
||||
public DefaultUnorderedComparator defaultUnorderedComparator() {
|
||||
return defaultUnorderedComparator;
|
||||
}
|
||||
|
||||
/** @return the container for the dataflow error-related builtins */
|
||||
public Type dataflowError() {
|
||||
return dataflowError.getType();
|
||||
|
@ -40,7 +40,6 @@ public final class UnresolvedConversion implements TruffleObject {
|
||||
* is returned. This is useful for certain subtyping relations, such as "any constructor is a
|
||||
* subtype of Any" or "Nat is a subtype of Int, is a subtype of Number".
|
||||
*
|
||||
* @param constructors the constructors hierarchy for which this symbol should be resolved
|
||||
* @return the resolved function definition, or null if not found
|
||||
*/
|
||||
public Function resolveFor(Type into, Type from) {
|
||||
|
@ -172,14 +172,6 @@ public final class EnsoDuration implements TruffleObject {
|
||||
return new EnsoDuration(duration.minus(interop.asDuration(durationObject)));
|
||||
}
|
||||
|
||||
@Builtin.Method(name = "compare_to_builtin", description = "Compares to other duration")
|
||||
@Builtin.Specialize
|
||||
@Builtin.WrapException(from = UnsupportedMessageException.class)
|
||||
public long compareTo(Object durationObject, InteropLibrary interop)
|
||||
throws UnsupportedMessageException {
|
||||
return duration.compareTo(interop.asDuration(durationObject));
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
public boolean isDuration() {
|
||||
return true;
|
||||
|
@ -202,7 +202,7 @@ public final class Type implements TruffleObject {
|
||||
@ExportMessage
|
||||
Object getMetaParents(@CachedLibrary("this") InteropLibrary lib)
|
||||
throws UnsupportedMessageException {
|
||||
if (isNothing(lib)) {
|
||||
if (isNothing(lib) || !hasMetaParents()) {
|
||||
throw UnsupportedMessageException.create();
|
||||
}
|
||||
assert supertype != null;
|
||||
|
@ -11,8 +11,8 @@ import com.oracle.truffle.api.library.ExportLibrary;
|
||||
import com.oracle.truffle.api.library.ExportMessage;
|
||||
import com.oracle.truffle.api.profiles.ConditionProfile;
|
||||
import org.enso.interpreter.dsl.Builtin;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.data.Vector;
|
||||
@ -58,8 +58,8 @@ public final class EnsoHashMap implements TruffleObject {
|
||||
return new EnsoHashMap(mapBuilder, snapshotSize);
|
||||
}
|
||||
|
||||
static EnsoHashMap createEmpty(HashCodeAnyNode hashCodeAnyNode, EqualsAnyNode equalsNode) {
|
||||
return new EnsoHashMap(EnsoHashMapBuilder.create(hashCodeAnyNode, equalsNode), 0);
|
||||
static EnsoHashMap createEmpty(HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
||||
return new EnsoHashMap(EnsoHashMapBuilder.create(hashCodeNode, equalsNode), 0);
|
||||
}
|
||||
|
||||
EnsoHashMapBuilder getMapBuilder() {
|
||||
@ -100,7 +100,7 @@ public final class EnsoHashMap implements TruffleObject {
|
||||
@Builtin.Method
|
||||
@Builtin.Specialize
|
||||
public static EnsoHashMap empty(
|
||||
@Cached HashCodeAnyNode hashCodeNode, @Cached EqualsAnyNode equalsNode) {
|
||||
@Cached HashCodeNode hashCodeNode, @Cached EqualsNode equalsNode) {
|
||||
return createEmpty(hashCodeNode, equalsNode);
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,8 @@ package org.enso.interpreter.runtime.data.hash;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
||||
import org.graalvm.collections.EconomicMap;
|
||||
import org.graalvm.collections.Equivalence;
|
||||
|
||||
@ -18,14 +18,14 @@ public final class EnsoHashMapBuilder {
|
||||
/** All entries stored by their sequential index. */
|
||||
private final List<StorageEntry> sequentialEntries;
|
||||
|
||||
private final HashCodeAnyNode hashCodeNode;
|
||||
private final EqualsAnyNode equalsNode;
|
||||
private final HashCodeNode hashCodeNode;
|
||||
private final EqualsNode equalsNode;
|
||||
private int size;
|
||||
|
||||
private EnsoHashMapBuilder(HashCodeAnyNode hashCodeAnyNode, EqualsAnyNode equalsNode) {
|
||||
this.storage = EconomicMap.create(new StorageStrategy(equalsNode, hashCodeAnyNode));
|
||||
private EnsoHashMapBuilder(HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
||||
this.storage = EconomicMap.create(new StorageStrategy(equalsNode, hashCodeNode));
|
||||
this.sequentialEntries = new ArrayList<>();
|
||||
this.hashCodeNode = hashCodeAnyNode;
|
||||
this.hashCodeNode = hashCodeNode;
|
||||
this.equalsNode = equalsNode;
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ public final class EnsoHashMapBuilder {
|
||||
* @param hashCodeNode Node that will be stored in the storage for invoking `hash_code` on keys.
|
||||
* @param equalsNode Node that will be stored in the storage for invoking `==` on keys.
|
||||
*/
|
||||
public static EnsoHashMapBuilder create(HashCodeAnyNode hashCodeNode, EqualsAnyNode equalsNode) {
|
||||
public static EnsoHashMapBuilder create(HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
||||
return new EnsoHashMapBuilder(hashCodeNode, equalsNode);
|
||||
}
|
||||
|
||||
@ -164,13 +164,13 @@ public final class EnsoHashMapBuilder {
|
||||
|
||||
/**
|
||||
* Custom {@link Equivalence} used for the {@link EconomicMap} that delegates {@code equals} to
|
||||
* {@link EqualsAnyNode} and {@code hash_code} to {@link HashCodeAnyNode}.
|
||||
* {@link EqualsNode} and {@code hash_code} to {@link HashCodeNode}.
|
||||
*/
|
||||
private static final class StorageStrategy extends Equivalence {
|
||||
private final EqualsAnyNode equalsNode;
|
||||
private final HashCodeAnyNode hashCodeNode;
|
||||
private final EqualsNode equalsNode;
|
||||
private final HashCodeNode hashCodeNode;
|
||||
|
||||
private StorageStrategy(EqualsAnyNode equalsNode, HashCodeAnyNode hashCodeNode) {
|
||||
private StorageStrategy(EqualsNode equalsNode, HashCodeNode hashCodeNode) {
|
||||
this.equalsNode = equalsNode;
|
||||
this.hashCodeNode = hashCodeNode;
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Map",
|
||||
@ -61,8 +61,8 @@ public abstract class HashMapInsertNode extends Node {
|
||||
EnsoHashMap doForeign(Object foreignMap, Object keyToInsert, Object valueToInsert,
|
||||
@CachedLibrary("foreignMap") InteropLibrary mapInterop,
|
||||
@CachedLibrary(limit = "3") InteropLibrary iteratorInterop,
|
||||
@Cached HashCodeAnyNode hashCodeNode,
|
||||
@Cached EqualsAnyNode equalsNode) {
|
||||
@Cached HashCodeNode hashCodeNode,
|
||||
@Cached EqualsNode equalsNode) {
|
||||
var mapBuilder = EnsoHashMapBuilder.create(hashCodeNode, equalsNode);
|
||||
try {
|
||||
Object entriesIterator = mapInterop.getHashEntriesIterator(foreignMap);
|
||||
|
@ -11,8 +11,8 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
|
||||
@BuiltinMethod(
|
||||
@ -49,11 +49,11 @@ public abstract class HashMapRemoveNode extends Node {
|
||||
)
|
||||
EnsoHashMap removeFromInteropMap(Object map, Object keyToRemove,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop,
|
||||
@Cached HashCodeAnyNode hashCodeNode,
|
||||
@Cached EqualsAnyNode equalsNode) {
|
||||
@Cached HashCodeNode hashCodeNode,
|
||||
@Cached EqualsNode equalsNode) {
|
||||
// We cannot simply call interop.isHashEntryExisting, because it would, most likely
|
||||
// use the default `hashCode` and `equals` Java methods. But we need to use our
|
||||
// EqualsAnyNode, so we do the check for non-existing key inside the while loop.
|
||||
// EqualsNode, so we do the check for non-existing key inside the while loop.
|
||||
boolean keyToRemoveFound = false;
|
||||
var mapBuilder = EnsoHashMapBuilder.create(hashCodeNode, equalsNode);
|
||||
try {
|
||||
|
@ -1,16 +1,23 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.theories.DataPoints;
|
||||
import org.junit.experimental.theories.Theories;
|
||||
import org.junit.experimental.theories.Theory;
|
||||
@ -19,22 +26,18 @@ import org.junit.runner.RunWith;
|
||||
@RunWith(Theories.class)
|
||||
public class EqualsTest extends TestBase {
|
||||
private static Context context;
|
||||
private EqualsAnyNode equalsNode;
|
||||
private TestRootNode testRootNode;
|
||||
private static EqualsNode equalsNode;
|
||||
private static TestRootNode testRootNode;
|
||||
|
||||
@BeforeClass
|
||||
public static void initContextAndData() {
|
||||
context = createDefaultContext();
|
||||
unwrappedValues = fetchAllUnwrappedValues();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void initNodes() {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
testRootNode = new TestRootNode();
|
||||
equalsNode = EqualsAnyNode.build();
|
||||
equalsNode = EqualsNode.build();
|
||||
testRootNode.insertChildren(equalsNode);
|
||||
return null;
|
||||
});
|
||||
@ -101,4 +104,75 @@ public class EqualsTest extends TestBase {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/** Test for some specific values, for which we know that they are equal. */
|
||||
@Test
|
||||
public void testDateEquality() {
|
||||
Object ensoDate =
|
||||
unwrapValue(
|
||||
context,
|
||||
createValue(
|
||||
context, "(Date.new 1999 3 23)", "import Standard.Base.Data.Time.Date.Date"));
|
||||
Object javaDate = unwrapValue(context, context.asValue(LocalDate.of(1999, 3, 23)));
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoDate, javaDate));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimeEquality() {
|
||||
Object ensoTime =
|
||||
unwrapValue(
|
||||
context,
|
||||
createValue(
|
||||
context,
|
||||
"Time_Of_Day.new 23 59",
|
||||
"import Standard.Base.Data.Time.Time_Of_Day.Time_Of_Day"));
|
||||
Object javaDate = unwrapValue(context, context.asValue(LocalTime.of(23, 59)));
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoTime, javaDate));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDateTimeEquality() {
|
||||
Object ensoDateTime =
|
||||
unwrapValue(
|
||||
context,
|
||||
createValue(
|
||||
context,
|
||||
"(Date_Time.new 1999 3 1 23 59)",
|
||||
"import Standard.Base.Data.Time.Date_Time.Date_Time"));
|
||||
Object javaDateTime =
|
||||
unwrapValue(
|
||||
context,
|
||||
context.asValue(
|
||||
ZonedDateTime.of(
|
||||
LocalDate.of(1999, 3, 1), LocalTime.of(23, 59), ZoneId.systemDefault())));
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoDateTime, javaDateTime));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVectorsEquality() {
|
||||
Object ensoVector =
|
||||
unwrapValue(context, createValue(context, "[1,2,3]", "from Standard.Base.import all"));
|
||||
Object javaVector = unwrapValue(context, context.asValue(List.of(1, 2, 3)));
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoVector, javaVector));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeAnyNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.junit.AfterClass;
|
||||
@ -24,22 +24,18 @@ public class HashCodeTest extends TestBase {
|
||||
private static Context context;
|
||||
private static final InteropLibrary interop = InteropLibrary.getUncached();
|
||||
|
||||
private HashCodeAnyNode hashCodeNode;
|
||||
private EqualsAnyNode equalsNode;
|
||||
private TestRootNode testRootNode;
|
||||
private static HashCodeNode hashCodeNode;
|
||||
private static EqualsNode equalsNode;
|
||||
private static TestRootNode testRootNode;
|
||||
|
||||
@BeforeClass
|
||||
public static void initContextAndData() {
|
||||
context = createDefaultContext();
|
||||
// Initialize datapoints here, to make sure that it is initialized just once.
|
||||
unwrappedValues = fetchAllUnwrappedValues();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void initNodes() {
|
||||
executeInContext(context, () -> {
|
||||
hashCodeNode = HashCodeAnyNode.build();
|
||||
equalsNode = EqualsAnyNode.build();
|
||||
hashCodeNode = HashCodeNode.build();
|
||||
equalsNode = EqualsNode.build();
|
||||
testRootNode = new TestRootNode();
|
||||
testRootNode.insertChildren(hashCodeNode, equalsNode);
|
||||
return null;
|
||||
|
@ -14,6 +14,7 @@ import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.polyglot.MethodNames.Module;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Language;
|
||||
@ -24,7 +25,7 @@ import org.graalvm.polyglot.proxy.ProxyExecutable;
|
||||
public abstract class TestBase {
|
||||
protected static Context createDefaultContext() {
|
||||
var context =
|
||||
Context.newBuilder("enso")
|
||||
Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(true)
|
||||
.allowAllAccess(true)
|
||||
@ -63,7 +64,9 @@ public abstract class TestBase {
|
||||
|
||||
/**
|
||||
* Unwraps the `receiver` field from the Value. This is a hack to allow us to test execute methods
|
||||
* of artificially created ASTs, e.g., single nodes.
|
||||
* of artificially created ASTs, e.g., single nodes. More specifically, only unwrapped values are
|
||||
* eligible to be passed to node's execute methods, we cannot pass {@link Value} directly to the
|
||||
* node's execute methods.
|
||||
*
|
||||
* <p>Does something similar to what {@link
|
||||
* com.oracle.truffle.tck.DebuggerTester#getSourceImpl(Source)} does, but uses a different hack
|
||||
@ -77,6 +80,42 @@ public abstract class TestBase {
|
||||
return unwrapper.args[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Enso value from the given source.
|
||||
*
|
||||
* @param src One-line assignment into a variable
|
||||
* @param imports Imports, may be empty.
|
||||
*/
|
||||
protected static Value createValue(Context ctx, String src, String imports) {
|
||||
if (src.lines().count() > 1 || imports == null) {
|
||||
throw new IllegalArgumentException("src should have one line, imports must not be null");
|
||||
}
|
||||
var sb = new StringBuilder();
|
||||
sb.append(imports);
|
||||
sb.append(System.lineSeparator());
|
||||
sb.append("my_var = ").append(src);
|
||||
sb.append(System.lineSeparator());
|
||||
Value tmpModule = ctx.eval("enso", sb.toString());
|
||||
return tmpModule.invokeMember(Module.EVAL_EXPRESSION, "my_var");
|
||||
}
|
||||
|
||||
protected static Value createValue(Context ctx, String src) {
|
||||
return createValue(ctx, src, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given source as if it was in an unnamed module.
|
||||
*
|
||||
* @param src The source code of the module
|
||||
* @return The value returned from the main method of the unnamed module.
|
||||
*/
|
||||
protected static Value evalModule(Context ctx, String src) {
|
||||
Value module = ctx.eval(Source.create("enso", src));
|
||||
Value assocType = module.invokeMember(Module.GET_ASSOCIATED_TYPE);
|
||||
Value mainMethod = module.invokeMember(Module.GET_METHOD, assocType, "main");
|
||||
return mainMethod.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -10,6 +10,7 @@ import java.time.LocalTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
@ -324,8 +325,9 @@ class ValuesGenerator {
|
||||
var collect = new ArrayList<Value>();
|
||||
if (languages.contains(Language.ENSO)) {
|
||||
collect.add(v(null, "import Standard.Base.Data.Time.Date.Date", "Date.now").type());
|
||||
collect.add(v(null, "import Standard.Base.Data.Time.Date.Date", "Date.new 1999 3 23").type());
|
||||
collect.add(v(null, "import Standard.Base.Data.Time.Date_Time.Date_Time", "Date_Time.now").type());
|
||||
collect.add(v(null, "import Standard.Base.Data.Time.Time_Zone.Time_Zone", "Time_Zone.new").type());
|
||||
collect.add(v(null, "import Standard.Base.Data.Time.Date_Time.Date_Time", "Date_Time.parse '2021-01-01T00:30:12.7102[UTC]'").type());
|
||||
collect.add(v(null, "import Standard.Base.Data.Time.Time_Of_Day.Time_Of_Day", "Time_Of_Day.now").type());
|
||||
collect.add(v(null, "import Standard.Base.Data.Time.Duration.Duration", "Duration.new").type());
|
||||
for (var v : collect) {
|
||||
@ -338,7 +340,9 @@ class ValuesGenerator {
|
||||
|
||||
if (languages.contains(Language.JAVA)) {
|
||||
collect.add(ctx.asValue(LocalDate.of(2022, 12, 10)));
|
||||
collect.add(ctx.asValue(LocalDate.of(1999, 3, 23)));
|
||||
collect.add(ctx.asValue(LocalTime.of(12, 35)));
|
||||
collect.add(ctx.asValue(ZonedDateTime.of(2021, 1, 1, 0, 30, 12, 710200000, ZoneId.of("Z"))));
|
||||
}
|
||||
|
||||
return collect;
|
||||
|
@ -55,11 +55,12 @@ class CodeLocationsTest extends InterpreterTest {
|
||||
withLocationsInstrumenter { instrumenter =>
|
||||
val code =
|
||||
"""import Standard.Base.Data.List.List
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main = (2-2 == 0).if_then_else (List.Cons 5 6) 0
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
instrumenter.assertNodeExists(44, 41, classOf[ApplicationNode])
|
||||
instrumenter.assertNodeExists(69, 13, classOf[ApplicationNode])
|
||||
instrumenter.assertNodeExists(73, 41, classOf[ApplicationNode])
|
||||
instrumenter.assertNodeExists(98, 13, classOf[ApplicationNode])
|
||||
eval(code)
|
||||
()
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ class ConstructorsTest extends InterpreterTest {
|
||||
"work with recursion" in {
|
||||
val testCode =
|
||||
"""import Standard.Base.Data.List.List
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| genList = i -> if i == 0 then List.Nil else List.Cons i (genList (i - 1))
|
||||
@ -86,6 +87,7 @@ class ConstructorsTest extends InterpreterTest {
|
||||
"be usable in code, with arbitrary definition order" in {
|
||||
val testCode =
|
||||
"""import Standard.Base.Nothing.Nothing
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|type C2
|
||||
| Cons2 a b
|
||||
|
@ -1,29 +0,0 @@
|
||||
package org.enso.interpreter.test.semantic
|
||||
|
||||
import org.enso.interpreter.test.{InterpreterContext, InterpreterTest}
|
||||
|
||||
class ConversionMethodsTest extends InterpreterTest {
|
||||
override def subject: String = "Methods"
|
||||
|
||||
override def specify(implicit
|
||||
interpreterContext: InterpreterContext
|
||||
): Unit = {
|
||||
"be defined in the global scope and dispatched to" in {
|
||||
val code =
|
||||
"""
|
||||
|type Foo
|
||||
| Mk_Foo foo
|
||||
|type Bar
|
||||
| Mk_Bar bar
|
||||
|type Baz
|
||||
| Mk_Baz baz
|
||||
|
|
||||
|Foo.from (that:Bar) = Foo.Mk_Foo that.bar
|
||||
|Foo.from (that:Baz) = Foo.Mk_Foo that.baz
|
||||
|
|
||||
|main = (Foo.from (Baz.Mk_Baz 10)).foo + (Foo.from (Bar.Mk_Bar 20)).foo
|
||||
|""".stripMargin
|
||||
eval(code) shouldEqual 30
|
||||
}
|
||||
}
|
||||
}
|
@ -70,6 +70,7 @@ class EvalTest extends InterpreterTest {
|
||||
"work in a recursive setting" in {
|
||||
val code =
|
||||
"""import Standard.Base.Runtime.Debug
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| fn = sumTo ->
|
||||
@ -84,6 +85,7 @@ class EvalTest extends InterpreterTest {
|
||||
"work inside a thunk passed to another function" in {
|
||||
val code =
|
||||
"""import Standard.Base.Runtime.Debug
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| fn = sumTo ->
|
||||
|
@ -59,6 +59,7 @@ class GlobalScopeTest extends InterpreterTest {
|
||||
"be able to mutually recurse in the global scope" in {
|
||||
val code =
|
||||
"""import Standard.Base.Nothing
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|Nothing.decrementCall = number ->
|
||||
| res = number - 1
|
||||
|
@ -32,6 +32,8 @@ class GroupingTest extends InterpreterTest {
|
||||
"work with forced terms and lazy arguments" in {
|
||||
val code =
|
||||
"""
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| ifTest = c -> (~ifT) -> ~ifF -> if c == 0 then ifT else ifF
|
||||
| sum = c -> acc -> ifTest c acc (@Tail_Call sum c-1 acc+c)
|
||||
|
@ -51,6 +51,7 @@ class LambdaShorthandArgsTest extends InterpreterTest {
|
||||
"work with mixfix functions" in {
|
||||
val code =
|
||||
"""from Standard.Base.Data.Numbers import all
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|Number.if_then_else self = ~t -> ~f -> if self == 0 then t else f
|
||||
|
|
||||
|
@ -43,6 +43,7 @@ class LambdaTest extends InterpreterTest {
|
||||
"work with recursion" in {
|
||||
val code =
|
||||
"""
|
||||
|import Standard.Base.Any.Any
|
||||
|main =
|
||||
| sumTo = x -> if x == 0 then 0 else x + (sumTo (x-1))
|
||||
| sumTo 10
|
||||
@ -105,6 +106,7 @@ class LambdaTest extends InterpreterTest {
|
||||
"recurse with closing over lexical scope" in {
|
||||
val code =
|
||||
"""
|
||||
|import Standard.Base.Any.Any
|
||||
|main =
|
||||
| summator = current ->
|
||||
| if current == 0 then 0 else (x -> summator (current - 1)) 0
|
||||
@ -135,6 +137,7 @@ class LambdaTest extends InterpreterTest {
|
||||
"call fully saturated lambdas returned with TCO" in {
|
||||
val code =
|
||||
"""from Standard.Base.Data.Numbers import Number
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|Number.if_then_else self = ~t -> ~f -> if self == 0 then t else f
|
||||
|
|
||||
|
@ -128,6 +128,7 @@ class NamedArgumentsTest extends InterpreterTest {
|
||||
"work in a recursive context" in {
|
||||
val code =
|
||||
"""import Standard.Base.Nothing
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|Nothing.summer = sumTo ->
|
||||
| summator = (acc = 0) -> current ->
|
||||
@ -194,6 +195,8 @@ class NamedArgumentsTest extends InterpreterTest {
|
||||
"be usable with constructors" in {
|
||||
val code =
|
||||
"""
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|type C2
|
||||
| Cons2 head rest
|
||||
|type Nil2
|
||||
@ -214,6 +217,8 @@ class NamedArgumentsTest extends InterpreterTest {
|
||||
"be usable and overridable in constructors" in {
|
||||
val code =
|
||||
"""
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|type Nil2
|
||||
|type C2
|
||||
| Cons2 head (rest = Nil2)
|
||||
|
@ -24,6 +24,7 @@ class SuspendedArgumentsTest extends InterpreterTest {
|
||||
"not get executed upfront" in {
|
||||
val code =
|
||||
"""import Standard.Base.IO
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| foo = i -> ~x -> ~y -> if i == 0 then x else y
|
||||
@ -36,6 +37,8 @@ class SuspendedArgumentsTest extends InterpreterTest {
|
||||
"work well with tail recursion" in {
|
||||
val code =
|
||||
"""
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| ifTest = c -> ~ifT -> ~ifF -> if c == 0 then ifT else ifF
|
||||
| sum = c -> acc -> ifTest c acc (@Tail_Call sum c-1 acc+c)
|
||||
@ -78,6 +81,7 @@ class SuspendedArgumentsTest extends InterpreterTest {
|
||||
"work properly with oversaturated arguments" in {
|
||||
val code =
|
||||
"""import Standard.Base.IO
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| ifTest = c -> ~ifT -> ~ifF -> if c == 0 then ifT else ifF
|
||||
|
@ -26,6 +26,7 @@ class BooleanTest extends InterpreterTest {
|
||||
val code =
|
||||
"""from Standard.Base.Data.Boolean import all
|
||||
|import Standard.Base.IO
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|Boolean.isTrue self = self
|
||||
|
|
||||
|
@ -11,6 +11,7 @@ class NumberTest extends InterpreterTest {
|
||||
"support equality comparisons" in {
|
||||
val code =
|
||||
"""import Standard.Base.IO
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main =
|
||||
| IO.println 7==5
|
||||
@ -23,6 +24,8 @@ class NumberTest extends InterpreterTest {
|
||||
"support a recursive sum case" in {
|
||||
val code =
|
||||
"""
|
||||
|import Standard.Base.Any.Any
|
||||
|
|
||||
|main = sumTo ->
|
||||
| summator = acc -> current ->
|
||||
| if current == 0 then acc else summator acc+current current-1
|
||||
|
@ -7,8 +7,18 @@ from Standard.Test import Bench
|
||||
type My
|
||||
Data x
|
||||
|
||||
compare_to self other =
|
||||
other.x . compare_to self.x
|
||||
type My_Comparator
|
||||
is_ordered = True
|
||||
|
||||
compare my_1 my_2 =
|
||||
comparator = Comparable.from my_2.x
|
||||
comparator.compare my_2.x my_1.x
|
||||
|
||||
hash my =
|
||||
comparator = Comparable.from my.x
|
||||
comparator.hash my.x
|
||||
|
||||
Comparable.from (_:My) = My_Comparator
|
||||
|
||||
vector_size = 100000
|
||||
iter_size = 20
|
||||
|
@ -9,7 +9,7 @@ compare_all_adjacent text_vector =
|
||||
res
|
||||
|
||||
bench =
|
||||
## The `Text.compare_to` benchmarks check both scenarios where the Texts are
|
||||
## The `Text` compare benchmarks check both scenarios where the Texts are
|
||||
short and very long - both checking the case where the difference appears
|
||||
early or late in the long string. This is to see well any overheads
|
||||
related to preprocessing the whole string and any possible early
|
||||
|
@ -57,7 +57,7 @@ bench =
|
||||
partially_sorted_vec = make_partially_sorted_vec vector_size
|
||||
random_vec = Utils.make_random_vec vector_size
|
||||
projection = x -> x % 10
|
||||
comparator = l -> r -> r.compare_to l
|
||||
comparator = l -> r -> Ordering.compare l r
|
||||
|
||||
Bench.measure (sorted_vec.sort) "Already Sorted" iter_size num_iterations
|
||||
Bench.measure (sorted_vec.sort Sort_Direction.Descending) "Sorted in Opposite Order" iter_size num_iterations
|
||||
@ -66,6 +66,6 @@ bench =
|
||||
Bench.measure (random_vec.sort) "Random Elements Ascending" iter_size num_iterations
|
||||
Bench.measure (random_vec.sort Sort_Direction.Descending) "Random Elements Descending" iter_size num_iterations
|
||||
Bench.measure (random_vec.sort on=projection) "Sorting with a Custom Projection" iter_size num_iterations
|
||||
Bench.measure (random_vec.sort by=comparator) "Sorting with a Custom Comparison" iter_size num_iterations
|
||||
Bench.measure (random_vec.sort by=comparator) "Sorting with the Default_Ordered_Comparator" iter_size num_iterations
|
||||
|
||||
main = bench
|
||||
|
@ -15,12 +15,16 @@ from project.Common_Table_Operations.Util import expect_column_names, run_defaul
|
||||
type My_Type
|
||||
Value x y
|
||||
|
||||
compare_to self other = case other of
|
||||
My_Type.Value ox oy ->
|
||||
self.x+self.y . compare_to ox+oy
|
||||
_ -> Ordering.Less
|
||||
type My_Type_Comparator
|
||||
is_ordered = True
|
||||
|
||||
compare my_1 my_2 =
|
||||
comp = Comparable.from my_1.x
|
||||
comp.compare (my_1.x + my_1.y) (my_2.x + my_2.y)
|
||||
|
||||
hash my_type = Comparable.from my_type.x . hash (my_type.x + my_type.y)
|
||||
|
||||
== self other = self.compare_to other == Ordering.Equal
|
||||
Comparable.from (_:My_Type) = My_Type_Comparator
|
||||
|
||||
main = run_default_backend spec
|
||||
|
||||
|
@ -26,15 +26,23 @@ polyglot java import org.enso.table.data.table.Table as Java_Table
|
||||
type My
|
||||
Data x y
|
||||
|
||||
== self that = case that of
|
||||
My.Data x1 y1 -> (self.x + self.y) == (x1 + y1)
|
||||
_ -> False
|
||||
|
||||
compare_to self that = self.x+self.y . compare_to that.x+that.y
|
||||
|
||||
frobnicate self = case self of
|
||||
My.Data x1 y1 -> My.Data y1 x1
|
||||
|
||||
type My_Comparator
|
||||
is_ordered = True
|
||||
|
||||
compare left right =
|
||||
left_sum = left.x + left.y
|
||||
right_sum = right.x + right.y
|
||||
(Comparable.from left_sum) . compare left_sum right_sum
|
||||
|
||||
hash my =
|
||||
sum = my.x + my.y
|
||||
Comparable.from sum . hash sum
|
||||
|
||||
Comparable.from (_:My) = My_Comparator
|
||||
|
||||
spec =
|
||||
make_varied_type_table =
|
||||
strs = ["strs", ["a", "b", "c", Nothing]]
|
||||
@ -462,7 +470,7 @@ spec =
|
||||
|
||||
Test.specify 'should allow passing a custom comparator' <|
|
||||
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 = (a.x-a.y).abs . compare_to (b.x-b.y).abs
|
||||
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]
|
||||
|
||||
|
@ -18,10 +18,10 @@ spec =
|
||||
False.to_text . should_equal "False"
|
||||
|
||||
Test.specify "should allow for comparing Bools" <|
|
||||
True.compare_to True . should_equal Ordering.Equal
|
||||
False.compare_to False . should_equal Ordering.Equal
|
||||
True.compare_to False . should_equal Ordering.Greater
|
||||
False.compare_to True . should_equal Ordering.Less
|
||||
(True == True) . should_be_true
|
||||
(False == False) . should_be_true
|
||||
(True > False) . should_be_true
|
||||
(False < True) . should_be_true
|
||||
|
||||
Test.specify "should allow == operator" <|
|
||||
True.should_equal True
|
||||
|
@ -3,6 +3,7 @@ import Standard.Base.Error.Illegal_Argument.Illegal_Argument
|
||||
import Standard.Base.Error.No_Such_Key.No_Such_Key
|
||||
import Standard.Base.Data.Time.Date_Time.Date_Time
|
||||
from Standard.Base.Data.Map import Map
|
||||
from Standard.Base.Data.Ordering import Default_Unordered_Comparator
|
||||
|
||||
from Standard.Test import Test, Test_Suite, Problems
|
||||
import Standard.Test.Extensions
|
||||
@ -75,6 +76,11 @@ spec =
|
||||
empty_map.is_empty . should_be_true
|
||||
non_empty.is_empty . should_be_false
|
||||
|
||||
Test.specify "should get default unordered comparator for polyglot maps" <|
|
||||
Comparable.from (Map.empty) . should_equal Default_Unordered_Comparator
|
||||
Comparable.from (js_empty_dict) . should_equal Default_Unordered_Comparator
|
||||
Comparable.from (JavaMap.of "A" 1 "B" 2) . should_equal Default_Unordered_Comparator
|
||||
|
||||
Test.specify "should compare two hash maps" <|
|
||||
(Map.singleton "a" 1).should_equal (Map.singleton "a" 1)
|
||||
(Map.singleton "b" 2).should_not_equal (Map.singleton "a" 1)
|
||||
@ -463,7 +469,7 @@ spec =
|
||||
map.to_vector.should_equal [["A", 1], ["B", 2], ["C", 3]]
|
||||
|
||||
Test.specify "should treat Java Map as Enso map" <|
|
||||
sort_by_keys vec = vec.sort by=x-> y-> x.first.compare_to y.first
|
||||
sort_by_keys vec = vec.sort by=x-> y-> Ordering.compare x.first y.first
|
||||
jmap = JavaMap.of "A" 1 "B" 2
|
||||
(sort_by_keys jmap.to_vector) . should_equal [["A", 1], ["B", 2]]
|
||||
(sort_by_keys (jmap.insert "C" 3 . to_vector)) . should_equal [["A", 1], ["B", 2], ["C", 3]]
|
||||
|
@ -385,29 +385,29 @@ spec =
|
||||
(very_negative >= hundred_factorial).should_be_false
|
||||
(very_negative >= Nothing).should_fail_with Type_Error.Error
|
||||
|
||||
Test.specify "should support compare_to" <|
|
||||
(1.compare_to 2).should_equal Ordering.Less
|
||||
(1.compare_to 1).should_equal Ordering.Equal
|
||||
(1.compare_to 0).should_equal Ordering.Greater
|
||||
(1.compare_to 1.01).should_equal Ordering.Less
|
||||
(1.compare_to 0.99).should_equal Ordering.Greater
|
||||
(3.compare_to hundred_factorial).should_equal Ordering.Less
|
||||
(3.compare_to very_negative).should_equal Ordering.Greater
|
||||
(3.compare_to Nothing).should_fail_with Type_Error.Error
|
||||
(1.01.compare_to 0.99).should_equal Ordering.Greater
|
||||
(1.01.compare_to 1.02).should_equal Ordering.Less
|
||||
(1.01.compare_to 1).should_equal Ordering.Greater
|
||||
(1.01.compare_to 2).should_equal Ordering.Less
|
||||
(3.14.compare_to hundred_factorial).should_equal Ordering.Less
|
||||
(3.14.compare_to very_negative).should_equal Ordering.Greater
|
||||
(1.5.compare_to Nothing).should_fail_with Type_Error.Error
|
||||
(hundred_factorial.compare_to 1).should_equal Ordering.Greater
|
||||
(hundred_factorial.compare_to 1.5).should_equal Ordering.Greater
|
||||
(very_negative.compare_to 1).should_equal Ordering.Less
|
||||
(very_negative.compare_to 1.5).should_equal Ordering.Less
|
||||
(hundred_factorial.compare_to very_negative).should_equal Ordering.Greater
|
||||
(very_negative.compare_to hundred_factorial).should_equal Ordering.Less
|
||||
(very_negative.compare_to Nothing).should_fail_with Type_Error.Error
|
||||
Test.specify "should be ordered by Default_Ordered_Comparator" <|
|
||||
Ordering.compare 1 2 . should_equal Ordering.Less
|
||||
Ordering.compare 1 1 . should_equal Ordering.Equal
|
||||
Ordering.compare 1 0 . should_equal Ordering.Greater
|
||||
Ordering.compare 1 1.01 . should_equal Ordering.Less
|
||||
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.Error
|
||||
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.Error
|
||||
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.Error
|
||||
|
||||
Test.specify "should expose exponentiation operations" <|
|
||||
(3.14 ^ 2.71).should_equal 22.216689546 epsilon=eps
|
||||
|
@ -13,12 +13,19 @@ import Standard.Test.Extensions
|
||||
type Ord
|
||||
Value number
|
||||
|
||||
compare_to : Ord -> Ordering
|
||||
compare_to self that = that.number.compare_to self.number
|
||||
# The comparison is reverted, i.e., `x < y` gives result for `y.number < x.number`.
|
||||
type Ord_Comparator
|
||||
is_ordered = True
|
||||
compare x y = (Comparable.from y.number) . compare y.number x.number
|
||||
hash x = (Comparable.from x.number) . hash x.number
|
||||
|
||||
Comparable.from (_:Ord) = Ord_Comparator
|
||||
|
||||
type No_Ord
|
||||
Value number
|
||||
|
||||
Comparable.from (_:No_Ord) = Incomparable
|
||||
|
||||
# Tests
|
||||
|
||||
spec = Test.group "Object Comparator" <|
|
||||
|
@ -37,7 +37,7 @@ spec = Test.group "Natural Order" <|
|
||||
Natural_Order.compare "100-200.300" "1.2.3" . should_equal Ordering.Greater
|
||||
Natural_Order.compare "1.2.3" "4.5.6" . should_equal Ordering.Less
|
||||
|
||||
".".compare_to "-" . should_equal Ordering.Greater
|
||||
Ordering.compare "." "-" . should_equal Ordering.Greater
|
||||
Natural_Order.compare "4-5-6" "4.5.6" . should_equal Ordering.Less
|
||||
Natural_Order.compare "4-5-6" "100-200.300" . should_equal Ordering.Less
|
||||
|
||||
|
@ -16,7 +16,7 @@ spec = Test.group "Lexicographic Order on Vectors" <|
|
||||
Vector_Lexicographic_Order.compare [1] [1] . should_equal Ordering.Equal
|
||||
|
||||
Test.specify "should work correctly with a custom comparator" <|
|
||||
comparator = x-> y-> x.a.compare_to y.a
|
||||
comparator = x-> y-> Ordering.compare x.a y.a
|
||||
Vector_Lexicographic_Order.compare [My_Type.Value "a" 1, My_Type.Value "b" 1, My_Type.Value "c" 1] [My_Type.Value "b" 1, My_Type.Value "a" 1, My_Type.Value "c" 1] element_comparator=comparator . should_equal Ordering.Less
|
||||
Vector_Lexicographic_Order.compare [My_Type.Value "a" 1, My_Type.Value "b" 1, My_Type.Value "c" 1] [My_Type.Value "a" 100, My_Type.Value "b" 2, My_Type.Value "c" 3] element_comparator=comparator . should_equal Ordering.Equal
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Error.Common.Type_Error
|
||||
import Standard.Base.Error.Incomparable_Values.Incomparable_Values
|
||||
|
||||
from Standard.Test import Test, Test_Suite
|
||||
import Standard.Test.Extensions
|
||||
@ -9,30 +10,56 @@ import Standard.Test.Extensions
|
||||
type Ord
|
||||
Value number
|
||||
|
||||
compare_to : Ord -> Ordering
|
||||
compare_to self that = if self.number == that.number then Ordering.Equal else
|
||||
if self.number > that.number then Ordering.Greater else Ordering.Less
|
||||
type Ord_Comparator
|
||||
is_ordered = True
|
||||
compare x y = (Comparable.from x.number) . compare x.number y.number
|
||||
hash x = (Comparable.from x.number) . hash x.number
|
||||
|
||||
Comparable.from (_:Ord) = Ord_Comparator
|
||||
|
||||
## Unordered pair
|
||||
type UPair
|
||||
Value x y
|
||||
|
||||
type UPair_Comparator
|
||||
is_ordered = False
|
||||
|
||||
equals pair1 pair2 =
|
||||
if pair1.x == pair2.x && pair1.y == pair2.y then True else
|
||||
if pair1.x == pair2.y && pair1.y == pair2.x then True else
|
||||
False
|
||||
|
||||
hash upair =
|
||||
x_comp = Comparable.from upair.x
|
||||
y_comp = Comparable.from upair.y
|
||||
(x_comp.hash upair.x) + (y_comp.hash upair.y)
|
||||
|
||||
Comparable.from (_ : UPair) = UPair_Comparator
|
||||
|
||||
type Parent
|
||||
Value child
|
||||
|
||||
# === The Tests ===
|
||||
|
||||
spec =
|
||||
Test.group "Default ordered comparator" <|
|
||||
Test.specify "should support custom ordered comparator" <|
|
||||
Ordering.compare (Ord.Value 1) (Ord.Value 2) . should_equal Ordering.Less
|
||||
Ordering.compare (Ord.Value 1) (Ord.Value 1) . should_equal Ordering.Equal
|
||||
Ordering.compare (Ord.Value 20) (Ord.Value 1) . should_equal Ordering.Greater
|
||||
|
||||
Test.specify "should support equality for custom ordered comparators in atom field" <|
|
||||
((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 Incomparable_Values when comparing types with unordered comparator" <|
|
||||
Ordering.compare (UPair.Value 1 2) (UPair.Value 2 1) . should_fail_with Incomparable_Values
|
||||
|
||||
Test.specify "should throw Type_Error when comparing different types" <|
|
||||
Ordering.compare (UPair.Value 1 2) (Ord.Value 2) . should_fail_with Type_Error.Error
|
||||
Ordering.compare 1 Nothing . should_fail_with Type_Error.Error
|
||||
|
||||
Test.group "Ordering" <|
|
||||
|
||||
Test.specify "should allow comparing Less" <|
|
||||
left = Ord.Value 1032
|
||||
right = Ord.Value 101111
|
||||
left.compare_to right . should_equal Ordering.Less
|
||||
|
||||
Test.specify "should allow comparing Equal" <|
|
||||
left = Ord.Value 1032
|
||||
right = Ord.Value 1032
|
||||
left.compare_to right . should_equal Ordering.Equal
|
||||
|
||||
Test.specify "should allow comparing Greater" <|
|
||||
left = Ord.Value 1032
|
||||
right = Ord.Value -1
|
||||
left.compare_to right . should_equal Ordering.Greater
|
||||
|
||||
Test.specify "should allow conversion to sign representation" <|
|
||||
Ordering.Less.to_sign . should_equal -1
|
||||
Ordering.Equal.to_sign . should_equal 0
|
||||
@ -44,15 +71,15 @@ spec =
|
||||
Ordering.from_sign 1 . should_equal Ordering.Greater
|
||||
|
||||
Test.specify "should be ordered itself" <|
|
||||
Ordering.Less.compare_to Ordering.Less . should_equal Ordering.Equal
|
||||
Ordering.Less.compare_to Ordering.Equal . should_equal Ordering.Less
|
||||
Ordering.Less.compare_to Ordering.Greater . should_equal Ordering.Less
|
||||
Ordering.Equal.compare_to Ordering.Less . should_equal Ordering.Greater
|
||||
Ordering.Equal.compare_to Ordering.Equal . should_equal Ordering.Equal
|
||||
Ordering.Equal.compare_to Ordering.Greater . should_equal Ordering.Less
|
||||
Ordering.Greater.compare_to Ordering.Less . should_equal Ordering.Greater
|
||||
Ordering.Greater.compare_to Ordering.Equal . should_equal Ordering.Greater
|
||||
Ordering.Greater.compare_to Ordering.Greater . should_equal Ordering.Equal
|
||||
Ordering.compare Ordering.Less Ordering.Less . should_equal Ordering.Equal
|
||||
Ordering.compare Ordering.Less Ordering.Equal . should_equal Ordering.Less
|
||||
Ordering.compare Ordering.Less Ordering.Greater . should_equal Ordering.Less
|
||||
Ordering.compare Ordering.Equal Ordering.Less . should_equal Ordering.Greater
|
||||
Ordering.compare Ordering.Equal Ordering.Equal . should_equal Ordering.Equal
|
||||
Ordering.compare Ordering.Equal Ordering.Greater . should_equal Ordering.Less
|
||||
Ordering.compare Ordering.Greater Ordering.Less . should_equal Ordering.Greater
|
||||
Ordering.compare Ordering.Greater Ordering.Equal . should_equal Ordering.Greater
|
||||
Ordering.compare Ordering.Greater Ordering.Greater . should_equal Ordering.Equal
|
||||
|
||||
Test.specify "should allow lexicographical composition" <|
|
||||
Ordering.Less.and_then Ordering.Less . should_equal Ordering.Less
|
||||
@ -66,8 +93,8 @@ spec =
|
||||
Ordering.Greater.and_then Ordering.Greater . should_equal Ordering.Greater
|
||||
|
||||
Test.specify "should fail with Type_Error for wrong type of that" <|
|
||||
Ordering.Less.compare_to 1 . should_fail_with Type_Error.Error
|
||||
Ordering.Less.compare_to Nothing . should_fail_with Type_Error.Error
|
||||
Ordering.Less.compare_to "Hello" . should_fail_with Type_Error.Error
|
||||
Ordering.compare Ordering.Less 1 . should_fail_with Type_Error.Error
|
||||
Ordering.compare Ordering.Less Nothing . should_fail_with Type_Error.Error
|
||||
Ordering.compare Ordering.Less "Hello" . should_fail_with Type_Error.Error
|
||||
|
||||
main = Test_Suite.run_main spec
|
||||
|
@ -11,12 +11,18 @@ import Standard.Test.Extensions
|
||||
type Ord
|
||||
Value number
|
||||
|
||||
Ord.compare_to : Ord -> Ordering
|
||||
Ord.compare_to self that = that.number.compare_to self.number
|
||||
type Ord_Comparator
|
||||
is_ordered = True
|
||||
compare x y = (Comparable.from x.number) . compare x.number y.number
|
||||
hash x = (Comparable.from x.number) . hash x.number
|
||||
|
||||
Comparable.from (_:Ord) = Ord_Comparator
|
||||
|
||||
type No_Ord
|
||||
Value number
|
||||
|
||||
Comparable.from (_:No_Ord) = Incomparable
|
||||
|
||||
# Tests
|
||||
|
||||
spec =
|
||||
@ -155,12 +161,12 @@ spec =
|
||||
text_set.compute Statistic.Kurtosis . should_fail_with Illegal_Argument.Error
|
||||
text_set.running Statistic.Sum . should_fail_with Illegal_Argument.Error
|
||||
|
||||
Test.specify "should be able to do Count, Minimum and Maximum on custom type with compare_to" <|
|
||||
Test.specify "should be able to do Count, Minimum and Maximum on custom type with custom ordered comparator" <|
|
||||
ord_set.compute . should_equal 3
|
||||
ord_set.compute Statistic.Minimum . should_equal (Ord.Value 10)
|
||||
ord_set.compute Statistic.Maximum . should_equal (Ord.Value 2)
|
||||
ord_set.compute Statistic.Minimum . should_equal (Ord.Value 2)
|
||||
ord_set.compute Statistic.Maximum . should_equal (Ord.Value 10)
|
||||
|
||||
Test.specify "should fail with Incomparable_Values on custom type without compare_to" <|
|
||||
Test.specify "should fail with Incomparable_Values on custom incomparable type" <|
|
||||
no_ord_set.compute . should_equal 3
|
||||
no_ord_set.compute Statistic.Minimum . should_fail_with Incomparable_Values
|
||||
no_ord_set.compute Statistic.Maximum . should_fail_with Incomparable_Values
|
||||
@ -189,7 +195,7 @@ spec =
|
||||
values = ["G", "AA", "B", "G", "D"]
|
||||
Statistic.rank_data values . should_equal [1.5, 5, 4, 1.5, 3]
|
||||
|
||||
Test.specify "should fail with Incomparable_Values on custom type without compare_to" <|
|
||||
Test.specify "should fail with Incomparable_Values on custom incomparable type" <|
|
||||
values = [No_Ord.Value 10, No_Ord.Value 2, No_Ord.Value 9]
|
||||
Statistic.rank_data values . should_fail_with Incomparable_Values
|
||||
|
||||
|
@ -91,39 +91,39 @@ spec =
|
||||
complex_letter_1 . should_equal complex_letter_2
|
||||
complex_letter_1 . should_equal complex_letter_3
|
||||
complex_letter_3 . should_equal complex_letter_2
|
||||
common_prefix+complex_letter_1+complex_letter_2+complex_letter_3 . compare_to common_prefix+complex_letter_3+complex_letter_1+complex_letter_2 . should_equal Ordering.Equal
|
||||
Ordering.compare (common_prefix+complex_letter_1+complex_letter_2+complex_letter_3) (common_prefix+complex_letter_3+complex_letter_1+complex_letter_2) . should_equal Ordering.Equal
|
||||
|
||||
'e\u{301}'=='e\u{302}' . should_be_false
|
||||
|
||||
'a\u0321\u0302'=='a\u0302\u0321' . should_be_true
|
||||
'a\u0321\u0302'=='A\u0302\u0321' . should_be_false
|
||||
|
||||
accent_1+"a" . compare_to accent_2+"a" . should_equal Ordering.Equal
|
||||
accent_1+"A" . compare_to accent_2+"a" . should_equal Ordering.Less
|
||||
Ordering.compare accent_1+"a" accent_2+"a" . should_equal Ordering.Equal
|
||||
Ordering.compare accent_1+"A" accent_2+"a" . should_equal Ordering.Less
|
||||
accent_1+"A" . compare_to_ignore_case accent_2+"a" . should_equal Ordering.Equal
|
||||
accent_1+"a" . compare_to accent_2+"b" . should_equal Ordering.Less
|
||||
Ordering.compare accent_1+"a" accent_2+"b" . should_equal Ordering.Less
|
||||
accent_1+"a" . compare_to_ignore_case accent_2+"B" . should_equal Ordering.Less
|
||||
accent_2+"a" . compare_to accent_1+"b" . should_equal Ordering.Less
|
||||
accent_1+"a" . compare_to accent_2+"B" . should_equal Ordering.Greater
|
||||
Ordering.compare accent_2+"a" accent_1+"b" . should_equal Ordering.Less
|
||||
Ordering.compare accent_1+"a" accent_2+"B" . should_equal Ordering.Greater
|
||||
accent_1+"a" . compare_to_ignore_case accent_2+"B" . should_equal Ordering.Less
|
||||
accent_1+"b" . compare_to accent_2+"a" . should_equal Ordering.Greater
|
||||
accent_2+"b" . compare_to accent_1+"a" . should_equal Ordering.Greater
|
||||
Ordering.compare accent_1+"b" accent_2+"a" . should_equal Ordering.Greater
|
||||
Ordering.compare accent_2+"b" accent_1+"a" . should_equal Ordering.Greater
|
||||
|
||||
# Handling of Nothing
|
||||
(accent_1 == Nothing) . should_be_false
|
||||
(accent_1 != Nothing) . should_be_true
|
||||
accent_1 . compare_to Nothing . should_fail_with Type_Error.Error
|
||||
Ordering.compare accent_1 Nothing . should_fail_with Type_Error.Error
|
||||
(accent_1 > Nothing) . should_fail_with Type_Error.Error
|
||||
accent_1 . compare_to_ignore_case Nothing . should_fail_with Type_Error.Error
|
||||
|
||||
earlier_suffix = "aooooz"
|
||||
later_suffix = "bo"
|
||||
common_prefix+complex_letter_1+earlier_suffix . compare_to common_prefix+complex_letter_2+later_suffix . should_equal Ordering.Less
|
||||
common_prefix+complex_letter_2+earlier_suffix . compare_to common_prefix+complex_letter_1+later_suffix . should_equal Ordering.Less
|
||||
common_prefix+complex_letter_2+earlier_suffix . compare_to common_prefix+complex_letter_3+later_suffix . should_equal Ordering.Less
|
||||
common_prefix+complex_letter_3+earlier_suffix . compare_to common_prefix+complex_letter_1+later_suffix . should_equal Ordering.Less
|
||||
common_prefix+complex_letter_3+later_suffix . compare_to common_prefix+complex_letter_1+earlier_suffix . should_equal Ordering.Greater
|
||||
common_prefix+complex_letter_1+later_suffix . compare_to common_prefix+complex_letter_2+earlier_suffix . should_equal Ordering.Greater
|
||||
Ordering.compare common_prefix+complex_letter_1+earlier_suffix common_prefix+complex_letter_2+later_suffix . should_equal Ordering.Less
|
||||
Ordering.compare common_prefix+complex_letter_2+earlier_suffix common_prefix+complex_letter_1+later_suffix . should_equal Ordering.Less
|
||||
Ordering.compare common_prefix+complex_letter_2+earlier_suffix common_prefix+complex_letter_3+later_suffix . should_equal Ordering.Less
|
||||
Ordering.compare common_prefix+complex_letter_3+earlier_suffix common_prefix+complex_letter_1+later_suffix . should_equal Ordering.Less
|
||||
Ordering.compare common_prefix+complex_letter_3+later_suffix common_prefix+complex_letter_1+earlier_suffix . should_equal Ordering.Greater
|
||||
Ordering.compare common_prefix+complex_letter_1+later_suffix common_prefix+complex_letter_2+earlier_suffix . should_equal Ordering.Greater
|
||||
|
||||
Test.specify "should correctly handle case-insensitive equality" <|
|
||||
"aBc" . equals_ignore_case "Abc" . should_be_true
|
||||
|
@ -122,6 +122,9 @@ spec_with name create_new_date parse_date =
|
||||
result ->
|
||||
Test.fail ("Unexpected result: " + result.to_text)
|
||||
|
||||
Test.specify "should get default ordered comparator for dates" <|
|
||||
Comparable.from (create_new_date 2023 2 3) . should_equal Default_Ordered_Comparator
|
||||
|
||||
Test.specify "should be comparable" <|
|
||||
date_1 = parse_date "2021-01-02"
|
||||
date_2 = parse_date "2021-01-01"
|
||||
@ -132,7 +135,7 @@ spec_with name create_new_date parse_date =
|
||||
date_1<date_2 . should_be_false
|
||||
|
||||
datetime = Date_Time.new 2021 1 2 12 40
|
||||
date_1.compare_to datetime . should_fail_with Type_Error.Error
|
||||
Ordering.compare date_1 datetime . should_fail_with Type_Error.Error
|
||||
date_1<datetime . should_fail_with Type_Error.Error
|
||||
date_1>=datetime . should_fail_with Type_Error.Error
|
||||
date_1==datetime . should_be_false
|
||||
|
@ -314,6 +314,11 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
|
||||
time . nanosecond . should_equal 0
|
||||
time . zone . zone_id . should_equal Time_Zone.utc.zone_id
|
||||
|
||||
Test.specify "should get default ordered comparator for datetimes" <|
|
||||
Comparable.from (create_new_datetime 2023 2 3 23 59) . should_equal Default_Ordered_Comparator
|
||||
Comparable.from (parse_datetime "2021-01-01T00:30:12.7102[UTC]") . should_equal Default_Ordered_Comparator
|
||||
Comparable.from (create_new_datetime 2022 10 31 2 30 55 1234) . should_equal Default_Ordered_Comparator
|
||||
|
||||
Test.specify "should be comparable" <|
|
||||
time_1 = parse_datetime "2021-01-01T00:30:12.7102[UTC]"
|
||||
time_2 = parse_datetime "2021-01-01T04:00:10.0+04:00"
|
||||
@ -324,12 +329,12 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
|
||||
time_1<time_2 . should_be_false
|
||||
|
||||
tod = Time_Of_Day.new 4 0 10
|
||||
time_1.compare_to tod . should_fail_with Type_Error.Error
|
||||
Ordering.compare time_1 tod . should_fail_with Type_Error.Error
|
||||
time_1<tod . should_fail_with Type_Error.Error
|
||||
time_1==tod . should_be_false
|
||||
|
||||
date = Date.new 2021 1 1
|
||||
time_1.compare_to date . should_fail_with Type_Error.Error
|
||||
Ordering.compare time_1 date . should_fail_with Type_Error.Error
|
||||
time_1==date . should_be_false
|
||||
|
||||
Test.specify "simple computations before Enso epoch should produce a warning" <|
|
||||
|
@ -47,7 +47,7 @@ spec =
|
||||
Test.specify "should be comparable" <|
|
||||
duration_1 = (Duration.new hours=5)
|
||||
duration_2 = (Duration.new minutes=1)
|
||||
duration_1.compare_to duration_1 . should_equal Ordering.Equal
|
||||
Ordering.compare duration_1 duration_1 . should_equal Ordering.Equal
|
||||
duration_1==duration_1 . should_be_true
|
||||
duration_1!=duration_2 . should_be_true
|
||||
duration_1>duration_2 . should_be_true
|
||||
|
@ -116,8 +116,7 @@ specWith name create_new_time parse_time =
|
||||
time_1!=time_2 . should_be_true
|
||||
time_1>time_2 . should_be_true
|
||||
time_1<time_2 . should_be_false
|
||||
|
||||
time_2.compare_to (Date_Time.new 1999 1 1 4 0 10) . should_fail_with Type_Error.Error
|
||||
time_2<(Date_Time.new 1999 1 1 4 0 10) . should_fail_with Type_Error.Error
|
||||
|
||||
Test.specify "should correctly determine the type of timeofday" <|
|
||||
new_timeofday = create_new_time 15 37 58
|
||||
|
@ -16,10 +16,12 @@ import Standard.Test.Extensions
|
||||
type T
|
||||
Value a b
|
||||
|
||||
== self that = self.a == that.a
|
||||
type T_Comparator
|
||||
is_ordered = True
|
||||
compare t1 t2 = Comparable.from t1.a . compare t1.a t2.a
|
||||
hash t = Comparable.from t.a . hash t.a
|
||||
|
||||
compare_to self that = if self == that then Ordering.Equal else
|
||||
if self.a > that.a then Ordering.Greater else Ordering.Less
|
||||
Comparable.from (_:T) = T_Comparator
|
||||
|
||||
type My_Error
|
||||
Value a
|
||||
@ -28,10 +30,10 @@ type Foo
|
||||
Value vec
|
||||
|
||||
compare_tco a b = case a.vec.length == b.vec.length of
|
||||
False -> a.vec.length . compare_to b.vec.length
|
||||
False -> Ordering.compare a.vec.length b.vec.length
|
||||
True ->
|
||||
go ix = if ix > a.vec.length then Ordering.Equal else
|
||||
cmp = (a.vec.at ix) . compare_to (b.vec.at ix)
|
||||
cmp = Ordering.compare (a.vec.at ix) (b.vec.at ix)
|
||||
case cmp of
|
||||
Ordering.Equal -> @Tail_Call go ix+1
|
||||
_ -> cmp
|
||||
@ -579,20 +581,20 @@ spec = Test.group "Vectors" <|
|
||||
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]
|
||||
small_vec.sort (on = _.b) . should_equal small_expected
|
||||
|
||||
Test.specify "should be able to use a custom comparator" <|
|
||||
Test.specify "should be able to use a custom compare function" <|
|
||||
small_vec = [2, 7, -3, 383, -392, 28, -90]
|
||||
small_expected = [383, 28, 7, 2, -3, -90, -392]
|
||||
small_vec.sort (by = l -> r -> r.compare_to l) . should_equal small_expected
|
||||
small_vec.sort (by = l -> r -> Ordering.compare r l) . should_equal small_expected
|
||||
|
||||
Test.specify "should allow tail-recursive comparators in sort" <|
|
||||
v = [Foo.Value [4,2,2], Foo.Value [1,2,3], Foo.Value [1,2,4]]
|
||||
r = [Foo.Value [1,2,3], Foo.Value [1,2,4], Foo.Value [4,2,2]]
|
||||
v.sort by=compare_tco . should_equal r
|
||||
|
||||
Test.specify "should be able to use a custom comparator and projection" <|
|
||||
Test.specify "should be able to use a custom compare function and projection" <|
|
||||
small_vec = [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 -1 10, T.Value 1 8, T.Value 1 3, T.Value -1 1, T.Value -20 0, T.Value 4 0]
|
||||
small_vec.sort (on = _.b) (by = l -> r -> r.compare_to l) . should_equal small_expected
|
||||
small_vec.sort (on = _.b) (by = l -> r -> Ordering.compare r l) . should_equal small_expected
|
||||
|
||||
Test.specify "should be able to sort in descending order" <|
|
||||
small_vec = [2, 7, -3, 383, -392, 28, -90]
|
||||
@ -653,7 +655,7 @@ spec = Test.group "Vectors" <|
|
||||
[1, 1.0, 2, 2.0].distinct . should_equal [1, 2]
|
||||
[].distinct . should_equal []
|
||||
|
||||
Test.specify "should correctly handle distinct with custom types like Atoms that implement compare_to" <|
|
||||
Test.specify "should correctly handle distinct with types that have custom comparators" <|
|
||||
[T.Value 1 2, T.Value 3 3, T.Value 1 2].distinct . should_equal [T.Value 1 2, T.Value 3 3]
|
||||
|
||||
Test.specify "should return a vector containing only unique elements up to some criteria" <|
|
||||
|
@ -14,32 +14,33 @@ type CustomEqType
|
||||
CustomEqType.C1 f1 -> f1
|
||||
CustomEqType.C2 f1 f2 -> f1 + f2
|
||||
|
||||
== self other = self.sum == other.sum
|
||||
type CustomEqType_Comparator
|
||||
is_ordered = False
|
||||
equals o1 o2 = o1.sum == o2.sum
|
||||
hash o =
|
||||
comp = Comparable.from o.sum
|
||||
comp.hash o.sum
|
||||
|
||||
Comparable.from (_:CustomEqType) = CustomEqType_Comparator
|
||||
|
||||
type Child
|
||||
Value number
|
||||
|
||||
== : Any -> Boolean
|
||||
== self other = case other of
|
||||
_ : Child -> (self.number % 100) == (other.number % 100)
|
||||
_ -> False
|
||||
type Child_Comparator
|
||||
is_ordered = False
|
||||
equals child1 child2 = child1.number % 100 == child2.number % 100
|
||||
hash child =
|
||||
comp = Comparable.from child.number
|
||||
comp.hash (child.number % 100)
|
||||
|
||||
Comparable.from (_:Child) = Child_Comparator
|
||||
|
||||
type Parent
|
||||
Value child
|
||||
|
||||
== : Any -> Boolean
|
||||
== self other = case other of
|
||||
_ : Parent -> self.child == other.child
|
||||
_ -> False
|
||||
|
||||
type GrandParent
|
||||
Value parent
|
||||
|
||||
== : Any -> Boolean
|
||||
== self other = case other of
|
||||
_ : GrandParent -> self.parent == other.parent
|
||||
_ -> False
|
||||
|
||||
type ManyFieldType
|
||||
Value f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15
|
||||
|
||||
@ -120,6 +121,9 @@ spec =
|
||||
((Point.Value 'ś' 23.0) == (Point.Value 's\u0301' 23)) . should_be_true
|
||||
|
||||
Test.specify "should dispatch to overriden `==` on atoms" <|
|
||||
(Child.Value 11 == Child.Value 111) . should_be_true
|
||||
|
||||
Test.specify "should dispatch to overriden `==` on atoms transitively" <|
|
||||
child1 = Child.Value 11
|
||||
parent1 = Parent.Value child1
|
||||
grand_parent1 = GrandParent.Value parent1
|
||||
@ -130,12 +134,17 @@ spec =
|
||||
|
||||
(grand_parent1 == grand_parent2).should_be_true
|
||||
|
||||
Test.specify "should handle `==` on types with many fields" <|
|
||||
Test.specify "should handle `==` on types with many fields with custom comparator" <|
|
||||
many_fields1 = ManyFieldType.Value (Child.Value 1) (Child.Value 2) (Child.Value 3) (Child.Value 4) (Child.Value 5) (Child.Value 6) (Child.Value 7) (Child.Value 8) (Child.Value 9) (Child.Value 10) (Child.Value 11) (Child.Value 12) (Child.Value 13) (Child.Value 14) (Child.Value 15)
|
||||
many_fields2 = ManyFieldType.Value (Child.Value 101) (Child.Value 102) (Child.Value 103) (Child.Value 104) (Child.Value 105) (Child.Value 106) (Child.Value 107) (Child.Value 108) (Child.Value 109) (Child.Value 110) (Child.Value 111) (Child.Value 112) (Child.Value 113) (Child.Value 114) (Child.Value 115)
|
||||
|
||||
(many_fields1 == many_fields2).should_be_true
|
||||
|
||||
Test.specify "should handle `==` on atoms with fields with mixed comparators" <|
|
||||
obj_1 = FourFieldType.Value (Child.Value 1) 42 (Child.Value 2) 83
|
||||
obj_2 = FourFieldType.Value (Child.Value 101) 42 (Child.Value 102) 83
|
||||
(obj_1 == obj_2).should_be_true
|
||||
|
||||
Test.specify "should be able to compare atoms with different constructors" <|
|
||||
((CustomEqType.C1 10) == (CustomEqType.C2 7 3)).should_be_true
|
||||
((CustomEqType.C1 0) == (CustomEqType.C2 7 3)).should_be_false
|
||||
@ -152,16 +161,16 @@ spec =
|
||||
four_field = FourFieldType.Value 1 2 3 4
|
||||
(rect == four_field).should_be_false
|
||||
|
||||
Test.specify "should handle `==` on types" pending="Blocked by https://www.pivotaltracker.com/story/show/184092284" <|
|
||||
Test.specify "should handle `==` on types" <|
|
||||
(Child == Child).should_be_true
|
||||
(Child == Point).should_be_false
|
||||
(Point == Child).should_be_false
|
||||
(JavaPath == Child).should_be_false
|
||||
(Child == JavaPath).should_be_false
|
||||
(Boolean == Any).should_be_false
|
||||
(Boolean == Boolean).should_be_true
|
||||
|
||||
Test.specify "should handle `==` on types with Any as first operand" pending="Any == Boolean resolves to Any.type.== static method call" <|
|
||||
(Any == Boolean).should_be_false
|
||||
(Any == Any).should_be_true
|
||||
(Boolean == Boolean).should_be_true
|
||||
|
||||
Test.specify "should dispatch to overriden `==` in vectors" <|
|
||||
([(Child.Value 1)] == [(Child.Value 101)]).should_be_true
|
||||
|
@ -3,3 +3,8 @@ 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
|
||||
|
||||
@Builtin_Type
|
||||
type Comparable
|
||||
|
Loading…
Reference in New Issue
Block a user