diff --git a/CHANGELOG.md b/CHANGELOG.md index 129d4423e243..a03061422fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ - [Made `Text.compare_to` correctly handle Unicode normalization][3282] - [Extend `Text.contains` API to support regex and case insensitive search.][3285] +- [Implemented new `Text.take` and `Text.drop` functions, replacing existing + functions][3287] [debug-shortcuts]: https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug @@ -75,6 +77,7 @@ [3283]: https://github.com/enso-org/enso/pull/3283 [3282]: https://github.com/enso-org/enso/pull/3282 [3285]: https://github.com/enso-org/enso/pull/3285 +[3287]: https://github.com/enso-org/enso/pull/3287 #### Enso Compiler diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso index 093f2d2e2fc8..2d6745125b09 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Range.enso @@ -171,3 +171,14 @@ type Range to_vector = length = Math.max 0 (this.end - this.start) Vector.new length (i -> i + this.start) + + ## Does the range contains the specified value + + > Example + Check if an index is in the range of a Vector + + vec = ["A", "B", "C", "D", "E"] + 0.up_to vec.length . contains 3 + contains : Integer -> Boolean + contains value = + (this.start <= value) && (this.end > value) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso index b9be433ab624..f7f45766c950 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso @@ -7,6 +7,7 @@ import Standard.Base.Data.Text.Regex import Standard.Base.Data.Text.Regex.Mode import Standard.Base.Data.Text.Line_Ending_Style import Standard.Base.Data.Text.Split_Kind +import Standard.Base.Data.Text.Text_Sub_Range import Standard.Base.Data.Locale import Standard.Base.Meta @@ -45,13 +46,10 @@ Text.length : Integer Text.length = iterator = BreakIterator.getCharacterInstance iterator.setText this - nxt = iterator.next count accum iter = if iter == -1 then accum else - counter = accum + 1 - next_nxt = iterator.next - @Tail_Call count counter next_nxt - count 0 nxt + @Tail_Call count (accum + 1) iterator.next + count 0 iterator.next ## Applies the provided `function` to each character in `this`. @@ -72,15 +70,10 @@ Text.each function = iterator = BreakIterator.getCharacterInstance iterator.setText this - fst = iterator.first - nxt = iterator.next - iterate prev nxt = if nxt == -1 then Nothing else function (Text_Utils.substring this prev nxt) - next_nxt = iterator.next - @Tail_Call iterate nxt next_nxt - iterate fst nxt - Nothing + @Tail_Call iterate nxt iterator.next + iterate iterator.first iterator.next ## ALIAS Get Character @@ -112,17 +105,10 @@ Text.at index = False -> iterator = BreakIterator.getCharacterInstance iterator.setText this - - loop prev next count = if count == index then (Text_Utils.substring this prev next) else - next_next = iterator.next - if next_next == -1 then count else - @Tail_Call loop next next_next (count + 1) - - first = iterator.next - result = if (first == -1) then 0 else (loop 0 first 0) - case result of - Integer -> Error.throw (Index_Out_Of_Bounds_Error index result) - _ -> result + first = iterator.next index + next = if first == -1 then -1 else iterator.next + if (next == -1) then (Error.throw (Index_Out_Of_Bounds_Error index this.length)) else + Text_Utils.substring this first next ## ALIAS Get Characters @@ -850,106 +836,116 @@ Text.repeat : Integer -> Text Text.repeat count = 0.up_to count . fold "" acc-> _-> acc + this -## Creates a new text by removing the first `count` characters of `this`, - returning an empty text if `count` is greater than or equal to the length of - `this`. - - Arguments: - - count: The number of characters to remove from the start of `this`. - - ! What is a Character? - A character is defined as an Extended Grapheme Cluster, see Unicode - Standard Annex 29. This is the smallest unit that still has semantic - meaning in most text-processing applications. - - > Example - Removing the first three characters from the text "ABBA". - - "ABBA".drop_first 3 -Text.drop_first : Integer -> Text -Text.drop_first count = - iterator = BreakIterator.getCharacterInstance - iterator.setText this - iterator.first - boundary = iterator.next count - if boundary == -1 then '' else Text_Utils.drop_first this boundary - -## Creates a new text by removing the last `count` characters of `this`, - returning an empty text if `count` is greater than or equal to the length of - `this`. - - Arguments: - - count: The number of characters to remove from the end of `this`. - - ! What is a Character? - A character is defined as an Extended Grapheme Cluster, see Unicode - Standard Annex 29. This is the smallest unit that still has semantic - meaning in most text-processing applications. - - > Example - Removing the last three characters from the text "ABBA". - - "ABBA".drop_last 3 -Text.drop_last : Integer -> Text -Text.drop_last count = - iterator = BreakIterator.getCharacterInstance - iterator.setText this - iterator.last - boundary = iterator.next -count - if boundary == -1 then '' else Text_Utils.substring this 0 boundary - -## Creates a new text by selecting the first `count` characters of `this`, - returning `this` if `count` is greater than or equal to the length of `this`. +## PRIVATE + Utility function taking a range pointing at grapheme clusters and converting to a range on the underlying code points +range_to_char_indices : Text -> Range -> Range ! Index_Out_Of_Bounds_Error +range_to_char_indices text range = + len = text.length + start = if range.start < 0 then range.start + len else range.start + end = if range.end == Nothing then len else (if range.end < 0 then range.end + len else range.end) + is_valid = (Range 0 len+1).contains + + case (Pair (is_valid start) (is_valid end)) of + Pair False _ -> Error.throw (Index_Out_Of_Bounds_Error range.start len) + Pair True False -> Error.throw (Index_Out_Of_Bounds_Error range.end len) + Pair True True -> + if start>=end then (Range 0 0) else + iterator = BreakIterator.getCharacterInstance + iterator.setText text + + start_index = iterator.next start + end_index = iterator.next (end - start) + Range start_index end_index + +## ALIAS first, last, left, right, mid, substring + Creates a new Text by selecting the specified range of the input. + + This can select a section of text from the beginning, end, or middle of the + input using various criteria defined by the range parameter. Arguments: - - count: The number of characters to take from the start of `this`. - - ! What is a Character? - A character is defined as an Extended Grapheme Cluster, see Unicode - Standard Annex 29. This is the smallest unit that still has semantic - meaning in most text-processing applications. - - > Example - Make a new text from the first two characters of "boo". - - "boo".take_first 2 -Text.take_first : Integer -> Text -Text.take_first count = - iterator = BreakIterator.getCharacterInstance - iterator.setText this - iterator.first - boundary = iterator.next count - if boundary == -1 then this else Text_Utils.substring this 0 boundary - -## Creates a new text by selecting the last `count` characters of `this`, - returning `this` if `count` is greater than or equal to the length of `this`. + - range: The section of the this text to return. + If a `Text_Sub_Range`, then the selection is interpreted following the rules of that type. + If a `Range`, the selection is specified by two indices, from and to. + + Returns: + The part of the input as specified by the range parameter. + + > Examples + Various different ways to take part of "Hello World!" + + "Hello World!".take First.new == "H" + "Hello World!".take (First 5) == "Hello" + "Hello World!".take (First 0) == "" + "Hello World!".take Last.new == "!" + "Hello World!".take (Last 6) == "World!" + "Hello World!".take (Before " ") == "Hello" + "Hello World!".take (Before_Last "o") == "Hello W" + "Hello World!".take (After " ") == "World!" + "Hello World!".take (After_Last "o") == "rld!" + "Hello World!".take (While c->c!=" ") == "Hello" + "Hello World!".take (Range 3 5) == "lo" + "Hello World!".take (Range -3 -1) == "ld" + "Hello World!".take (Range -3 Nothing) == "ld!" + "Hello World!".take (Range 5 Nothing) == " World!" + "Hello World!".take (Range 5 12) == " World!" + "Hello World!".take (Range 12 12) == "" +Text.take : (Text_Sub_Range | Range) -> Text ! Index_Out_Of_Bounds_Error +Text.take range = + char_range = case range of + Range _ _ -> here.range_to_char_indices this range + _ -> range.to_char_range this + if char_range.is_error then char_range else + Text_Utils.substring this char_range.start char_range.end + +## Creates a new Text by removing the specified range of the input. + + This can select a section of text from the beginning, end, or middle of the + input using various criteria defined by the range parameter. Arguments: - - count: The number of characters to take from the end of `this`. - - ! What is a Character? - A character is defined as an Extended Grapheme Cluster, see Unicode - Standard Annex 29. This is the smallest unit that still has semantic - meaning in most text-processing applications. - - > Example - Make a new text from the last two characters of "boo". - - "boo".take_last 2 -Text.take_last : Integer -> Text -Text.take_last count = - iterator = BreakIterator.getCharacterInstance - iterator.setText this - iterator.last - boundary = iterator.next -count - if boundary == -1 then this else Text_Utils.drop_first this boundary + - range: The section of the this text to return. + If a `Text_Sub_Range`, then the selection is interpreted following the rules of that type. + If a `Range`, the selection is specified by two indices, from and to. + + Returns: + The part of the input as specified by the range parameter. + + > Examples + Various different ways to take part of "Hello World!" + + "Hello World!".drop First.new == "ello World!" + "Hello World!".drop (First 5) == " World!" + "Hello World!".drop (First 0) == "Hello World!" + "Hello World!".drop Last.new == "Hello World" + "Hello World!".drop (Last 6) == "Hello " + "Hello World!".drop (Before " ") == " World!" + "Hello World!".drop (Before_Last "o") == "orld!" + "Hello World!".drop (After " ") == "Hello " + "Hello World!".drop (After_Last "o") == "Hello Wo" + "Hello World!".drop (While c->c!=" ") == " World!" + "Hello World!".drop (Range 3 5) == "Hel World!" + "Hello World!".drop (Range -3 -1) == "Hello Wor!" + "Hello World!".drop (Range -3 Nothing) == "Hello Wor" + "Hello World!".drop (Range 5 Nothing) == "Hello" + "Hello World!".drop (Range 5 12) == "Hello" + "Hello World!".drop (Range 12 12) == "Hello World!" +Text.drop : (Text_Sub_Range | Range) -> Text ! Index_Out_Of_Bounds_Error +Text.drop range = + char_range = case range of + Range _ _ -> here.range_to_char_indices this range + _ -> range.to_char_range this + if char_range.start == 0 then Text_Utils.drop_first this char_range.end else + prefix = Text_Utils.substring this 0 char_range.start + if char_range.end == (Text_Utils.char_length this) then prefix else + prefix + Text_Utils.drop_first this char_range.end ## ALIAS Lower Case Converts each character in `this` to lower case. Arguments: - - locale: specifies the locale for charater case mapping. Defaults to the + - locale: specifies the locale for character case mapping. Defaults to the `Locale.default` locale. ! What is a Character? @@ -978,7 +974,7 @@ Text.to_lower_case locale=Locale.default = Converts each character in `this` to upper case. Arguments: - - locale: specifies the locale for charater case mapping. Defaults to + - locale: specifies the locale for character case mapping. Defaults to `Locale.default`. ! What is a Character? @@ -1001,4 +997,3 @@ Text.to_lower_case locale=Locale.default = Text.to_upper_case : Locale.Locale -> Text Text.to_upper_case locale=Locale.default = UCharacter.toUpperCase locale.java_locale this - diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Sub_Range.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Sub_Range.enso new file mode 100644 index 000000000000..ffa8724ab0ea --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Sub_Range.enso @@ -0,0 +1,126 @@ +from Standard.Base import all +from Standard.Base.Data.Text.Extensions import Index_Out_Of_Bounds_Error + +polyglot java import com.ibm.icu.text.BreakIterator +polyglot java import org.enso.base.Text_Utils + +## Type defining a substring of a Text +type Text_Sub_Range + ## Select the first `count` characters. + Select an empty string if `count` is less than or equal to 0. + Select the entire string if `count` is greater than the length of the input. + type First (count : Integer = 1) + + ## Select the last `count` characters. + Select an empty string if `count` is less than or equal to 0. + Select the entire string if `count` is greater than the length of the input. + type Last (count : Integer = 1) + + ## Select characters until the first instance of `delimiter`. + Select an empty string if `delimiter` is empty. + Select the entire string if the input does not contain `delimiter`. + type Before (delimiter : Text) + + ## Select characters until the last instance of `delimiter`. + Select an empty string if `delimiter` is empty. + Select the entire string if the input does not contain `delimiter`. + type Before_Last (delimiter : Text) + + ## Select characters after the first instance of `delimiter`. + Select an empty string if the input does not contain `delimiter`. + type After (delimiter : Text) + + ## Select characters after the last instance of `delimiter`. + Select an empty string if the input does not contain `delimiter`. + type After_Last (delimiter : Text) + + ## Select characters while the predicate returns `True`. + type While (predicate : (Text -> Boolean)) + + ## PRIVATE + Finds code-point indices corresponding to the part of the input matching the `Text_Sub_Range`. + to_char_range : Text -> Range + to_char_range text = + + ## Utility function to find char indices for Text_Sub_Range. + Arguments: + - text: Text to search + - predicate: Function to test each character, receives: + - index: current index + - start: index the char array to start of grapheme cluster + - end: index the char array to start of next grapheme cluster + If the predicate returns True for a given character, the loop will exit. + Returns: either a Pair of char indices for current grapheme cluster or + Pair -1 (char array length) if not found. + find_sub_range_end = text->predicate-> + iterator = BreakIterator.getCharacterInstance + iterator.setText text + + loop index start end = + if end == -1 then (Pair -1 start) else + if predicate index start end then (Pair start end) else + @Tail_Call loop (index + 1) end iterator.next + + loop 0 0 iterator.next + + case this of + First count -> + if count <= 0 then (Range 0 0) else + iterator = BreakIterator.getCharacterInstance + iterator.setText text + start_index = iterator.next count + Range 0 (if start_index == -1 then (Text_Utils.char_length text) else start_index) + Last count -> + if count <= 0 then (Range 0 0) else + first_count = text.length - count + iterator = BreakIterator.getCharacterInstance + iterator.setText text + start_index = iterator.next first_count + Range (if start_index == -1 then 0 else start_index) (Text_Utils.char_length text) + Before delimiter -> + if delimiter.is_empty then (Range 0 0) else + index = Text_Utils.index_of text delimiter + if index == -1 then (Range 0 (Text_Utils.char_length text)) else + (Range 0 index) + Before_Last delimiter -> + if delimiter.is_empty then (Range 0 (Text_Utils.char_length text)) else + index = Text_Utils.last_index_of text delimiter + if index == -1 then (Range 0 (Text_Utils.char_length text)) else + (Range 0 index) + After delimiter -> + if delimiter.is_empty then (Range 0 (Text_Utils.char_length text)) else + index = Text_Utils.index_of text delimiter + if index == -1 then (Range 0 0) else + (Range (index + Text_Utils.char_length delimiter) (Text_Utils.char_length text)) + After_Last delimiter -> + if delimiter.is_empty then (Range 0 0) else + index = Text_Utils.last_index_of text delimiter + if index == -1 then (Range 0 0) else + (Range (index + Text_Utils.char_length delimiter) (Text_Utils.char_length text)) + While predicate -> + indices = find_sub_range_end text _-> start-> end-> + predicate (Text_Utils.substring text start end) . not + if indices.first == -1 then (Range 0 indices.second) else + Range 0 indices.first + +## UNSTABLE + A temporary workaround to allow the `First` constructor to work with default + arguments. + + It is needed, because there are issues with relying on default arguments of + Atom constructors, as described in the following issue: + https://github.com/enso-org/enso/issues/1600 + Once that issue is fixed, it can be removed. +First.new : Integer -> First +First.new (count = 1) = First count + +## UNSTABLE + A temporary workaround to allow the `Last` constructor to work with default + arguments. + + It is needed, because there are issues with relying on default arguments of + Atom constructors, as described in the following issue: + https://github.com/enso-org/enso/issues/1600 + Once that issue is fixed, it can be removed. +Last.new : Integer -> Last +Last.new (count = 1) = Last count diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column_Mapping.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column_Mapping.enso index d21561917f03..471be9540c5c 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column_Mapping.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Column_Mapping.enso @@ -32,7 +32,7 @@ type Column_Mapping ## Selects columns by position starting at the first column until the new_names is exhausted. - type By_Position (new_names : Vector Text) + type By_Position (new_names : [Text]) ## UNSTABLE A temporary workaround to allow the By_Name constructor to work with default arguments. diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso index 4bfc2410c8fd..370eae07a6f8 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Data/Table.enso @@ -257,9 +257,9 @@ type Table - If duplicate columns, names or indices are provided, a `Duplicate_Column_Selectors`. - If a column index is out of range, a `Column_Indexes_Out_Of_Range`. - - If two distinct indices would refer to the same column, a - `Input_Indices_Already_Matched`, indicating that the additional - indices will not introduce additional columns. + - If two distinct indices refer to the same column, an + `Input_Indices_Already_Matched`, with the column included the first + time it is matched. - If there are no columns in the output table, a `No_Output_Columns`. - warnings: A `Warning_System` instance specifying how to handle warnings. This is a temporary workaround to allow for testing the @@ -311,9 +311,9 @@ type Table - If duplicate columns, names or indices are provided, a `Duplicate_Column_Selectors`. - If a column index is out of range, a `Column_Indexes_Out_Of_Range`. - - If two distinct indices would refer to the same column, a - `Input_Indices_Already_Matched`, indicating that the additional - indices will not introduce additional columns. + - If two distinct indices refer to the same column, an + `Input_Indices_Already_Matched`, with the column included the first + time it is matched. - If there are no columns in the output table, a `No_Output_Columns`. - warnings: A `Warning_System` instance specifying how to handle warnings. This is a temporary workaround to allow for testing the @@ -366,9 +366,9 @@ type Table - If duplicate columns, names or indices are provided, a `Duplicate_Column_Selectors`. - If a column index is out of range, a `Column_Indexes_Out_Of_Range`. - - If two distinct indices would refer to the same column, a - `Input_Indices_Already_Matched`, indicating that the additional - indices will not introduce additional columns. + - If two distinct indices refer to the same column, an + `Input_Indices_Already_Matched`, with the column included the first + time it is matched. - warnings: A `Warning_System` instance specifying how to handle warnings. This is a temporary workaround to allow for testing the warning mechanism. Once the proper warning system is implemented, this @@ -449,9 +449,10 @@ type Table - If duplicate columns, names or indices are provided, a `Duplicate_Column_Selectors`. - If a column index is out of range, a `Column_Indexes_Out_Of_Range`. - - If two distinct indices would refer to the same column, a - `Input_Indices_Already_Matched`, indicating that the additional - indices will not introduce additional columns. + - If two distinct indices refer to the same column, an + `Input_Indices_Already_Matched`. + - If in `By_Position` mode and more names than columns are provided, + a `Too_Many_Column_Names_Provided`. - If any of the new names are invalid, an `Invalid_Output_Column_Names`. - If any of the new names clash either with existing names or each @@ -481,14 +482,6 @@ type Table them as warnings by default. The following problems can occur: - - If a column in columns is not in the input table, a - `Missing_Input_Columns`. - - If duplicate columns, names or indices are provided, a - `Duplicate_Column_Selectors`. - - If a column index is out of range, a `Column_Indexes_Out_Of_Range`. - - If two distinct indices would refer to the same column, a - `Input_Indices_Already_Matched`, indicating that the additional - indices will not introduce additional columns. - If any of the new names are invalid, an `Invalid_Output_Column_Names`. - If any of the new names clash either with existing names or each diff --git a/distribution/lib/Standard/Table/0.0.0-dev/src/Error.enso b/distribution/lib/Standard/Table/0.0.0-dev/src/Error.enso index 3167ffaee9f7..076a9b141c5b 100644 --- a/distribution/lib/Standard/Table/0.0.0-dev/src/Error.enso +++ b/distribution/lib/Standard/Table/0.0.0-dev/src/Error.enso @@ -56,13 +56,12 @@ Duplicate_Column_Selectors.to_display_text : Text Duplicate_Column_Selectors.to_display_text = "The provided Column_Selector has duplicate entries: "+this.duplicate_selectors.short_display_text+"." -## Indicates that the provided indices matched columns which were already - matched by other indices, so they do not introduce any new columns to the - input. +## Indicates that the provided indices matched columns already matched by + others, so they do not introduce any new columns to the input. - For example, if the table has only one column, then selecting - `By_Index [0, -1]` will only yield this single column and - `Input_Indices_Already_Matched [-1]` will be raised. + For example, if the table has only one column, then selecting + `By_Index [0, -1]` will only yield this single column and + `Input_Indices_Already_Matched [-1]` will be raised. type Input_Indices_Already_Matched (indices : [Integer]) Input_Indices_Already_Matched.to_display_text : Text diff --git a/std-bits/base/src/main/java/org/enso/base/Text_Utils.java b/std-bits/base/src/main/java/org/enso/base/Text_Utils.java index 6e8a7e11d257..bdc2215730a6 100644 --- a/std-bits/base/src/main/java/org/enso/base/Text_Utils.java +++ b/std-bits/base/src/main/java/org/enso/base/Text_Utils.java @@ -221,6 +221,50 @@ public static String replace(String str, String oldSequence, String newSequence) return str.replace(oldSequence, newSequence); } + /** + * Gets the length of char array of a string + * + * @param str the string to measure + * @return length of the string + */ + public static long char_length(String str) { + return str.length(); + } + + /** + * Find the first index of needle in the haystack + * + * @param haystack the string to search + * @param needle the substring that is searched for + * @return index of the first needle or -1 if not found. + */ + public static long index_of(String haystack, String needle) { + StringSearch search = new StringSearch(needle, haystack); + int pos = search.first(); + return pos == StringSearch.DONE ? -1 : pos; + } + + /** + * Find the last index of needle in the haystack + * + * @param haystack the string to search + * @param needle the substring that is searched for + * @return index of the last needle or -1 if not found. + */ + public static long last_index_of(String haystack, String needle) { + StringSearch search = new StringSearch(needle, haystack); + int pos = search.first(); + if (pos == StringSearch.DONE) { + return -1; + } + + for (int next = search.next(); next != StringSearch.DONE; next = search.next()) { + pos = next; + } + + return pos; + } + /** * Normalizes the string to its canonical Unicode form using NFD decomposition. * diff --git a/test/Tests/src/Data/Range_Spec.enso b/test/Tests/src/Data/Range_Spec.enso index 0f4b183dc11f..358848cc1132 100644 --- a/test/Tests/src/Data/Range_Spec.enso +++ b/test/Tests/src/Data/Range_Spec.enso @@ -45,3 +45,15 @@ spec = Test.group "Range" <| 1.up_to 10 . find (> 10) . should_be_a Nothing Test.specify "should allow conversion to vector" <| 1.up_to 6 . to_vector . should_equal [1, 2, 3, 4, 5] + + Test.specify "should allow checking if a value is in the range" + 0.up_to 10 . contains 5 . should_be_true + 0.up_to 10 . contains 0 . should_be_true + 0.up_to 10 . contains 9 . should_be_true + 0.up_to 10 . contains 10 . should_be_false + 0.up_to 0 . contains 10 . should_be_false + 0.up_to 0 . contains 0 . should_be_false + 3.up_to 5 . contains 2 . should_be_false + 0.up_to 10 . contains -3 . should_be_false + +main = Test.Suite.run_main here.spec diff --git a/test/Tests/src/Data/Text_Spec.enso b/test/Tests/src/Data/Text_Spec.enso index 88d3734c1c73..358fb845b535 100644 --- a/test/Tests/src/Data/Text_Spec.enso +++ b/test/Tests/src/Data/Text_Spec.enso @@ -4,6 +4,7 @@ from Standard.Base.Data.Text.Extensions import Index_Out_Of_Bounds_Error import Standard.Base.Data.Text.Regex.Engine.Default as Default_Engine import Standard.Base.Data.Locale import Standard.Base.Data.Text.Split_Kind +from Standard.Base.Data.Text.Text_Sub_Range import all import Standard.Test type Auto a @@ -154,10 +155,148 @@ spec = Test.specify "should allow selecting substrings by characters" <| txt = kshi + facepalm + accent_1 + accent_2 - txt.take_first 2 . should_equal (kshi + facepalm) - txt.drop_first 2 . should_equal (accent_1 + accent_2) - txt.take_last 2 . should_equal (accent_1 + accent_2) - txt.drop_last 2 . should_equal (kshi + facepalm) + txt.take (First 2) . should_equal (kshi + facepalm) + txt.drop (First 2) . should_equal (accent_1 + accent_2) + txt.take (Last 2) . should_equal (accent_1 + accent_2) + txt.drop (Last 2) . should_equal (kshi + facepalm) + + Test.specify "take should work as in the examples" <| + "Hello World!".take First.new . should_equal "H" + "Hello World!".take (First 5) . should_equal "Hello" + "Hello World!".take (First 100) . should_equal "Hello World!" + "Hello World!".take (First 0) . should_equal "" + "Hello World!".take Last.new . should_equal "!" + "Hello World!".take (Last 6) . should_equal "World!" + "Hello World!".take (Last 100) . should_equal "Hello World!" + "Hello World!".take (Before " ") . should_equal "Hello" + "Hello World!".take (Before "z") . should_equal "Hello World!" + "Hello World!".take (Before_Last "o") . should_equal "Hello W" + "Hello World!".take (Before_Last "z") . should_equal "Hello World!" + "Hello World!".take (After " ") . should_equal "World!" + "Hello World!".take (After "z") . should_equal "" + "Hello World!".take (After_Last "o") . should_equal "rld!" + "Hello World!".take (After_Last "z") . should_equal "" + "Hello World!".take (While c->c!=" ") . should_equal "Hello" + "Hello World!".take (While c->c!="z") . should_equal "Hello World!" + "Hello World!".take (Range 3 5) . should_equal "lo" + "Hello World!".take (Range -3 -1) . should_equal "ld" + "Hello World!".take (Range -3 Nothing) . should_equal "ld!" + "Hello World!".take (Range 5 Nothing) . should_equal " World!" + "Hello World!".take (Range 5 12) . should_equal " World!" + "Hello World!".take (Range 12 12) . should_equal "" + + Test.specify "take should report Index_Out_Of_Bounds_Error for invalid Ranges" <| + "Hello World!".take (Range 0 14) . should_fail_with Index_Out_Of_Bounds_Error + "Hello World!".take (Range 13 12) . should_fail_with Index_Out_Of_Bounds_Error + "Hello World!".take (Range -13 10) . should_fail_with Index_Out_Of_Bounds_Error + "Hello World!".take (Range 0 -20) . should_fail_with Index_Out_Of_Bounds_Error + + Test.specify "take should work on grapheme clusters" <| + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (First 2) . should_equal 'He\u{302}' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (First 5) . should_equal 'He\u{302}llo\u{308}' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (Last 6) . should_equal 'Wo\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (Last 5) . should_equal 'o\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (Before 'e\u{302}') . should_equal 'H' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (Before 'e') . should_equal 'He\u{302}llo\u{308} Wo\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Before_Last 'o\u{308}') . should_equal 'He\u{302}llo\u{308} W' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Before_Last 'o') . should_equal 'He\u{302}llo\u{308} Wo\u{308}rld!' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (After 'e\u{302}') . should_equal 'llo\u{308} Wo\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (After 'e\u{308}') . should_equal '' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.take (After 'e') . should_equal '' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (After_Last 'o\u{308}') . should_equal 'rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (After_Last 'o') . should_equal '' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (While c->c!='e\u{302}') . should_equal 'H' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (While c->c!='e') . should_equal 'He\u{302}llo\u{308} Wo\u{308}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Range 3 5) . should_equal 'lo\u{308}' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Range -3 -1) . should_equal 'ld' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Range -3 Nothing) . should_equal 'ld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Range 5 Nothing) . should_equal ' Wo\u{308}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Range 5 12) . should_equal ' Wo\u{308}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.take (Range 12 12) . should_equal '' + + Test.specify "take should work on emojis" <| + '✨🚀🚧😍😃😎😙😉☺'.take First.new . should_equal '✨' + '✨🚀🚧😍😃😎😙😉☺'.take (First 2) . should_equal '✨🚀' + '✨🚀🚧😍😃😎😙😉☺'.take Last.new . should_equal '☺' + '✨🚀🚧😍😃😎😙😉☺'.take (Last 3) . should_equal '😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.take (Before '😍') . should_equal '✨🚀🚧' + '✨🚀🚧😍😃😍😎😙😉☺'.take (Before_Last '😍') . should_equal '✨🚀🚧😍😃' + '✨🚀🚧😍😃😍😎😙😉☺'.take (After '😍') . should_equal '😃😍😎😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.take (After_Last '😍') . should_equal '😎😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.take (While c->c!="😃") . should_equal '✨🚀🚧😍' + '✨🚀🚧😍😃😍😎😙😉☺'.take (Range 3 6) . should_equal '😍😃😍' + '✨🚀🚧😍😃😍😎😙😉☺'.take (Range 3 Nothing) . should_equal '😍😃😍😎😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.take (Range -3 Nothing) . should_equal '😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.take (Range -3 -1) . should_equal '😙😉' + + Test.specify "drop should work as in the examples" <| + "Hello World!".drop First.new . should_equal "ello World!" + "Hello World!".drop (First 5) . should_equal " World!" + "Hello World!".drop (First 100) . should_equal "" + "Hello World!".drop (First 0) . should_equal "Hello World!" + "Hello World!".drop Last.new . should_equal "Hello World" + "Hello World!".drop (Last 6) . should_equal "Hello " + "Hello World!".drop (Last 100) . should_equal "" + "Hello World!".drop (Before " ") . should_equal " World!" + "Hello World!".drop (Before "z") . should_equal "" + "Hello World!".drop (Before_Last "o") . should_equal "orld!" + "Hello World!".drop (Before_Last "z") . should_equal "" + "Hello World!".drop (After " ") . should_equal "Hello " + "Hello World!".drop (After "z") . should_equal "Hello World!" + "Hello World!".drop (After_Last "o") . should_equal "Hello Wo" + "Hello World!".drop (After_Last "z") . should_equal "Hello World!" + "Hello World!".drop (While c->c!=" ") . should_equal " World!" + "Hello World!".drop (While c->c!="z") . should_equal "" + "Hello World!".drop (Range 3 5) . should_equal "Hel World!" + "Hello World!".drop (Range -3 -1) . should_equal "Hello Wor!" + "Hello World!".drop (Range -3 Nothing) . should_equal "Hello Wor" + "Hello World!".drop (Range 5 Nothing) . should_equal "Hello" + "Hello World!".drop (Range 5 12) . should_equal "Hello" + "Hello World!".drop (Range 12 12) . should_equal "Hello World!" + + Test.specify "drop should report Index_Out_Of_Bounds_Error for invalid Ranges" <| + "Hello World!".drop (Range 0 14) . should_fail_with Index_Out_Of_Bounds_Error + "Hello World!".drop (Range 13 12) . should_fail_with Index_Out_Of_Bounds_Error + "Hello World!".drop (Range -13 10) . should_fail_with Index_Out_Of_Bounds_Error + "Hello World!".drop (Range 0 -20) . should_fail_with Index_Out_Of_Bounds_Error + + Test.specify "drop should work on grapheme clusters" <| + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (First 2) . should_equal 'llo\u{308} Wo\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (First 5) . should_equal ' Wo\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (Last 6) . should_equal 'He\u{302}llo\u{308} ' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (Last 5) . should_equal 'He\u{302}llo\u{308} W' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (Before 'e\u{302}') . should_equal 'e\u{302}llo\u{308} Wo\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (Before 'e') . should_equal '' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Before_Last 'o\u{308}') . should_equal 'o\u{308}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Before_Last 'o') . should_equal '' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (After 'e\u{302}') . should_equal 'He\u{302}' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (After 'e\u{308}') . should_equal 'He\u{302}llo\u{308} Wo\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{301}rld!'.drop (After 'e') . should_equal 'He\u{302}llo\u{308} Wo\u{301}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (After_Last 'o\u{308}') . should_equal 'He\u{302}llo\u{308} Wo\u{308}' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (After_Last 'o') . should_equal 'He\u{302}llo\u{308} Wo\u{308}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (While c->c!='e\u{302}') . should_equal 'e\u{302}llo\u{308} Wo\u{308}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (While c->c!='e') . should_equal '' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Range 3 5) . should_equal 'He\u{302}l Wo\u{308}rld!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Range -3 -1) . should_equal 'He\u{302}llo\u{308} Wo\u{308}r!' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Range -3 Nothing) . should_equal 'He\u{302}llo\u{308} Wo\u{308}r' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Range 5 Nothing) . should_equal 'He\u{302}llo\u{308}' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Range 5 12) . should_equal 'He\u{302}llo\u{308}' + 'He\u{302}llo\u{308} Wo\u{308}rld!'.drop (Range 12 12) . should_equal 'He\u{302}llo\u{308} Wo\u{308}rld!' + + Test.specify "drop should work on emojis" <| + '✨🚀🚧😍😃😎😙😉☺'.drop First.new . should_equal '🚀🚧😍😃😎😙😉☺' + '✨🚀🚧😍😃😎😙😉☺'.drop (First 2) . should_equal '🚧😍😃😎😙😉☺' + '✨🚀🚧😍😃😎😙😉☺'.drop Last.new . should_equal '✨🚀🚧😍😃😎😙😉' + '✨🚀🚧😍😃😎😙😉☺'.drop (Last 3) . should_equal '✨🚀🚧😍😃😎' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (Before '😍') . should_equal '😍😃😍😎😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (Before_Last '😍') . should_equal '😍😎😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (After '😍') . should_equal '✨🚀🚧😍' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (After_Last '😍') . should_equal '✨🚀🚧😍😃😍' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (While c->c!="😃") . should_equal '😃😍😎😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (Range 3 6) . should_equal '✨🚀🚧😎😙😉☺' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (Range 3 Nothing) . should_equal '✨🚀🚧' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (Range -3 Nothing) . should_equal '✨🚀🚧😍😃😍😎' + '✨🚀🚧😍😃😍😎😙😉☺'.drop (Range -3 -1) . should_equal '✨🚀🚧😍😃😍😎☺' Test.specify "should correctly convert character case" <| "FooBar Baz".to_lower_case.should_equal "foobar baz" diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index c717189cb405..24644db5d1a0 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -1,5 +1,6 @@ from Standard.Base import all import Standard.Base.System.Platform +from Standard.Base.Data.Text.Text_Sub_Range import Last import Standard.Test @@ -80,8 +81,8 @@ spec = Test.group "Meta-Value Manipulation" <| _ -> Nothing Test.specify "should allow to get the source location of a frame" pending=location_pending <| src = Meta.get_source_location 0 - loc = "Meta_Spec.enso:82:15-40" - src.take_last loc.length . should_equal loc + loc = "Meta_Spec.enso:83:15-40" + src.take (Last loc.length) . should_equal loc Test.specify "should allow to get qualified type names of values" <| x = 42