diff --git a/RELEASES.md b/RELEASES.md index 744d0da021c9..71d5409966c5 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,6 +20,9 @@ - Removed reflective access when loading the OpenCV library ([#1727](https://github.com/enso-org/enso/pull/1727)). Illegal reflective access operations were deprecated and will be denied in future JVM releases. +- Overhauled the types we use for errors throughout the standard library + ([#1734](https://github.com/enso-org/enso/pull/1734)). They are now much more + informative, and should provide more clarity when things go wrong. ## Miscellaneous diff --git a/distribution/std-lib/Standard/src/Base/Data/Json.enso b/distribution/std-lib/Standard/src/Base/Data/Json.enso index aec3b98ca73b..fbb744673f32 100644 --- a/distribution/std-lib/Standard/src/Base/Data/Json.enso +++ b/distribution/std-lib/Standard/src/Base/Data/Json.enso @@ -153,7 +153,8 @@ type Parse_Error message Converts the error to a display representation. Parse_Error.to_display_text : Text -Parse_Error.to_display_text = "Parse error in parsing JSON: " + this.message.to_text + "." +Parse_Error.to_display_text = + "Parse error in parsing JSON: " + this.message.to_text + "." ## Gets the value associated with the given key in this object. @@ -169,8 +170,22 @@ Parse_Error.to_display_text = "Parse error in parsing JSON: " + this.message.to_ import Standard.Examples example_get = Examples.json_object.get "title" -Object.get : Text -> Json ! Nothing -Object.get field = this.fields.get field +Object.get : Text -> Json ! No_Such_Field_Error +Object.get field = this.fields.get field . map_error case _ of + Map.No_Value_For_Key_Error _ -> No_Such_Field_Error field + x -> x + +## UNSTABLE + + An error indicating that there is no such field in the JSON object. +type No_Such_Field_Error field_name + +## UNSTABLE + + Pretty prints the no such field error. +No_Such_Field_Error.to_display_text : Text +No_Such_Field_Error.to_display_text = + "The field " + this.field_name.to_text + " is not present in this object." ## UNSTABLE @@ -267,3 +282,4 @@ Base.Boolean.to_json = Boolean this Nothing.to_json Nothing.to_json : Null Nothing.to_json = Null + diff --git a/distribution/std-lib/Standard/src/Base/Data/List.enso b/distribution/std-lib/Standard/src/Base/Data/List.enso index 426e59e6573c..6a8f20c1cea5 100644 --- a/distribution/std-lib/Standard/src/Base/Data/List.enso +++ b/distribution/std-lib/Standard/src/Base/Data/List.enso @@ -265,10 +265,10 @@ type List import Standard.Examples example_head = Examples.list.head - head : Any ! Nothing + head : Any ! Empty_Error head = case this of Cons a _ -> a - Nil -> Error.throw Nothing + Nil -> Error.throw Empty_Error ## Get all elements from the list except the first. @@ -278,10 +278,10 @@ type List import Standard.Examples example_tail = Examples.list.tail - tail : List ! Nothing + tail : List ! Empty_Error tail = case this of Cons _ b -> b - Nil -> Error.throw Nothing + Nil -> Error.throw Empty_Error ## Get all elements from the list except the last. @@ -291,14 +291,14 @@ type List import Standard.Examples example_init = Examples.list.init - init : List ! Nothing + init : List ! Empty_Error init = init' x y = case y of Nil -> Nil Cons a b -> Cons x (init' a b) case this of Cons a b -> init' a b - Nil -> Error.throw Nothing + Nil -> Error.throw Empty_Error ## Get the last element of the list. @@ -308,9 +308,9 @@ type List import Standard.Examples example_last = Examples.list.last - last : Any | Nothing + last : Any ! Empty_Error last = case this.fold Nothing (_ -> r -> r) of - Nothing -> Error.throw Nothing + Nothing -> Error.throw Empty_Error a -> a ## Get the first element from the list. @@ -321,7 +321,7 @@ type List import Standard.Examples example_first = Examples.list.first - first : Any ! Nothing + first : Any ! Empty_Error first = this.head ## Get all elements from the list except the first. @@ -332,9 +332,20 @@ type List import Standard.Examples example_rest = Examples.list.rest - rest : List ! Nothing + rest : List ! Empty_Error rest = this.tail +## UNSTABLE + + An error representing that the list is empty. +type Empty_Error + +## UNSTABLE + + Pretty prints the empty error. +Empty_Error.to_display_text : Text +Empty_Error.to_display_text = "The List is empty." + ## PRIVATE A helper for the `map` function. @@ -346,6 +357,7 @@ type List Uses unsafe field mutation under the hood, to rewrite `map` in a tail-recursive manner. The mutation is purely internal and does not leak to the user-facing API. +map_helper : List -> Any -> (Any -> Any) -> Nothing map_helper list cons f = case list of Cons h t -> res = Cons (f h) Nil diff --git a/distribution/std-lib/Standard/src/Base/Data/Map.enso b/distribution/std-lib/Standard/src/Base/Data/Map.enso index 7d72d97b5755..4314408983f6 100644 --- a/distribution/std-lib/Standard/src/Base/Data/Map.enso +++ b/distribution/std-lib/Standard/src/Base/Data/Map.enso @@ -39,7 +39,7 @@ singleton key value = Bin 1 key value Tip Tip import Standard.Base.Data.Map.Internal example_from_vector = Map.from_vector [[1, 2], [3, 4]] -from_vector : Vector.Vector -> Map +from_vector : Vector.Vector Any -> Map from_vector vec = vec.fold Map.empty (m -> el -> m.insert (el.at 0) (el.at 1)) ## A key-value store. This type assumes all keys are pairwise comparable, @@ -114,7 +114,7 @@ type Map import Standard.Examples example_to_vector = Examples.map.to_vector - to_vector : Vector.Vector + to_vector : Vector.Vector Any to_vector = builder = Vector.new_builder to_vector_with_builder m = case m of @@ -177,8 +177,8 @@ type Map insert : Any -> Any -> Map insert key value = Internal.insert this key value - ## Gets the value associated with `key` in this map, or throws a `Nothing`, - if `key` is not present. + ## Gets the value associated with `key` in this map, or throws a + `No_Value_For_Key_Error` if `key` is not present. Arguments: - key: The key to look up in the map. @@ -190,10 +190,10 @@ type Map import Standard.Examples example_get = Examples.map.get 1 - get : Any -> Any ! Nothing + get : Any -> Any ! No_Value_For_Key_Error get key = go map = case map of - Tip -> Error.throw Nothing + Tip -> Error.throw (No_Value_For_Key_Error key) Bin _ k v l r -> if k == key then v else if k > key then @Tail_Call go l else @Tail_Call go r @@ -446,5 +446,12 @@ type Map Arguments: - key: The key that was asked for. -type No_Value_For_Key key +type No_Value_For_Key_Error key + +## UNSTABLE + + Converts the error into a human-readable representation. +No_Value_For_Key_Error.to_display_text : Text +No_Value_For_Key_Error.to_display_text = + "The map contained no value for the key " + this.key.to_text + "." diff --git a/distribution/std-lib/Standard/src/Base/Data/Number/Extensions.enso b/distribution/std-lib/Standard/src/Base/Data/Number/Extensions.enso index ccb5717c314a..63834435460b 100644 --- a/distribution/std-lib/Standard/src/Base/Data/Number/Extensions.enso +++ b/distribution/std-lib/Standard/src/Base/Data/Number/Extensions.enso @@ -1,4 +1,4 @@ -from Standard.Base import all +from Standard.Base import all hiding Parse_Error polyglot java import java.lang.Double polyglot java import java.lang.Math @@ -231,7 +231,7 @@ Number.to_json : Json.Number Number.to_json = Json.Number this ## Parses a textual representation of a decimal into a decimal number, returning - `Nothing` if the text does not represent a valid decimal. + a `Parse_Error` if the text does not represent a valid decimal. Arguments: - text: The text to parse into a decimal. @@ -240,7 +240,20 @@ Number.to_json = Json.Number this Parse the text "7.6" into a decimal number. Decimal.parse 7.6 -Decimal.parse : Text -> Decimal ! Nothing +Decimal.parse : Text -> Decimal ! Parse_Error Decimal.parse text = - Panic.recover (Double.parseDouble text) . catch (_ -> Error.throw Nothing) + Panic.recover (Double.parseDouble text) . catch _-> + Error.throw (Parse_Error text) + +## UNSTABLE + + A syntax error when parsing a double. +type Parse_Error text + +## UNSTABLE + + Pretty print the syntax error. +Parse_Error.to_display_text : Text +Parse_Error.to_display_text = + "Could not parse " + this.text.to_text + " as a double." diff --git a/distribution/std-lib/Standard/src/Base/Data/Time.enso b/distribution/std-lib/Standard/src/Base/Data/Time.enso index e1d46619aa53..88c961dcdd6c 100644 --- a/distribution/std-lib/Standard/src/Base/Data/Time.enso +++ b/distribution/std-lib/Standard/src/Base/Data/Time.enso @@ -442,7 +442,8 @@ type Time example_format = Time.parse "2020-10-08T16:41:13+03:00[Europe/Moscow]" . format "EEE MMM d (HH:mm)" format : Text -> Text - format pattern = DateTimeFormatter.ofPattern pattern . format this.internal_zoned_date_time + format pattern = + DateTimeFormatter.ofPattern pattern . format this.internal_zoned_date_time type Time_Error diff --git a/distribution/std-lib/Standard/src/Base/Data/Time/Duration.enso b/distribution/std-lib/Standard/src/Base/Data/Time/Duration.enso index 3e0e080b0360..e9fd8cade267 100644 --- a/distribution/std-lib/Standard/src/Base/Data/Time/Duration.enso +++ b/distribution/std-lib/Standard/src/Base/Data/Time/Duration.enso @@ -20,7 +20,11 @@ polyglot java import java.time.Period as Java_Period example_between = Duration.between Time.now (Time.new 2010 10 20) between : Time -> Time -> Duration between start_inclusive end_exclusive = - Duration (Java_Period.ofDays 0 . normalized) (Java_Duration.between start_inclusive.internal_zoned_date_time end_exclusive.internal_zoned_date_time) + period = Java_Period.ofDays 0 . normalized + start = start_inclusive.internal_zoned_date_time + end = end_exclusive.internal_zoned_date_time + duration = Java_Duration.between start end + Duration period duration type Duration @@ -52,7 +56,10 @@ type Duration example_add = 1.month + 12.hours + : Duration -> Duration - + that = Duration (this.internal_period . plus that.internal_period . normalized) (this.internal_duration . plus that.internal_duration) + + that = + period = this.internal_period . plus that.internal_period . normalized + duration = this.internal_duration . plus that.internal_duration + Duration period duration ## Subtract the specified amount of time from this duration. @@ -73,7 +80,10 @@ type Duration example_subtract = 7.months - 30.minutes - : Duration -> Duration - - that = Duration (this.internal_period . minus that.internal_period . normalized) (this.internal_duration . minus that.internal_duration) + - that = + period = this.internal_period . minus that.internal_period . normalized + duration = this.internal_duration . minus that.internal_duration + Duration period duration ## Get the portion of the duration expressed in nanoseconds. @@ -167,13 +177,16 @@ type Duration seconds and nanosecnods. > Example - Convert duration of a year and a hour to a vector returning `[1, 0, 0, 1, 0, 0, 0]`. + Convert duration of a year and a hour to a vector returning + `[1, 0, 0, 1, 0, 0, 0]`. + import Standard.Base.Data.Time.Duration example_to_vec = (1.year + 1.hour).to_vector > Example - Convert duration of 800 nanoseconds to a vector returning `[0, 0, 0, 0, 0, 0, 800]` + Convert duration of 800 nanoseconds to a vector returning + `[0, 0, 0, 0, 0, 0, 800]` import Standard.Base.Data.Time.Duration diff --git a/distribution/std-lib/Standard/src/Base/Data/Time/Time_Of_Day.enso b/distribution/std-lib/Standard/src/Base/Data/Time/Time_Of_Day.enso index c8f9d4f6f31b..e83ecccdaf3f 100644 --- a/distribution/std-lib/Standard/src/Base/Data/Time/Time_Of_Day.enso +++ b/distribution/std-lib/Standard/src/Base/Data/Time/Time_Of_Day.enso @@ -113,7 +113,7 @@ new (hour = 0) (minute = 0) (second = 0) (nanosecond = 0) = import Standard.Base.Data.Time.Time_Of_Day example_parse = Time_Of_Day.parse "4:30AM" "h:mma" -parse : Text -> Text ! Nothing -> Locale -> Time_Of_Day ! Time.Time_Error +parse : Text -> Text | Nothing -> Locale -> Time_Of_Day ! Time.Time_Error parse text pattern=Nothing locale=Locale.default = result = Panic.recover <| case pattern of Nothing -> LocalTime.parse text diff --git a/distribution/std-lib/Standard/src/Base/Data/Vector.enso b/distribution/std-lib/Standard/src/Base/Data/Vector.enso index 0ad124928aa3..263746134635 100644 --- a/distribution/std-lib/Standard/src/Base/Data/Vector.enso +++ b/distribution/std-lib/Standard/src/Base/Data/Vector.enso @@ -20,7 +20,7 @@ from Builtins import Array Create a copy of the given vector (`my_vec`). Vector.new my_vec.length (ix -> my_vec.at ix) -new : Number -> (Number -> Any) -> Vector +new : Number -> (Number -> Any) -> Vector Any new length constructor = arr = Array.new length 0.up_to length . each ix-> arr.set_at ix (constructor ix) @@ -40,7 +40,7 @@ new length constructor = Create a vector containing 50 elements, each being the number `42`. Vector.fill length=50 item=42 -fill : Number -> Any -> Vector +fill : Number -> Any -> Vector Any fill length ~item = arr = Array.new length 0.up_to length . each ix-> arr.set_at ix item @@ -87,7 +87,7 @@ new_builder = Builder.new If this didn't happen then it would be possible for the underlying array to be mutated under the hood, and sneak mutability into our immutable data. -from_array : Any -> Vector.Vector +from_array : Any -> Vector.Vector Any from_array arr = here.new (Polyglot.get_array_size arr) (arr.at _) ## The basic, immutable, vector type. @@ -161,17 +161,17 @@ type Vector Arguments: - function: A binary operation that takes two items and combines them. - If the vector is empty, it throws Nothing. + If the vector is empty, it throws an `Empty_Error`. > Example Compute the sum of all the elements in a vector. [0, 1, 2] . reduce (+) - reduce : (Any -> Any -> Any) -> Any ! Nothing + reduce : (Any -> Any -> Any) -> Any ! Empty_Error reduce function = case this.not_empty of True -> this.tail.fold this.head function - False -> Error.throw Nothing + False -> Error.throw Empty_Error ## Computes the sum of the values in the vector. @@ -182,12 +182,12 @@ type Vector Compute the sum of all fo the elements in a vector. [0, 1, 2].sum - sum : (Any ! Nothing) + sum : Any ! (Empty_Error | No_Such_Method_Error) sum = result = Panic.recover <| this.reduce (+) result.map_error x->case x of - No_Such_Method_Error _ _ -> Nothing - Nothing -> Nothing + No_Such_Method_Error _ _ -> x + Empty_Error -> x _ -> Panic.throw x ## Checks whether a predicate holds for at least one element of this vector. @@ -299,7 +299,7 @@ type Vector Selecting all elements that are greater than 3. [1, 2, 3, 4, 5].filter (> 3) - filter : (Any -> Boolean) -> Vector + filter : (Any -> Boolean) -> Vector Any filter predicate = check acc ix = if predicate ix then acc + [ix] else acc this.fold [] check @@ -315,7 +315,7 @@ type Vector Add 1 to each element of the vector. [1, 2, 3] . map +1 - map : (Any -> Any) -> Vector + map : (Any -> Any) -> Vector Any map function = here.new this.length i-> function (this.at i) @@ -330,7 +330,7 @@ type Vector Replace each number `n` in the vector with itself repeated n times. [0, 1, 2] . flat_map (n -> Vector.fill n n) - flat_map : (Any -> Vector) -> Vector + flat_map : (Any -> Vector Any) -> Vector Any flat_map function = mapped = this.map function length = mapped.fold 0 acc-> elem-> acc + elem.length @@ -354,7 +354,7 @@ type Vector Sum numbers with their indices in a vector. [1, 2, 3].map_with_index (+) - map_with_index : (Integer -> Any -> Any) -> Vector + map_with_index : (Integer -> Any -> Any) -> Vector Any map_with_index function = here.new this.length i-> function i (this.at i) ## Applies a function to each element of the vector. @@ -381,7 +381,7 @@ type Vector Reverse a two-element vector. [1, 2].reverse - reverse : Vector + reverse : Vector Any reverse = here.new this.length (i -> this.at (this.length - (1 + i))) ## Generates a human-readable text representation of the vector. @@ -425,7 +425,7 @@ type Vector Concatenate two single-element vectors. [1] + [2] - + : Vector -> Vector + + : Vector Any -> Vector Any + that = this_len = this.length arr = Array.new (this_len + that.length) @@ -444,7 +444,7 @@ type Vector Add 1 to the start of the vector. [2, 3].prepend 1 - prepend : Any -> Vector + prepend : Any -> Vector Any prepend element = [element] + this ## Add `element` to the end of `this` vector. @@ -456,7 +456,7 @@ type Vector Add 3 to the end of the vector. [1, 2].append 3 - append : Any -> Vector + append : Any -> Vector Any append element = this + [element] ## When `this` is a vector of text values, concatenates all the values by @@ -484,7 +484,7 @@ type Vector Remove the first element from the start of the vector. [1, 2, 3, 4, 5].drop_start 1 - drop_start : Integer -> Vector + drop_start : Integer -> Vector Any drop_start count = if count >= this.length then here.new 0 (x -> x) else here.new (this.length - count) (i -> this.at i+count) @@ -497,7 +497,7 @@ type Vector Remove the last two elements from the end of the vector. [1, 2, 3, 4, 5].drop_end 2 - drop_end : Integer -> Vector + drop_end : Integer -> Vector Any drop_end count = if count >= this.length then here.new 0 (x -> x) else this.take_start (this.length - count) @@ -511,7 +511,7 @@ type Vector Create a new vector from the first two elements of the vector. [1, 2, 3, 4, 5].take_start 2 - take_start : Integer -> Vector + take_start : Integer -> Vector Any take_start count = if count >= this.length then this else here.new count this.at @@ -525,7 +525,7 @@ type Vector Create a new vector from the last two elements of the vector. [1, 2, 3, 4, 5].take_end 3 - take_end : Integer -> Vector + take_end : Integer -> Vector Any take_end count = if count >= this.length then this else this.drop_start (this.length - count) @@ -550,7 +550,7 @@ type Vector of both elements. [1, 2, 3].zip [4, 5, 6] == [[1, 4], [2, 5], [3, 6]] - zip : Vector -> (Any -> Any -> Any) -> Vector + zip : Vector Any -> (Any -> Any -> Any) -> Vector Any zip that function=[_,_] = len = Math.min this.length that.length here.new len i-> function (this.at i) (that.at i) @@ -574,7 +574,7 @@ type Vector Extending vector to the length of 5 returns `[1, 2, 3, 4, 5]` [1, 2, 3, 4, 5].pad 5 0 - pad : Integer -> Any -> Vector + pad : Integer -> Any -> Vector Any pad n elem = if this.length >= n then this else this + (here.fill n-this.length elem) @@ -588,15 +588,15 @@ type Vector to_json : Json.Array to_json = Json.Array (this.map .to_json) - ## Get the first element from the vector, or an error `Nothing` if the - vector is empty. + ## Get the first element from the vector, or an `Empty_Error` if the vector + is empty. > Example The following code returns 1. [1, 2, 3, 4].head - head : Any ! Nothing - head = if this.length >= 1 then this.at 0 else Error.throw Nothing + head : Any ! Empty_Error + head = if this.length >= 1 then this.at 0 else Error.throw Empty_Error ## Get all elements in the vector except the first. @@ -604,8 +604,9 @@ type Vector The following code returns [2, 3, 4]. [1, 2, 3, 4].tail - tail : Vector ! Nothing - tail = if this.length >= 1 then this.drop_start 1 else Error.throw Nothing + tail : Vector ! Empty_Error + tail = if this.length >= 1 then this.drop_start 1 else + Error.throw Empty_Error ## Get the all elements in the vector except the last. @@ -613,30 +614,31 @@ type Vector The following code returns [1, 2, 3]. [1, 2, 3, 4].init - init : Vector ! Nothing - init = if this.length >= 1 then this.drop_end 1 else Error.throw Nothing + init : Vector ! Empty_Error + init = if this.length >= 1 then this.drop_end 1 else Error.throw Empty_Error - ## Get the last element of the vector, or an error `Nothing` if the vector - is empty. + ## Get the last element of the vector, or an `Empty_Error` if the vector is + empty. > Example The following code returns 4. [1, 2, 3, 4].last - last : Vector ! Nothing - last = if this.length >= 1 then (this.take_end 1).at 0 else Error.throw Nothing + last : Vector ! Empty_Error + last = if this.length >= 1 then (this.take_end 1).at 0 else + Error.throw Empty_Error - ## Get the first element from the vector, or an error `Nothing` if the - vector is empty. + ## Get the first element from the vector, or an `Empty_Error` if the vector + is empty. > Example The following code returns 1. [1, 2, 3, 4].first - first : Vector ! Nothing + first : Vector ! Empty_Error first = this.head - ## Get the second element from the vector, or an error `Nothing` if the + ## Get the second element from the vector, or a `Singleton_Error` if the vector doesn't have a second element. Useful when tuples are implemented as vectors. @@ -645,20 +647,16 @@ type Vector The following code returns 2. [1, 2, 3, 4].second - second : Vector ! Nothing - second = if this.length >= 2 then this.at 1 else Error.throw Nothing + second : Vector ! Singleton_Error + second = if this.length >= 2 then this.at 1 else + Error.throw (Singleton_Error this) ## Get all elements in the vector except the first. > Example The following code returns [2, 3, 4]. [1, 2, 3, 4].rest - - > Example - Empty vectors return `Nothing`. - - [].rest == Nothing - rest : Vector ! Nothing + rest : Vector ! Empty_Error rest = this.tail ## Sort the Vector. @@ -702,7 +700,7 @@ type Vector Sorting a vector of `Pair`s on the first element, descending. [Pair 1 2, Pair -1 8].sort (_.first) (order = Sort_Order.Descending) - sort : (Any -> Any) -> (Any -> Any -> Ordering) -> Sort_Order -> Vector + sort : (Any -> Any) -> (Any -> Any -> Ordering) -> Sort_Order -> Vector Any sort (on = x -> x) (by = (_.compare_to _)) (order = Sort_Order.Ascending) = ## Prepare the destination array that will underlie the vector. We do not want to sort in place on the original vector, as `sort` is not @@ -715,7 +713,8 @@ type Vector does both. comp_ascending l r = by (on l) (on r) comp_descending l r = by (on r) (on l) - compare = if order == Sort_Order.Ascending then comp_ascending else comp_descending + compare = if order == Sort_Order.Ascending then comp_ascending else + comp_descending new_vec_arr.sort compare @@ -731,6 +730,7 @@ type Vector json.to_text type Builder + ## PRIVATE A builder type for Enso vectors. @@ -814,7 +814,7 @@ type Builder bldr.append 10 bldr.append 100 bldr.to_vector - to_vector : Vector + to_vector : Vector Any to_vector = old_array = this.to_array new_array = Array.new this.length @@ -839,3 +839,29 @@ Index_Out_Of_Bounds_Error.to_display_text : Text Index_Out_Of_Bounds_Error.to_display_text = "The index " + this.index.to_text + " is out of bounds in a vector with length " + this.length.to_text + "." +## UNSTABLE + + An error that indicates that the vector is empty. +type Empty_Error + +## UNSTABLE + + Pretty prints the empty error. +Empty_Error.to_display_text : Text +Empty_Error.to_display_text = "The vector is empty." + +## UNSTABLE + + An error that indicates that the vector only has one element. + + Arguments: + - vec: The vector that only has one element. +type Singleton_Error vec + +## UNSTABLE + + Pretty prints the singleton error. +Singleton_Error.to_display_text : Text +Singleton_Error.to_display_text = + "The vector " + this.vec.to_text + " has only one element." + diff --git a/distribution/std-lib/Standard/src/Base/System/File.enso b/distribution/std-lib/Standard/src/Base/System/File.enso index f40cd7004c03..6efe3a213467 100644 --- a/distribution/std-lib/Standard/src/Base/System/File.enso +++ b/distribution/std-lib/Standard/src/Base/System/File.enso @@ -12,7 +12,8 @@ polyglot java import java.nio.file.NoSuchFileException ## Creates a new file object, pointing to the given path. Arguments: - - path: The path to the file that you want to create. + - path: The path to the file that you want to create, or a file itself. The + latter is a no-op. > Example Create a new file pointing to the `data.csv` file in the project directory. @@ -21,8 +22,10 @@ polyglot java import java.nio.file.NoSuchFileException import Standard.Examples example_new = File.new Examples.csv_path -new : Text -> File -new path = File (Prim_Io.get_file path) +new : (Text | File) -> File +new path = case path of + Text -> File (Prim_Io.get_file path) + _ -> path ## Open and read the file at the provided `path`. diff --git a/distribution/std-lib/Standard/src/Table.enso b/distribution/std-lib/Standard/src/Table.enso index 36145c0b3cfd..619d3bee3270 100644 --- a/distribution/std-lib/Standard/src/Table.enso +++ b/distribution/std-lib/Standard/src/Table.enso @@ -34,8 +34,7 @@ from Standard.Table.Data.Order_Rule export Order_Rule - Nested properties are not supported and not included in the resulting dataframe. - > Example - Converts a JSON array containing key-value pairs into a table for the + > Example Converts a JSON array containing key-value pairs into a table for the provided headers. import Standard.Examples @@ -86,9 +85,9 @@ Json.Array.to_table fields = case this of example_to_table = json = Examples.geo_json json.to_table -Json.Object.to_table : Vector -> Table ! Nothing +Json.Object.to_table : Vector -> Table ! Invalid_Format_Error Json.Object.to_table fields=Nothing = - if this.get_type != Geo_Json.Feature_Collection.to_text then Error.throw Nothing else + if this.get_type != Geo_Json.Feature_Collection.to_text then Error.throw (Invalid_Format_Error this "not being a feature collection") else case this.get "features" of Json.Array items -> feature_rows = items.map .get_feature_row @@ -105,5 +104,17 @@ Json.Object.to_table fields=Nothing = [n, rows.map (_.at i)] Table.new cols - _ -> Error.throw Nothing + _ -> Error.throw (Invalid_Format_Error this "not having the 'features' key.") + +## UNSTABLE + + An error representing an invalid format for conversion. +type Invalid_Format_Error input message + +## UNSTABLE + + Provides a human-readable representation of the Invalid_Format_Error. +Invalid_Format_Error.to_display_text : Text +Invalid_Format_Error.to_display_text = + "The input " + this.input.to_text + " had an invalid format due to: " + this.message.to_text + "." diff --git a/distribution/std-lib/Standard/src/Table/Data/Column.enso b/distribution/std-lib/Standard/src/Table/Data/Column.enso index 0f020199f2c2..df6d0022391c 100644 --- a/distribution/std-lib/Standard/src/Table/Data/Column.enso +++ b/distribution/std-lib/Standard/src/Table/Data/Column.enso @@ -956,7 +956,7 @@ type Column Returns the first element in the column, if it exists. If the column is empty, this method will return a dataflow error - containing `Nothing`. + containing an `Empty_Error`. > Example Get the first element of a column. @@ -964,15 +964,15 @@ type Column import Standard.Examples example_first = Examples.integer_column.first - first : Any ! Nothing - first = this.at 0 . catch (_ -> Error.throw Nothing) + first : Any ! Empty_Error + first = this.at 0 . catch (_ -> Error.throw Empty_Error) ## UNSTABLE Returns the first element in the column, if it exists. If the column is empty, this method will return a dataflow error - containing `Nothing`. + containing an `Empty_Error`. > Example Get the first element of a column. @@ -980,7 +980,7 @@ type Column import Standard.Examples example_head = Examples.integer_column.head - head : Any ! Nothing + head : Any ! Empty_Error head = this.first ## UNSTABLE @@ -988,7 +988,7 @@ type Column Returns the last element in the column, if it exists. If the column is empty, this method will return a dataflow error - containing `Nothing`. + containing an `Empty_Error`. > Example Get the last element of a column. @@ -996,8 +996,8 @@ type Column import Standard.Examples example_last = Examples.integer_column.last - last : Any ! Nothing - last = this.at (this.length - 1) . catch (_ -> Error.throw Nothing) + last : Any ! Empty_Error + last = this.at (this.length - 1) . catch (_ -> Error.throw Empty_Error) ## UNSTABLE @@ -1187,6 +1187,17 @@ Index_Out_Of_Bounds_Error.to_display_text = len_text = this.length.to_text "The index " + ix_text + " is out of bounds in a column of length " + len_text + "." +## UNSTABLE + + An error for when the column contains no elements. +type Empty_Error + +## UNSTABLE + + Pretty prints the error. +Empty_Error.to_display_text : Text +Empty_Error.to_display_text = "The column is empty." + ## PRIVATE Executes a vectorized binary operation over the provided column. diff --git a/distribution/std-lib/Standard/src/Table/Data/Table.enso b/distribution/std-lib/Standard/src/Table/Data/Table.enso index 3c4c78e1680b..6f43c438d734 100644 --- a/distribution/std-lib/Standard/src/Table/Data/Table.enso +++ b/distribution/std-lib/Standard/src/Table/Data/Table.enso @@ -623,7 +623,7 @@ type Table Returns the first row in the table, if it exists. If the table is empty, this method will return a dataflow error - containing `Nothing`. + containing an `Empty_Error`. > Example Get the first row from the table. @@ -631,17 +631,17 @@ type Table import Standard.Examples example_first = Examples.inventory_table.first - first : Table ! Nothing + first : Table ! Empty_Error first = table = this.take_start 1 - if table.row_count != 1 then Error.throw Nothing else table + if table.row_count != 1 then Error.throw Empty_Error else table ## UNSTABLE Returns the first row in the table, if it exists. If the table is empty, this method will return a dataflow error - containing `Nothing`. + containing an `Empty_Error`. > Example Get the first row from the table. @@ -649,7 +649,7 @@ type Table import Standard.Examples example_head = Examples.inventory_table.head - head : Table ! Nothing + head : Table ! Empty_Error head = this.first ## UNSTABLE @@ -657,7 +657,7 @@ type Table Returns the last row in the table, if it exists. If the table is empty, this method will return a dataflow error - containing `Nothing`. + containing an `Empty_Error`. > Example Get the last row from the table. @@ -665,10 +665,10 @@ type Table import Standard.Examples example_last = Examples.inventory_table.last - last : Table ! Nothing + last : Table ! Empty_Error last = table = this.take_end 1 - if table.row_count != 1 then Error.throw Nothing else table + if table.row_count != 1 then Error.throw Empty_Error else table ## UNSTABLE @@ -743,9 +743,9 @@ type Aggregate_Table import Standard.Examples example_at = Examples.aggregate_table.at "transaction_id" - at : Text -> Column ! Nothing + at : Text -> Column ! No_Such_Column_Error at name = case this.java_table.getColumnByName name of - Nothing -> Error.throw Nothing + Nothing -> Error.throw (No_Such_Column_Error name) c -> Column.Aggregate_Column c ## Prints an ASCII-art table with this data to the standard output. @@ -788,6 +788,17 @@ type No_Index_Set_Error No_Index_Set_Error.to_display_text : Text No_Index_Set_Error.to_display_text = "The table does not have an index set." +## UNSTABLE + + An error returned when the table contains no rows. +type Empty_Error + +## UNSTABLE + + Pretty prints the error. +Empty_Error.to_display_text : Text +Empty_Error.to_display_text = "The table is empty." + ## PRIVATE from_columns cols = Table (Java_Table.new cols.to_array) diff --git a/distribution/std-lib/Standard/src/Visualization/Scatter_Plot.enso b/distribution/std-lib/Standard/src/Visualization/Scatter_Plot.enso index 113161599ba2..f62d994ef8b7 100644 --- a/distribution/std-lib/Standard/src/Visualization/Scatter_Plot.enso +++ b/distribution/std-lib/Standard/src/Visualization/Scatter_Plot.enso @@ -67,7 +67,7 @@ type PointData name = this.to_text.to_lower_case ## PRIVATE - fallback_column : Table -> Column ! Nothing + fallback_column : Table -> Column ! No_Fallback_Column fallback_column table = case this of X -> table.index.catch_ <| this.iota table.row_count Y -> @@ -77,7 +77,7 @@ type PointData is_good c = is_good_enough c && (this.is_recognized c).not candidates.find is_good . catch_ <| candidates.find is_good_enough - _ -> Error.throw Nothing + _ -> Error.throw No_Fallback_Column ## PRIVATE @@ -93,6 +93,14 @@ type PointData named = table.lookup_ignore_case this.name named.catch_ <| this.fallback_column table +## PRIVATE +type No_Fallback_Column + +## PRIVATE +No_Fallback_Column.to_display_text : Text +No_Fallback_Column.to_display_text = + "No fallback column found for the scatter plot." + ## PRIVATE Generates JSON that describes points data. diff --git a/test/Table_Tests/src/Column_Spec.enso b/test/Table_Tests/src/Column_Spec.enso index 174d6743a708..99b720538ab1 100644 --- a/test/Table_Tests/src/Column_Spec.enso +++ b/test/Table_Tests/src/Column_Spec.enso @@ -34,12 +34,12 @@ spec = Test.group "Columns" <| Test.specify "should be able to get the first / head element" <| test_column.first . should_equal 1 test_column.head . should_equal 1 - empty_column.first.should_fail_with Nothing - empty_column.head.should_fail_with Nothing + empty_column.first.should_fail_with Column.Empty_Error + empty_column.head.should_fail_with Column.Empty_Error Test.specify "should be able to get the last element" <| test_column.last . should_equal 6 - empty_column.last.should_fail_with Nothing + empty_column.last.should_fail_with Column.Empty_Error Test.specify "should be able to be reversed" <| expected_1 = Column.from_vector "Test" [6, 4, 2, 5, 3, 1] diff --git a/test/Table_Tests/src/Table_Spec.enso b/test/Table_Tests/src/Table_Spec.enso index 8baf3fd28f82..aced2c4fa69b 100644 --- a/test/Table_Tests/src/Table_Spec.enso +++ b/test/Table_Tests/src/Table_Spec.enso @@ -1,6 +1,8 @@ from Standard.Base import all from Standard.Table import all +from Standard.Table.Data.Table as Table_Internal import Empty_Error + import Standard.Table.Data.Storage import Standard.Test @@ -16,6 +18,7 @@ My.frobnicate = case this of My x1 y1 -> My y1 x1 spec = + Test.group "JSON serialization" <| Test.specify "should serialize all column types to correct JSON" <| c_1 = [1, 2, 3, Nothing] @@ -572,7 +575,7 @@ spec = empty_col = ['col_1', []] empty_table = Table.new [empty_ix, empty_col] . set_index 'ix' - empty_table.first.should_fail_with Nothing + empty_table.first.should_fail_with Empty_Error Test.specify "should allow getting the last row" <| i_1 = ['ix', [1, 2, 3]] @@ -597,7 +600,7 @@ spec = empty_col = ['col_1', []] empty_table = Table.new [empty_ix, empty_col] . set_index 'ix' - empty_table.last.should_fail_with Nothing + empty_table.last.should_fail_with Empty_Error Test.specify "should allow reversing the table" <| i_1 = ['ix', [1, 2, 3]] diff --git a/test/Tests/src/Data/Json_Spec.enso b/test/Tests/src/Data/Json_Spec.enso index 1fca1a4dbb15..b0f995ef59d1 100644 --- a/test/Tests/src/Data/Json_Spec.enso +++ b/test/Tests/src/Data/Json_Spec.enso @@ -28,6 +28,7 @@ Text.should_render_itself = spec = Test.group "JSON Deserialization" <| + Test.specify "should parse JSON structures" <| "0 ".should_parse_as 0 " 123 ".should_parse_as 123 @@ -54,6 +55,7 @@ spec = object_json.should_parse_as <| y_v = Json.from_pairs [["z", Nothing], ["w", Nothing]] Json.from_pairs [["foo", "bar"], ["baz", ["foo", "x", False]], ["y", y_v]] + Test.specify "should report meaningful parsing errors" <| "foo".should_fail_parsing_with "Expected a false" "[,]".should_fail_parsing_with "Expected a start of a JSON value" @@ -64,6 +66,7 @@ spec = "y": {"z": null, "w": null} } deep_err.should_fail_parsing_with "Expected a comma" "123 4".should_fail_parsing_with "Expected end of input" + Test.specify "should parse and convert JSON into domain model" <| book_1 = Book "Lord of the Rings" <| Author "J. R. R. Tolkien" 1892 @@ -77,7 +80,9 @@ spec = parsed = Json.parse json_string domain = parsed.into (Vector.Vector (Book title=Text (Author name=Text year_of_birth=Number))) domain.should_equal books + Test.group "JSON Serialization" <| + Test.specify "should print JSON structures to valid json" <| "0".should_render_itself "123".should_render_itself @@ -93,6 +98,7 @@ spec = '["foo","foo\\n",null,true]'.should_render_itself object_json = '{"baz":["foo","x",false],"foo":"bar","y":{"w":null,"z":null}}' object_json.should_render_itself + Test.specify "should convert arbitrary types to JSON" <| 1.to_json.should_equal (Json.Number 1) 1.54.to_json.should_equal (Json.Number 1.54) @@ -104,3 +110,15 @@ spec = t = Json.String "Author" fields = Map.empty . insert "type" t . insert "name" n . insert "year_of_birth" y Json.Object fields + + Test.group "JSON" <| + + Test.specify "should allow getting object fields" <| + object = Json.parse ''' + { "foo": "bar", + "baz": ["foo", "x", false], + "y": {"z": null, "w": null} } + + object.get "foo" . should_equal (Json.String "bar") + object.get "bar" . should_fail_with Json.No_Such_Field_Error + diff --git a/test/Tests/src/Data/List_Spec.enso b/test/Tests/src/Data/List_Spec.enso index 04a51aed58dc..82bbf9167986 100644 --- a/test/Tests/src/Data/List_Spec.enso +++ b/test/Tests/src/Data/List_Spec.enso @@ -1,5 +1,7 @@ from Standard.Base import all +import Standard.Base.Data.List + import Standard.Test spec = Test.group "List" <| @@ -60,20 +62,20 @@ spec = Test.group "List" <| empty.take_start 2 . should_equal Nil Test.specify "should allow getting the head of the list with `.head`" <| l.head . should_equal 1 - empty.head.catch . should_equal Nothing + empty.head.should_fail_with List.Empty_Error Test.specify "should allow getting the tail of the list with `.tail`" <| l.tail . should_equal (Cons 2 (Cons 3 Nil)) - empty.tail.catch . should_equal Nothing + empty.tail.should_fail_with List.Empty_Error Test.specify "should allow getting the init of the list with `.init`" <| l.init . should_equal (Cons 1 (Cons 2 Nil)) - empty.init.catch . should_equal Nothing + empty.init.should_fail_with List.Empty_Error Test.specify "should allow getting the last element of the list with `.last`" <| l.last . should_equal 3 - empty.last.catch . should_equal Nothing + empty.last.should_fail_with List.Empty_Error Test.specify "should allow getting the head of the list with `.first`" <| l.first . should_equal 1 - empty.first.catch . should_equal Nothing + empty.first.should_fail_with List.Empty_Error Test.specify "should allow getting the tail of the list with `.rest`" <| l.rest . should_equal (Cons 2 (Cons 3 Nil)) - empty.rest.catch . should_equal Nothing + empty.rest.should_fail_with List.Empty_Error diff --git a/test/Tests/src/Data/Map_Spec.enso b/test/Tests/src/Data/Map_Spec.enso index 5ded8df4b01d..702ffa30a365 100644 --- a/test/Tests/src/Data/Map_Spec.enso +++ b/test/Tests/src/Data/Map_Spec.enso @@ -1,5 +1,7 @@ from Standard.Base import all +from Standard.Base.Data.Map import No_Value_For_Key_Error + import Standard.Test spec = Test.group "Maps" <| @@ -43,7 +45,7 @@ spec = Test.group "Maps" <| m.get "foo" . should_equal 134 m.get "bar" . should_equal 654 m.get "baz" . should_equal "spam" - (m.get "nope").should_fail_with Nothing + (m.get "nope").should_fail_with No_Value_For_Key_Error Test.specify "should support get_or_else" <| m = Map.empty . insert 2 3 m.get_or_else 2 0 . should_equal 3 diff --git a/test/Tests/src/Data/Numbers_Spec.enso b/test/Tests/src/Data/Numbers_Spec.enso index c78216b08895..3ae25b10d9cc 100644 --- a/test/Tests/src/Data/Numbers_Spec.enso +++ b/test/Tests/src/Data/Numbers_Spec.enso @@ -1,8 +1,11 @@ from Standard.Base import all +from Standard.Base.Data.Number.Extensions import Parse_Error + import Standard.Test Integer.is_even = this % 2 == 0 + Decimal.get_fun_factor = "Wow, " + this.to_text + " is such a fun number!" spec = @@ -12,43 +15,59 @@ spec = almost_max_long_times_three_plus_1 = 27670116110564327419 almost_max_long_times_three_decimal = 27670116110564327418.8 hundred_factorial = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 + Test.group "Integers" <| + Test.specify "should be of unbound size when multiplied" <| 1.up_to 101 . fold 1 (*) . should_equal hundred_factorial + Test.specify "should be of unbound size when added" <| (almost_max_long + almost_max_long + almost_max_long).should_equal almost_max_long_times_three - Test.specify "should be of unbound size when subtracted" <| (0 - almost_max_long - almost_max_long - almost_max_long).should_equal almost_max_long_times_three.negate + + Test.specify "should be of unbound size when subtracted" <| + (0 - almost_max_long - almost_max_long - almost_max_long).should_equal almost_max_long_times_three.negate + Test.specify "should be of unbound size when dividing" <| expected = 3372816184472482867110284450043137767873196479305249187406461598235841786750685581361224832688174410089430537516012695688121622150430744676 ((1.up_to 101 . fold 1 (*)).div 3*almost_max_long).should_equal expected + Test.specify "should be of unbound size when taking remainder" <| expected = 3191479909175673432 ((1.up_to 101 . fold 1 (*)) % 3*almost_max_long).should_equal expected + Test.specify "should allow defining extension methods through the Integer type for any number size" <| 876543.is_even.should_be_false (1.up_to 101 . fold 1 (*)).is_even.should_be_true + Test.specify "should handle the negation edge cases" <| x = 9223372036854775808 y = -x z = -9223372036854775808 y.should_equal z + Test.specify "should handle equality between small and big integers" <| (1 == hundred_factorial).should_be_false (hundred_factorial == 1).should_be_false + Test.specify "should properly handle going to big numbers and back" <| ((almost_max_long * 3) / 3) . should_equal almost_max_long + Test.specify "should use floating point arithmetic for division" <| (3 / 4) . should_equal 0.75 epsilon=eps (almost_max_long * 2 / almost_max_long_times_three) . should_equal 0.6666666 epsilon=eps + Test.specify "should support integer division" <| (10.div 3) . should_equal 3 (10.div 0).should_fail_with Arithmetic_Error + Test.specify "should support integral binary literals" <| lit = 2_01101101 lit . should_equal 109 + Test.specify "should support integral hexadecimal literals" <| lit = 16_6D lit . should_equal 109 + Test.specify "should support bitwise and" <| left = 2_01101101 right = 2_11000100 @@ -58,6 +77,7 @@ spec = left.bit_and big_right . should_equal 2_01101100 big_left.bit_and right . should_equal 2_11000000 big_left.bit_and big_right . should_equal 16_17ffffffffffffff8 + Test.specify "should support bitwise or" <| left = 2_01101101 right = 2_11000100 @@ -67,6 +87,7 @@ spec = left.bit_or big_right . should_equal 16_17ffffffffffffffd big_left.bit_or right . should_equal 16_17ffffffffffffffe big_left.bit_or right . should_equal 16_17ffffffffffffffe + Test.specify "should support bitwise exclusive or" <| left = 2_01101101 right = 2_11000100 @@ -76,6 +97,7 @@ spec = left.bit_xor big_right . should_equal 16_17fffffffffffff91 big_left.bit_xor right . should_equal 16_17fffffffffffff3e big_left.bit_xor big_right . should_equal 2_00000110 + Test.specify "should support bitwise negation" <| bits = 2_01101101 big_bits = 16_17ffffffffffffffa @@ -83,6 +105,7 @@ spec = bits.bit_not.bit_not . should_equal bits big_bits.bit_not . should_equal -16_17ffffffffffffffb big_bits.bit_not.bit_not . should_equal big_bits + Test.specify "should support left bit shifts" <| positive_bits = 2_01101101 negative_bits = -2_01101101 @@ -93,28 +116,28 @@ spec = positive_bits.bit_shift_l 64 . should_equal 16_6d0000000000000000 positive_bits.bit_shift_l -2 . should_equal 2_011011 positive_bits.bit_shift_l -64 . should_equal 0 - (positive_bits.bit_shift_l positive_big_bits).should_error_with Arithmetic_Error + (positive_bits.bit_shift_l positive_big_bits).should_fail_with Arithmetic_Error positive_bits.bit_shift_l negative_big_bits . should_equal 0 negative_bits.bit_shift_l 2 . should_equal -436 negative_bits.bit_shift_l 64 . should_equal -2010695104034341126144 negative_bits.bit_shift_l -2 . should_equal -28 negative_bits.bit_shift_l -64 . should_equal -1 - (negative_bits.bit_shift_l positive_big_bits).should_error_with Arithmetic_Error + (negative_bits.bit_shift_l positive_big_bits).should_fail_with Arithmetic_Error negative_bits.bit_shift_l negative_big_bits . should_equal -1 positive_big_bits.bit_shift_l 2 . should_equal 110680464442257309672 positive_big_bits.bit_shift_l 64 . should_equal 510423550381407695084381446705395007488 positive_big_bits.bit_shift_l -2 . should_equal 6917529027641081854 positive_big_bits.bit_shift_l -100 . should_equal 0 - (positive_big_bits.bit_shift_l positive_big_bits).should_error_with Arithmetic_Error + (positive_big_bits.bit_shift_l positive_big_bits).should_fail_with Arithmetic_Error positive_big_bits.bit_shift_l negative_big_bits . should_equal 0 negative_big_bits.bit_shift_l 2 . should_equal -110680464442257309672 negative_big_bits.bit_shift_l 64 . should_equal -510423550381407695084381446705395007488 negative_big_bits.bit_shift_l -2 . should_equal -6917529027641081855 negative_big_bits.bit_shift_l -100 . should_equal -1 - (negative_big_bits.bit_shift_l positive_big_bits).should_error_with Arithmetic_Error + (negative_big_bits.bit_shift_l positive_big_bits).should_fail_with Arithmetic_Error negative_big_bits.bit_shift_l negative_big_bits . should_equal -1 Test.specify "should support right bit shifts, preserving sign" <| positive_bits = 2_01101101 @@ -126,39 +149,50 @@ spec = positive_bits.bit_shift_r 64 . should_equal (positive_bits.bit_shift_l -64) positive_bits.bit_shift_r -2 . should_equal (positive_bits.bit_shift_l 2) positive_bits.bit_shift_r -64 . should_equal (positive_bits.bit_shift_l 64) - (positive_bits.bit_shift_r negative_big_bits).should_error_with Arithmetic_Error + (positive_bits.bit_shift_r negative_big_bits).should_fail_with Arithmetic_Error positive_bits.bit_shift_r positive_big_bits . should_equal 0 negative_bits.bit_shift_r 2 . should_equal (negative_bits.bit_shift_l -2) negative_bits.bit_shift_r 64 . should_equal (negative_bits.bit_shift_l -64) negative_bits.bit_shift_r -2 . should_equal (negative_bits.bit_shift_l 2) negative_bits.bit_shift_r -64 . should_equal (negative_bits.bit_shift_l 64) - (negative_bits.bit_shift_r negative_big_bits).should_error_with Arithmetic_Error + (negative_bits.bit_shift_r negative_big_bits).should_fail_with Arithmetic_Error negative_bits.bit_shift_r positive_big_bits . should_equal -1 positive_big_bits.bit_shift_r 2 . should_equal (positive_big_bits.bit_shift_l -2) positive_big_bits.bit_shift_r 64 . should_equal (positive_big_bits.bit_shift_l -64) positive_big_bits.bit_shift_r -2 . should_equal (positive_big_bits.bit_shift_l 2) positive_big_bits.bit_shift_r -100 . should_equal (positive_big_bits.bit_shift_l 100) - (positive_big_bits.bit_shift_r negative_big_bits).should_error_with Arithmetic_Error + (positive_big_bits.bit_shift_r negative_big_bits).should_fail_with Arithmetic_Error positive_big_bits.bit_shift_r positive_big_bits . should_equal 0 negative_big_bits.bit_shift_r 2 . should_equal (negative_big_bits.bit_shift_l -2) negative_big_bits.bit_shift_r 64 . should_equal (negative_big_bits.bit_shift_l -64) negative_big_bits.bit_shift_r -2 . should_equal (negative_big_bits.bit_shift_l 2) negative_big_bits.bit_shift_r -100 . should_equal (negative_big_bits.bit_shift_l 100) - (negative_big_bits.bit_shift_r negative_big_bits).should_error_with Arithmetic_Error + (negative_big_bits.bit_shift_r negative_big_bits).should_fail_with Arithmetic_Error negative_big_bits.bit_shift_r positive_big_bits . should_equal -1 + Test.group "Decimals" <| + Test.specify "should exist and expose basic arithmetic operations" <| ((1.5 + 1.5)*1.3 / 2 - 3) . should_equal -1.05 epsilon=eps + Test.specify "should allow defining extension methods through the Decimal type" <| 32.5.get_fun_factor.should_equal "Wow, 32.5 is such a fun number!" + + Test.specify "should be able to be parsed" <| + Decimal.parse "32.5" . should_equal 32.5 + Decimal.parse "aaaa" . should_fail_with Parse_Error + Test.group "Numbers" <| + Test.specify "should define addition" <| 2+3 . should_equal 5 + Test.specify "should define multiplication" <| 2*3 . should_equal 6 + Test.specify "should expose comparison operators" <| (3 < hundred_factorial).should_be_true (hundred_factorial < 3).should_be_false @@ -166,6 +200,7 @@ spec = (3.0 <= 3.4).should_be_true (3.0 >= 2).should_be_true (-hundred_factorial > 0).should_be_false + Test.specify "should expose exponentiation operations" <| (3.14 ^ 2.71).should_equal 22.216689546 epsilon=eps (3.14 ^ 14).should_equal 9057640.36635 epsilon=eps @@ -174,17 +209,20 @@ spec = (2 ^ 10).should_equal 1024 (2 ^ 0.5).should_equal 1.41421356237 epsilon=eps (a^2)^0.5 . should_equal a epsilon=eps + Test.specify "should expose more involved mathematical functions" <| Math.Pi.sin.should_equal 0 epsilon=eps (Math.Pi / 4).sin.should_equal (2 ^ 0.5)/2 epsilon=eps (Math.Pi / 6).cos.should_equal (3.sqrt / 2) epsilon=eps (17 ^ 0.13).log base=17 . should_equal 0.13 epsilon=eps 0.exp.should_equal 1 + Test.specify "should allow calculating the floor value" <| 1.2314.floor . should_equal 1 1.floor . should_equal 1 almost_max_long_times_three_decimal.floor.to_decimal . should_equal almost_max_long_times_three.to_decimal almost_max_long_times_three.floor . should_equal almost_max_long_times_three + Test.specify "should allow calculating the ceil value" <| 1.2314.ceil . should_equal 2 1.ceil . should_equal 1 diff --git a/test/Tests/src/Data/Vector_Spec.enso b/test/Tests/src/Data/Vector_Spec.enso index f0f2df623022..d1dca94ee0c4 100644 --- a/test/Tests/src/Data/Vector_Spec.enso +++ b/test/Tests/src/Data/Vector_Spec.enso @@ -36,12 +36,12 @@ spec = Test.group "Vectors" <| Test.specify "should allow to reduce elements if it is non-empty" <| [1,2,3].reduce (+) . should_equal 6 - [].reduce (+) . should_fail_with Nothing + [].reduce (+) . should_fail_with Vector.Empty_Error Test.specify "should allow summing elements if they define +" <| [1,2,3].sum . should_equal 6 - [].sum . should_fail_with Nothing - [T 1 2, T 3 4].sum . should_fail_with Nothing + [].sum . should_fail_with Vector.Empty_Error + [T 1 2, T 3 4].sum . should_fail_with No_Such_Method_Error Test.specify "should check exists" <| vec = [1, 2, 3, 4, 5] @@ -130,7 +130,7 @@ spec = Test.group "Vectors" <| empty_vec = [] non_empty_vec.head . should_equal 1 singleton_vec.head . should_equal 1 - empty_vec.head . should_fail_with Nothing + empty_vec.head . should_fail_with Vector.Empty_Error Test.specify "should allow getting the tail of the vector" <| non_empty_vec = [1, 2, 3, 4, 5] @@ -138,7 +138,7 @@ spec = Test.group "Vectors" <| empty_vec = [] non_empty_vec.tail . should_equal [2, 3, 4, 5] singleton_vec.tail . should_equal [] - empty_vec.tail . should_fail_with Nothing + empty_vec.tail . should_fail_with Vector.Empty_Error Test.specify "should allow getting the init of the vector" <| non_empty_vec = [1, 2, 3, 4, 5] @@ -146,7 +146,7 @@ spec = Test.group "Vectors" <| empty_vec = [] non_empty_vec.init . should_equal [1, 2, 3, 4] singleton_vec.init . should_equal [] - empty_vec.init . should_fail_with Nothing + empty_vec.init . should_fail_with Vector.Empty_Error Test.specify "should allow getting the last element of the vector" <| non_empty_vec = [1, 2, 3, 4, 5] @@ -154,7 +154,7 @@ spec = Test.group "Vectors" <| empty_vec = [] non_empty_vec.last . should_equal 5 singleton_vec.last . should_equal 1 - empty_vec.last . should_fail_with Nothing + empty_vec.last . should_fail_with Vector.Empty_Error Test.specify "should allow getting the first element" <| non_empty_vec = [1, 2, 3, 4, 5] @@ -162,7 +162,7 @@ spec = Test.group "Vectors" <| empty_vec = [] non_empty_vec.first . should_equal 1 singleton_vec.first . should_equal 1 - empty_vec.first . should_fail_with Nothing + empty_vec.first . should_fail_with Vector.Empty_Error Test.specify "should allow getting the rest of the vector" <| non_empty_vec = [1, 2, 3, 4, 5] @@ -170,7 +170,7 @@ spec = Test.group "Vectors" <| empty_vec = [] non_empty_vec.rest . should_equal [2, 3, 4, 5] singleton_vec.rest . should_equal [] - empty_vec.rest . should_fail_with Nothing + empty_vec.rest . should_fail_with Vector.Empty_Error Test.specify "should be able to be sorted" <| empty_vec = [] diff --git a/test/Tests/src/System/File_Spec.enso b/test/Tests/src/System/File_Spec.enso index 3d9190be7f76..c662c6519d24 100644 --- a/test/Tests/src/System/File_Spec.enso +++ b/test/Tests/src/System/File_Spec.enso @@ -6,34 +6,51 @@ spec = sample_file = Enso_Project.data / "sample.txt" non_existent_file = File.new "does_not_exist.txt" Test.group "File Operations" <| + + Test.specify "should allow creating a new file" <| + path = sample_file.path + File.new path + + Test.specify "should have `new` be a no-op on a file" <| + file = File.new sample_file + file . should_equal sample_file + Test.specify "should allow reading a file" <| contents = sample_file.read contents.should .start_with "Cupcake ipsum dolor sit amet." + Test.specify "should allow reading a file to byte vector" <| contents = sample_file.read_bytes contents.take_start 6 . should_equal [67, 117, 112, 99, 97, 107] + Test.specify "should handle exceptions when reading a non-existent file" <| file = File.new "does_not_exist.txt" successfully_failed = Panic.recover file.read . catch e-> case e of File.No_Such_File_Error _ -> True _ -> False successfully_failed . should_be_true + Test.specify "should check if file exists" <| non_existent_file.exists.should_be_false sample_file.exists.should_be_true + Test.specify "should check if file is a directory" <| sample_file.is_directory.should_be_false Enso_Project.root.is_directory.should_be_true + Test.specify "should get file name" <| sample_file.name.should_equal "sample.txt" + Test.specify "should convert a file to absolute" <| abs = File.new "foo.txt" . absolute through_cwd = (File.current_directory / "foo.txt") abs.should_equal through_cwd + Test.specify "should normalize file" <| f_1 = File.new "foo" f_2 = File.new "bar/../baz/../foo" f_2.normalize.should_equal f_1 + Test.specify "should allow reading a file byte by byte" <| f = Enso_Project.data / "short.txt" f.delete_if_exists @@ -46,6 +63,7 @@ spec = stream.read_byte.should_equal -1 f.delete f.exists.should_be_false + Test.specify "should write and append to files" <| f = Enso_Project.data / "work.txt" f.delete_if_exists @@ -57,8 +75,12 @@ spec = f.read.should_equal 'line 1!\nline 2!' f.delete f.exists.should_be_false + Test.specify "should open and read the file in one shot" <| path_name = sample_file.path contents = File.read path_name contents.should .start_with "Cupcake ipsum dolor sit amet." + file = sample_file + contents_2 = File.read file + contents_2.should .start_with "Cupcake ipsum dolor sit amet."