diff options
author | Louis Pilfold <louis@lpil.uk> | 2021-07-22 21:44:57 +0100 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2021-07-22 21:44:57 +0100 |
commit | 8d56e443e1df58a320629dfeae42270f9e7ef4da (patch) | |
tree | b4e92d91324a3a622553d247f44f8030310c4839 | |
parent | 0cca8008dd81ee5cf4086bfd28280b2f398096c7 (diff) | |
download | gleam_stdlib-8d56e443e1df58a320629dfeae42270f9e7ef4da.tar.gz gleam_stdlib-8d56e443e1df58a320629dfeae42270f9e7ef4da.zip |
Convert more string functions
-rw-r--r-- | src/gleam/bit_builder.gleam | 28 | ||||
-rw-r--r-- | src/gleam/iterator.gleam | 1555 | ||||
-rw-r--r-- | src/gleam/string.gleam | 494 | ||||
-rw-r--r-- | src/gleam/string_builder.gleam | 4 | ||||
-rw-r--r-- | src/gleam_stdlib.erl | 2 | ||||
-rw-r--r-- | src/gleam_stdlib.js | 85 | ||||
-rw-r--r-- | test/gleam/string_test.gleam | 354 |
7 files changed, 1351 insertions, 1171 deletions
diff --git a/src/gleam/bit_builder.gleam b/src/gleam/bit_builder.gleam index 738c79c..f3cf026 100644 --- a/src/gleam/bit_builder.gleam +++ b/src/gleam/bit_builder.gleam @@ -20,25 +20,25 @@ if erlang { /// /// Runs in constant time. /// - pub external fn prepend(to: BitBuilder, prefix: BitString) -> BitBuilder = - "gleam_stdlib" "iodata_prepend" + pub fn prepend(to: BitBuilder, prefix: BitString) -> BitBuilder { + append_builder(from_bit_string(prefix), to) + } /// Appends a bit string to the end of a builder. /// /// Runs in constant time. /// - pub external fn append(to: BitBuilder, suffix: BitString) -> BitBuilder = - "gleam_stdlib" "iodata_append" + pub fn append(to: BitBuilder, suffix: BitString) -> BitBuilder { + append_builder(to, from_bit_string(suffix)) + } /// Prepends a builder onto the start of another. /// /// Runs in constant time. /// - pub external fn prepend_builder( - to: BitBuilder, - prefix: BitBuilder, - ) -> BitBuilder = - "gleam_stdlib" "iodata_prepend" + pub fn prepend_builder(to: BitBuilder, prefix: BitBuilder) -> BitBuilder { + append_builder(prefix, to) + } /// Appends a builder onto the end of another. /// @@ -54,15 +54,17 @@ if erlang { /// /// Runs in constant time. /// - pub external fn prepend_string(to: BitBuilder, prefix: String) -> BitBuilder = - "gleam_stdlib" "iodata_prepend" + pub fn prepend_string(to: BitBuilder, prefix: String) -> BitBuilder { + append_builder(from_string(prefix), to) + } /// Appends a string onto the end of a builder. /// /// Runs in constant time. /// - pub external fn append_string(to: BitBuilder, suffix: String) -> BitBuilder = - "gleam_stdlib" "iodata_append" + pub fn append_string(to: BitBuilder, suffix: String) -> BitBuilder { + append_builder(to, from_string(suffix)) + } /// Joins a list of builders into a single builders. /// diff --git a/src/gleam/iterator.gleam b/src/gleam/iterator.gleam index fc296e9..e7405bb 100644 --- a/src/gleam/iterator.gleam +++ b/src/gleam/iterator.gleam @@ -1,892 +1,889 @@ +import gleam/list + if erlang { - import gleam/list - import gleam/map.{Map} import gleam/option.{None, Option, Some} + import gleam/map.{Map} +} - // Internal private representation of an Iterator - type Action(element) { - Stop - Continue(element, fn() -> Action(element)) - } +// Internal private representation of an Iterator +type Action(element) { + Stop + Continue(element, fn() -> Action(element)) +} - /// An iterator is a lazily evaluated sequence of element. - /// - /// Iterators are useful when working with collections that are too large to - /// fit in memory (or those that are infinite in size) as they only require the - /// elements currently being processed to be in memory. - /// - /// As a lazy data structure no work is done when an iterator is filters, - /// mapped, etc, instead a new iterator is returned with these transformations - /// applied to the stream. Once the stream has all the required transformations - /// applied it can be evaluated using functions such as `fold` and `to_list`. - /// - pub opaque type Iterator(element) { - Iterator(continuation: fn() -> Action(element)) - } +/// An iterator is a lazily evaluated sequence of element. +/// +/// Iterators are useful when working with collections that are too large to +/// fit in memory (or those that are infinite in size) as they only require the +/// elements currently being processed to be in memory. +/// +/// As a lazy data structure no work is done when an iterator is filters, +/// mapped, etc, instead a new iterator is returned with these transformations +/// applied to the stream. Once the stream has all the required transformations +/// applied it can be evaluated using functions such as `fold` and `to_list`. +/// +pub opaque type Iterator(element) { + Iterator(continuation: fn() -> Action(element)) +} - // Public API for iteration - pub type Step(element, accumulator) { - Next(element: element, accumulator: accumulator) - Done - } +// Public API for iteration +pub type Step(element, accumulator) { + Next(element: element, accumulator: accumulator) + Done +} - // Shortcut for an empty iterator. - fn stop() -> Action(element) { - Stop - } +// Shortcut for an empty iterator. +fn stop() -> Action(element) { + Stop +} - // Creating Iterators - fn do_unfold( - initial: acc, - f: fn(acc) -> Step(element, acc), - ) -> fn() -> Action(element) { - fn() { - case f(initial) { - Next(x, acc) -> Continue(x, do_unfold(acc, f)) - Done -> Stop - } +// Creating Iterators +fn do_unfold( + initial: acc, + f: fn(acc) -> Step(element, acc), +) -> fn() -> Action(element) { + fn() { + case f(initial) { + Next(x, acc) -> Continue(x, do_unfold(acc, f)) + Done -> Stop } } +} - /// Creates an iterator from a given function and accumulator. - /// - /// The function is called on the accumulator and returns either `Done`, - /// indicating the iterator has no more elements, or `Next` which contains a - /// new element and accumulator. The element is yielded by the iterator and the - /// new accumulator is used with the function to compute the next element in - /// the sequence. - /// - /// ## Examples - /// - /// > unfold(from: 5, with: fn(n) { - /// > case n { - /// > 0 -> Done - /// > n -> Next(element: n, accumulator: n - 1) - /// > } - /// > }) - /// > |> to_list - /// [5, 4, 3, 2, 1] - /// - pub fn unfold( - from initial: acc, - with f: fn(acc) -> Step(element, acc), - ) -> Iterator(element) { - initial - |> do_unfold(f) - |> Iterator - } +/// Creates an iterator from a given function and accumulator. +/// +/// The function is called on the accumulator and returns either `Done`, +/// indicating the iterator has no more elements, or `Next` which contains a +/// new element and accumulator. The element is yielded by the iterator and the +/// new accumulator is used with the function to compute the next element in +/// the sequence. +/// +/// ## Examples +/// +/// > unfold(from: 5, with: fn(n) { +/// > case n { +/// > 0 -> Done +/// > n -> Next(element: n, accumulator: n - 1) +/// > } +/// > }) +/// > |> to_list +/// [5, 4, 3, 2, 1] +/// +pub fn unfold( + from initial: acc, + with f: fn(acc) -> Step(element, acc), +) -> Iterator(element) { + initial + |> do_unfold(f) + |> Iterator +} - // TODO: test - /// Creates an iterator that yields values created by calling a given function - /// repeatedly. - /// - pub fn repeatedly(f: fn() -> element) -> Iterator(element) { - unfold(Nil, fn(_) { Next(f(), Nil) }) - } +// TODO: test +/// Creates an iterator that yields values created by calling a given function +/// repeatedly. +/// +pub fn repeatedly(f: fn() -> element) -> Iterator(element) { + unfold(Nil, fn(_) { Next(f(), Nil) }) +} - /// Creates an iterator that returns the same value infinitely. - /// - /// ## Examples - /// - /// > repeat(10) - /// > |> take(4) - /// > |> to_list - /// [10, 10, 10, 10] - /// - pub fn repeat(x: element) -> Iterator(element) { - repeatedly(fn() { x }) - } +/// Creates an iterator that returns the same value infinitely. +/// +/// ## Examples +/// +/// > repeat(10) +/// > |> take(4) +/// > |> to_list +/// [10, 10, 10, 10] +/// +pub fn repeat(x: element) -> Iterator(element) { + repeatedly(fn() { x }) +} - /// Creates an iterator that yields each element from the given list. - /// - /// ## Examples - /// - /// > from_list([1, 2, 3, 4]) |> to_list - /// [1, 2, 3, 4] - /// - pub fn from_list(list: List(element)) -> Iterator(element) { - let yield = fn(acc) { - case acc { - [] -> Done - [head, ..tail] -> Next(head, tail) - } +/// Creates an iterator that yields each element from the given list. +/// +/// ## Examples +/// +/// > from_list([1, 2, 3, 4]) |> to_list +/// [1, 2, 3, 4] +/// +pub fn from_list(list: List(element)) -> Iterator(element) { + let yield = fn(acc) { + case acc { + [] -> Done + [head, ..tail] -> Next(head, tail) } - unfold(list, yield) } + unfold(list, yield) +} - // Consuming Iterators - fn do_fold( - continuation: fn() -> Action(e), - f: fn(e, acc) -> acc, - accumulator: acc, - ) -> acc { - case continuation() { - Continue(elem, next) -> do_fold(next, f, f(elem, accumulator)) - Stop -> accumulator - } +// Consuming Iterators +fn do_fold( + continuation: fn() -> Action(e), + f: fn(e, acc) -> acc, + accumulator: acc, +) -> acc { + case continuation() { + Continue(elem, next) -> do_fold(next, f, f(elem, accumulator)) + Stop -> accumulator } +} - /// Reduces an iterator of elements into a single value by calling a given - /// function on each element in turn. - /// - /// If called on an iterator of infinite length then this function will never - /// return. - /// - /// If you do not care about the end value and only wish to evaluate the - /// iterator for side effects consider using the `run` function instead. - /// - /// ## Examples - /// - /// > [1, 2, 3, 4] - /// > |> from_list - /// > |> fold(from: 0, with: fn(element, acc) { element + acc }) - /// 10 - /// - pub fn fold( - over iterator: Iterator(e), - from initial: acc, - with f: fn(e, acc) -> acc, - ) -> acc { - iterator.continuation - |> do_fold(f, initial) - } +/// Reduces an iterator of elements into a single value by calling a given +/// function on each element in turn. +/// +/// If called on an iterator of infinite length then this function will never +/// return. +/// +/// If you do not care about the end value and only wish to evaluate the +/// iterator for side effects consider using the `run` function instead. +/// +/// ## Examples +/// +/// > [1, 2, 3, 4] +/// > |> from_list +/// > |> fold(from: 0, with: fn(element, acc) { element + acc }) +/// 10 +/// +pub fn fold( + over iterator: Iterator(e), + from initial: acc, + with f: fn(e, acc) -> acc, +) -> acc { + iterator.continuation + |> do_fold(f, initial) +} - // TODO: test - /// Evaluates all elements emitted by the given iterator. This function is useful for when - /// you wish to trigger any side effects that would occur when evaluating - /// the iterator. - /// - pub fn run(iterator: Iterator(e)) -> Nil { - fold(iterator, Nil, fn(_, _) { Nil }) - } +// TODO: test +/// Evaluates all elements emitted by the given iterator. This function is useful for when +/// you wish to trigger any side effects that would occur when evaluating +/// the iterator. +/// +pub fn run(iterator: Iterator(e)) -> Nil { + fold(iterator, Nil, fn(_, _) { Nil }) +} - /// Evaluates an iterator and returns all the elements as a list. - /// - /// If called on an iterator of infinite length then this function will never - /// return. - /// - /// ## Examples - /// - /// > [1, 2, 3] |> from_list |> map(fn(x) { x * 2 }) |> to_list - /// [2, 4, 6] - /// - pub fn to_list(iterator: Iterator(element)) -> List(element) { - iterator - |> fold([], fn(e, acc) { [e, ..acc] }) - |> list.reverse +/// Evaluates an iterator and returns all the elements as a list. +/// +/// If called on an iterator of infinite length then this function will never +/// return. +/// +/// ## Examples +/// +/// > [1, 2, 3] |> from_list |> map(fn(x) { x * 2 }) |> to_list +/// [2, 4, 6] +/// +pub fn to_list(iterator: Iterator(element)) -> List(element) { + iterator + |> fold([], fn(e, acc) { [e, ..acc] }) + |> list.reverse +} + +/// Eagerly accesses the first value of an interator, returning a `Next` +/// that contains the first value and the rest of the iterator. +/// +/// If called on an empty iterator, `Done` is returned. +/// +/// ## Examples +/// +/// > assert Next(head, tail) = +/// > [1, 2, 3, 4] +/// > |> from_list +/// > |> step +/// > head +/// 1 +/// +/// > tail |> to_list +/// [2, 3, 4] +/// +/// > empty() |> step +/// Done +/// +pub fn step(iterator: Iterator(e)) -> Step(e, Iterator(e)) { + case iterator.continuation() { + Stop -> Done + Continue(e, a) -> Next(e, Iterator(a)) } +} - /// Eagerly accesses the first value of an interator, returning a `Next` - /// that contains the first value and the rest of the iterator. - /// - /// If called on an empty iterator, `Done` is returned. - /// - /// ## Examples - /// - /// > assert Next(head, tail) = - /// > [1, 2, 3, 4] - /// > |> from_list - /// > |> step - /// > head - /// 1 - /// - /// > tail |> to_list - /// [2, 3, 4] - /// - /// > empty() |> step - /// Done - /// - pub fn step(iterator: Iterator(e)) -> Step(e, Iterator(e)) { - case iterator.continuation() { - Stop -> Done - Continue(e, a) -> Next(e, Iterator(a)) +fn do_take(continuation: fn() -> Action(e), desired: Int) -> fn() -> Action(e) { + fn() { + case desired > 0 { + False -> Stop + True -> + case continuation() { + Stop -> Stop + Continue(e, next) -> Continue(e, do_take(next, desired - 1)) + } } } +} - fn do_take(continuation: fn() -> Action(e), desired: Int) -> fn() -> Action(e) { - fn() { +/// Creates an iterator that only yields the first `desired` elements. +/// +/// If the iterator does not have enough elements all of them are yielded. +/// +/// ## Examples +/// +/// > [1, 2, 3, 4, 5] |> from_list |> take(up_to: 3) |> to_list +/// [1, 2, 3] +/// +/// > [1, 2] |> from_list |> take(up_to: 3) |> to_list +/// [1, 2] +/// +pub fn take(from iterator: Iterator(e), up_to desired: Int) -> Iterator(e) { + iterator.continuation + |> do_take(desired) + |> Iterator +} + +fn do_drop(continuation: fn() -> Action(e), desired: Int) -> Action(e) { + case continuation() { + Stop -> Stop + Continue(e, next) -> case desired > 0 { - False -> Stop - True -> - case continuation() { - Stop -> Stop - Continue(e, next) -> Continue(e, do_take(next, desired - 1)) - } + True -> do_drop(next, desired - 1) + False -> Continue(e, next) } - } } +} - /// Creates an iterator that only yields the first `desired` elements. - /// - /// If the iterator does not have enough elements all of them are yielded. - /// - /// ## Examples - /// - /// > [1, 2, 3, 4, 5] |> from_list |> take(up_to: 3) |> to_list - /// [1, 2, 3] - /// - /// > [1, 2] |> from_list |> take(up_to: 3) |> to_list - /// [1, 2] - /// - pub fn take(from iterator: Iterator(e), up_to desired: Int) -> Iterator(e) { - iterator.continuation - |> do_take(desired) - |> Iterator - } +/// Evaluates and discards the first N elements in an iterator, returning a new +/// iterator. +/// +/// If the iterator does not have enough elements an empty iterator is +/// returned. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// > [1, 2, 3, 4, 5] |> from_list |> drop(up_to: 3) |> to_list +/// [4, 5] +/// +/// > [1, 2] |> from_list |> drop(up_to: 3) |> to_list +/// [] +/// +pub fn drop(from iterator: Iterator(e), up_to desired: Int) -> Iterator(e) { + fn() { do_drop(iterator.continuation, desired) } + |> Iterator +} - fn do_drop(continuation: fn() -> Action(e), desired: Int) -> Action(e) { +fn do_map(continuation: fn() -> Action(a), f: fn(a) -> b) -> fn() -> Action(b) { + fn() { case continuation() { Stop -> Stop - Continue(e, next) -> - case desired > 0 { - True -> do_drop(next, desired - 1) - False -> Continue(e, next) - } + Continue(e, continuation) -> Continue(f(e), do_map(continuation, f)) } } +} - /// Evaluates and discards the first N elements in an iterator, returning a new - /// iterator. - /// - /// If the iterator does not have enough elements an empty iterator is - /// returned. - /// - /// This function does not evaluate the elements of the iterator, the - /// computation is performed when the iterator is later run. - /// - /// ## Examples - /// - /// > [1, 2, 3, 4, 5] |> from_list |> drop(up_to: 3) |> to_list - /// [4, 5] - /// - /// > [1, 2] |> from_list |> drop(up_to: 3) |> to_list - /// [] - /// - pub fn drop(from iterator: Iterator(e), up_to desired: Int) -> Iterator(e) { - fn() { do_drop(iterator.continuation, desired) } - |> Iterator - } +/// Creates an iterator from an existing iterator and a transformation function. +/// +/// Each element in the new iterator will be the result of calling the given +/// function on the elements in the given iterator. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// > [1, 2, 3] |> from_list |> map(fn(x) { x * 2 }) |> to_list +/// [2, 4, 6] +/// +pub fn map(over iterator: Iterator(a), with f: fn(a) -> b) -> Iterator(b) { + iterator.continuation + |> do_map(f) + |> Iterator +} - fn do_map(continuation: fn() -> Action(a), f: fn(a) -> b) -> fn() -> Action(b) { - fn() { - case continuation() { - Stop -> Stop - Continue(e, continuation) -> Continue(f(e), do_map(continuation, f)) - } - } +fn do_append(first: fn() -> Action(a), second: fn() -> Action(a)) -> Action(a) { + case first() { + Continue(e, first) -> Continue(e, fn() { do_append(first, second) }) + Stop -> second() } +} - /// Creates an iterator from an existing iterator and a transformation function. - /// - /// Each element in the new iterator will be the result of calling the given - /// function on the elements in the given iterator. - /// - /// This function does not evaluate the elements of the iterator, the - /// computation is performed when the iterator is later run. - /// - /// ## Examples - /// - /// > [1, 2, 3] |> from_list |> map(fn(x) { x * 2 }) |> to_list - /// [2, 4, 6] - /// - pub fn map(over iterator: Iterator(a), with f: fn(a) -> b) -> Iterator(b) { - iterator.continuation - |> do_map(f) - |> Iterator - } +/// Appends two iterators, producing a new iterator. +/// +/// This function does not evaluate the elements of the iterators, the +/// computation is performed when the resulting iterator is later run. +/// +/// ## Examples +/// +/// > [1, 2] |> from_list |> append([3, 4] |> from_list) |> to_list +/// [1, 2, 3, 4] +/// +pub fn append(to first: Iterator(a), suffix second: Iterator(a)) -> Iterator(a) { + fn() { do_append(first.continuation, second.continuation) } + |> Iterator +} - fn do_append(first: fn() -> Action(a), second: fn() -> Action(a)) -> Action(a) { - case first() { - Continue(e, first) -> Continue(e, fn() { do_append(first, second) }) - Stop -> second() - } +fn do_flatten(flattened: fn() -> Action(Iterator(a))) -> Action(a) { + case flattened() { + Stop -> Stop + Continue(it, next_iterator) -> + do_append(it.continuation, fn() { do_flatten(next_iterator) }) } +} - /// Appends two iterators, producing a new iterator. - /// - /// This function does not evaluate the elements of the iterators, the - /// computation is performed when the resulting iterator is later run. - /// - /// ## Examples - /// - /// > [1, 2] |> from_list |> append([3, 4] |> from_list) |> to_list - /// [1, 2, 3, 4] - /// - pub fn append( - to first: Iterator(a), - suffix second: Iterator(a), - ) -> Iterator(a) { - fn() { do_append(first.continuation, second.continuation) } - |> Iterator - } +/// Flattens an iterator of iterators, creating a new iterator. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// > from_list([[1, 2], [3, 4]]) |> map(from_list) |> flatten |> to_list +/// [1, 2, 3, 4] +/// +pub fn flatten(iterator: Iterator(Iterator(a))) -> Iterator(a) { + fn() { do_flatten(iterator.continuation) } + |> Iterator +} - fn do_flatten(flattened: fn() -> Action(Iterator(a))) -> Action(a) { - case flattened() { - Stop -> Stop - Continue(it, next_iterator) -> - do_append(it.continuation, fn() { do_flatten(next_iterator) }) - } - } +/// Creates an iterator from an existing iterator and a transformation function. +/// +/// Each element in the new iterator will be the result of calling the given +/// function on the elements in the given iterator and then flattening the +/// results. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// > [1, 2] |> from_list |> flat_map(fn(x) { from_list([x, x + 1]) }) |> to_list +/// [1, 2, 2, 3] +/// +pub fn flat_map( + over iterator: Iterator(a), + with f: fn(a) -> Iterator(b), +) -> Iterator(b) { + iterator + |> map(f) + |> flatten +} - /// Flattens an iterator of iterators, creating a new iterator. - /// - /// This function does not evaluate the elements of the iterator, the - /// computation is performed when the iterator is later run. - /// - /// ## Examples - /// - /// > from_list([[1, 2], [3, 4]]) |> map(from_list) |> flatten |> to_list - /// [1, 2, 3, 4] - /// - pub fn flatten(iterator: Iterator(Iterator(a))) -> Iterator(a) { - fn() { do_flatten(iterator.continuation) } - |> Iterator +fn do_filter( + continuation: fn() -> Action(e), + predicate: fn(e) -> Bool, +) -> Action(e) { + case continuation() { + Stop -> Stop + Continue(e, iterator) -> + case predicate(e) { + True -> Continue(e, fn() { do_filter(iterator, predicate) }) + False -> do_filter(iterator, predicate) + } } +} - /// Creates an iterator from an existing iterator and a transformation function. - /// - /// Each element in the new iterator will be the result of calling the given - /// function on the elements in the given iterator and then flattening the - /// results. - /// - /// This function does not evaluate the elements of the iterator, the - /// computation is performed when the iterator is later run. - /// - /// ## Examples - /// - /// > [1, 2] |> from_list |> flat_map(fn(x) { from_list([x, x + 1]) }) |> to_list - /// [1, 2, 2, 3] - /// - pub fn flat_map( - over iterator: Iterator(a), - with f: fn(a) -> Iterator(b), - ) -> Iterator(b) { - iterator - |> map(f) - |> flatten - } +/// Creates an iterator from an existing iterator and a predicate function. +/// +/// The new iterator will contain elements from the first iterator for which +/// the given function returns `True`. +/// +/// This function does not evaluate the elements of the iterator, the +/// computation is performed when the iterator is later run. +/// +/// ## Examples +/// +/// > import gleam/int +/// > [1, 2, 3, 4] |> from_list |> filter(int.is_even) |> to_list +/// [2, 4] +/// +pub fn filter( + iterator: Iterator(a), + for predicate: fn(a) -> Bool, +) -> Iterator(a) { + fn() { do_filter(iterator.continuation, predicate) } + |> Iterator +} - fn do_filter( - continuation: fn() -> Action(e), - predicate: fn(e) -> Bool, - ) -> Action(e) { - case continuation() { - Stop -> Stop - Continue(e, iterator) -> - case predicate(e) { - True -> Continue(e, fn() { do_filter(iterator, predicate) }) - False -> do_filter(iterator, predicate) - } +/// Creates an iterator that repeats a given iterator infinitely. +/// +/// ## Examples +/// +/// > [1, 2] |> from_list |> cycle |> take(6) +/// [1, 2, 1, 2, 1, 2] +/// +pub fn cycle(iterator: Iterator(a)) -> Iterator(a) { + repeat(iterator) + |> flatten +} + +/// Creates an iterator of ints, starting at a given start int and stepping by +/// one to a given end int. +/// +/// ## Examples +/// +/// > range(from: 1, to: 5) |> to_list +/// [1, 2, 3, 4] +/// +/// > range(from: 1, to: -2) |> to_list +/// [1, 0, -1] +/// +/// > range(from: 0, to: 0) |> to_list +/// [] +/// +pub fn range(from start: Int, to stop: Int) -> Iterator(Int) { + let increment = case start < stop { + True -> 1 + False -> -1 + } + + let next_step = fn(current) { + case current == stop { + True -> Done + False -> Next(current, current + increment) } } - /// Creates an iterator from an existing iterator and a predicate function. - /// - /// The new iterator will contain elements from the first iterator for which - /// the given function returns `True`. - /// - /// This function does not evaluate the elements of the iterator, the - /// computation is performed when the iterator is later run. - /// - /// ## Examples - /// - /// > import gleam/int - /// > [1, 2, 3, 4] |> from_list |> filter(int.is_even) |> to_list - /// [2, 4] - /// - pub fn filter( - iterator: Iterator(a), - for predicate: fn(a) -> Bool, - ) -> Iterator(a) { - fn() { do_filter(iterator.continuation, predicate) } - |> Iterator - } + unfold(start, next_step) +} - /// Creates an iterator that repeats a given iterator infinitely. - /// - /// ## Examples - /// - /// > [1, 2] |> from_list |> cycle |> take(6) - /// [1, 2, 1, 2, 1, 2] - /// - pub fn cycle(iterator: Iterator(a)) -> Iterator(a) { - repeat(iterator) - |> flatten +fn do_find(continuation: fn() -> Action(a), f: fn(a) -> Bool) -> Result(a, Nil) { + case continuation() { + Stop -> Error(Nil) + Continue(e, next) -> + case f(e) { + True -> Ok(e) + False -> do_find(next, f) + } } +} - /// Creates an iterator of ints, starting at a given start int and stepping by - /// one to a given end int. - /// - /// ## Examples - /// - /// > range(from: 1, to: 5) |> to_list - /// [1, 2, 3, 4] - /// - /// > range(from: 1, to: -2) |> to_list - /// [1, 0, -1] - /// - /// > range(from: 0, to: 0) |> to_list - /// [] - /// - pub fn range(from start: Int, to stop: Int) -> Iterator(Int) { - let increment = case start < stop { - True -> 1 - False -> -1 - } +/// Finds the first element in a given iterator for which the given function returns +/// True. +/// +/// Returns `Error(Nil)` if the function does not return True for any of the +/// elements. +/// +/// ## Examples +/// +/// > find(from_list([1, 2, 3]), fn(x) { x > 2 }) +/// Ok(3) +/// +/// > find(from_list([1, 2, 3]), fn(x) { x > 4 }) +/// Error(Nil) +/// +/// > find(empty(), fn(x) { True }) +/// Error(Nil) +/// +pub fn find( + in haystack: Iterator(a), + one_that is_desired: fn(a) -> Bool, +) -> Result(a, Nil) { + haystack.continuation + |> do_find(is_desired) +} - let next_step = fn(current) { - case current == stop { - True -> Done - False -> Next(current, current + increment) - } +fn do_index( + continuation: fn() -> Action(element), + next: Int, +) -> fn() -> Action(#(Int, element)) { + fn() { + case continuation() { + Stop -> Stop + Continue(e, continuation) -> + Continue(#(next, e), do_index(continuation, next + 1)) } - - unfold(start, next_step) } +} + +/// Wraps values yielded from an iterator with indices, starting from 0. +/// +/// ## Examples +/// +/// > from_list(["a", "b", "c"]) |> index |> to_list +/// [#(0, "a"), #(1, "b"), #(2, "c")] +/// +pub fn index(over iterator: Iterator(element)) -> Iterator(#(Int, element)) { + iterator.continuation + |> do_index(0) + |> Iterator +} + +/// Creates an iterator that inifinitely applies a function to a value. +/// +/// ## Examples +/// +/// > iterate(1, fn(n) { n * 3 }) |> take(5) +/// [1, 3, 9, 27, 81] +/// +pub fn iterate( + from initial: element, + with f: fn(element) -> element, +) -> Iterator(element) { + unfold(initial, fn(element) { Next(element, f(element)) }) +} - fn do_find( - continuation: fn() -> Action(a), - f: fn(a) -> Bool, - ) -> Result(a, Nil) { +fn do_take_while( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, +) -> fn() -> Action(element) { + fn() { case continuation() { - Stop -> Error(Nil) + Stop -> Stop Continue(e, next) -> - case f(e) { - True -> Ok(e) - False -> do_find(next, f) + case predicate(e) { + False -> Stop + True -> Continue(e, do_take_while(next, predicate)) } } } +} - /// Finds the first element in a given iterator for which the given function returns - /// True. - /// - /// Returns `Error(Nil)` if the function does not return True for any of the - /// elements. - /// - /// ## Examples - /// - /// > find(from_list([1, 2, 3]), fn(x) { x > 2 }) - /// Ok(3) - /// - /// > find(from_list([1, 2, 3]), fn(x) { x > 4 }) - /// Error(Nil) - /// - /// > find(empty(), fn(x) { True }) - /// Error(Nil) - /// - pub fn find( - in haystack: Iterator(a), - one_that is_desired: fn(a) -> Bool, - ) -> Result(a, Nil) { - haystack.continuation - |> do_find(is_desired) - } +/// Creates an iterator that yields elements while the predicate returns `True`. +/// +/// ## Examples +/// +/// > from_list([1, 2, 3, 2, 4]) |> take_while(satisfying: fn(x) { x < 3 }) |> to_list +/// [1, 2] +/// +pub fn take_while( + in iterator: Iterator(element), + satisfying predicate: fn(element) -> Bool, +) -> Iterator(element) { + iterator.continuation + |> do_take_while(predicate) + |> Iterator +} - fn do_index( - continuation: fn() -> Action(element), - next: Int, - ) -> fn() -> Action(#(Int, element)) { - fn() { - case continuation() { - Stop -> Stop - Continue(e, continuation) -> - Continue(#(next, e), do_index(continuation, next + 1)) +fn do_drop_while( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, +) -> Action(element) { + case continuation() { + Stop -> Stop + Continue(e, next) -> + case predicate(e) { + False -> Continue(e, next) + True -> do_drop_while(next, predicate) } - } - } - - /// Wraps values yielded from an iterator with indices, starting from 0. - /// - /// ## Examples - /// - /// > from_list(["a", "b", "c"]) |> index |> to_list - /// [#(0, "a"), #(1, "b"), #(2, "c")] - /// - pub fn index(over iterator: Iterator(element)) -> Iterator(#(Int, element)) { - iterator.continuation - |> do_index(0) - |> Iterator } +} - /// Creates an iterator that inifinitely applies a function to a value. - /// - /// ## Examples - /// - /// > iterate(1, fn(n) { n * 3 }) |> take(5) - /// [1, 3, 9, 27, 81] - /// - pub fn iterate( - from initial: element, - with f: fn(element) -> element, - ) -> Iterator(element) { - unfold(initial, fn(element) { Next(element, f(element)) }) - } +/// Creates an iterator that drops elements while the predicate returns `True`, +/// and then yields the remaining elements. +/// +/// ## Examples +/// +/// > from_list([1, 2, 3, 4, 2, 5]) |> drop_while(satisfying: fn(x) { x < 4 }) |> to_list +/// [4, 2, 5] +/// +pub fn drop_while( + in iterator: Iterator(element), + satisfying predicate: fn(element) -> Bool, +) -> Iterator(element) { + fn() { do_drop_while(iterator.continuation, predicate) } + |> Iterator +} - fn do_take_while( - continuation: fn() -> Action(element), - predicate: fn(element) -> Bool, - ) -> fn() -> Action(element) { - fn() { - case continuation() { - Stop -> Stop - Continue(e, next) -> - case predicate(e) { - False -> Stop - True -> Continue(e, do_take_while(next, predicate)) - } +fn do_scan( + continuation: fn() -> Action(element), + f: fn(element, acc) -> acc, + accumulator: acc, +) -> fn() -> Action(acc) { + fn() { + case continuation() { + Stop -> Stop + Continue(el, next) -> { + let accumulated = f(el, accumulator) + Continue(accumulated, do_scan(next, f, accumulated)) } } } +} - /// Creates an iterator that yields elements while the predicate returns `True`. - /// - /// ## Examples - /// - /// > from_list([1, 2, 3, 2, 4]) |> take_while(satisfying: fn(x) { x < 3 }) |> to_list - /// [1, 2] - /// - pub fn take_while( - in iterator: Iterator(element), - satisfying predicate: fn(element) -> Bool, - ) -> Iterator(element) { - iterator.continuation - |> do_take_while(predicate) - |> Iterator - } +/// Creates an iterator from an existing iterator and a stateful function. +/// +/// Specifically, this behaves like `fold`, but yields intermediate results. +/// +/// ## Examples +/// +/// Generate a sequence of partial sums: +/// > from_list([1, 2, 3, 4, 5]) |> scan(from: 0, with: fn(el, acc) { acc + el }) |> to_list +/// [1, 3, 6, 10, 15] +/// +pub fn scan( + over iterator: Iterator(element), + from initial: acc, + with f: fn(element, acc) -> acc, +) -> Iterator(acc) { + iterator.continuation + |> do_scan(f, initial) + |> Iterator +} - fn do_drop_while( - continuation: fn() -> Action(element), - predicate: fn(element) -> Bool, - ) -> Action(element) { - case continuation() { +fn do_zip( + left: fn() -> Action(a), + right: fn() -> Action(b), +) -> fn() -> Action(#(a, b)) { + fn() { + case left() { Stop -> Stop - Continue(e, next) -> - case predicate(e) { - False -> Continue(e, next) - True -> do_drop_while(next, predicate) + Continue(el_left, next_left) -> + case right() { + Stop -> Stop + Continue(el_right, next_right) -> + Continue(#(el_left, el_right), do_zip(next_left, next_right)) } } } +} - /// Creates an iterator that drops elements while the predicate returns `True`, - /// and then yields the remaining elements. - /// - /// ## Examples - /// - /// > from_list([1, 2, 3, 4, 2, 5]) |> drop_while(satisfying: fn(x) { x < 4 }) |> to_list - /// [4, 2, 5] - /// - pub fn drop_while( - in iterator: Iterator(element), - satisfying predicate: fn(element) -> Bool, - ) -> Iterator(element) { - fn() { do_drop_while(iterator.continuation, predicate) } - |> Iterator - } - - fn do_scan( - continuation: fn() -> Action(element), - f: fn(element, acc) -> acc, - accumulator: acc, - ) -> fn() -> Action(acc) { - fn() { - case continuation() { - Stop -> Stop - Continue(el, next) -> { - let accumulated = f(el, accumulator) - Continue(accumulated, do_scan(next, f, accumulated)) - } - } - } - } +/// Zips two iterators together, emitting values from both +/// until the shorter one runs out. +/// +/// ## Examples +/// +/// > from_list(["a", "b", "c"]) |> zip(range(20, 30)) |> to_list +/// [#("a", 20), #("b", 21), #("c", 22)] +/// +pub fn zip(left: Iterator(a), right: Iterator(b)) -> Iterator(#(a, b)) { + do_zip(left.continuation, right.continuation) + |> Iterator +} - /// Creates an iterator from an existing iterator and a stateful function. - /// - /// Specifically, this behaves like `fold`, but yields intermediate results. - /// - /// ## Examples - /// - /// Generate a sequence of partial sums: - /// > from_list([1, 2, 3, 4, 5]) |> scan(from: 0, with: fn(el, acc) { acc + el }) |> to_list - /// [1, 3, 6, 10, 15] - /// - pub fn scan( - over iterator: Iterator(element), - from initial: acc, - with f: fn(element, acc) -> acc, - ) -> Iterator(acc) { - iterator.continuation - |> do_scan(f, initial) - |> Iterator - } +// Result of collecting a single chunk by key +type Chunk(element, key) { + AnotherBy(List(element), key, element, fn() -> Action(element)) + LastBy(List(element)) +} - fn do_zip( - left: fn() -> Action(a), - right: fn() -> Action(b), - ) -> fn() -> Action(#(a, b)) { - fn() { - case left() { - Stop -> Stop - Continue(el_left, next_left) -> - case right() { - Stop -> Stop - Continue(el_right, next_right) -> - Continue(#(el_left, el_right), do_zip(next_left, next_right)) - } +fn next_chunk( + continuation: fn() -> Action(element), + f: fn(element) -> key, + previous_key: key, + current_chunk: List(element), +) -> Chunk(element, key) { + case continuation() { + Stop -> LastBy(list.reverse(current_chunk)) + Continue(e, next) -> { + let key = f(e) + case key == previous_key { + True -> next_chunk(next, f, key, [e, ..current_chunk]) + False -> AnotherBy(list.reverse(current_chunk), key, e, next) } } } +} - /// Zips two iterators together, emitting values from both - /// until the shorter one runs out. - /// - /// ## Examples - /// - /// > from_list(["a", "b", "c"]) |> zip(range(20, 30)) |> to_list - /// [#("a", 20), #("b", 21), #("c", 22)] - /// - pub fn zip(left: Iterator(a), right: Iterator(b)) -> Iterator(#(a, b)) { - do_zip(left.continuation, right.continuation) - |> Iterator - } - - // Result of collecting a single chunk by key - type Chunk(element, key) { - AnotherBy(List(element), key, element, fn() -> Action(element)) - LastBy(List(element)) +fn do_chunk( + continuation: fn() -> Action(element), + f: fn(element) -> key, + previous_key: key, + previous_element: element, +) -> Action(List(element)) { + case next_chunk(continuation, f, previous_key, [previous_element]) { + LastBy(chunk) -> Continue(chunk, stop) + AnotherBy(chunk, key, el, next) -> + Continue(chunk, fn() { do_chunk(next, f, key, el) }) } +} - fn next_chunk( - continuation: fn() -> Action(element), - f: fn(element) -> key, - previous_key: key, - current_chunk: List(element), - ) -> Chunk(element, key) { - case continuation() { - Stop -> LastBy(list.reverse(current_chunk)) - Continue(e, next) -> { - let key = f(e) - case key == previous_key { - True -> next_chunk(next, f, key, [e, ..current_chunk]) - False -> AnotherBy(list.reverse(current_chunk), key, e, next) - } - } +/// Creates an iterator that emits chunks of elements +/// for which `f` returns the same value. +/// +/// ## Examples +/// +/// > from_list([1, 2, 2, 3, 4, 4, 6, 7, 7]) |> chunk(by: fn(n) { n % 2 }) |> to_list +/// [[1], [2, 2], [3], [4, 4, 6], [7, 7]] +/// +pub fn chunk( + over iterator: Iterator(element), + by f: fn(element) -> key, +) -> Iterator(List(element)) { + fn() { + case iterator.continuation() { + Stop -> Stop + Continue(e, next) -> do_chunk(next, f, f(e), e) } } + |> Iterator +} - fn do_chunk( - continuation: fn() -> Action(element), - f: fn(element) -> key, - previous_key: key, - previous_element: element, - ) -> Action(List(element)) { - case next_chunk(continuation, f, previous_key, [previous_element]) { - LastBy(chunk) -> Continue(chunk, stop) - AnotherBy(chunk, key, el, next) -> - Continue(chunk, fn() { do_chunk(next, f, key, el) }) - } - } +// Result of collecting a single sized chunk +type SizedChunk(element) { + Another(List(element), fn() -> Action(element)) + Last(List(element)) + NoMore +} - /// Creates an iterator that emits chunks of elements - /// for which `f` returns the same value. - /// - /// ## Examples - /// - /// > from_list([1, 2, 2, 3, 4, 4, 6, 7, 7]) |> chunk(by: fn(n) { n % 2 }) |> to_list - /// [[1], [2, 2], [3], [4, 4, 6], [7, 7]] - /// - pub fn chunk( - over iterator: Iterator(element), - by f: fn(element) -> key, - ) -> Iterator(List(element)) { - fn() { - case iterator.continuation() { - Stop -> Stop - Continue(e, next) -> do_chunk(next, f, f(e), e) +fn next_sized_chunk( + continuation: fn() -> Action(element), + left: Int, + current_chunk: List(element), +) -> SizedChunk(element) { + case continuation() { + Stop -> + case current_chunk { + [] -> NoMore + remaining -> Last(list.reverse(remaining)) } - } - |> Iterator - } - - // Result of collecting a single sized chunk - type SizedChunk(element) { - Another(List(element), fn() -> Action(element)) - Last(List(element)) - NoMore - } - - fn next_sized_chunk( - continuation: fn() -> Action(element), - left: Int, - current_chunk: List(element), - ) -> SizedChunk(element) { - case continuation() { - Stop -> - case current_chunk { - [] -> NoMore - remaining -> Last(list.reverse(remaining)) - } - Continue(e, next) -> { - let chunk = [e, ..current_chunk] - case left > 1 { - False -> Another(list.reverse(chunk), next) - True -> next_sized_chunk(next, left - 1, chunk) - } + Continue(e, next) -> { + let chunk = [e, ..current_chunk] + case left > 1 { + False -> Another(list.reverse(chunk), next) + True -> next_sized_chunk(next, left - 1, chunk) } } } +} - fn do_sized_chunk( - continuation: fn() -> Action(element), - count: Int, - ) -> fn() -> Action(List(element)) { - fn() { - case next_sized_chunk(continuation, count, []) { - NoMore -> Stop - Last(chunk) -> Continue(chunk, stop) - Another(chunk, next_element) -> - Continue(chunk, do_sized_chunk(next_element, count)) - } +fn do_sized_chunk( + continuation: fn() -> Action(element), + count: Int, +) -> fn() -> Action(List(element)) { + fn() { + case next_sized_chunk(continuation, count, []) { + NoMore -> Stop + Last(chunk) -> Continue(chunk, stop) + Another(chunk, next_element) -> + Continue(chunk, do_sized_chunk(next_element, count)) } } +} - /// Creates an iterator that emits chunks of given size. - /// - /// If the last chunk does not have `count` elements, it is yielded - /// as a partial chunk, with less than `count` elements. - /// - /// For any `count` less than 1 this function behaves as if it was set to 1. - /// - /// ## Examples - /// - /// > from_list([1, 2, 3, 4, 5, 6]) |> chunk(into: 2) |> to_list - /// [[1, 2], [3, 4], [5, 6]] - /// - /// > from_list([1, 2, 3, 4, 5, 6, 7, 8]) |> chunk(into: 3) |> to_list - /// [[1, 2, 3], [4, 5, 6], [7, 8]] - /// - pub fn sized_chunk( - over iterator: Iterator(element), - into count: Int, - ) -> Iterator(List(element)) { - iterator.continuation - |> do_sized_chunk(count) - |> Iterator - } +/// Creates an iterator that emits chunks of given size. +/// +/// If the last chunk does not have `count` elements, it is yielded +/// as a partial chunk, with less than `count` elements. +/// +/// For any `count` less than 1 this function behaves as if it was set to 1. +/// +/// ## Examples +/// +/// > from_list([1, 2, 3, 4, 5, 6]) |> chunk(into: 2) |> to_list +/// [[1, 2], [3, 4], [5, 6]] +/// +/// > from_list([1, 2, 3, 4, 5, 6, 7, 8]) |> chunk(into: 3) |> to_list +/// [[1, 2, 3], [4, 5, 6], [7, 8]] +/// +pub fn sized_chunk( + over iterator: Iterator(element), + into count: Int, +) -> Iterator(List(element)) { + iterator.continuation + |> do_sized_chunk(count) + |> Iterator +} - fn do_intersperse( - continuation: fn() -> Action(element), - separator: element, - ) -> Action(element) { - case continuation() { - Stop -> Stop - Continue(e, next) -> { - let next_interspersed = fn() { do_intersperse(next, separator) } - Continue(separator, fn() { Continue(e, next_interspersed) }) - } +fn do_intersperse( + continuation: fn() -> Action(element), + separator: element, +) -> Action(element) { + case continuation() { + Stop -> Stop + Continue(e, next) -> { + let next_interspersed = fn() { do_intersperse(next, separator) } + Continue(separator, fn() { Continue(e, next_interspersed) }) } } +} - /// Creates an iterator that yields the given element - /// between elements emitted by the underlying iterator. - /// - /// ## Examples - /// - /// > empty() |> intersperse(with: 0) |> to_list - /// [] - /// - /// > from_list([1]) |> intersperse(with: 0) |> to_list - /// [1] - /// - /// > from_list([1, 2, 3, 4, 5]) |> intersperse(with: 0) |> to_list - /// [1, 0, 2, 0, 3, 0, 4, 0, 5] - /// - pub fn intersperse( - over iterator: Iterator(element), - with elem: element, - ) -> Iterator(element) { - fn() { - case iterator.continuation() { - Stop -> Stop - Continue(e, next) -> Continue(e, fn() { do_intersperse(next, elem) }) - } +/// Creates an iterator that yields the given element +/// between elements emitted by the underlying iterator. +/// +/// ## Examples +/// +/// > empty() |> intersperse(with: 0) |> to_list +/// [] +/// +/// > from_list([1]) |> intersperse(with: 0) |> to_list +/// [1] +/// +/// > from_list([1, 2, 3, 4, 5]) |> intersperse(with: 0) |> to_list +/// [1, 0, 2, 0, 3, 0, 4, 0, 5] +/// +pub fn intersperse( + over iterator: Iterator(element), + with elem: element, +) -> Iterator(element) { + fn() { + case iterator.continuation() { + Stop -> Stop + Continue(e, next) -> Continue(e, fn() { do_intersperse(next, elem) }) } - |> Iterator } + |> Iterator +} - fn do_any( - continuation: fn() -> Action(element), - predicate: fn(element) -> Bool, - ) -> Bool { - case continuation() { - Stop -> False - Continue(e, next) -> predicate(e) || do_any(next, predicate) - } +fn do_any( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, +) -> Bool { + case continuation() { + Stop -> False + Continue(e, next) -> predicate(e) || do_any(next, predicate) } +} - /// Returns `True` if any element emitted by the iterator satisfies the given predicate, - /// `False` otherwise. - /// - /// This function short-circuits once it finds a satisfying element. - /// - /// An empty iterator results in `False`. - /// - /// ## Examples - /// - /// > empty() |> any(fn(n) { n % 2 == 0 }) - /// False - /// - /// > from_list([1, 2, 5, 7, 9]) |> any(fn(n) { n % 2 == 0 }) - /// True - /// - /// > from_list([1, 3, 5, 7, 9]) |> any(fn(n) { n % 2 == 0 }) - /// False - /// - pub fn any( - in iterator: Iterator(element), - satisfying predicate: fn(element) -> Bool, - ) -> Bool { - iterator.continuation - |> do_any(predicate) - } +/// Returns `True` if any element emitted by the iterator satisfies the given predicate, +/// `False` otherwise. +/// +/// This function short-circuits once it finds a satisfying element. +/// +/// An empty iterator results in `False`. +/// +/// ## Examples +/// +/// > empty() |> any(fn(n) { n % 2 == 0 }) +/// False +/// +/// > from_list([1, 2, 5, 7, 9]) |> any(fn(n) { n % 2 == 0 }) +/// True +/// +/// > from_list([1, 3, 5, 7, 9]) |> any(fn(n) { n % 2 == 0 }) +/// False +/// +pub fn any( + in iterator: Iterator(element), + satisfying predicate: fn(element) -> Bool, +) -> Bool { + iterator.continuation + |> do_any(predicate) +} - fn do_all( - continuation: fn() -> Action(element), - predicate: fn(element) -> Bool, - ) -> Bool { - case continuation() { - Stop -> True - Continue(e, next) -> predicate(e) && do_all(next, predicate) - } +fn do_all( + continuation: fn() -> Action(element), + predicate: fn(element) -> Bool, +) -> Bool { + case continuation() { + Stop -> True + Continue(e, next) -> predicate(e) && do_all(next, predicate) } +} - /// Returns `True` if all elements emitted by the iterator satisfy the given predicate, - /// `False` otherwise. - /// - /// This function short-circuits once it finds a non-satisfying element. - /// - /// An empty iterator results in `True`. - /// - /// ## Examples - /// - /// > empty() |> all(fn(n) { n % 2 == 0 }) - /// True - /// - /// > from_list([2, 4, 6, 8]) |> all(fn(n) { n % 2 == 0 }) - /// True - /// - /// > from_list([2, 4, 5, 8]) |> all(fn(n) { n % 2 == 0 }) - /// False - /// - pub fn all( - in iterator: Iterator(element), - satisfying predicate: fn(element) -> Bool, - ) -> Bool { - iterator.continuation - |> do_all(predicate) - } +/// Returns `True` if all elements emitted by the iterator satisfy the given predicate, +/// `False` otherwise. +/// +/// This function short-circuits once it finds a non-satisfying element. +/// +/// An empty iterator results in `True`. +/// +/// ## Examples +/// +/// > empty() |> all(fn(n) { n % 2 == 0 }) +/// True +/// +/// > from_list([2, 4, 6, 8]) |> all(fn(n) { n % 2 == 0 }) +/// True +/// +/// > from_list([2, 4, 5, 8]) |> all(fn(n) { n % 2 == 0 }) +/// False +/// +pub fn all( + in iterator: Iterator(element), + satisfying predicate: fn(element) -> Bool, +) -> Bool { + iterator.continuation + |> do_all(predicate) +} +if erlang { fn update_group_with( el: element, ) -> fn(Option(List(element))) -> List(element) { diff --git a/src/gleam/string.gleam b/src/gleam/string.gleam index 8379061..33f2ca7 100644 --- a/src/gleam/string.gleam +++ b/src/gleam/string.gleam @@ -182,134 +182,183 @@ if javascript { "../gleam_stdlib.js" "less_than" } -if erlang { - external fn erl_slice(String, Int, Int) -> String = - "string" "slice" - - /// Takes a substring given a start and end Grapheme indexes. Negative indexes - /// are taken starting from the *end* of the list. - /// - /// ## Examples - /// > slice(from: "gleam", at_index: 1, length: 2) - /// "le" - /// - /// > slice(from: "gleam", at_index: 1, length: 10) - /// "leam" - /// - /// > slice(from: "gleam", at_index: 10, length: 3) - /// "" - /// - /// > slice(from: "gleam", at_index: -2, length: 2) - /// "am" - /// - /// > slice(from: "gleam", at_index: -12, length: 2) - /// "" - /// - pub fn slice( - from string: String, - at_index idx: Int, - length len: Int, - ) -> String { - case len < 0 { - True -> "" - False -> - case idx < 0 { - True -> { - let translated_idx = length(string) + idx - case translated_idx < 0 { - True -> "" - False -> erl_slice(string, translated_idx, len) - } +/// Takes a substring given a start and end Grapheme indexes. Negative indexes +/// are taken starting from the *end* of the list. +/// +/// ## Examples +/// > slice(from: "gleam", at_index: 1, length: 2) +/// "le" +/// +/// > slice(from: "gleam", at_index: 1, length: 10) +/// "leam" +/// +/// > slice(from: "gleam", at_index: 10, length: 3) +/// "" +/// +/// > slice(from: "gleam", at_index: -2, length: 2) +/// "am" +/// +/// > slice(from: "gleam", at_index: -12, length: 2) +/// "" +/// +pub fn slice(from string: String, at_index idx: Int, length len: Int) -> String { + case len < 0 { + True -> "" + False -> + case idx < 0 { + True -> { + let translated_idx = length(string) + idx + case translated_idx < 0 { + True -> "" + False -> do_slice(string, translated_idx, len) } - False -> erl_slice(string, idx, len) } - } + False -> do_slice(string, idx, len) + } } +} - /// Drops contents of the first string that occur before the second string. - /// If the first string does not contain the second string, the first string is returned. - /// - /// ## Examples - /// > crop(from: "The Lone Gunmen", before: "Lone") - /// "Lone Gunmen" - /// - pub fn crop(from string: String, before substring: String) -> String { +if erlang { + external fn do_slice(String, Int, Int) -> String = + "string" "slice" +} + +if javascript { + external fn do_slice(String, Int, Int) -> String = + "../gleam_stdlib.js" "slice_string" +} + +/// Drops contents of the first string that occur before the second string. +/// If the first string does not contain the second string, the first string is returned. +/// +/// ## Examples +/// > crop(from: "The Lone Gunmen", before: "Lone") +/// "Lone Gunmen" +/// +pub fn crop(from string: String, before substring: String) -> String { + do_crop(string, substring) +} + +if erlang { + fn do_crop(string: String, substring: String) -> String { string |> erl_contains(substring) |> dynamic.string() |> result.unwrap(string) } - /// Drops *n* Graphemes from the left side of a string. - /// - /// ## Examples - /// > drop_left(from: "The Lone Gunmen", up_to: 2) - /// "e Lone Gunmen" - /// - pub fn drop_left(from string: String, up_to num_graphemes: Int) -> String { - case num_graphemes < 0 { - True -> string - False -> slice(string, num_graphemes, length(string) - num_graphemes) - } + external fn erl_contains(String, String) -> Dynamic = + "string" "find" +} + +if javascript { + external fn do_crop(String, String) -> String = + "../gleam_stdlib.js" "crop_string" +} + +/// Drops *n* Graphemes from the left side of a string. +/// +/// ## Examples +/// > drop_left(from: "The Lone Gunmen", up_to: 2) +/// "e Lone Gunmen" +/// +pub fn drop_left(from string: String, up_to num_graphemes: Int) -> String { + case num_graphemes < 0 { + True -> string + False -> slice(string, num_graphemes, length(string) - num_graphemes) } +} - /// Drops *n* Graphemes from the right side of a string. - /// - /// ## Examples - /// > drop_right(from: "Cigarette Smoking Man", up_to: 2) - /// "Cigarette Smoking M" - /// - pub fn drop_right(from string: String, up_to num_graphemes: Int) -> String { - case num_graphemes < 0 { - True -> string - False -> slice(string, 0, length(string) - num_graphemes) - } +/// Drops *n* Graphemes from the right side of a string. +/// +/// ## Examples +/// > drop_right(from: "Cigarette Smoking Man", up_to: 2) +/// "Cigarette Smoking M" +/// +pub fn drop_right(from string: String, up_to num_graphemes: Int) -> String { + case num_graphemes < 0 { + True -> string + False -> slice(string, 0, length(string) - num_graphemes) } +} - external fn erl_contains(String, String) -> Dynamic = - "string" "find" +/// Checks if the first string contains the second. +/// +/// ## Examples +/// +/// > contains(does: "theory", contain: "ory") +/// True +/// +/// > contains(does: "theory", contain: "the") +/// True +/// +/// > contains(does: "theory", contain: "THE") +/// False +/// +pub fn contains(does haystack: String, contain needle: String) -> Bool { + do_contains(haystack, needle) +} - /// Checks if the first string contains the second. - /// - /// ## Examples - /// - /// > contains(does: "theory", contain: "ory") - /// True - /// - /// > contains(does: "theory", contain: "the") - /// True - /// - /// > contains(does: "theory", contain: "THE") - /// False - /// - pub fn contains(does haystack: String, contain needle: String) -> Bool { +if erlang { + fn do_contains(haystack: String, needle: String) -> Bool { haystack |> erl_contains(needle) |> dynamic.atom |> result.is_error } +} - /// Checks whether the first string starts with the second one. - /// - /// ## Examples - /// - /// > starts_with("theory", "ory") - /// False - /// - pub external fn starts_with(String, String) -> Bool = +if javascript { + fn do_contains(haystack: String, needle: String) -> Bool { + index_of(haystack, needle) != -1 + } + + external fn index_of(String, String) -> Int = + "../gleam_stdlib.js" "index_of" +} + +/// Checks whether the first string starts with the second one. +/// +/// ## Examples +/// +/// > starts_with("theory", "ory") +/// False +/// +pub fn starts_with(string: String, prefix: String) -> Bool { + do_starts_with(string, prefix) +} + +if erlang { + external fn do_starts_with(String, String) -> Bool = "gleam_stdlib" "string_starts_with" +} - /// Checks whether the first string ends with the second one. - /// - /// ## Examples - /// - /// > ends_with("theory", "ory") - /// True - /// - pub external fn ends_with(String, String) -> Bool = +if javascript { + external fn do_starts_with(String, String) -> Bool = + "../gleam_stdlib.js" "starts_with" +} + +/// Checks whether the first string ends with the second one. +/// +/// ## Examples +/// +/// > ends_with("theory", "ory") +/// True +/// +pub fn ends_with(string: String, suffix: String) -> Bool { + do_ends_with(string, suffix) +} + +if erlang { + external fn do_ends_with(String, String) -> Bool = "gleam_stdlib" "string_ends_with" } +if javascript { + external fn do_ends_with(String, String) -> Bool = + "../gleam_stdlib.js" "ends_with" +} + /// Creates a list of strings by splitting a given string on a given substring. /// /// ## Examples @@ -324,25 +373,32 @@ pub fn split(x: String, on substring: String) -> List(String) { |> list.map(with: string_builder.to_string) } +/// Splits a string a single time on the given substring. +/// +/// Returns an error if substring not present. +/// +/// ## Examples +/// +/// > split_once("home/gleam/desktop/", on: "/") +/// Ok(#("home", "gleam/desktop/")) +/// +/// > split_once("home/gleam/desktop/", on: "?") +/// Error(Nil) +/// +pub fn split_once( + x: String, + on substring: String, +) -> Result(#(String, String), Nil) { + do_split_once(x, substring) +} + if erlang { external fn erl_split(String, String) -> List(String) = "string" "split" - /// Splits a string a single time on the given substring. - /// - /// Returns an error if substring not present. - /// - /// ## Examples - /// - /// > split_once("home/gleam/desktop/", on: "/") - /// Ok(#("home", "gleam/desktop/")) - /// - /// > split_once("home/gleam/desktop/", on: "?") - /// Error(Nil) - /// - pub fn split_once( + fn do_split_once( x: String, - on substring: String, + substring: String, ) -> Result(#(String, String), Nil) { case erl_split(x, substring) { [first, rest] -> Ok(#(first, rest)) @@ -351,6 +407,14 @@ if erlang { } } +if javascript { + external fn do_split_once( + x: String, + substring: String, + ) -> Result(#(String, String), Nil) = + "../gleam_stdlib.js" "split_once" +} + /// Creates a new string by joining two strings together. /// /// This function copies both strings and runs in linear time. If you find @@ -386,38 +450,38 @@ pub fn concat(strings: List(String)) -> String { |> string_builder.to_string } -if erlang { - /// Creates a new string by repeating a string a given number of times. - /// - /// This function runs in linear time. - /// - /// ## Examples - /// - /// > repeat("ha", times: 3) - /// "hahaha" - /// - pub fn repeat(string: String, times times: Int) -> String { - iterator.repeat(string) - |> iterator.take(times) - |> iterator.to_list - |> concat - } +/// Creates a new string by repeating a string a given number of times. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// > repeat("ha", times: 3) +/// "hahaha" +/// +pub fn repeat(string: String, times times: Int) -> String { + iterator.repeat(string) + |> iterator.take(times) + |> iterator.to_list + |> concat +} - /// Joins many strings together with a given separator. - /// - /// This function runs in linear time. - /// - /// ## Examples - /// - /// > join(["home","evan","Desktop"], with: "/") - /// "home/evan/Desktop" - /// - pub fn join(strings: List(String), with separator: String) -> String { - strings - |> list.intersperse(with: separator) - |> concat - } +/// Joins many strings together with a given separator. +/// +/// This function runs in linear time. +/// +/// ## Examples +/// +/// > join(["home","evan","Desktop"], with: "/") +/// "home/evan/Desktop" +/// +pub fn join(strings: List(String), with separator: String) -> String { + strings + |> list.intersperse(with: separator) + |> concat +} +if erlang { type Direction { Leading Trailing @@ -460,66 +524,110 @@ if erlang { pub fn pad_right(string: String, to length: Int, with pad_string: String) { erl_pad(string, length, Trailing, pad_string) } +} - external fn erl_trim(String, Direction) -> String = - "string" "trim" +/// Removes whitespace on both sides of a String. +/// +/// ## Examples +/// +/// > trim(" hats \n") +/// "hats" +/// +pub fn trim(string: String) -> String { + do_trim(string) +} - /// Removes whitespace on both sides of a String. - /// - /// ## Examples - /// - /// > trim(" hats \n") - /// "hats" - /// - pub fn trim(string: String) -> String { +if erlang { + fn do_trim(string: String) -> String { erl_trim(string, Both) } - /// Removes whitespace on the left of a String. - /// - /// ## Examples - /// - /// > trim_left(" hats \n") - /// "hats \n" - /// - pub fn trim_left(string: String) -> String { + external fn erl_trim(String, Direction) -> String = + "string" "trim" +} + +if javascript { + external fn do_trim(string: String) -> String = + "../gleam_stdlib.js" "trim" +} + +/// Removes whitespace on the left of a String. +/// +/// ## Examples +/// +/// > trim_left(" hats \n") +/// "hats \n" +/// +pub fn trim_left(string: String) -> String { + do_trim_left(string) +} + +if erlang { + fn do_trim_left(string: String) -> String { erl_trim(string, Leading) } +} - /// Removes whitespace on the right of a String. - /// - /// ## Examples - /// - /// > trim_right(" hats \n") - /// " hats" - /// - pub fn trim_right(string: String) -> String { +if javascript { + external fn do_trim_left(string: String) -> String = + "../gleam_stdlib.js" "trim_left" +} + +/// Removes whitespace on the right of a String. +/// +/// ## Examples +/// +/// > trim_right(" hats \n") +/// " hats" +/// +pub fn trim_right(string: String) -> String { + do_trim_right(string) +} + +if erlang { + fn do_trim_right(string: String) -> String { erl_trim(string, Trailing) } +} - /// Splits a non-empty string into its head and tail. This lets you - /// pattern match on strings exactly as you would with lists. - /// - /// ## Examples - /// > pop_grapheme("gleam") - /// Ok(#("g", "leam")) - /// - /// > pop_grapheme("") - /// Error(Nil) - /// - pub external fn pop_grapheme(string: String) -> Result(#(String, String), Nil) = +if javascript { + external fn do_trim_right(string: String) -> String = + "../gleam_stdlib.js" "trim_right" +} + +/// Splits a non-empty string into its head and tail. This lets you +/// pattern match on strings exactly as you would with lists. +/// +/// ## Examples +/// > pop_grapheme("gleam") +/// Ok(#("g", "leam")) +/// +/// > pop_grapheme("") +/// Error(Nil) +/// +pub fn pop_grapheme(string: String) -> Result(#(String, String), Nil) { + do_pop_grapheme(string) +} + +if erlang { + external fn do_pop_grapheme(string: String) -> Result(#(String, String), Nil) = "gleam_stdlib" "string_pop_grapheme" +} - /// Converts a string to a list of Graphemes. - /// - /// > to_graphemes("abc") - /// ["a", "b", "c"] - /// - pub fn to_graphemes(string: String) -> List(String) { - case pop_grapheme(string) { - Ok(#(grapheme, rest)) -> [grapheme, ..to_graphemes(rest)] - _ -> [] - } +if javascript { + external fn do_pop_grapheme(string: String) -> Result(#(String, String), Nil) = + "../gleam_stdlib.js" "pop_grapheme" +} + +/// Converts a string to a list of Graphemes. +/// +/// > to_graphemes("abc") +/// ["a", "b", "c"] +/// +pub fn to_graphemes(string: String) -> List(String) { + case pop_grapheme(string) { + Ok(#(grapheme, rest)) -> [grapheme, ..to_graphemes(rest)] + _ -> [] } } diff --git a/src/gleam/string_builder.gleam b/src/gleam/string_builder.gleam index f39ce49..ac6817f 100644 --- a/src/gleam/string_builder.gleam +++ b/src/gleam/string_builder.gleam @@ -127,7 +127,7 @@ pub fn to_string(builder: StringBuilder) -> String { if erlang { external fn do_to_string(StringBuilder) -> String = - "erlang" "iolist_to_binary" + "unicode" "characters_to_binary" } if javascript { @@ -232,7 +232,7 @@ if erlang { "string" "split" fn do_split(iodata: StringBuilder, pattern: String) -> List(StringBuilder) { - do_split(iodata, pattern, All) + erl_split(iodata, pattern, All) } } diff --git a/src/gleam_stdlib.erl b/src/gleam_stdlib.erl index 836bdbe..49a3b0c 100644 --- a/src/gleam_stdlib.erl +++ b/src/gleam_stdlib.erl @@ -12,7 +12,7 @@ bit_string_int_to_u32/1, bit_string_int_from_u32/1, bit_string_append/2, bit_string_part_/3, decode_bit_string/1, compile_regex/2, regex_match/2, regex_split/2, regex_scan/2, - base_decode64/1, wrap_list/1, rescue/1, get_line/1]). + base_decode64/1, wrap_list/1, get_line/1]). should_equal(Actual, Expected) -> ?assertEqual(Expected, Actual), diff --git a/src/gleam_stdlib.js b/src/gleam_stdlib.js index a45aa89..89908de 100644 --- a/src/gleam_stdlib.js +++ b/src/gleam_stdlib.js @@ -1,3 +1,5 @@ +const Nil = undefined; + function to_list(array) { let list = []; for (let item of array.reverse()) { @@ -6,15 +8,23 @@ function to_list(array) { return list; } +function Ok(x) { + return { type: "Ok", 0: x }; +} + +function Error(x) { + return { type: "Error", 0: x }; +} + export function identity(x) { return x; } export function parse_int(value) { if (/^[-+]?(\d+)$/.test(value)) { - return { type: "Ok", 0: Number(value) }; + return Ok(Number(value)); } else { - return { type: "Error", 0: null }; + return Error(Nil); } } @@ -35,9 +45,10 @@ export function string_reverse(string) { } export function string_length(string) { - if (Intl && Intl.Segmenter) { + let iterator = graphemes_iterator(string); + if (iterator) { let i = 0; - for (let _ of new Intl.Segmenter("en-gb").segment(string)) { + for (let _ of iterator) { i++; } return i; @@ -46,6 +57,27 @@ export function string_length(string) { } } +function graphemes_iterator(string) { + if (Intl && Intl.Segmenter) { + return new Intl.Segmenter("en-gb").segment(string)[Symbol.iterator](); + } +} + +export function pop_grapheme(string) { + let first; + let iterator = graphemes_iterator(string); + if (iterator) { + first = iterator.next().value?.segment; + } else { + first = string.match(/./u)?.[0]; + } + if (first) { + return Ok([first, string.slice(first.length)]); + } else { + return Error(Nil); + } +} + export function lowercase(string) { return string.toLowerCase(); } @@ -71,7 +103,7 @@ export function split(xs, pattern) { } export function join(xs) { - return xs.flat().join(""); + return xs.flat(Infinity).join(""); } export function byte_size(data) { @@ -83,3 +115,46 @@ export function byte_size(data) { return data.length; } } + +export function slice_string(string, from, length) { + return string.slice(from, from + length); +} + +export function crop_string(string, substring) { + return string.substring(string.indexOf(substring)); +} + +export function index_of(haystack, needle) { + return haystack.indexOf(needle) | 0; +} + +export function starts_with(haystack, needle) { + return haystack.startsWith(needle); +} + +export function ends_with(haystack, needle) { + return haystack.endsWith(needle); +} + +export function split_once(haystack, needle) { + let index = haystack.indexOf(needle); + if (index >= 0) { + let before = haystack.slice(0, index); + let after = haystack.slice(index + needle.length); + return Ok([before, after]); + } else { + return Error(Nil); + } +} + +export function trim(string) { + return string.trim(); +} + +export function trim_left(string) { + return string.trimLeft(); +} + +export function trim_right(string) { + return string.trimRight(); +} diff --git a/test/gleam/string_test.gleam b/test/gleam/string_test.gleam index a890d0f..b6762ca 100644 --- a/test/gleam/string_test.gleam +++ b/test/gleam/string_test.gleam @@ -43,20 +43,18 @@ pub fn split_test() { |> should.equal(["Gleam", "Erlang,Elixir"]) } -if erlang { - pub fn split_once_test() { - "Gleam,Erlang,Elixir" - |> string.split_once(",") - |> should.equal(Ok(#("Gleam", "Erlang,Elixir"))) - - "Gleam" - |> string.split_once(",") - |> should.equal(Error(Nil)) - - "" - |> string.split_once(",") - |> should.equal(Error(Nil)) - } +pub fn split_once_test() { + "Gleam,Erlang,Elixir" + |> string.split_once(",") + |> should.equal(Ok(#("Gleam", "Erlang,Elixir"))) + + "Gleam" + |> string.split_once(",") + |> should.equal(Error(Nil)) + + "" + |> string.split_once(",") + |> should.equal(Error(Nil)) } pub fn replace_test() { @@ -65,12 +63,10 @@ pub fn replace_test() { |> should.equal("Gleam++Erlang++Elixir") } -if erlang { - pub fn append_test() { - "Test" - |> string.append(" Me") - |> should.equal("Test Me") - } +pub fn append_test() { + "Test" + |> string.append(" Me") + |> should.equal("Test Me") } pub fn compare_test() { @@ -90,179 +86,179 @@ pub fn compare_test() { |> should.equal(order.Gt) } -if erlang { - pub fn contains_test() { - "gleam" - |> string.contains("ea") - |> should.equal(True) +pub fn contains_test() { + "gleam" + |> string.contains("ea") + |> should.equal(True) - "gleam" - |> string.contains("x") - |> should.equal(False) + "gleam" + |> string.contains("x") + |> should.equal(False) - string.contains(does: "bellwether", contain: "bell") - |> should.equal(True) - } + string.contains(does: "bellwether", contain: "bell") + |> should.equal(True) +} - pub fn concat_test() { - ["Hello", ", ", "world!"] - |> string.concat - |> should.equal("Hello, world!") - } +pub fn concat_test() { + ["Hello", ", ", "world!"] + |> string.concat + |> should.equal("Hello, world!") +} - pub fn repeat_test() { - "hi" - |> string.repeat(times: 3) - |> should.equal("hihihi") +pub fn repeat_test() { + "hi" + |> string.repeat(times: 3) + |> should.equal("hihihi") - "hi" - |> string.repeat(0) - |> should.equal("") + "hi" + |> string.repeat(0) + |> should.equal("") - "hi" - |> string.repeat(-1) - |> should.equal("") - } + "hi" + |> string.repeat(-1) + |> should.equal("") +} - pub fn join_test() { - ["Hello", "world!"] - |> string.join(with: ", ") - |> should.equal("Hello, world!") +pub fn join_test() { + ["Hello", "world!"] + |> string.join(with: ", ") + |> should.equal("Hello, world!") - ["Hello", "world!"] - |> string.join(with: "-") - |> should.equal("Hello-world!") - } + ["Hello", "world!"] + |> string.join(with: "-") + |> should.equal("Hello-world!") +} - pub fn trim_test() { - " hats \n" - |> string.trim() - |> should.equal("hats") - } +pub fn trim_test() { + " hats \n" + |> string.trim() + |> should.equal("hats") +} - pub fn trim_left_test() { - " hats \n" - |> string.trim_left() - |> should.equal("hats \n") - } +pub fn trim_left_test() { + " hats \n" + |> string.trim_left() + |> should.equal("hats \n") +} - pub fn trim_right_test() { - " hats \n" - |> string.trim_right() - |> should.equal(" hats") - } +pub fn trim_right_test() { + " hats \n" + |> string.trim_right() + |> should.equal(" hats") +} - pub fn starts_with_test() { - "theory" - |> string.starts_with("") - |> should.equal(True) +pub fn starts_with_test() { + "theory" + |> string.starts_with("") + |> should.equal(True) - "theory" - |> string.starts_with("the") - |> should.equal(True) + "theory" + |> string.starts_with("the") + |> should.equal(True) - "theory" - |> string.starts_with("ory") - |> should.equal(False) + "theory" + |> string.starts_with("ory") + |> should.equal(False) - "theory" - |> string.starts_with("theory2") - |> should.equal(False) - } + "theory" + |> string.starts_with("theory2") + |> should.equal(False) +} - pub fn ends_with_test() { - "theory" - |> string.ends_with("") - |> should.equal(True) +pub fn ends_with_test() { + "theory" + |> string.ends_with("") + |> should.equal(True) - "theory" - |> string.ends_with("ory") - |> should.equal(True) + "theory" + |> string.ends_with("ory") + |> should.equal(True) - "theory" - |> string.ends_with("the") - |> should.equal(False) + "theory" + |> string.ends_with("the") + |> should.equal(False) - "theory" - |> string.ends_with("theory2") - |> should.equal(False) - } + "theory" + |> string.ends_with("theory2") + |> should.equal(False) +} - pub fn slice_test() { - "gleam" - |> string.slice(at_index: 1, length: 2) - |> should.equal("le") +pub fn slice_test() { + "gleam" + |> string.slice(at_index: 1, length: 2) + |> should.equal("le") - "gleam" - |> string.slice(at_index: 1, length: 10) - |> should.equal("leam") + "gleam" + |> string.slice(at_index: 1, length: 10) + |> should.equal("leam") - "gleam" - |> string.slice(at_index: 10, length: 3) - |> should.equal("") + "gleam" + |> string.slice(at_index: 10, length: 3) + |> should.equal("") - "gleam" - |> string.slice(at_index: -2, length: 2) - |> should.equal("am") + "gleam" + |> string.slice(at_index: -2, length: 2) + |> should.equal("am") - "gleam" - |> string.slice(at_index: -12, length: 2) - |> should.equal("") + "gleam" + |> string.slice(at_index: -12, length: 2) + |> should.equal("") - "gleam" - |> string.slice(at_index: 2, length: -3) - |> should.equal("") - } + "gleam" + |> string.slice(at_index: 2, length: -3) + |> should.equal("") +} - pub fn crop_test() { - "gleam" - |> string.crop("gl") - |> should.equal("gleam") +pub fn crop_test() { + "gleam" + |> string.crop("gl") + |> should.equal("gleam") - "gleam" - |> string.crop("le") - |> should.equal("leam") + "gleam" + |> string.crop("le") + |> should.equal("leam") - string.crop(from: "gleam", before: "ea") - |> should.equal("eam") + string.crop(from: "gleam", before: "ea") + |> should.equal("eam") - "gleam" - |> string.crop("") - |> should.equal("gleam") + "gleam" + |> string.crop("") + |> should.equal("gleam") - "gleam" - |> string.crop("!") - |> should.equal("gleam") - } + "gleam" + |> string.crop("!") + |> should.equal("gleam") +} - pub fn drop_left_test() { - "gleam" - |> string.drop_left(up_to: 2) - |> should.equal("eam") +pub fn drop_left_test() { + "gleam" + |> string.drop_left(up_to: 2) + |> should.equal("eam") - "gleam" - |> string.drop_left(up_to: 6) - |> should.equal("") + "gleam" + |> string.drop_left(up_to: 6) + |> should.equal("") - "gleam" - |> string.drop_left(up_to: -2) - |> should.equal("gleam") - } + "gleam" + |> string.drop_left(up_to: -2) + |> should.equal("gleam") +} - pub fn drop_right_test() { - "gleam" - |> string.drop_right(up_to: 2) - |> should.equal("gle") +pub fn drop_right_test() { + "gleam" + |> string.drop_right(up_to: 2) + |> should.equal("gle") - "gleam" - |> string.drop_right(up_to: 5) - |> should.equal("") + "gleam" + |> string.drop_right(up_to: 5) + |> should.equal("") - "gleam" - |> string.drop_right(up_to: -2) - |> should.equal("gleam") - } + "gleam" + |> string.drop_right(up_to: -2) + |> should.equal("gleam") +} +if erlang { pub fn pad_left_test() { "121" |> string.pad_left(to: 5, with: ".") @@ -298,35 +294,37 @@ if erlang { |> string.pad_right(to: 5, with: "XY") |> should.equal("121XYXY") } +} - pub fn pop_grapheme_test() { - "gleam" - |> string.pop_grapheme() - |> should.equal(Ok(#("g", "leam"))) +pub fn pop_grapheme_test() { + "gleam" + |> string.pop_grapheme() + |> should.equal(Ok(#("g", "leam"))) - "g" - |> string.pop_grapheme() - |> should.equal(Ok(#("g", ""))) + "g" + |> string.pop_grapheme() + |> should.equal(Ok(#("g", ""))) - "" - |> string.pop_grapheme() - |> should.equal(Error(Nil)) - } + "" + |> string.pop_grapheme() + |> should.equal(Error(Nil)) +} - pub fn to_graphemes_test() { - "abc" - |> string.to_graphemes() - |> should.equal(["a", "b", "c"]) +pub fn to_graphemes_test() { + "abc" + |> string.to_graphemes() + |> should.equal(["a", "b", "c"]) - "a" - |> string.to_graphemes() - |> should.equal(["a"]) + "a" + |> string.to_graphemes() + |> should.equal(["a"]) - "" - |> string.to_graphemes() - |> should.equal([]) - } + "" + |> string.to_graphemes() + |> should.equal([]) +} +if erlang { pub fn utf_codepoint_test() { string.utf_codepoint(1114444) |> should.be_error |